- Semantic elements over generic
div/span - Minimal wrappers; avoid nesting without purpose
- Minimal classes; prefer element and attribute selectors
- ARIA attributes where native semantics fall short
- No inline styles; keep presentation in CSS
- Use
<code-block lang="...">for fenced code examples
- Native CSS nesting; no preprocessors
light-dark()for theming viacolor-scheme- CSS custom properties for shared values
- Specific
transitionproperties, notall - Gate motion behind
prefers-reduced-motion - Mobile breakpoint at
768px
- Functional style;
const, arrow functions, expressions - Minimal intermediates; prefer composition
- No frameworks or external dependencies
- Handle async errors (e.g.
.catchon clipboard) - No DOM writes from user input without escaping
- Avoid dependencies if possible; prefer
npxfor external tools - Single file; CSS and JS are embedded in
index.html - Keep the page under 1000 lines total
- Test in both light and dark mode
- Verify mobile layout before committing
- Fix lint warnings in prose, not in the linter config
- Place an
.htmlfile in the project root - Register it in
app.js:.get('/path', 'file.html')
- Add a text route in
app.js:.get('/path', 'body')
Omit the body to respond with ok: .get('/health')
- Place files in a directory (e.g.
assets/) - Register in
app.js:.static('/assets')
- Only
GETrequests; everything else returns405 - File extensions must have a known MIME type (see
Server.DEFAULTS.mime) - Pages are loaded into memory at startup; restart to pick up changes
- Static directories must be within the project root
index.htmlat root is auto-detected and served at/