Frontend Conventions (common)
Apply this base layer to every browser-facing change. Read the target file, its nearest siblings,
the package manifest, TypeScript config, lint config, and public exports before editing. Preserve a
coherent local pattern unless it conflicts with a rule below or a current platform standard.
This skill owns generic implementation quality, not repository placement. In the Agora workspace,
use write-platform for terminal application shells and product policy, write-design-system for
uikit visual contracts, and nodelib for reusable non-visual client runtime or tooling configuration.
Load plan-ui-design before deciding a new or materially changed flow, interaction pattern,
information hierarchy, component family, or visual language. This skill owns implementation quality;
plan-ui-design owns the human-facing contract that implementation must preserve.
Rendered-UI hard gate: Start Storybook with BROWSER=none and --no-open, inspect the exact
changed story in the integrated browser, keep the server live through review, and put its freshly
verified actual Markdown link in the completion report beside the PR and task or issue links. Never
put a local-only Storybook URL in a GitHub PR body. A screenshot or placeholder is never a substitute.
Use this authority order when guidance conflicts:
- Repository contracts and supported-browser policy.
- Normative web standards and WCAG.
- Official framework or tool documentation for the installed version.
- Maintained FOSS guidance from standards bodies and established organizations.
- Local convention and personal preference.
Read references/standards.md before choosing a browser API, accessibility
pattern, security boundary, design-token model, or unfamiliar framework feature. Verify versioned
APIs against current official documentation rather than relying on memory.
After every edit
Use repository scripts as declared; do not invent parallel commands:
pnpm format # write formatting when the repo provides it
pnpm lint # formatting check + types + ESLint
a-novel test --type=pnpm -y # all pnpm tests discovered by the workspace CLI
a-novel build --type=pnpm -y # production/package builds
Run the narrowest package or test target while iterating, then run all applicable gates before the
change is ready. Load use-a-novel-cli whenever running test or build commands. A production build
is mandatory for routing, SSR, package-export, bundler, or environment-boundary changes.
Live UI review — mandatory handoff
Treat Storybook as a mandatory review surface for every change that affects rendered UI. Do not
declare UI work complete from source inspection, unit tests, screenshots, or a static build alone.
The handoff contract is non-negotiable:
- Add or update the smallest story or docs page that renders the changed UI and its meaningful
states. If the repository has no usable Storybook, establish it within the agreed scope or report
the missing review surface as a blocker.
- Start the repository's Storybook command with
BROWSER=none and --no-open. The process must
not launch an external browser tab and must stay live for the operator unless they ask to stop it.
- Wait for readiness, discover the actual listening URL and port, and verify that the exact changed
story or docs route responds.
- Open that route in the integrated browser and inspect it. Prefer it over the Storybook root when
handing off a specific component.
- Keep localhost and other local-only Storybook links out of GitHub PR descriptions. They are
session-scoped review surfaces, not durable PR metadata.
- In the final completion report that hands rendered UI work back, include at least one direct
Storybook link beside the PR and planning task or issue links. Use a clickable inline link such as
[Button — Storybook](http://127.0.0.1:6006/?path=/docs/button--docs), substituting the actual live
URL and route. Repeat it in any later report that hands completed UI work back; an earlier report
does not satisfy the current handoff.
- Re-resolve every linked route immediately before sending the completion report. If the server
stopped or changed, restart it and verify the new link before reporting completion.
A screenshot, placeholder such as “visual preview,” stale URL, or instruction to find an earlier
link does not satisfy this contract.
Dependencies
- Load
choose-dependency before adding, replacing, or evaluating a package.
- Prefer the web platform, an existing workspace package, or an existing dependency in that order.
- Before implementing reusable code in a terminal app, route visual contracts to uikit and
framework-agnostic runtime or shared build/lint/test/localization configuration to nodelib.
- Require an explicit reason for every runtime dependency: capability, maintenance owner, release
health, browser cost, license, security posture, and why the current stack cannot provide it.
- Prefer standards-body or established-organization ownership. Do not adopt an unmaintained package
or a narrow personal utility for code that is straightforward to own.
- Import the narrowest documented public entry point. Never deep-import private package files.
- Keep browser bundles free of Node-only modules and server-only transitive dependencies.
TypeScript and modules
- Keep
strict enabled. Do not introduce any, disable strict checks, or hide errors with broad
casts. Use unknown, narrow it, and validate untrusted data at the boundary.
- Model impossible states out with discriminated unions. Represent absence deliberately; do not use
empty strings, magic numbers, or non-null assertions as state management.
- Prefer inference inside a function and explicit types at exported, callback, network, storage, and
component boundaries. Use
satisfies when checking a value without widening it.
- Use
type imports for type-only dependencies and ES modules exclusively.
- Keep domain data serializable unless the boundary explicitly supports richer values.
- Treat API, URL, storage, message, and DOM data as untrusted at runtime even when TypeScript types it.
- Catch
unknown; preserve the original cause when translating errors. Never silently discard a
rejected promise.
Naming and files
- Name Svelte components in PascalCase and plain TypeScript/CSS modules in lowercase camelCase or a
single lowercase word, matching the surrounding package.
- Mirror source names in tests and stories:
Button.svelte.test.ts, Button.stories.svelte, and
retry.test.ts. Follow SvelteKit route filenames exactly where its router owns the convention.
- Name exported types and components in PascalCase; functions, values, and props in camelCase; and
true constants in SCREAMING_SNAKE_CASE only when the package already uses that distinction.
- Give booleans a state or capability name (
disabled, hasError, canRetry) and event callbacks
the behavior they represent (onSubmit, onDismiss). Avoid generic data, item, or handler
when a domain name is available.
Components and controllers
- Keep components presentational: render semantic HTML and accessibility state, translate native events
into semantic requests, and own only DOM mechanics such as element references, focus movement,
measurements, and transient typeahead bookkeeping.
- Put meaningful rendered state and its transition rules in a pure controller with no DOM access,
component rendering, route imports, storage, session, or network calls. A controller may use the
framework reactive primitive in a
.svelte.ts module.
- Let a stateful component accept at most one controller. Do not split its contract across bindable
state props, change callbacks, and a controller; stateless components need no controller.
- Expose semantic controller methods (
open, close, select, setChecked) rather than generic
setters. The component reports intent; the controller may accept, transform, or reject it.
- Export the controller contract and a configurable default factory. Callers may supply another
implementation that satisfies the same contract, including fixed-state Storybook controllers.
- Unit-test controller transitions without rendering. Test the component boundary for DOM semantics,
accessibility behavior, and rejected transitions.
HTML and interaction
- Use the native element with the required behavior:
button for actions, a for navigation,
labels and controls for forms, and landmarks/headings for document structure.
- Add ARIA only when native HTML cannot express the contract. A role is a promise to implement its
keyboard interaction, focus behavior, state, and accessible name.
- Set
type="button" on non-submit buttons. Keep form submission, validation, autofill, and error
association available without pointer input.
- Preserve logical source order and normal tab order. Never use a positive
tabindex.
- Expose a visible focus indicator. Do not remove outlines without an equal or stronger replacement.
- Treat persistent state as stronger than transient input. Selected, checked, expanded, invalid,
loading, and disabled meaning must remain clear while a control is hovered, active, or focused.
- For reusable content-bearing components, prefer the framework's native composition primitive over
a string-only label prop when text, markup, icons, or nested components are semantically safe.
- Support keyboard, pointer, touch, zoom, reflow, text spacing, and assistive technology. Do not make
color, hover, drag, or animation the only way to understand or operate a control.
- Provide useful alternative text and accessible names. Decorative media stays silent.
- Set document and changed-passage language correctly. Use logical CSS properties so RTL does not
require a second component implementation.
- Treat WCAG 2.2 AA as the minimum acceptance baseline, not a guarantee supplied by automation.
CSS and responsive layout
- Prefer normal flow, Grid, Flexbox, logical properties, and container/media queries over measured
JavaScript layout. Use feature queries for optional enhancements.
- Start from the smallest supported viewport and let content determine breakpoints. Avoid device-name
breakpoints and user-agent sniffing.
- Use design tokens for product colors, spacing, type, radii, borders, elevation, and motion. A raw
value is acceptable only for a local algorithmic constant with no design meaning.
- Use relative units for type and layout. Keep line height unitless. Reserve pixels for genuinely
device-bound details such as a one-device-pixel hairline when the token contract calls for it.
- Keep selectors shallow and component-scoped. Avoid
!important; fix cascade ownership instead.
- Preserve content at 200% text zoom and 400% page zoom/reflow. Do not clip user content to force a
mockup height.
- Respect
prefers-reduced-motion, forced colors, increased contrast where supported, and user font
settings. Motion must not be required to understand state.
- Animate compositor-friendly properties when possible and never add animation without a purpose.
State, data, and browser boundaries
- Give each piece of state one owner. Derive values instead of synchronizing duplicate state.
- Put shareable navigation state in the URL. Keep ephemeral interaction state local to the smallest
component that owns it.
- Represent loading, empty, error, stale, and success states explicitly. Preserve useful content
during refresh when the product contract allows it.
- Cancel or supersede obsolete asynchronous work. Guard against out-of-order responses.
- Keep rendering pure. Synchronize with external systems in framework lifecycle primitives, not in
getters, templates, or module import side effects.
- Keep server-only code, environment variables, credentials, and privileged API calls out of browser
bundles. Assume every shipped byte and source map is public.
- Use progressive enhancement for navigation and forms when the framework supports it. A network or
JavaScript failure should degrade intentionally rather than strand the user.
Security and privacy
- Never place secrets or long-lived credentials in client code, browser storage, logs, analytics, or
error messages. Follow the repository authentication model.
- Do not render untrusted HTML. Use text interpolation; if rich HTML is a real product requirement,
choose and configure a maintained sanitizer through
choose-dependency and test bypass cases.
- Use safe URL construction and validate protocols before navigation. Do not concatenate executable
markup, CSS, script, or query fragments from untrusted input.
- Collect and persist only required data. Do not add analytics, remote fonts, third-party scripts,
beacons, or cross-origin calls without explicit product and security approval.
- Preserve CSP compatibility: avoid inline script generation,
eval, and undocumented third-party
origins.
Performance and compatibility
- Set an explicit browser baseline in project configuration. Use feature detection and progressive
enhancement; add a polyfill only after measuring need and bundle cost.
- Prefer semantic markup and CSS over JavaScript. Lazy-load non-critical routes and heavy features,
not primary content or interaction affordances.
- Reserve media dimensions, serve responsive assets, and avoid layout thrashing. Batch DOM reads and
writes only after measurement proves imperative layout is necessary.
- Measure before optimizing. Track user-facing latency and Core Web Vitals for applications; do not
trade correctness or accessibility for an unmeasured micro-optimization.
- Handle offline, timeout, retry, and partial-response behavior at the owning boundary. Never retry a
non-idempotent operation invisibly.
Completion checklist
- Semantic and keyboard behavior works without a mouse.
- Focus, zoom/reflow, reduced motion, forced colors, long content, localization, and RTL were
considered in proportion to the change.
- Persistent states remain stable under transient interactions, and reusable content APIs compose
naturally without sacrificing native semantics.
- Trust boundaries validate runtime data and do not expose secrets.
- Loading, empty, error, and success paths are intentional.
write-frontend-tests covers changed behavior.
- Storybook is running, the changed UI was inspected there, and a freshly verified direct local link
is in the completion report beside the PR and task or issue links, never in the PR body.
- Format, lint, pnpm tests, and production build pass.
1---2name: write-frontend3description: Base frontend conventions for EVERY browser-facing repository in a-novel and a-novel-kit — semantic HTML, accessible CSS, strict TypeScript, browser security, performance, data/state boundaries, dependency policy, mandatory live-Storybook handoff, and validation. Load it for ANY HTML, CSS, TypeScript, browser API, platform-* application, uikit, Storybook, or nodelib-browser work. Load `plan-ui-design` before non-trivial user-flow, interaction, information-architecture, or visual-direction work. Pair with `write-svelte` for .svelte files, `write-frontend-tests` for frontend tests or stories, and `write-design-system` for tokens or reusable UI. Service REST clients under pkg/js also load `write-js-package`.4---56# Frontend Conventions (common)78Apply this base layer to every browser-facing change. Read the target file, its nearest siblings,9the package manifest, TypeScript config, lint config, and public exports before editing. Preserve a10coherent local pattern unless it conflicts with a rule below or a current platform standard.1112This skill owns generic implementation quality, not repository placement. In the Agora workspace,13use `write-platform` for terminal application shells and product policy, `write-design-system` for14uikit visual contracts, and nodelib for reusable non-visual client runtime or tooling configuration.1516Load `plan-ui-design` before deciding a new or materially changed flow, interaction pattern,17information hierarchy, component family, or visual language. This skill owns implementation quality;18`plan-ui-design` owns the human-facing contract that implementation must preserve.1920**Rendered-UI hard gate:** Start Storybook with `BROWSER=none` and `--no-open`, inspect the exact21changed story in the integrated browser, keep the server live through review, and put its freshly22verified actual Markdown link in the completion report beside the PR and task or issue links. Never23put a local-only Storybook URL in a GitHub PR body. A screenshot or placeholder is never a substitute.2425Use this authority order when guidance conflicts:26271. Repository contracts and supported-browser policy.282. Normative web standards and WCAG.293. Official framework or tool documentation for the installed version.304. Maintained FOSS guidance from standards bodies and established organizations.315. Local convention and personal preference.3233Read [references/standards.md](references/standards.md) before choosing a browser API, accessibility34pattern, security boundary, design-token model, or unfamiliar framework feature. Verify versioned35APIs against current official documentation rather than relying on memory.3637## After every edit3839Use repository scripts as declared; do not invent parallel commands:4041```bash42pnpm format # write formatting when the repo provides it43pnpm lint # formatting check + types + ESLint44a-novel test --type=pnpm -y # all pnpm tests discovered by the workspace CLI45a-novel build --type=pnpm -y # production/package builds46```4748Run the narrowest package or test target while iterating, then run all applicable gates before the49change is ready. Load `use-a-novel-cli` whenever running test or build commands. A production build50is mandatory for routing, SSR, package-export, bundler, or environment-boundary changes.5152## Live UI review — mandatory handoff5354Treat Storybook as a mandatory review surface for every change that affects rendered UI. Do not55declare UI work complete from source inspection, unit tests, screenshots, or a static build alone.5657The handoff contract is non-negotiable:58591. Add or update the smallest story or docs page that renders the changed UI and its meaningful60 states. If the repository has no usable Storybook, establish it within the agreed scope or report61 the missing review surface as a blocker.622. Start the repository's Storybook command with `BROWSER=none` and `--no-open`. The process must63 not launch an external browser tab and must stay live for the operator unless they ask to stop it.643. Wait for readiness, discover the actual listening URL and port, and verify that the exact changed65 story or docs route responds.664. Open that route in the integrated browser and inspect it. Prefer it over the Storybook root when67 handing off a specific component.685. Keep localhost and other local-only Storybook links out of GitHub PR descriptions. They are69 session-scoped review surfaces, not durable PR metadata.706. In the final completion report that hands rendered UI work back, include at least one direct71 Storybook link beside the PR and planning task or issue links. Use a clickable inline link such as72 `[Button — Storybook](http://127.0.0.1:6006/?path=/docs/button--docs)`, substituting the actual live73 URL and route. Repeat it in any later report that hands completed UI work back; an earlier report74 does not satisfy the current handoff.757. Re-resolve every linked route immediately before sending the completion report. If the server76 stopped or changed, restart it and verify the new link before reporting completion.7778A screenshot, placeholder such as “visual preview,” stale URL, or instruction to find an earlier79link does not satisfy this contract.8081## Dependencies8283- Load `choose-dependency` before adding, replacing, or evaluating a package.84- Prefer the web platform, an existing workspace package, or an existing dependency in that order.85- Before implementing reusable code in a terminal app, route visual contracts to uikit and86 framework-agnostic runtime or shared build/lint/test/localization configuration to nodelib.87- Require an explicit reason for every runtime dependency: capability, maintenance owner, release88 health, browser cost, license, security posture, and why the current stack cannot provide it.89- Prefer standards-body or established-organization ownership. Do not adopt an unmaintained package90 or a narrow personal utility for code that is straightforward to own.91- Import the narrowest documented public entry point. Never deep-import private package files.92- Keep browser bundles free of Node-only modules and server-only transitive dependencies.9394## TypeScript and modules9596- Keep `strict` enabled. Do not introduce `any`, disable strict checks, or hide errors with broad97 casts. Use `unknown`, narrow it, and validate untrusted data at the boundary.98- Model impossible states out with discriminated unions. Represent absence deliberately; do not use99 empty strings, magic numbers, or non-null assertions as state management.100- Prefer inference inside a function and explicit types at exported, callback, network, storage, and101 component boundaries. Use `satisfies` when checking a value without widening it.102- Use `type` imports for type-only dependencies and ES modules exclusively.103- Keep domain data serializable unless the boundary explicitly supports richer values.104- Treat API, URL, storage, message, and DOM data as untrusted at runtime even when TypeScript types it.105- Catch `unknown`; preserve the original cause when translating errors. Never silently discard a106 rejected promise.107108## Naming and files109110- Name Svelte components in PascalCase and plain TypeScript/CSS modules in lowercase camelCase or a111 single lowercase word, matching the surrounding package.112- Mirror source names in tests and stories: `Button.svelte.test.ts`, `Button.stories.svelte`, and113 `retry.test.ts`. Follow SvelteKit route filenames exactly where its router owns the convention.114- Name exported types and components in PascalCase; functions, values, and props in camelCase; and115 true constants in SCREAMING_SNAKE_CASE only when the package already uses that distinction.116- Give booleans a state or capability name (`disabled`, `hasError`, `canRetry`) and event callbacks117 the behavior they represent (`onSubmit`, `onDismiss`). Avoid generic `data`, `item`, or `handler`118 when a domain name is available.119120## Components and controllers121122- Keep components presentational: render semantic HTML and accessibility state, translate native events123 into semantic requests, and own only DOM mechanics such as element references, focus movement,124 measurements, and transient typeahead bookkeeping.125- Put meaningful rendered state and its transition rules in a pure controller with no DOM access,126 component rendering, route imports, storage, session, or network calls. A controller may use the127 framework reactive primitive in a `.svelte.ts` module.128- Let a stateful component accept at most one controller. Do not split its contract across bindable129 state props, change callbacks, and a controller; stateless components need no controller.130- Expose semantic controller methods (`open`, `close`, `select`, `setChecked`) rather than generic131 setters. The component reports intent; the controller may accept, transform, or reject it.132- Export the controller contract and a configurable default factory. Callers may supply another133 implementation that satisfies the same contract, including fixed-state Storybook controllers.134- Unit-test controller transitions without rendering. Test the component boundary for DOM semantics,135 accessibility behavior, and rejected transitions.136137## HTML and interaction138139- Use the native element with the required behavior: `button` for actions, `a` for navigation,140 labels and controls for forms, and landmarks/headings for document structure.141- Add ARIA only when native HTML cannot express the contract. A role is a promise to implement its142 keyboard interaction, focus behavior, state, and accessible name.143- Set `type="button"` on non-submit buttons. Keep form submission, validation, autofill, and error144 association available without pointer input.145- Preserve logical source order and normal tab order. Never use a positive `tabindex`.146- Expose a visible focus indicator. Do not remove outlines without an equal or stronger replacement.147- Treat persistent state as stronger than transient input. Selected, checked, expanded, invalid,148 loading, and disabled meaning must remain clear while a control is hovered, active, or focused.149- For reusable content-bearing components, prefer the framework's native composition primitive over150 a string-only label prop when text, markup, icons, or nested components are semantically safe.151- Support keyboard, pointer, touch, zoom, reflow, text spacing, and assistive technology. Do not make152 color, hover, drag, or animation the only way to understand or operate a control.153- Provide useful alternative text and accessible names. Decorative media stays silent.154- Set document and changed-passage language correctly. Use logical CSS properties so RTL does not155 require a second component implementation.156- Treat WCAG 2.2 AA as the minimum acceptance baseline, not a guarantee supplied by automation.157158## CSS and responsive layout159160- Prefer normal flow, Grid, Flexbox, logical properties, and container/media queries over measured161 JavaScript layout. Use feature queries for optional enhancements.162- Start from the smallest supported viewport and let content determine breakpoints. Avoid device-name163 breakpoints and user-agent sniffing.164- Use design tokens for product colors, spacing, type, radii, borders, elevation, and motion. A raw165 value is acceptable only for a local algorithmic constant with no design meaning.166- Use relative units for type and layout. Keep line height unitless. Reserve pixels for genuinely167 device-bound details such as a one-device-pixel hairline when the token contract calls for it.168- Keep selectors shallow and component-scoped. Avoid `!important`; fix cascade ownership instead.169- Preserve content at 200% text zoom and 400% page zoom/reflow. Do not clip user content to force a170 mockup height.171- Respect `prefers-reduced-motion`, forced colors, increased contrast where supported, and user font172 settings. Motion must not be required to understand state.173- Animate compositor-friendly properties when possible and never add animation without a purpose.174175## State, data, and browser boundaries176177- Give each piece of state one owner. Derive values instead of synchronizing duplicate state.178- Put shareable navigation state in the URL. Keep ephemeral interaction state local to the smallest179 component that owns it.180- Represent loading, empty, error, stale, and success states explicitly. Preserve useful content181 during refresh when the product contract allows it.182- Cancel or supersede obsolete asynchronous work. Guard against out-of-order responses.183- Keep rendering pure. Synchronize with external systems in framework lifecycle primitives, not in184 getters, templates, or module import side effects.185- Keep server-only code, environment variables, credentials, and privileged API calls out of browser186 bundles. Assume every shipped byte and source map is public.187- Use progressive enhancement for navigation and forms when the framework supports it. A network or188 JavaScript failure should degrade intentionally rather than strand the user.189190## Security and privacy191192- Never place secrets or long-lived credentials in client code, browser storage, logs, analytics, or193 error messages. Follow the repository authentication model.194- Do not render untrusted HTML. Use text interpolation; if rich HTML is a real product requirement,195 choose and configure a maintained sanitizer through `choose-dependency` and test bypass cases.196- Use safe URL construction and validate protocols before navigation. Do not concatenate executable197 markup, CSS, script, or query fragments from untrusted input.198- Collect and persist only required data. Do not add analytics, remote fonts, third-party scripts,199 beacons, or cross-origin calls without explicit product and security approval.200- Preserve CSP compatibility: avoid inline script generation, `eval`, and undocumented third-party201 origins.202203## Performance and compatibility204205- Set an explicit browser baseline in project configuration. Use feature detection and progressive206 enhancement; add a polyfill only after measuring need and bundle cost.207- Prefer semantic markup and CSS over JavaScript. Lazy-load non-critical routes and heavy features,208 not primary content or interaction affordances.209- Reserve media dimensions, serve responsive assets, and avoid layout thrashing. Batch DOM reads and210 writes only after measurement proves imperative layout is necessary.211- Measure before optimizing. Track user-facing latency and Core Web Vitals for applications; do not212 trade correctness or accessibility for an unmeasured micro-optimization.213- Handle offline, timeout, retry, and partial-response behavior at the owning boundary. Never retry a214 non-idempotent operation invisibly.215216## Completion checklist217218- Semantic and keyboard behavior works without a mouse.219- Focus, zoom/reflow, reduced motion, forced colors, long content, localization, and RTL were220 considered in proportion to the change.221- Persistent states remain stable under transient interactions, and reusable content APIs compose222 naturally without sacrificing native semantics.223- Trust boundaries validate runtime data and do not expose secrets.224- Loading, empty, error, and success paths are intentional.225- `write-frontend-tests` covers changed behavior.226- Storybook is running, the changed UI was inspected there, and a freshly verified direct local link227 is in the completion report beside the PR and task or issue links, never in the PR body.228- Format, lint, pnpm tests, and production build pass.