Fix the Stage 1 Dashboard UX defects found in assembled review
CI / verify (pull_request) Successful in 1m13s
CI / infrastructure-smoke (pull_request) Successful in 34s
CI / package-release (pull_request) Successful in 48s
CI / migration-smoke (pull_request) Successful in 2m22s
CI / image-build (pull_request) Successful in 1m8s
CI / deploy-smoke (same-origin) (pull_request) Successful in 49s
CI / package-publication (pull_request) Successful in 57s
CI / deploy-smoke (split) (pull_request) Successful in 3m38s
CI / verify (pull_request) Successful in 1m13s
CI / infrastructure-smoke (pull_request) Successful in 34s
CI / package-release (pull_request) Successful in 48s
CI / migration-smoke (pull_request) Successful in 2m22s
CI / image-build (pull_request) Successful in 1m8s
CI / deploy-smoke (same-origin) (pull_request) Successful in 49s
CI / package-publication (pull_request) Successful in 57s
CI / deploy-smoke (split) (pull_request) Successful in 3m38s
The assembled Stage 1 Dashboard was reviewed by hand on a desktop, with the sidebar collapsed, and on an iPhone. This fixes what that review found, and records the decisions in ADR 0064: - Discover is its own page at /settings/discover, with Installed and Updates beneath it as children. There is no Browse item any more. /settings/discover/browse is still read, as an alias, and is respelled in place. Parents and children now read as a hierarchy: a guide line, text-only children, a disclosure chevron, and parent and current states that look different. - The sidebar is navigation only. Display pairing moves from the sidebar and drawer to Settings -> Displays: the paired displays, a deliberate Add display dialog, and revocation. Nothing takes focus on arrival; the code field is focused only once Add display is chosen. - Collapsed icons are centred. Nav-link layout moves from inline styles to the shell stylesheet, and every collapsed item is one square centred on the rail. - /settings is an overview of the installation: displays paired, modules installed and running, and a line for each other area. It reads only existing endpoints. - Phones get a sticky, safe-area-aware header holding the menu trigger and the page name. New pages start at the top, and focus no longer scrolls content under the header. - @ekklesia/ui draws checkbox and radio indicators from tokens rather than accent-color, so the selected state is the strong accent in Light and Dark alike. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
186668915b
commit
405e509bcb
@@ -3,6 +3,7 @@
|
||||
- Status: Accepted
|
||||
- Date: 2026-10-14
|
||||
- Module-declared icons and pages decided by: [0063](0063-module-navigation-contract-manifest-v9.md)
|
||||
- Device panel placement and the phone menu placement superseded by: [0064](0064-stage-1-shell-review-corrections.md)
|
||||
- Extends: [0026](0026-minimal-ekklesia-module-ui-host.md),
|
||||
[0027](0027-dashboard-navigation-and-flat-modules.md),
|
||||
[0039](0039-shared-design-tokens-and-elements.md),
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
- Status: Accepted
|
||||
- Date: 2026-10-15
|
||||
- Module → Settings return link decided by: [0063](0063-module-navigation-contract-manifest-v9.md)
|
||||
- Discover's address, the landing page, and the device panel's move decided by: [0064](0064-stage-1-shell-review-corrections.md)
|
||||
- Extends: [0027](0027-dashboard-navigation-and-flat-modules.md),
|
||||
[0028](0028-module-operational-lifecycle.md),
|
||||
[0029](0029-module-configuration-and-secrets.md),
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
# 0064: Discover is its own page, the sidebar is navigation only, Settings opens on an overview, and phones get a sticky header
|
||||
|
||||
- Status: Accepted
|
||||
- Date: 2026-10-15
|
||||
- Supersedes, in part: [0058](0058-dashboard-shell-navigation-and-lucide-icons.md) (the device panel
|
||||
in the sidebar and drawer; "no application top bar" on phones),
|
||||
[0061](0061-settings-hierarchy-and-module-settings-routes.md) (Discover entered at
|
||||
`/settings/discover/browse`; the landing page as an index)
|
||||
- Extends: [0036](0036-managed-device-identity-and-pairing.md),
|
||||
[0039](0039-shared-design-tokens-and-elements.md),
|
||||
[0057](0057-dashboard-urls-are-host-owned-routes.md),
|
||||
[0060](0060-light-dark-system-theme-and-motion.md),
|
||||
[0063](0063-module-navigation-contract-manifest-v9.md)
|
||||
|
||||
## Context
|
||||
|
||||
The assembled Stage 1 Dashboard was reviewed by hand on a real desktop, with the sidebar expanded and
|
||||
collapsed, and on a real iPhone in Safari. The review found defects that no unit test had caught,
|
||||
because each only shows in the assembled product:
|
||||
|
||||
- **Discover and Browse were the same thing twice.** ADR 0061 made Discover a navigation item that
|
||||
opened Browse, at `/settings/discover/browse`, with Browse, Installed, and Updates listed beneath it.
|
||||
In use, "Discover" and "Browse" named one page, and the children looked as if they had simply
|
||||
appeared, with no relationship to Discover.
|
||||
- **The sidebar still held the Stage 0 device panel.** It showed a *Display devices* heading, a pairing
|
||||
code field, and the list of paired screens beneath the navigation. A form in the navigation is not
|
||||
navigation. The field also held focus as the page loaded on the review device, although nobody had
|
||||
asked to pair anything.
|
||||
- **Collapsed icons sat off centre.** `DashboardLink` set `justify-content: flex-start` inline. An
|
||||
inline style outranks the stylesheet, so the collapsed rule's `justify-content: center` never
|
||||
applied, and every glyph sat at the left of its 47px box, with 1px on its left and 26px on its
|
||||
right. The toggle above the items had its own, different geometry.
|
||||
- **The Settings landing page repeated the sidebar.** ADR 0061 called it "an index, not a dashboard":
|
||||
each section was named and described in a line. It listed exactly what the sidebar beside it already
|
||||
listed.
|
||||
- **On the phone the menu scrolled away.** ADR 0058 put the menu button above the page as "the one
|
||||
control there, not a bar". It scrolled out of reach with the page. It also did not respect the
|
||||
status bar or notch. A page opened from the drawer could start part-way down, because focusing
|
||||
`<main>` scrolled it to the very top of the viewport.
|
||||
- **The light theme's selected radio looked wrong.** Radios were native, tinted with
|
||||
`accent-color: accent.base`. Given an accent, each browser picks its own contrasting mark, and on
|
||||
the light theme's pale teal (`#4fb3a8`) that mark was a near-black dot and ring. The shared
|
||||
`Checkbox` in `@ekklesia/ui` used the same technique.
|
||||
|
||||
ROADMAP §5.3 and DESIGN.md §9.4 still name `/settings/discover/browse` as canonical. This decision
|
||||
reverses that at the maintainer's direction after the review, and the product documents need the
|
||||
same correction.
|
||||
|
||||
## Decision
|
||||
|
||||
### Discover is its own page, at `/settings/discover`
|
||||
|
||||
`/settings/discover` is where modules are found and installed. It is Discover's own page, titled
|
||||
*Discover modules*, not the parent of a separate Browse page. Its children, beneath it, are:
|
||||
|
||||
```text
|
||||
/settings/discover Discover: find and install modules
|
||||
/settings/discover/installed Installed: see and manage installed modules
|
||||
/settings/discover/updates Updates: module and platform updates
|
||||
```
|
||||
|
||||
- **No Browse item.** The navigation names Installed and Updates beneath Discover, and nothing else.
|
||||
- **The old address is an alias, never a link.** `/settings/discover/browse` still opens Discover's
|
||||
page, so a bookmark from before this decision keeps working. The shell then respells it in place to
|
||||
`/settings/discover` (`replace`, keeping any `?return=`), exactly as it corrects a trailing slash
|
||||
(ADR 0057). Nothing links to it.
|
||||
- **Internal name.** The route keeps `view: "browse"` as the internal name of Discover's own page.
|
||||
Only its address changes. The API already serves the Dashboard throughout `/settings/*`, so no server
|
||||
or proxy change is needed.
|
||||
|
||||
### Parents and children read as a hierarchy
|
||||
|
||||
The same treatment applies to every parent in the navigation: Discover with Installed and Updates,
|
||||
Settings with its sections, and a module with its declared pages (ADR 0063).
|
||||
|
||||
- **Children are text only**, marked `data-nav-level="child"`. On desktop they use the small control
|
||||
height and regular weight. In the drawer they keep the large touch target.
|
||||
- **They hang from a guide line** that runs down from beneath the parent's icon. Their text starts past
|
||||
the parent's label.
|
||||
- **Exactly one item is current** (`aria-current="page"`, selected fill, and weight). The parent of the
|
||||
open child is marked `data-nav-ancestor`: primary text, bold, and the strong accent on its icon, but
|
||||
no fill. A parent and its child are therefore never both "selected", and the relationship reads at a
|
||||
glance.
|
||||
- **An item that opens in place carries a disclosure chevron.** It points right while closed and down
|
||||
while open, and it rotates over `motion.duration-short`, which is zero when motion is reduced
|
||||
(ADR 0060). It is decorative: the nested list, named by its parent, carries the structure for
|
||||
assistive technology.
|
||||
- **Collapsed, the rail shows parents only.** Discover is current for every Discover page, and its
|
||||
children, which need the labels the rail has no room for, are not drawn. A module's pages were
|
||||
already hidden in the rail.
|
||||
|
||||
### The sidebar is navigation only; displays are paired in Settings → Displays
|
||||
|
||||
The device panel leaves the sidebar and the drawer entirely. Its working functionality moves, rather
|
||||
than being rebuilt, to **Settings → Displays** (`/settings/displays`). This is the move ADR 0061
|
||||
deferred, and the first platform Settings page that does real work. The page has:
|
||||
|
||||
- a page header with **Add display**;
|
||||
- **Paired displays:** one card per screen, with its name, a *Paired* or *Revoked* badge, when it was
|
||||
paired, when it was last seen, and **Revoke**;
|
||||
- an **Add display** dialog: the code shown on the screen, then **Find display**; then the display's
|
||||
one name, then **Pair display**;
|
||||
- the existing in-product confirmation before revoking;
|
||||
- empty, loading, failed (with Retry), and "not available to you" states.
|
||||
|
||||
It uses the same platform-owned display identity, the same endpoints (`GET /api/v1/devices`,
|
||||
`POST /api/v1/device-pairing/lookups` and `/approvals`, `POST /api/v1/devices/<id>/revoke`), and the
|
||||
same permission (`core.devices.manage`) as before (ADR 0036). No display concept is added.
|
||||
|
||||
The device list already returned `createdAt` and `lastSeenAt`, and the client now reads them.
|
||||
`lastSeenAt` is written at most every few minutes (`DEVICE_LAST_SEEN_THROTTLE_MS`), so the page says
|
||||
when a screen was last in touch, coarsely ("within the last hour", "3 days ago"). It never says a
|
||||
screen is online.
|
||||
|
||||
**Nothing takes focus by itself.** No page load or navigation focuses a form field. Focus moves into
|
||||
the code field only once someone has chosen **Add display**, because typing the code is then plainly
|
||||
the next thing to do. It moves to the name field once a display is found. The dialog's focus trap
|
||||
returns focus to **Add display** when the dialog closes. The button focuses itself before opening the
|
||||
dialog, because Safari does not focus a button that is clicked.
|
||||
|
||||
### `/settings` opens on an overview of the installation
|
||||
|
||||
`/settings` stays a real landing page, never a redirect (ADR 0061). It now answers "what state is this
|
||||
installation in?" instead of repeating the sidebar:
|
||||
|
||||
- **Displays:** how many are paired, and which was most recently seen. The link is *Manage displays*,
|
||||
or *Add a display* when none are paired.
|
||||
- **Modules:** how many are installed, and whether all are running or how many are turned off or
|
||||
cannot start. The links are *Manage modules* and each running module's *<name> settings*.
|
||||
- **General, Branding, and System:** one restrained line each about what is true today, such as
|
||||
*Using the default name and colours*, with the one link that changes it. There are no large
|
||||
*Not available yet* boxes.
|
||||
|
||||
The overview reads only existing data, and only for what this person may see:
|
||||
|
||||
- the Displays summary uses `GET /api/v1/devices`, read only when Displays is visible;
|
||||
- the Modules summary uses `GET /api/v1/module-lifecycle`, read only when Discover is visible. That
|
||||
endpoint requires `core.modules.lifecycle.manage`, the same permission Discover does.
|
||||
|
||||
A refusal or failure shows as a quiet *Couldn't check …* line. No backend API was added.
|
||||
|
||||
### Phones get a restrained sticky header
|
||||
|
||||
Below the compact breakpoint, a `<header>` holds the menu trigger and the open page's name. It is
|
||||
`position: sticky` at the top while the page scrolls, and the drawer opens from that stable trigger.
|
||||
It is not a desktop top bar and holds nothing else. The page name is a visual echo of the page's own
|
||||
`<h1>`, so it is `aria-hidden`. Wide screens still have no top bar.
|
||||
|
||||
- **Safe areas.** The Dashboard page declares `viewport-fit=cover`, so the shell lays itself out with
|
||||
`env(safe-area-inset-*)`:
|
||||
- the header clears the status bar or notch;
|
||||
- the page, the drawer, and a sidebar shown on a wide landscape phone clear the notch and the home
|
||||
indicator.
|
||||
- **Viewport height.** The shell's minimum height is `100dvh`, falling back to `100vh`, so Safari's
|
||||
collapsing toolbar never leaves the page a toolbar's height too tall.
|
||||
- **Scrolling and focus.**
|
||||
- A new page starts at its top, as a full page load would. Focus moves to `<main>` with
|
||||
`preventScroll`, so the page's top is never scrolled under the header.
|
||||
- Focus that returns to the trigger after the drawer closes does not scroll either.
|
||||
- `scroll-padding-top` keeps anything else scrolled into view, such as a focused field, below the
|
||||
header.
|
||||
- **Page margin.** A phone's margin is `spacing.lg` instead of `spacing.xl`.
|
||||
|
||||
### Collapsed geometry belongs to the stylesheet
|
||||
|
||||
A navigation link's layout and its open-page look are classes in the shell stylesheet: `ek-shell-link`,
|
||||
plus `[aria-current="page"]`. They are never inline styles. Collapsed, every navigation item has the
|
||||
same geometry, whatever its level:
|
||||
|
||||
- a `control.height-md` square;
|
||||
- `display: flex`, centred on the rail with auto margins;
|
||||
- no padding and no gap;
|
||||
- its glyph centred.
|
||||
|
||||
The rail scrolls without a visible scrollbar, which would otherwise take its width from one side. The
|
||||
tooltip, the keyboard order, and each item's visually hidden name are unchanged. No icon is nudged by
|
||||
a one-off offset.
|
||||
|
||||
### Choice indicators are drawn from tokens, in `@ekklesia/ui`
|
||||
|
||||
`@ekklesia/ui`'s stylesheet draws the checkbox (`ek-checkbox__input`) and a radio (`ek-radio__input`)
|
||||
itself, as native inputs with `appearance: none`. They are never tinted with `accent-color`.
|
||||
|
||||
- **Unselected:** a `border.control` ring on `background.surface`.
|
||||
- **Selected:** `accent.strong` in both themes. The radio is a ring, a gap of the surface, and a dot.
|
||||
The checkbox is a filled box with a surface-coloured check.
|
||||
- **Other states:** hover strengthens the ring to `border.strong`, disabled uses
|
||||
`state.disabled-opacity`, and forced colours use the system's own selection colours.
|
||||
- **Size:** a larger control sets `--ek-choice-size`.
|
||||
|
||||
The Appearance page's radios use this shared indicator. Its rows keep their own focus ring, so the
|
||||
radio inside draws none of its own.
|
||||
|
||||
No `Radio` component is added: ADR 0039 adds an element to `@ekklesia/ui` once two independent
|
||||
consumers need it, and the shell is the only one. The change is to the existing stylesheet and the
|
||||
existing `Checkbox`, whose selected state now matches. That is additive for modules: a module using
|
||||
`Checkbox` gets the same, now consistent, selected state.
|
||||
|
||||
## Alternatives
|
||||
|
||||
- **Keep Browse and make it look more like a child.** Rejected: the review's finding was that
|
||||
Discover and Browse are one concept. Two names for one page is the defect, not its styling.
|
||||
- **Remove `/settings/discover/browse` outright.** Rejected: it shipped, and a bookmark should not
|
||||
become *Page not found*. An alias respelled in place costs nothing and is never linked.
|
||||
- **Pair displays from a route of its own (`/settings/displays/add`).** Deferred, not rejected. A dialog
|
||||
keeps the routing contract unchanged and gets focus handling from the shared `Dialog`. A deep link
|
||||
to *Add display*, for example from Signage, can add a route later without changing this page.
|
||||
- **Keep the device panel, collapsed by default.** Rejected: the sidebar is navigation, and a form in
|
||||
it is the defect.
|
||||
- **Redirect `/settings` to General, or keep the index.** Rejected: the redirect, as in ADR 0061. The
|
||||
index repeated the sidebar.
|
||||
- **New backend endpoints for richer overview cards** (version, health, update counts). Rejected for
|
||||
now: the overview uses what exists, and System, Updates, and Branding gain real summaries with the
|
||||
work that builds them.
|
||||
- **A full top bar on every screen size.** Rejected: DESIGN.md §4.2 keeps navigation in the sidebar.
|
||||
Phones need only the menu kept within reach.
|
||||
- **Fix the radio by changing `accent.base`, or with page-specific colours.** Rejected: a browser's
|
||||
mark on an accent-tinted control is its own choice and differs between engines. Drawing the
|
||||
indicator from tokens is the only way to get one result in every browser and both themes.
|
||||
- **Offset individual icons to centre them.** Rejected: the cause was an inline style outranking the
|
||||
collapsed rule, and one geometry for every item removes it.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Discover's canonical address is `/settings/discover`. Nothing links to `/browse`, and it respells
|
||||
itself away.
|
||||
- The shell no longer asks for the device list on every page. Displays are read on Settings →
|
||||
Displays and the Settings overview, for people allowed to see them.
|
||||
- Released Signage 0.1.5 tells administrators to enter a screen's code "under Display devices in the
|
||||
Dashboard sidebar". That text is now stale and must change in Signage. The pairing itself, and every
|
||||
paired display, is unaffected.
|
||||
- `@ekklesia/ui`'s checkbox looks different when checked: the strong accent instead of the browser's
|
||||
accent-tinted control. Its behaviour and markup are unchanged.
|
||||
- ROADMAP §5.3 and DESIGN.md §9.4, §12.1, and decision 98 still describe Browse at
|
||||
`/settings/discover/browse` and need the same correction.
|
||||
|
||||
## Deferred
|
||||
|
||||
The real General, Branding, System, Installed, and Updates pages, and richer overview summaries with
|
||||
them. Display rooms or locations, rename, and history (DESIGN.md §11). A routable *Add display* for
|
||||
modules to link to. Signage consuming platform displays (ROADMAP §5.8).
|
||||
@@ -66,9 +66,10 @@ ADRs capture durable choices that affect more than one package or phase.
|
||||
- [0055: Native redistributable packages carry corresponding-source artifacts](0055-corresponding-source-artifacts.md) — Accepted
|
||||
- [0056: A device credential lasts while the device is in use](0056-device-credentials-last-while-in-use.md) — Accepted
|
||||
- [0057: Dashboard URLs are real, host-owned routes](0057-dashboard-urls-are-host-owned-routes.md) — Accepted
|
||||
- [0058: The Dashboard shell's navigation, and Lucide as Ekklesia's icon vocabulary](0058-dashboard-shell-navigation-and-lucide-icons.md) — Accepted
|
||||
- [0058: The Dashboard shell's navigation, and Lucide as Ekklesia's icon vocabulary](0058-dashboard-shell-navigation-and-lucide-icons.md) — Accepted (device panel and phone menu placement superseded by 0064)
|
||||
- [0059: Shared interaction primitives — confirmation, toasts, errors, loading, Back, and no native dialogs](0059-shared-interaction-primitives.md) — Accepted
|
||||
- [0060: Light, Dark, and System appearance; reduced motion; and one radius scale](0060-light-dark-system-theme-and-motion.md) — Accepted
|
||||
- [0061: Settings is one host-owned hierarchy; module lifecycle is Discover, and a module's settings live at `/settings/<module-id>`](0061-settings-hierarchy-and-module-settings-routes.md) — Accepted
|
||||
- [0061: Settings is one host-owned hierarchy; module lifecycle is Discover, and a module's settings live at `/settings/<module-id>`](0061-settings-hierarchy-and-module-settings-routes.md) — Accepted (Discover's address and the landing page superseded by 0064)
|
||||
- [0062: The account area, and appearance preferences that belong to the account](0062-account-area-and-synced-appearance-preferences.md) — Accepted
|
||||
- [0063: Module navigation — manifest v9 icons and pages, real module routes, and return-to-origin](0063-module-navigation-contract-manifest-v9.md) — Accepted
|
||||
- [0064: Discover is its own page, the sidebar is navigation only, Settings opens on an overview, and phones get a sticky header](0064-stage-1-shell-review-corrections.md) — Accepted
|
||||
|
||||
Reference in New Issue
Block a user