Add the module navigation contract: manifest v9, module routes, return-to-origin

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]>
This commit is contained in:
ekklesia
2026-10-15 08:21:50 -05:00
co-authored by Claude Opus 5.5
parent 7447381c8f
commit 4671622b9d
69 changed files with 4221 additions and 372 deletions
+13 -2
View File
@@ -532,8 +532,10 @@ reported `blocked · configuration_missing · bucket` — never imported, never
from inside its own `start` hook — and its required dependents block behind it through ADR 0028's
existing mechanism. Doctor and `pnpm modules:validate` report the same thing while still importing no
module code. That change introduced `manifestVersion: 5` as a hard cutover; the current strict
manifest is version 8 after ADR 0050 added the optional database ABI declaration. Version 8 is a
hard cutover; a version 7 source module or package must be updated and rebuilt.
manifest was version 8 after ADR 0050 added the optional database ABI declaration, a hard cutover
that requires a version 7 source module or package to be updated and rebuilt. Version 9 (ADR 0063)
adds optional module navigation, and is the first version that keeps its predecessor accepted:
released version 8 modules run unchanged.
**Ekklesia does not store secrets, and does not pretend to.** There is no encryption at rest here and
no key management, so a `secret` key's value is supplied by the deployment
@@ -951,6 +953,15 @@ and updated (`/settings/discover/browse`, `/installed`, `/updates`); configuring
Settings page. A person sees only what the server says they may administer, and no Settings link at all
when that is nothing. The pages are placeholders until each section is built.
**Modules are first-class parts of the Dashboard**
([ADR 0063](docs/architecture/decisions/0063-module-navigation-contract-manifest-v9.md)). A module
declares its Lucide icon and its primary pages in manifest version 9, and the sidebar shows those
pages beneath it while it is open. Every page has a real address: `/signage/timelines`, and routes
beneath a page such as `/media/sermons/123`. A refresh, a bookmark, and Back and Forward all stay put.
A module moves between its own pages, and opens its Settings page, through a narrow host navigation
capability. Settings' Back returns to exactly the module page that opened it. Released manifest
version 8 modules keep working unchanged, with the generic icon and a single page.
**Your account** is at the bottom of the sidebar, showing your name, email, and initials
([ADR 0062](docs/architecture/decisions/0062-account-area-and-synced-appearance-preferences.md)).
`/account` shows who you are signed in as. On `/account/appearance` you choose **System, Light, or