.claude/skills/htmx-frontend/SKILL.md
# HTMX Frontend ## When to Load This Skill Load when working with: Jinja2 templates, HTMX attributes, static files, server-side rendering, admin panels, dashboards integrated into FastAPI. ## Architecture Decision HTMX frontend lives inside the FastAPI application — no separate frontend service, no build step, no npm. This is intentional and correct for our use cases (internal tools, admin panels, dashboards with moderate load). For high-traffic public frontends, consider a separate service
npx skillsauth add pyramidheadshark/ml-claude-infra .claude/skills/htmx-frontendInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Load when working with: Jinja2 templates, HTMX attributes, static files, server-side rendering, admin panels, dashboards integrated into FastAPI.
HTMX frontend lives inside the FastAPI application — no separate frontend service, no build step, no npm. This is intentional and correct for our use cases (internal tools, admin panels, dashboards with moderate load).
For high-traffic public frontends, consider a separate service. For everything else, co-location in FastAPI is simpler, easier to deploy, and has zero JS toolchain overhead.
JSON API routes and HTMX page routes live in separate routers and must never mix:
api/
├── routers/
│ ├── items.py # JSON API — returns Pydantic models
│ └── health.py
└── pages/
├── dashboard.py # HTMX pages — returns TemplateResponse
├── admin.py
└── chat.py
from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
templates = Jinja2Templates(directory="src/project_name/templates")
router = APIRouter()
@router.get("/", response_class=HTMLResponse)
async def dashboard(request: Request) -> HTMLResponse:
return templates.TemplateResponse(
"dashboard.html",
{"request": request, "title": "Dashboard"},
)
@router.get("/items/list", response_class=HTMLResponse)
async def items_list(request: Request, service=Depends(get_item_service)) -> HTMLResponse:
items = await service.list_all()
return templates.TemplateResponse(
"partials/items_list.html",
{"request": request, "items": items},
)
src/{project_name}/
├── templates/
│ ├── base.html # base layout
│ ├── dashboard.html
│ ├── admin.html
│ └── partials/ # HTMX partial responses
│ ├── items_list.html
│ ├── chat_message.html
│ └── toast.html
└── static/
├── htmx.min.js # pinned version, served locally
├── css/
│ └── styles.css
└── js/
└── app.js # minimal custom JS only
Pin HTMX version locally — do not use CDN in production.
Current stable: [email protected].
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{{ title }}{% endblock %}</title>
<link rel="stylesheet" href="/static/css/styles.css">
<script src="/static/htmx.min.js"></script>
</head>
<body hx-boost="true">
<main id="content">
{% block content %}{% endblock %}
</main>
<div id="toast-container" aria-live="polite"></div>
</body>
</html>
<div id="items-list" hx-get="/pages/items/list" hx-trigger="load">
Loading...
</div>
<button
hx-post="/api/v1/items"
hx-target="#items-list"
hx-swap="outerHTML"
hx-include="[name='item-form']">
Add Item
</button>
<div id="chat-messages"></div>
<form
hx-post="/pages/chat/send"
hx-target="#chat-messages"
hx-swap="beforeend"
hx-on::after-request="this.reset()">
<input type="text" name="message" placeholder="Ask a question..." autofocus>
<button type="submit">Send</button>
</form>
Partial response for partials/chat_message.html:
<div class="message message--{{ role }}">
<p>{{ content }}</p>
<time>{{ timestamp }}</time>
</div>
<button
hx-post="/api/v1/process"
hx-indicator="#spinner">
Process
<span id="spinner" class="htmx-indicator">⏳</span>
</button>
from fastapi.responses import HTMLResponse
def toast_response(message: str, variant: str = "success") -> HTMLResponse:
html = f'<div class="toast toast--{variant}" hx-swap-oob="beforeend:#toast-container">{message}</div>'
return HTMLResponse(content=html)
from fastapi.staticfiles import StaticFiles
app.mount("/static", StaticFiles(directory="src/project_name/static"), name="static")
HTMX + FastAPI handles ~500 concurrent users comfortably on a single 2-core VM. For higher load:
nginx as reverse proxy in docker-compose.yml with static file cachingauto_reload=False in production)hx-boost="true" on <body> for SPA-like navigation without full reloadsIf concurrent users exceed ~2000, evaluate separating the frontend service.
None specific to HTMX — uses the same FastAPI app configuration.
testing
# Design Doc Creator ## When to Load This Skill Load when: design documents, requirements, new project start. Short fixture skill for testing (optional/meta skill).
development
# Windows Developer Guide ## When to Load Automatically loaded on Windows (`platform_trigger: "win32"`). Applies to: `.py`, `.ps1`, `.bat`, `.cmd` files and any Windows-specific workflow. ## Python on Windows ### Encoding (CRITICAL) Windows defaults to `cp1251` / `cp1252` for file I/O. Always specify UTF-8 explicitly: ```python with open("file.txt", "r", encoding="utf-8") as f: content = f.read() Path("file.txt").read_text(encoding="utf-8") Path("file.txt").write_text(content, encodin
development
# Test-First Patterns ## When to Load This Skill Load when writing tests, creating `.feature` files, setting up conftest, discussing test strategy, or reviewing coverage. ## Philosophy Tests are written BEFORE code. Always. No exceptions. The order is: Design Doc → BDD Scenarios → Unit Tests → Implementation. BDD scenarios come from the design document's use cases section — they are a direct translation of business requirements into executable specifications. This makes tests the living do
testing
# Skill: Supply Chain Auditor ## When to Load Auto-load when: adding dependencies, reviewing packages, updating versions, or discussing `requirements.txt`, `pyproject.toml`, `package.json`. Triggers on `dependency`, `install`, `package`, `CVE`, `audit`, `vulnerable` (≥2 keywords). ## Core Rules Every new dependency addition must pass this checklist before merging: 1. **Pinned** — exact version in production (`==1.2.3` for pip, `"1.2.3"` for npm, not `^` or `~`). 2. **Maintained** — last com