Platform Application Conventions
This skill is the platform counterpart to write-go-service: write-frontend owns universal web
rules, write-svelte owns framework mechanics, write-frontend-tests owns tests and stories, and
this skill owns the terminal application's architecture and delivery sequence.
Load plan-ui-design before choosing a visual direction or interaction language. Load
choose-dependency before adding a package, write-dockerfiles before changing the root
Dockerfile, write-github-actions before changing CI, and write-project-docs before changing
root project documentation. Use plan-client-server-boundary when a platform composes or changes a
backend capability.
Ownership boundaries
A platform is one deployable SvelteKit application and one root package. It is a terminal product,
not a general-purpose client library. Apply this ownership split before adding code or configuration:
- Platform owns its shell and UX: routes, screen and shell compositions, route models, product
copy and catalogs, supported locales, product URL and local-storage policy, and service-specific
authentication, session, client, and health wiring.
- Nodelib owns reusable non-visual client infrastructure: framework-agnostic runtime helpers and
shared build, lint, test, SvelteKit, and localization configuration. A representative extraction
fixture belongs beside the shared preset it validates.
- Uikit owns reusable visual contracts: design tokens, components, and shared Storybook theme,
preview, decorator, docs, and visual-test defaults.
Consume released kit packages through documented public exports. Do not publish app code or keep a
platform-local copy of a generic helper, preset, visual primitive, or Storybook default.
Keep responsibilities explicit:
src/routes/ owns URLs, layouts, server loads, form actions, redirects, and HTTP endpoints.
src/lib/ui/ owns app-specific pure screen and shell compositions. It may depend on uikit, but
not on route globals, storage, cookies, environment secrets, or live network clients.
src/lib/application/ owns pure workflow state, URL codecs, commands, and orchestration contracts.
src/lib/client/ owns browser adapters such as preference persistence and history integration.
src/lib/server/ owns sessions, privileged backend clients, environment configuration, and
server-only policy. Nothing browser-reachable imports it.
Use the nearest established equivalent when a repository already has a coherent structure; do not
rename working boundaries merely to match these directory names. Dependencies point inward: routes
and adapters compose pure application/UI code, never the reverse.
Keep platform configuration entrypoints thin: import shared factories and supply only product paths,
catalog policy, environment policy, or test selection. Generic defaults and their representative
fixtures stay with the shared package; product catalogs, route models, and workflows stay in the
platform.
When a missing piece is reusable, plan and release it from the owning kit repository first: visual
contracts go to uikit, while runtime helpers and tooling presets go to nodelib. Then pin the exact
release in the platform and remove any superseded local implementation. Do not grow either a private
design system or a private client-infrastructure library inside the app.
Decompose branches and PRs by one user-visible capability or one application boundary. Platform
work does not use a backend service's layer-by-layer branch stack.
Rendering and configuration
Use @sveltejs/adapter-node and SSR as the default. Opt a private route family out with
export const ssr = false only when its browser-only behavior is deliberate and tested; never
disable SSR globally to hide unsafe module initialization or a hydration bug.
Read secrets and privileged endpoints only from SvelteKit's server-only environment modules. Expose
an explicitly public value through the framework's PUBLIC_ contract only when it is safe to ship
in source. A secret-shaped VITE_* variable is a build failure, not runtime configuration.
Storybook-first delivery
Build every user-visible screen in this order:
- Define the screen controller contract: serializable rendered state plus semantic transition methods.
- Implement the screen or shell composition without route, session, storage, or network imports.
- Add deterministic stories for the meaningful state matrix: loading, empty, error, success,
permissions, narrow/wide layouts, long translations, and interaction states as applicable.
- Start Storybook with
BROWSER=none and --no-open; inspect the exact stories in the integrated
browser at narrow and wide viewports and keep the verified link for the handoff.
- Add component and interaction tests at the pure boundary.
- Implement the pure controller and unit-test its transitions; then let the route file construct it from
loads, actions, URL state, storage adapters, and browser adapters.
- Add an end-to-end test only for a critical browser or cross-boundary journey that smaller tests
cannot prove. Any committed browser test runs in CI.
Stories are an executable design surface, not production fixtures. Keep mocks typed, local,
deterministic, and incapable of contacting real services. Route files translate backend and
framework results into the same controller contracts the stories exercise.
For each routed screen or layout, keep screen.svelte and controller.svelte.ts beside the SvelteKit
route files. screen.svelte is the pure UI component and accepts one screen controller.
controller.svelte.ts contains pure reactive state and semantic transitions without rendering or DOM
access. +page.svelte or +layout.svelte performs only final wiring plus route-owned metadata such as
the document title. Do not add a connector component that merely renders the screen.
Import shared Storybook preview, theme, decorators, and test defaults from uikit's development
package. Keep a local Storybook entrypoint only for product-specific context and story discovery; do
not copy the reusable preview into the platform.
State ownership
Every state has one canonical owner:
- Path and search parameters own shareable, navigation-relevant UI state. Centralize codecs,
validate every value, choose deliberate defaults, and preserve unrelated parameters.
- Local storage owns non-sensitive, device-local preferences and recoverable user-specific
drafts. Namespace and version keys, migrate or discard unknown versions, and read them only behind
an SSR-safe client adapter.
- Server session cookies own authentication credentials. Use
HttpOnly, Secure in production,
and an intentional SameSite policy; tokens never enter browser storage or serialized load data.
- Component memory owns transient interaction and form input that should not survive navigation.
Do not duplicate one state across URL, storage, and component memory. Decide whether a URL change
deserves a history entry: use push semantics for navigable choices and replace semantics for
normalization or high-frequency adjustments. Server rendering and first hydration must derive the
same visible state; apply client-only preferences after the browser boundary without hiding a
hydration mismatch.
Never persist passwords, short codes, access tokens, refresh tokens, or unredacted backend errors.
Authentication and server policy
Treat the SvelteKit server as a backend-for-frontend when a platform consumes an authentication
service:
- Browser forms submit to same-origin server actions. The server validates input, calls the typed
service client, rotates session cookies, and returns a small serializable result.
- Reuse runtime schemas exported by the published typed service client for service-owned fields; do
not rewrite the same contract in a second validator. Use repository-pinned Valibot schemas for
app-owned input and
sveltekit-superforms for non-trivial form state and progressive enhancement.
Uikit owns rendered fields and errors, so platforms do not import Formsnap or another component layer.
- Server loads establish the session and authorization context. UI visibility is convenience;
privileged routes and actions enforce permissions again on the server.
- Create an anonymous service session lazily, only when an anonymous protected operation needs it.
An ordinary page load must not mint a token merely to render signed-out UI.
- Refresh a rejected or expiring session at one server-owned boundary. Clear cookies only after a
definitive authentication rejection; preserve them and expose a recoverable unavailable state on
network, timeout, or downstream 5xx failures. Do not silently turn an outage into signed-out state.
- A logout that only clears platform cookies is not token revocation. Describe it accurately and
plan a backend capability when revocation is required.
- A short-code
GET only parses a sanitized display model; it never verifies or consumes the code.
Completion uses POST to the current URL so no hidden field duplicates the credential. Return
Cache-Control: no-store, Referrer-Policy: no-referrer, and X-Robots-Tag: noindex.
- Never serialize the raw code, raw target, password, or token through load/action data, rendered
HTML, stories, logs, or error copy. After completion, use POST/redirect/GET to a clean status-only
URL; collapse rejected, expired, consumed, or unknown targets when the service cannot safely
distinguish them.
- Map service failures to stable user-facing error categories. Log only sanitized operational
context on the server; do not expose service internals or secrets in UI copy.
Progressive enhancement is required for essential authentication forms. JavaScript may improve
focus, pending state, and modal behavior, but it must not become the only path to submit or recover.
Localization
Keep source messages in static, reviewable repository files. All visible product copy, accessible
names, validation messages, titles, and metadata use message keys; logs and protocol tokens do not.
- Import the shared extraction/type/status preset and request-localization runtime from nodelib.
Platform configuration supplies only its locales, paths, namespaces, and product exceptions.
- Keep the preset's framework extraction fixture in nodelib. Platform stories and tests exercise
real product copy and translated UI states; they do not duplicate a generic toolchain fixture.
- Support plurals and contextual variants through the selected message format, not key
concatenation or runtime grammar.
- Treat the source locale as authoritative. CI compiles messages and fails for missing, invalid, or
stale translations using the repository's pinned tooling.
- When a product requires runtime message catalogs, keep the static catalogs as the runtime source
of truth; generated type declarations or indexes are derived artifacts and are never hand-edited.
Do not replace a requested message-catalog workflow with generated per-message functions merely
for type safety.
- Resolve visible messages atomically where they render. Svelte components read the request- or
tree-scoped i18next instance from the shared native context and call it with statically
discoverable keys; they do not receive pretranslated screen-sized dictionaries.
- Declare each product message only in its static catalog. Do not mirror catalogs into
*Copy
interfaces, copy-builder functions, story-only dictionaries, or other programmatic message maps.
Stable domain and action-result codes remain application state; translate them at the rendering
boundary instead of returning visible prose from wiring.
- Install the same product-owned localization provider around the application, Storybook stories,
and component-test render trees. Storybook global decorators and test wrappers may select locale
and catalogs, but must not redeclare messages or substitute ad hoc copy objects.
- Under SSR, create request-scoped localization state. Never keep a mutable process-global locale
or dictionary that can leak one user's language into another request.
- Pin any framework-specific extraction adapter and prove it against a representative component in
CI. Prefer statically discoverable keys; prohibit dynamic key construction unless an explicit
catalog allowlist keeps missing- and unused-key checks sound.
- Do not require a hosted translation service or a network call at build or runtime.
- Exercise long translations and at least one alternate locale in stories. Preserve logical layout
and leave an RTL path even when the initial locales are left-to-right.
Container and health contract
Ship one multi-stage root Dockerfile that builds the locked root package and runs the minimal
production SvelteKit server as a non-root user. Keep build-only credentials out of image layers and
the final image. Follow write-dockerfiles for implementation details.
Build the real container in CI, not only the Node artifact. Run the frozen install inside the exact
image and keep its Node pin compatible with every locked dependency engine; a floating host CI
runtime can hide a stale image pin. When Corepack/pnpm configures a global binary directory, put
that exact directory on PATH before running global config commands. Pass private-registry
credentials as BuildKit secrets, remove temporary registry config in the same layer, and prove the
final image contains neither credential nor build-only tooling.
Expose two distinct probes:
GET /ping is cheap liveness and has no downstream dependency. The container HEALTHCHECK uses
it so a service outage does not restart a healthy platform process.
GET /healthcheck is readiness/diagnostics. It fans out with bounded timeouts only to registered,
typed dependencies the platform actually uses and returns a stable nested status map. Sanitize
errors and preserve partial results so one failure does not hide the others.
Never turn a request parameter into an arbitrary healthcheck URL. Configuration selects from known
clients, and concurrency plus deadlines bound the fan-out cost.
CI and publication
The required pipeline runs format/style checks, type checking, localization validation, unit and
component tests, Storybook tests with accessibility checks, the production build, the static
Storybook build, dependency audit, and container build as applicable.
Publish only the static Storybook artifact through the shared GitHub Pages action. Production
deployment remains the container's concern. A Pages workflow uses GitHub's supported artifact and
deployment actions, declares the Pages environment, and does not cancel an in-progress deployment.
Never include secrets, private environment values, source maps containing secrets, or production
API credentials in Storybook.
Completion checklist
- Pure screens and their complete state matrix render in Storybook before route wiring lands.
- Keyboard, focus, narrow layout, long copy, reduced motion, and automated accessibility checks pass.
- Static catalogs are the sole declaration of visible messages; app, Storybook, and tests use the
same request- or tree-scoped localization provider.
- URL codecs round-trip and invalid parameters normalize predictably; persisted preferences are
versioned and SSR-safe.
- No authentication secret reaches browser storage, logs, client bundles, or serialized page data.
- Server actions and session transitions have unit tests, including expiry and downstream failure.
/ping, /healthcheck, production build, Storybook build, and the container are verified.
pnpm lint, a-novel test --type=pnpm -y, and a-novel build --type=pnpm -y pass.
- The live Storybook URL and any intentionally deferred end-to-end coverage appear in the handoff.
1---2name: write-platform3description: Application-architecture conventions for a-novel client-side PLATFORM repos (`app/platform-*`): SvelteKit route and server boundaries, app-owned screen composition, Storybook-first delivery, URL and browser-state ownership, secure sessions, localization, container health, and CI. Load it whenever creating, editing, reviewing, or debugging a platform application. ALWAYS load `write-frontend`; add `write-svelte` for Svelte/SvelteKit files and `write-frontend-tests` for tests or stories. Use `write-design-system` only when changing reusable uikit contracts.4---56# Platform Application Conventions78This skill is the platform counterpart to `write-go-service`: `write-frontend` owns universal web9rules, `write-svelte` owns framework mechanics, `write-frontend-tests` owns tests and stories, and10this skill owns the terminal application's architecture and delivery sequence.1112Load `plan-ui-design` before choosing a visual direction or interaction language. Load13`choose-dependency` before adding a package, `write-dockerfiles` before changing the root14`Dockerfile`, `write-github-actions` before changing CI, and `write-project-docs` before changing15root project documentation. Use `plan-client-server-boundary` when a platform composes or changes a16backend capability.1718## Ownership boundaries1920A platform is one deployable SvelteKit application and one root package. It is a terminal product,21not a general-purpose client library. Apply this ownership split before adding code or configuration:2223- **Platform** owns its shell and UX: routes, screen and shell compositions, route models, product24 copy and catalogs, supported locales, product URL and local-storage policy, and service-specific25 authentication, session, client, and health wiring.26- **Nodelib** owns reusable non-visual client infrastructure: framework-agnostic runtime helpers and27 shared build, lint, test, SvelteKit, and localization configuration. A representative extraction28 fixture belongs beside the shared preset it validates.29- **Uikit** owns reusable visual contracts: design tokens, components, and shared Storybook theme,30 preview, decorator, docs, and visual-test defaults.3132Consume released kit packages through documented public exports. Do not publish app code or keep a33platform-local copy of a generic helper, preset, visual primitive, or Storybook default.3435Keep responsibilities explicit:3637- `src/routes/` owns URLs, layouts, server loads, form actions, redirects, and HTTP endpoints.38- `src/lib/ui/` owns app-specific pure screen and shell compositions. It may depend on uikit, but39 not on route globals, storage, cookies, environment secrets, or live network clients.40- `src/lib/application/` owns pure workflow state, URL codecs, commands, and orchestration contracts.41- `src/lib/client/` owns browser adapters such as preference persistence and history integration.42- `src/lib/server/` owns sessions, privileged backend clients, environment configuration, and43 server-only policy. Nothing browser-reachable imports it.4445Use the nearest established equivalent when a repository already has a coherent structure; do not46rename working boundaries merely to match these directory names. Dependencies point inward: routes47and adapters compose pure application/UI code, never the reverse.4849Keep platform configuration entrypoints thin: import shared factories and supply only product paths,50catalog policy, environment policy, or test selection. Generic defaults and their representative51fixtures stay with the shared package; product catalogs, route models, and workflows stay in the52platform.5354When a missing piece is reusable, plan and release it from the owning kit repository first: visual55contracts go to uikit, while runtime helpers and tooling presets go to nodelib. Then pin the exact56release in the platform and remove any superseded local implementation. Do not grow either a private57design system or a private client-infrastructure library inside the app.5859Decompose branches and PRs by one user-visible capability or one application boundary. Platform60work does not use a backend service's layer-by-layer branch stack.6162## Rendering and configuration6364Use `@sveltejs/adapter-node` and SSR as the default. Opt a private route family out with65`export const ssr = false` only when its browser-only behavior is deliberate and tested; never66disable SSR globally to hide unsafe module initialization or a hydration bug.6768Read secrets and privileged endpoints only from SvelteKit's server-only environment modules. Expose69an explicitly public value through the framework's `PUBLIC_` contract only when it is safe to ship70in source. A secret-shaped `VITE_*` variable is a build failure, not runtime configuration.7172## Storybook-first delivery7374Build every user-visible screen in this order:75761. Define the screen controller contract: serializable rendered state plus semantic transition methods.772. Implement the screen or shell composition without route, session, storage, or network imports.783. Add deterministic stories for the meaningful state matrix: loading, empty, error, success,79 permissions, narrow/wide layouts, long translations, and interaction states as applicable.804. Start Storybook with `BROWSER=none` and `--no-open`; inspect the exact stories in the integrated81 browser at narrow and wide viewports and keep the verified link for the handoff.825. Add component and interaction tests at the pure boundary.836. Implement the pure controller and unit-test its transitions; then let the route file construct it from84 loads, actions, URL state, storage adapters, and browser adapters.857. Add an end-to-end test only for a critical browser or cross-boundary journey that smaller tests86 cannot prove. Any committed browser test runs in CI.8788Stories are an executable design surface, not production fixtures. Keep mocks typed, local,89deterministic, and incapable of contacting real services. Route files translate backend and90framework results into the same controller contracts the stories exercise.9192For each routed screen or layout, keep `screen.svelte` and `controller.svelte.ts` beside the SvelteKit93route files. `screen.svelte` is the pure UI component and accepts one screen controller.94`controller.svelte.ts` contains pure reactive state and semantic transitions without rendering or DOM95access. `+page.svelte` or `+layout.svelte` performs only final wiring plus route-owned metadata such as96the document title. Do not add a connector component that merely renders the screen.9798Import shared Storybook preview, theme, decorators, and test defaults from uikit's development99package. Keep a local Storybook entrypoint only for product-specific context and story discovery; do100not copy the reusable preview into the platform.101102## State ownership103104Every state has one canonical owner:105106- **Path and search parameters** own shareable, navigation-relevant UI state. Centralize codecs,107 validate every value, choose deliberate defaults, and preserve unrelated parameters.108- **Local storage** owns non-sensitive, device-local preferences and recoverable user-specific109 drafts. Namespace and version keys, migrate or discard unknown versions, and read them only behind110 an SSR-safe client adapter.111- **Server session cookies** own authentication credentials. Use `HttpOnly`, `Secure` in production,112 and an intentional `SameSite` policy; tokens never enter browser storage or serialized load data.113- **Component memory** owns transient interaction and form input that should not survive navigation.114115Do not duplicate one state across URL, storage, and component memory. Decide whether a URL change116deserves a history entry: use push semantics for navigable choices and replace semantics for117normalization or high-frequency adjustments. Server rendering and first hydration must derive the118same visible state; apply client-only preferences after the browser boundary without hiding a119hydration mismatch.120121Never persist passwords, short codes, access tokens, refresh tokens, or unredacted backend errors.122123## Authentication and server policy124125Treat the SvelteKit server as a backend-for-frontend when a platform consumes an authentication126service:127128- Browser forms submit to same-origin server actions. The server validates input, calls the typed129 service client, rotates session cookies, and returns a small serializable result.130- Reuse runtime schemas exported by the published typed service client for service-owned fields; do131 not rewrite the same contract in a second validator. Use repository-pinned Valibot schemas for132 app-owned input and `sveltekit-superforms` for non-trivial form state and progressive enhancement.133 Uikit owns rendered fields and errors, so platforms do not import Formsnap or another component layer.134- Server loads establish the session and authorization context. UI visibility is convenience;135 privileged routes and actions enforce permissions again on the server.136- Create an anonymous service session lazily, only when an anonymous protected operation needs it.137 An ordinary page load must not mint a token merely to render signed-out UI.138- Refresh a rejected or expiring session at one server-owned boundary. Clear cookies only after a139 definitive authentication rejection; preserve them and expose a recoverable unavailable state on140 network, timeout, or downstream 5xx failures. Do not silently turn an outage into signed-out state.141- A logout that only clears platform cookies is not token revocation. Describe it accurately and142 plan a backend capability when revocation is required.143- A short-code `GET` only parses a sanitized display model; it never verifies or consumes the code.144 Completion uses `POST` to the current URL so no hidden field duplicates the credential. Return145 `Cache-Control: no-store`, `Referrer-Policy: no-referrer`, and `X-Robots-Tag: noindex`.146- Never serialize the raw code, raw target, password, or token through load/action data, rendered147 HTML, stories, logs, or error copy. After completion, use POST/redirect/GET to a clean status-only148 URL; collapse rejected, expired, consumed, or unknown targets when the service cannot safely149 distinguish them.150- Map service failures to stable user-facing error categories. Log only sanitized operational151 context on the server; do not expose service internals or secrets in UI copy.152153Progressive enhancement is required for essential authentication forms. JavaScript may improve154focus, pending state, and modal behavior, but it must not become the only path to submit or recover.155156## Localization157158Keep source messages in static, reviewable repository files. All visible product copy, accessible159names, validation messages, titles, and metadata use message keys; logs and protocol tokens do not.160161- Import the shared extraction/type/status preset and request-localization runtime from nodelib.162 Platform configuration supplies only its locales, paths, namespaces, and product exceptions.163- Keep the preset's framework extraction fixture in nodelib. Platform stories and tests exercise164 real product copy and translated UI states; they do not duplicate a generic toolchain fixture.165- Support plurals and contextual variants through the selected message format, not key166 concatenation or runtime grammar.167- Treat the source locale as authoritative. CI compiles messages and fails for missing, invalid, or168 stale translations using the repository's pinned tooling.169- When a product requires runtime message catalogs, keep the static catalogs as the runtime source170 of truth; generated type declarations or indexes are derived artifacts and are never hand-edited.171 Do not replace a requested message-catalog workflow with generated per-message functions merely172 for type safety.173- Resolve visible messages atomically where they render. Svelte components read the request- or174 tree-scoped i18next instance from the shared native context and call it with statically175 discoverable keys; they do not receive pretranslated screen-sized dictionaries.176- Declare each product message only in its static catalog. Do not mirror catalogs into `*Copy`177 interfaces, copy-builder functions, story-only dictionaries, or other programmatic message maps.178 Stable domain and action-result codes remain application state; translate them at the rendering179 boundary instead of returning visible prose from wiring.180- Install the same product-owned localization provider around the application, Storybook stories,181 and component-test render trees. Storybook global decorators and test wrappers may select locale182 and catalogs, but must not redeclare messages or substitute ad hoc copy objects.183- Under SSR, create request-scoped localization state. Never keep a mutable process-global locale184 or dictionary that can leak one user's language into another request.185- Pin any framework-specific extraction adapter and prove it against a representative component in186 CI. Prefer statically discoverable keys; prohibit dynamic key construction unless an explicit187 catalog allowlist keeps missing- and unused-key checks sound.188- Do not require a hosted translation service or a network call at build or runtime.189- Exercise long translations and at least one alternate locale in stories. Preserve logical layout190 and leave an RTL path even when the initial locales are left-to-right.191192## Container and health contract193194Ship one multi-stage root `Dockerfile` that builds the locked root package and runs the minimal195production SvelteKit server as a non-root user. Keep build-only credentials out of image layers and196the final image. Follow `write-dockerfiles` for implementation details.197198Build the real container in CI, not only the Node artifact. Run the frozen install inside the exact199image and keep its Node pin compatible with every locked dependency engine; a floating host CI200runtime can hide a stale image pin. When Corepack/pnpm configures a global binary directory, put201that exact directory on `PATH` before running global config commands. Pass private-registry202credentials as BuildKit secrets, remove temporary registry config in the same layer, and prove the203final image contains neither credential nor build-only tooling.204205Expose two distinct probes:206207- `GET /ping` is cheap liveness and has no downstream dependency. The container `HEALTHCHECK` uses208 it so a service outage does not restart a healthy platform process.209- `GET /healthcheck` is readiness/diagnostics. It fans out with bounded timeouts only to registered,210 typed dependencies the platform actually uses and returns a stable nested status map. Sanitize211 errors and preserve partial results so one failure does not hide the others.212213Never turn a request parameter into an arbitrary healthcheck URL. Configuration selects from known214clients, and concurrency plus deadlines bound the fan-out cost.215216## CI and publication217218The required pipeline runs format/style checks, type checking, localization validation, unit and219component tests, Storybook tests with accessibility checks, the production build, the static220Storybook build, dependency audit, and container build as applicable.221222Publish only the static Storybook artifact through the shared GitHub Pages action. Production223deployment remains the container's concern. A Pages workflow uses GitHub's supported artifact and224deployment actions, declares the Pages environment, and does not cancel an in-progress deployment.225Never include secrets, private environment values, source maps containing secrets, or production226API credentials in Storybook.227228## Completion checklist229230- Pure screens and their complete state matrix render in Storybook before route wiring lands.231- Keyboard, focus, narrow layout, long copy, reduced motion, and automated accessibility checks pass.232- Static catalogs are the sole declaration of visible messages; app, Storybook, and tests use the233 same request- or tree-scoped localization provider.234- URL codecs round-trip and invalid parameters normalize predictably; persisted preferences are235 versioned and SSR-safe.236- No authentication secret reaches browser storage, logs, client bundles, or serialized page data.237- Server actions and session transitions have unit tests, including expiry and downstream failure.238- `/ping`, `/healthcheck`, production build, Storybook build, and the container are verified.239- `pnpm lint`, `a-novel test --type=pnpm -y`, and `a-novel build --type=pnpm -y` pass.240- The live Storybook URL and any intentionally deferred end-to-end coverage appear in the handoff.