* feat(ui): save appearance preferences per user profile
When a Control UI connection is bound to an authenticated user profile
(trusted-proxy, Tailscale, GitHub identity), theme, theme mode, and accent
become per-profile: stored in the existing user_preferences KV store via
users.prefs.set, overriding gateway-wide ui.prefs, with live cross-device
updates over a new additive users.prefs.changed event scoped to the same
merged profile. Restore default deletes only the profile key and falls back
to the gateway-wide value. talk.config projects the caller's profile accent
for native clients. Profile-less connections (token/password/none auth)
keep the existing gateway-config behavior byte-identically.
Release-note context: on multi-person gateways appearance choices are now
personal and follow you across devices; an admin restyling their own UI no
longer reskins the whole team.
* chore(protocol): allowlist users.prefs.changed for mobile clients
iOS and Android resolve the profile accent through talk.config on connect
and config refresh; live per-profile appearance push on natives is a named
follow-up.
* chore(protocol): regenerate Swift/Kotlin models for users.prefs.changed
* fix(ui): honor explicit defaults and identity switches for profile appearance
Addresses the three ClawSweeper review findings:
- resetValue for profile-bound appearance keys is the deletion fallback
(gateway value), so an explicit product-default selection persists as a
profile write instead of being misclassified as a reset.
- An identity switch between two known scopes forces a full appearance
reconcile and clears appearance keys the returning identity never set,
so a shared browser cannot keep the previous profile's look (boot keeps
the last-seen shortcut).
- talk.config joins the profile-dependent dispatch gate so a pending
GitHub identity sync completes before the profile accent is projected;
token clients pass through untouched.
* chore(ui): satisfy assertion-safety ratchet and docs formatting
* refactor(ui): extract server-prefs storage primitives
server-prefs.ts crossed the 700-line cap after the review fixes; the
stateless localStorage persistence primitives move to
server-prefs-storage.ts (no behavior change). Shrink-prunes the
assertion-safety baseline for the moved casts.
* fix(ui): keep imported custom themes out of profile storage
Custom palettes are browser-local by contract, so a profile must never
carry theme=custom to a browser that cannot render it. The wire contract
drops custom from the storable theme values (readers self-heal any stored
value), a profile-bound custom selection stays retained browser-local
(including the offline-queued path), and the exhaustive theme test now
encodes the exception.
* feat(ui): user-selectable accent color for the Control UI
Adds an Accent color section to Settings -> Appearance: curated preset
swatches plus a native custom color input. The choice persists as the
gateway-synced ui.prefs.accent (#rrggbb) and takes precedence over the
operator-level ui.seamColor, falling back to the active theme's accent
when cleared. The shared apply path now also derives readable
--accent-foreground/--primary-foreground ink from accent luminance, so
light accents keep dark text on primary controls.
Bundled accent-invariant cleanup: the embedded terminal resolves its
background/cursor/foreground from live computed tokens instead of a
hand-copied claw palette (fixes desync on knot/dash/custom themes),
a baked rgba(255,92,92) gradient now uses color-mix over --accent, and
duplicated diff-tint literals collapse into --danger-subtle/--ok-subtle.
Environment branding moved behind a lazy runtime module (-2.5KB gzip
startup JS).
* fix(ui): derive status glows and tints from semantic tokens
Status-dot glows, callout gradients, compaction-indicator borders, chip
borders, and dreams pulse animations baked literal green/blue/amber/red
rgba values -- several stale copies of old --danger/--info hexes -- so
they ignored theme families and light mode's deeper status hues. All 22
sites now color-mix over --ok/--warn/--warn-strong/--danger/--info.
* fix(ui): scope default accent swatch chip vars to a swatch class
Reusing .settings-theme-card--<theme> on the accent swatch made theme-card
Playwright locators resolve to two elements (strict-mode violation in the
prefs-reconnect e2e suite). The default swatch now uses
.settings-accent-theme--<theme>, added alongside the theme-card selectors
in the theme-invariant chip var blocks.
* fix(ui): carry --primary-hover through the accent override
The accent apply path set --primary but not --primary-hover, so dark
Claw/Knot primary buttons reverted to the theme hover color while an
accent was active (ClawSweeper P2). Derive it from --primary like the
accent hover, clear it on reset, and prove the resolved hover color via
a painted probe in the appearance e2e.
* fix(ui): give embedded settings sections the shared section rhythm
The agents tab panel hosted settings sections in a bare div, so sections
stacked with zero separation; the bespoke .agents-main margin rule in
agents.css missed the nested tabpanel entirely. Descriptions also pulled
up to 4px under control-height header actions (squeezed Verify/Save rows).
Add a .settings-stack primitive to settings.css for embedded surfaces,
use it on the agent tab panel, delete the page-local margin fork, and let
section descriptions clear action-bearing headers.
* test(agents): export getRuntimeConfigSourceSnapshot from runtime-snapshot mock
Main's checks-node-compact-large shard is red: #126531 routed
provider-model-routes through projectConfigOntoRuntimeSourceSnapshot,
which reads getRuntimeConfigSourceSnapshot, and this suite's explicit
vi.mock factory did not export it (24 failures). Return null so the
projection no-ops and resolvers keep reading the provided config.
* fix(ui): soften default dark-mode body text to ~12:1 contrast
Drop --text/--chat-text from #d4d4d8 (12.87:1 on --bg) to #cdcdd1
(12.01:1 on --bg, 11.10:1 on --card) so dark body text sits inside
the 10-12 comfort band instead of above it; brighter body text
halates on dark backgrounds. Light mode, openknot, dash, --text-strong,
and --muted are unchanged.
* fix(ui): dim dark body text a further step to ~11.4:1
#cdcdd1 -> #c8c8cc (11.41:1 on --bg, 10.54:1 on --card), mid-band
instead of top-of-band.
* fix(ui): settle dark body text at ~10:1 on --bg
#c8c8cc -> #bcbcc0 (10.05:1 on --bg, 9.29:1 on --card) per visual
preview pick; low end of the dark comfort band on the page, slightly
under it on cards, still well above AAA.
* docs(ui): sync design-system guides with new dark --text token
ClawSweeper P3: color-tokens.md and accessibility.md still published
#d4d4d8 (~12.9:1); both now name #bcbcc0 (~10.1:1).
Shadow-DOM components carried fallback colors that never matched the
actual tokens: panel-tab-strip fell back to teal #4ec9a8 for the coral
--accent, resizable-divider to Bootstrap blue #007bff, and --border to a
generic #333. Align all fallbacks with the claw-family values in
styles/base.css so token-less rendering matches the product palette.
Also document the intentionally dark image-lightbox chrome and the
terminal theme's hand-mirrored token values, and regenerate the drifted
values in ui/docs/design-system/color-tokens.md (--muted, --muted-strong,
--primary, --danger, --info, light status colors) from base.css truth.
* feat(ui): add settings design-language foundation (tokens, settings.css, helpers)
* refactor(ui): move settings workspace shell styles into settings.css
* refactor(ui): migrate all settings surfaces to the unified settings design language
Every settings surface (General quick+advanced schema form, Appearance,
Communications, Automation, MCP, Infrastructure, AI Agents, Channels,
Connection, About, Profile, Model Providers, Plugins, Skills, Skill
Workshop chrome, Devices, Worktrees, Debug, Logs, Activity, Automations,
Sessions, Usage chrome) now renders through the settings-ui helpers and
settings.css: sections as plain uppercase headings outside one flat
group surface, hairline-divided rows, a single toggle/segmented/status
vocabulary, status dots instead of pills, and no cards-in-cards or enter
animations. Deletes the qs-*/cfg-*/config-section-card/account-card/
plugins-row/cron-editor-card/session-badge/agent-pill families and
orphaned shared rules.
* style(ui-docs): format design-system docs
* fix(ui): make activity statusKind lookup exhaustive for consistent-return lint
* fix(ui): address settings design-language review findings
Label-wrapped toggle rows with required accessible names, named selects
and cron form controls, busy-disabled segmented controls with restored
theme-transition anchors, collapsible skill groups, semantic plugin
headings, gateway connected status, paired-devices empty state, logs
subtitle, cron prompt help, and keyboard-accessible session goal
objectives.
* fix(ui): harden settings primitives per review round two
Wrap row action clusters, fully-inset nav focus ring, restore number
steppers in the schema form, cap node capability lines, keep the named
Activity region landmark, and associate cron help text via the row
label.
* fix(ui): skip empty section headings and fix the toggle doc example
* docs(ui): import renderSettingsToggleRow in the settings example
* fix(ui): rename number stepper delta to avoid shadowing
* feat(ui): session-first sidebar, context ring, and warm light theme for Control UI
* test(ui): drop unnecessary non-null assertion in sidebar recents test