Add the account area and account-synced Appearance preferences
CI / verify (pull_request) Successful in 1m10s
CI / infrastructure-smoke (pull_request) Successful in 34s
CI / package-release (pull_request) Successful in 50s
CI / migration-smoke (pull_request) Successful in 2m26s
CI / image-build (pull_request) Successful in 1m12s
CI / deploy-smoke (same-origin) (pull_request) Successful in 42s
CI / package-publication (pull_request) Successful in 54s
CI / deploy-smoke (split) (pull_request) Successful in 3m17s

Stage 1 PR 6 (ADR 0062). /account shows who is signed in; /account/appearance
chooses System, Light, or Dark and reduced motion (System, Reduce, Allow).

- Core: ekklesia_core_user_preference, one typed, CHECK-constrained,
  nullable column per preference (null = never chosen), migration
  0008_user_preferences (additive), @ekklesia/core/account store, and the
  authenticated-baseline permission core.account.self.manage.
- API: GET /api/v1/account (name, email, preferences) and partial
  PATCH /api/v1/account/preferences, self-only by construction: no user id
  is accepted anywhere; trusted Origin and body limit on the mutation.
- Shell: the account control shows name, email, and initials (never the
  unvetted image URL) and offers Account, Appearance, Sign out; native
  radio groups apply a choice at once through the ADR 0060 controller and
  save it; the server's answer wins and a failed save reverts.
- First paint: a classic, same-origin, render-blocking appearance.js in
  <head> applies a two-word localStorage cache before paint (no inline
  script, CSP unchanged). The account replaces the cache on every load,
  sign-out clears it, and other tabs follow via the storage event.
- Deploy smoke: real-Chromium checks for light/dark, OS emulation,
  overrides, reload persistence, pre-paint, Shadow DOM modules, reduced
  motion, the display staying light, and phone width; the browser
  container gets a 512 MiB /dev/shm so a long session no longer fails
  requests with ERR_INSUFFICIENT_RESOURCES.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ekklesia
2026-10-14 23:56:50 -05:00
co-authored by Claude Opus 5.5
parent 8571e6854c
commit cd0287537e
88 changed files with 6404 additions and 158 deletions
+10 -1
View File
@@ -112,6 +112,8 @@ list:
| `core.dashboard.navigation.manage` | `restricted` | Organize the Dashboard: create, rename, order, and delete navigation groups. |
| `core.modules.lifecycle.manage` | `restricted` | Read and change which installed modules are enabled. |
| `core.modules.configuration.manage` | `restricted` | Read and change an installed module's own configuration values. |
| `core.devices.manage` | `restricted` | Approve display device pairing, list paired devices, and revoke a device. |
| `core.account.self.manage` | `authenticated` | Read your own account and read and change your own preferences. |
Every Core permission is a **platform** permission and can never be declared, gated, or influenced by
a module — `core.*` is a reserved namespace (see below). `core.modules.lifecycle.manage` gates both
@@ -122,6 +124,13 @@ milder version of the write, because the lifecycle view is the only place a *dis
`core.modules.ui.read` deliberately covers only what is mountable. Holding it confers no authority
inside any module.
`core.account.self.manage` is the one Core permission every signed-in person holds by baseline
besides `core.modules.ui.read`: choosing your own appearance is not church administration
([ADR 0062](../architecture/decisions/0062-account-area-and-synced-appearance-preferences.md)). It is
**self-only by construction** rather than by a check: the account routes take the account from the
session and accept no user id at all, so the permission, however it is held, confers nothing over
anyone else's account. A device never satisfies its `authenticated` baseline.
`core.modules.configuration.manage` is deliberately **separate from** the lifecycle permission
([ADR 0029](../architecture/decisions/0029-module-configuration-and-secrets.md)): deciding whether a
module runs and deciding how it behaves are different authorities, and someone trusted to tune a
@@ -235,7 +244,7 @@ generic explicit-grant HTTP API, enforcement middleware, and now its own mounted
`pnpm doctor`'s `modules.permissions` check composes the platform permission registry from the
same already-discovered module list `modules.discovery`/`modules.dependencies` already produced —
it never re-discovers modules itself. A successful composition reports a pass with a safe
declaration count (for example `5 permissions registered` with zero installed modules, since Core's
declaration count (for example `7 permissions registered` with zero installed modules, since Core's
own declarations are always present); zero installed modules still composes successfully. If module
discovery itself failed, the check is skipped, matching the existing schema/migration checks'
pattern; if discovery succeeded but composition failed, schema and migration checks still run