Stage 1 PR 7 (ADR 0063). A module becomes a first-class part of the Dashboard.
- Manifest v9 = v8 plus optional `navigation`: a Lucide icon from the curated
MODULE_NAVIGATION_ICONS set, and up to 12 primary pages (stable id, safe
label, kebab-case path relative to the module root; no traversal, schemes,
queries, duplicates, or nesting). Validated before activation.
- Version 8 stays accepted beside version 9, each read strictly as itself
(discriminated union); no released manifest is rewritten. The template
moves to v9; the released Signage 0.1.5 manifest is a test fixture.
- Real module routes: /<id> is the landing view, /<id>/<page> a declared
page, anything beneath a page a module-owned detail route; anything else
is not-found and mounts nothing. api/assets/display are never pages.
- Sidebar: declared icons, pages nested under an open module, one
aria-current, collapsed and drawer behaviour; v8 modules unchanged.
- module-ui-sdk: ModuleUiHostContext.navigation (location, subscribe,
navigate, link, settings), refusing anything outside the module's routes;
undefined on the display host. Mount ABI unchanged.
- Return-to-origin: one `?return=` carrier on Settings URLs, validated by
parseReturnTarget and against running modules' declared routes; Settings
Back returns to exactly the originating module page, across refreshes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Stage 1's later screens and modules need the interactions DESIGN.md
fixes once for the product (ADR 0059). @ekklesia/ui gains them,
additively:
- ConfirmationDialog: simple, destructive, or typed-phrase
confirmation, with severity chosen by the caller. Destructive is
marked by symbol and frame, not colour alone, and starts on Cancel.
A required phrase must match exactly (forgiving only surrounding
whitespace and NFC). While busy, Cancel and Escape are disabled.
- ToastProvider / useToasts: a top-center toast system per React tree,
never a package singleton (ADR 0040). Live regions are mounted
before any toast arrives; errors are assertive and persist until
dismissed; routine toasts last at least three seconds and longer for
longer text. Timers pause on hover and focus; duplicates refresh;
errors are never displaced; every timer is cleared on unmount.
- ErrorState: recovery actions, plus collapsed technical details and a
reference, rendered as bounded inert text.
- Skeleton / SkeletonGroup: an aria-busy region labelled once, with
every animation stopped under prefers-reduced-motion.
- PageHeader `back` (link or button) above the page's one h1.
- Dialog: the focus trap ADR 0039 deferred, plus focus restoration and
an accessible description. Escape is now handled on the dialog
itself, so a drawer it was opened from stays open.
The shell validates Back targets (parseReturnTarget, dashboardBack):
only a canonical Dashboard page on this origin is accepted, otherwise
Back goes to the page's parent. It never uses history.back().
Biome's scope-aware suspicious/noAlert is now an error in the root
biome.json. tests/native-dialogs.test.ts proves it catches real
dialogs and not lookalikes, and scans for the forms the rule misses.
It supersedes the shell-only regex test.
Co-Authored-By: Claude Opus 5.5 <[email protected]>