# 0061: Settings is one host-owned hierarchy; module lifecycle is Discover, and a module's settings live at `/settings/` - Status: Accepted - Date: 2026-10-15 - Extends: [0027](0027-dashboard-navigation-and-flat-modules.md), [0028](0028-module-operational-lifecycle.md), [0029](0029-module-configuration-and-secrets.md), [0057](0057-dashboard-urls-are-host-owned-routes.md), [0058](0058-dashboard-shell-navigation-and-lucide-icons.md), [0059](0059-shared-interaction-primitives.md) ## Context DESIGN.md makes Settings the one home for shared administration (§9): church-wide configuration in Settings, personal configuration under the account area (§9.2), every module's settings reachable through Settings rather than an independent system inside the module (§9.3), and people without management permission not shown administration they cannot carry out (§9.2). ROADMAP §5.3 asks that the navigation and ownership model exist before every category is functional. ADR 0057 reserved `/settings` and served the Dashboard page throughout it, but defined no page there. Three things had to be decided before any section could be built: the address of each page, how the Settings hierarchy sits in the shell's one sidebar (ADR 0058), and how the browser learns which Settings destinations a person may see — it knew only that they were signed in, plus each module's own permission hints (ADR 0026). ## Decision ### The URL model ```text /settings the Settings landing page /settings/general General: how this installation works for everyone /settings/branding Branding: the church's name, logo, and colours /settings/displays Displays: paired screens /settings/discover enters Discover at Browse (respelled in place, below) /settings/discover/browse Browse: find and install modules /settings/discover/installed Installed: see and manage installed modules /settings/discover/updates Updates: module and platform updates /settings/system System: version, health, and diagnostics /settings/ that module's settings /settings//… reserved for a module's deeper settings pages ``` The distinction is the decision: - **`/settings/discover/…` is module lifecycle** — finding, installing, turning on and off, inspecting, updating, and eventually removing modules. - **`/settings/` is configuring that module.** It is never nested under Discover or under a `modules` segment: a module's settings are a first-class Settings destination. **"Modules" is the product's word**, in the interface as in the architecture — never "features", "apps", "extensions", or "add-ons". "Discover" is the name of a navigation destination, not a replacement for it: inside Discover the pages say "Discover modules", "Installed modules", and "Module updates". Implementation terms — Core, a platform module — never appear. **Entering Discover opens Browse, at one address.** `/settings/discover` reads as Browse, whose canonical spelling is `/settings/discover/browse`; the shell respells it in place (`replace`, no new history entry), exactly as it corrects a trailing slash (ADR 0057). There is one meaningful Browse address rather than two, and Discover's own item in the navigation links to it. **`/settings` is a real landing page**, never a redirect to the first section: the two groups the person may open, each item named and described in a line. It is an index, not a dashboard. Anything deeper than these addresses is `not-found` in the shell until its page exists. The API already serves the page throughout `/settings/*`, and the proxies already forward it (ADR 0057), so deeper pages need no server or deployment change. ### Module ids cannot collide with a Settings section A module id is a **stable URL identifier**: its page is `/` and its settings `/settings/`. So the platform's own Settings section names join the top-level names in Core's one list, `RESERVED_MODULE_IDS` (`core/src/modules/reserved-ids.ts`), now in two documented groups: | Group | Names | | --- | --- | | Top-level namespaces (`/`) | `account`, `api`, `assets`, `display`, `settings` | | Settings sections (`/settings/`) | `branding`, `discover`, `displays`, `general`, `system` | Discovery refuses a module that declares any of them (`MODULE_ID_RESERVED`), at startup, in `pnpm modules:validate`, and in package verification. Look-alikes stay valid — `general-info`, `discovery`, `display-board`, `systems` — and so does `modules`, since module management is Discover. Discover's views live beneath `/settings/discover/`, so they cannot collide with a module and are not reserved. `apps/api` tests hold Core's Settings group equal to the shell's `SETTINGS_SECTIONS`, so a section cannot be added to one without the other. ### Settings sits in the same sidebar Settings is one item in the shell's one navigation (ADR 0058), after the modules and above the account area. While a Settings page is open it expands in place into two labelled groups — **Platform** (General, Branding, Displays, Discover, System) and **Modules** (each module's settings) — and inside Discover, Browse, Installed, and Updates beneath it. The rest of the navigation stays where it is, so leaving Settings is one click on any other item; it is not a second application with its own shell. The phone drawer shows the identical hierarchy. Exactly one item carries `aria-current="page"`. ### Which destinations a person sees is evaluated by the server `GET /api/v1/dashboard/settings` (`apps/api/src/dashboard-settings-routes.ts`), readable by any signed-in person (`core.modules.ui.read`, like the navigation beside it), answers which sections and which modules' settings this person may see, through Core's one authorization evaluator: | Destination | Shown to | | --- | --- | | Displays | `core.devices.manage` | | Discover | `core.modules.lifecycle.manage` — deciding which modules run | | a module's settings | `core.modules.configuration.manage` (ADR 0029), for every module that declares configuration | | General, Branding, System | the **platform owner** only, until each defines its own permission | General, Branding, and System have no permission yet because nothing in them exists to authorize. Rather than invent three speculative permissions, they are shown only to the platform owner — the one principal who already holds every permission — and each gains a delegable permission in the work that builds it. Platform updates in Discover → Updates will likewise get their own permission then. A module **has settings** when its manifest declares configuration (ADR 0029). That is the existing, authoritative signal; no manifest change is needed. A disabled or blocked module with settings is listed with `available: false`. These are **presentation hints, not authority**, as ADR 0026's are. Every route behind a section keeps its own enforcement, unchanged, and a test proves the data behind each is still refused without its permission. An evaluation failure is a `503`, never an empty answer that would look like a permissions problem. The browser decides nothing about permissions itself. ### What each Settings address shows - A section or module the person may see shows its page. - A section the person may not see, or a module they know of but cannot configure (or that has no settings), shows *These settings aren't available* — without saying which. - A disabled module's settings show *This module is turned off*. Never a silent redirect. - A name that is no module the person knows of is *Page not found*. - When the Settings view cannot be loaded, the Settings link is withheld and its pages say so, with Retry; the rest of the Dashboard keeps working. - When a person can see nothing in Settings, the Settings link is not shown at all. ### Pages are honest placeholders Every section, Discover view, and module's settings page in this decision is a placeholder: Back to Settings (`PageHeader`, ADR 0059), a title and one-line description, and *Not available yet* with what will happen there. No fake controls, no dead fields. The work that builds each replaces its placeholder. Back goes to the page's parent, Settings. A validated return target (ADR 0059) takes precedence once something carries one, so `/signage` → `/settings/signage` can return to `/signage`; how a module links into its settings with a return target is the module navigation contract's to define. ## Alternatives - **`/settings/modules` for module management, and `/settings/modules/` for a module's settings.** Rejected: it nests a module's configuration under its lifecycle and makes "configure Signage" two levels deep. Discover is lifecycle; `/settings/` is configuration. - **`/settings/discover` as a page of its own beside `/settings/discover/browse`.** Rejected: two meaningful addresses for one view. - **Redirecting `/settings` to the first section.** Rejected: a landing page says what Settings holds, and the first section differs per person. - **Replacing the module navigation with the Settings hierarchy while in Settings.** Rejected: DESIGN.md keeps one sidebar with Settings in it (§4.2), and leaving Settings should not require a second navigation model. - **Probing each section's API for a `403` from the browser.** Rejected: several requests per page view, and it confuses "may not" with "failed". - **Defining General, Branding, and System permissions now.** Rejected as speculative; the owner-only interim rule is deny-by-default with the owner's existing bypass. - **A manifest field declaring "has settings".** Unnecessary: declared configuration already says so; module-owned settings pages arrive with the module navigation contract. - **Reserving Discover's view names.** Unnecessary: they live under `/settings/discover/`. ## Consequences - Every later Settings section has an address, a navigation entry, a permission rule, and a placeholder to replace. - A module may not be called `branding`, `discover`, `displays`, `general`, or `system`. No known module is; one that was would fail discovery and must be renamed. - The shell asks one more endpoint at bootstrap, alongside the navigation. - A person without any administrative permission sees no Settings link. ## Deferred The real General, Branding, Displays, Discover (catalog, installation, lifecycle, updates), and System pages, and each one's permission; module settings screens and their deeper pages; how a module links to its settings with a validated return target (the module navigation contract, manifest v9); moving the transitional device panel into Settings → Displays; and the account area.