Owner-approved design (2026-09-20): a Keys section right after General,
two groups over the merged auth API (GET /api/keys masked statuses,
PUT/DELETE /api/keys/:id, immediate effect, shape-gated 400s):
- Model providers: OpenRouter pinned as an always-visible row (the
app's lifeline — synthesized when absent so first boot shows it),
stored provider keys render as they appear, and an Add-provider
dropdown lists every pi-ai provider that takes an API key. The
dropdown reads GET /api/keys' optional providers array — the
Platform interlude (master-routed) derives it from pi-ai's own
env-key table; until it lands the group shows a pending note
(rolePins pattern — absent field, no drift, no guessed routes).
- Tools & services: the curated rows — Deepgram (dictation) and the
Google pair (search key + CSE ID) — with description hints and docs
links (external links route through the Electron shell to the real
browser).
- Rows: always-visible paste inputs (owner choice; no reveal —
plaintext never returns from the store), masked status line
(…tail · chars · environment|saved), the shadowedByEnv explanation
when a real env var owns the name, per-row inline 400 messages, Save
on Enter/button, Delete when present (env-owned keys honestly keep
their env status after a file delete).
- Unknown auth.json vars surface as read-only dimmed rows — preserved
by the store, not editable through the curated surface.
classifyKeys/keyStatusLine pure exports drive the two-group
projection; tests: SSR loading state, first-boot synthesis, provider
row classification, addable filtering, unknown-var read-only status
line formatting. Nav grows to six sections (Keys second).
Live-verified: nav order, structure, bad-paste 400s inline, save
round-trip with env-shadow explanation, delete round-trip, env-owned
OpenRouter status stability. Gates: typecheck, lint, 748/748, build.
The validation surface for the settings foundation — deliberately thin,
real read/write against the delta record, polish is a later wave.
Mount (owner decisions): dedicated page, single scroll; entry = sidebar
bottom row + ⌘/Ctrl+, (open-settings in the registry); chat defaults NOT
shown (they live on /chat/new); mode dark-only (light palette = design
wave); final tool list = research/demo/visual/pdf (master-adjudicated).
Sections (PUT /api/settings/[section], replace-whole per section):
- General: auto-update toggle.
- Agent: six background-role model+thinking selects (Default = config
pin; a stored id stays VISIBLE even when the catalog hasn't loaded or
dropped it — a select showing Default over a live override would lie),
the four tool toggles, prompt override switches (file-gated) with
source/override text editors over the segment routes.
- Appearance: applied LIVE as runtime CSS variables by an
AppearanceProvider at the root layout — proseFontSize → the one
reading-size var, fontFamily → --font-prose, background → --background
+ the derived raised/accent/edge palette (dark curve, today's exact
spacing: +8/+22/+47 per channel, clamped), sessionNameSerif →
--font-title (titles flip with ZERO chat.tsx edits), headingWeight →
new --lh-heading-weight token, bold/italic colors → the lh-ink vars.
Mode renders honestly DISABLED.
- Graph: the ladder grays a rung when any ANCESTOR is effectively off
(stored values persist, resolve inert — switch shows stored, graying
shows effective).
- Keyboard: view/rebind/reset over the registry; overrides persist in
lh:keymap and apply INSTANTLY (dispatcher reads per keydown); bare
modifier presses are ignored while armed; displayOnly actions list but
never rebind.
First-boot fix: {settings: null} is no-record, not fetch-failure.
CROSS-LANE (flagged for master): src/core/keyboard.ts gained the
open-settings action/binding and exports saveOverrides/loadOverrides
(one-liners; no existing pins); app/layout.tsx mounts the provider.
Live-verified: structure, record writes (general/tools/graph/appearance),
prose-size applying live (16→20px), rebind + chord navigation from
another page, ladder graying, no console errors. Gates: typecheck,
lint, 689/689 (28 new), build.