mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 17:53:39 +00:00
* 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.
77 lines
8.8 KiB
JSON
77 lines
8.8 KiB
JSON
{
|
|
"$comment": "Gateway events each mobile client intentionally does not handle yet, with a one-line reason. Consumed by scripts/check-protocol-event-coverage.mjs (pnpm check:protocol-coverage). Adding a gateway event without a client handler requires either handling it or adding an entry here; the check also fails when an entry goes stale (event removed or now handled).",
|
|
"ios": {
|
|
"controlUi.sessionPullRequests.changed": "Sidebar PR indicators are a Control UI surface; native apps do not render session PR chips.",
|
|
"cron": "Cron run activity is not surfaced in the iOS app.",
|
|
"device.pair.changed": "Device label projection refresh is a Control UI surface; iOS does not render the Gateway Devices page.",
|
|
"device.pair.requested": "Device pairing flows poll via device.pair.* methods on iOS.",
|
|
"device.pair.resolved": "Device pairing flows poll via device.pair.* methods on iOS.",
|
|
"device.pair.setup.completed": "Generated setup lifecycle is a Control UI surface; iOS direct Watch setup completes through Watch connectivity.",
|
|
"device.pair.setup.deliveryUncertain": "Generated setup recovery is a Control UI surface; native clients receive the handoff response directly.",
|
|
"heartbeat": "iOS liveness uses tick and WebSocket-level ping; heartbeat is unused.",
|
|
"node.pair.requested": "Node pairing state is fetched on demand; no push consumer on iOS yet.",
|
|
"node.pair.resolved": "Node pairing state is fetched on demand; no push consumer on iOS yet.",
|
|
"node.presence": "Node activity is reported by macOS nodes and consumed by gateway routing; iOS has no presence reporter yet.",
|
|
"node.runnerInventory.changed": "Session-host placement refresh is a Control UI surface; iOS does not offer device runner placement yet.",
|
|
"openclaw.approval.requested": "OpenClaw system-agent config approvals are a web/desktop operator surface; iOS has no operator-approval prompt.",
|
|
"openclaw.approval.resolved": "OpenClaw system-agent config approvals are a web/desktop operator surface; iOS has no operator-approval prompt.",
|
|
"plugin.approval.requested": "Plugin approval prompts are not implemented on iOS.",
|
|
"plugin.approval.resolved": "Plugin approval prompts are not implemented on iOS.",
|
|
"portal.changed": "Control-UI-only surface; native apps have no portal viewer yet.",
|
|
"presence": "Presence roster is a control-UI (web/desktop) surface; iOS does not render it.",
|
|
"session.approval": "Native approval review uses exec.approval push/nudge delivery; the session-scoped approval stream is a Control UI chat surface.",
|
|
"session.operation": "Chat UI derives run state from chat/agent events; no session.operation consumer yet.",
|
|
"session.sharing": "Session visibility/membership management is a Control UI operator surface; iOS reads visibility/sharingRole from session rows and has no sharing editor.",
|
|
"session.sharing.evidence": "Principal-less sharing evidence is a Control UI operator surface; iOS refreshes canonical session rows through sessions.changed.",
|
|
"session.suggestion": "The suggestion queue is a Control UI collaboration surface; iOS does not render or resolve session suggestions.",
|
|
"session.tool": "Session tool stream is not rendered by the iOS chat surface yet.",
|
|
"session.typing": "Collaborative typing state is a Control UI-only ephemeral indicator; iOS does not render it.",
|
|
"shutdown": "iOS relies on socket close plus reconnect/backoff instead of the shutdown notice.",
|
|
"skills.changed": "Skills settings is a macOS operator surface; iOS does not expose skill management yet.",
|
|
"task.suggestion": "Task suggestion cards are a Control UI-only surface; iOS does not render them.",
|
|
"terminal.data": "Embedded terminal is a web/desktop surface; iOS has no terminal client.",
|
|
"terminal.exit": "Embedded terminal is a web/desktop surface; iOS has no terminal client.",
|
|
"ui.command": "Web Control UI-only layout commands; iOS does not consume them.",
|
|
"update.available": "Gateway self-update notices do not apply to iOS; app updates ship via the App Store.",
|
|
"users.prefs.changed": "iOS resolves the profile accent through talk.config on connect and config refresh; live per-profile appearance push is a named follow-up.",
|
|
"voicewake.routing.changed": "iOS only consumes voicewake.changed trigger updates; routing changes are not surfaced."
|
|
},
|
|
"android": {
|
|
"controlUi.sessionPullRequests.changed": "Sidebar PR indicators are a Control UI surface; native apps do not render session PR chips.",
|
|
"cron": "Cron run activity is not surfaced in the Android app.",
|
|
"device.pair.changed": "Device label projection refresh is a Control UI surface; Android does not render the Gateway Devices page.",
|
|
"device.pair.requested": "Device pairing flows poll via device.pair.* methods on Android.",
|
|
"device.pair.resolved": "Device pairing flows poll via device.pair.* methods on Android.",
|
|
"device.pair.setup.completed": "Generated setup lifecycle is a Control UI surface; Android consumes setup codes but does not issue or track them.",
|
|
"device.pair.setup.deliveryUncertain": "Generated setup recovery is a Control UI surface; Android consumes setup codes but does not issue or track them.",
|
|
"heartbeat": "Android liveness uses tick and WebSocket-level ping; heartbeat is unused.",
|
|
"node.invoke.cancel": "Cancel targets streaming agent.cli.claude.run.v1 invokes; app nodes never advertise agent runs, so no cancel can address them.",
|
|
"node.invoke.input": "Carries terminal keystrokes/resize to a node PTY relay invoke; the relay runs on gateway/CLI node hosts and app nodes never host it, so Android has no consumer.",
|
|
"node.pair.requested": "Android's bounded operator session lacks operator.pairing; onboarding refreshes node approval with explicit node.list requests.",
|
|
"node.pair.resolved": "Android's bounded operator session lacks operator.pairing; onboarding refreshes node approval with explicit node.list requests.",
|
|
"node.presence": "Node activity is reported by macOS nodes and consumed by gateway routing; Android has no presence reporter yet.",
|
|
"node.runnerInventory.changed": "Session-host placement refresh is a Control UI surface; Android does not offer device runner placement yet.",
|
|
"openclaw.approval.requested": "OpenClaw system-agent config approvals are a web/desktop operator surface; Android has no operator-approval prompt.",
|
|
"openclaw.approval.resolved": "OpenClaw system-agent config approvals are a web/desktop operator surface; Android has no operator-approval prompt.",
|
|
"plugin.approval.requested": "Plugin approval prompts are not implemented on Android.",
|
|
"plugin.approval.resolved": "Plugin approval prompts are not implemented on Android.",
|
|
"portal.changed": "Control-UI-only surface; native apps have no portal viewer yet.",
|
|
"presence": "Presence roster is a control-UI (web/desktop) surface; Android does not render it.",
|
|
"session.approval": "Native approval review uses exec.approval push/nudge delivery; the session-scoped approval stream is a Control UI chat surface.",
|
|
"session.operation": "Chat UI derives run state from chat/agent events; no session.operation consumer yet.",
|
|
"session.sharing": "Session visibility/membership management is a Control UI operator surface; Android reads visibility/sharingRole from session rows and has no sharing editor.",
|
|
"session.sharing.evidence": "Principal-less sharing evidence is a Control UI operator surface; Android refreshes canonical session rows through sessions.changed.",
|
|
"session.suggestion": "The suggestion queue is a Control UI collaboration surface; Android does not render or resolve session suggestions.",
|
|
"session.tool": "Session tool stream is not rendered by the Android chat surface yet.",
|
|
"session.typing": "Collaborative typing state is a Control UI-only ephemeral indicator; Android does not render it.",
|
|
"shutdown": "Android relies on socket close plus reconnect/backoff instead of the shutdown notice.",
|
|
"skills.changed": "Skills settings is a macOS operator surface; Android does not expose skill management yet.",
|
|
"talk.mode": "Android toggles talk mode locally; gateway talk.mode sync is not consumed.",
|
|
"task.suggestion": "Task suggestion cards are a Control UI-only surface; Android does not render them.",
|
|
"terminal.data": "Embedded terminal is a web/desktop surface; Android has no terminal client.",
|
|
"terminal.exit": "Embedded terminal is a web/desktop surface; Android has no terminal client.",
|
|
"ui.command": "Web Control UI-only layout commands; Android does not consume them.",
|
|
"users.prefs.changed": "Android resolves the profile accent through talk.config on connect and config refresh; live per-profile appearance push is a named follow-up.",
|
|
"voicewake.routing.changed": "Android reads voicewake state on demand via voicewake.get; no push consumer yet."
|
|
}
|
|
}
|