feat(mobile): themes with appearance override and custom import - #6617
feat(mobile): themes with appearance override and custom import#6617amanthanvi wants to merge 32 commits into
Conversation
Adds a Plan Mode (Legacy) toggle to mobile Settings (default off) that gates the /plan and /default slash commands and forces outgoing turns to default mode when off, mirroring web semantics across every send path (composer, new task, queued outbox). Adds submit-time parsing and slash support on the new-task composer. Introduces per-environment synced client preferences (contracts schema, event-sourced server projection with additive migration, incremental shell stream item) consumed by web and mobile with LWW reconciliation, clock-skew clamping, and offline device-local fallback. Closes pingdotgg#6401
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adds a mobile theming system with an Appearance override (System/Light/Dark), built-in theme picker, and device-local custom theme import, while introducing a synced-client-preferences contract + server plumbing so appearance mode + selected theme id (and legacy plan mode) can converge across web/desktop/mobile per environment.
Changes:
- Introduces
SyncedClientPreferences(schema + RPCs) and threads it through orchestration read models, shell stream, receipts, event store, projector, and a new SQLite projection/migration. - Implements mobile Appearance “Color scheme” UI with built-in themes, ThemeFile v1 JSON paste-import, device-local theme library persistence, and theming applied across markdown/review/source/terminal surfaces.
- Fixes native CSS hex alpha decoding (iOS/Android) and switches to a scheme-neutral splash configuration.
Reviewed changes
Copilot reviewed 86 out of 86 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/contracts/src/syncedClientPreferences.ts | New schemas for synced client preferences + patch requests |
| packages/contracts/src/settings.test.ts | Contract tests for synced preferences decoding/patch rules |
| packages/contracts/src/rpc.ts | Adds syncedClientPreferences get/patch WS RPCs |
| packages/contracts/src/orchestration.ts | Extends orchestration models/events/commands for preferences |
| packages/contracts/src/orchestration.test.ts | Ensures legacy shell decoding + patch validation |
| packages/contracts/src/index.ts | Re-exports syncedClientPreferences |
| packages/client-runtime/src/state/shellReducer.ts | Applies client-preferences shell stream updates |
| packages/client-runtime/src/state/shellReducer.test.ts | Tests shellReducer preference updates |
| packages/client-runtime/src/state/server.ts | Adds environment RPC command for patching preferences |
| docs/user/plan-mode.md | New user doc for Plan Mode (Legacy) sync behavior |
| docs/user/appearance.md | New user doc for appearance + themes + import limits |
| docs/README.md | Links new appearance doc from docs index |
| apps/web/src/components/settings/ThemeEditorHost.tsx | Switches web theme editor to synced-theme hook |
| apps/web/src/components/settings/SettingsPanels.tsx | Switches appearance panel/restore to synced-theme hook |
| apps/server/src/ws.ts | Streams preference updates incrementally + adds RPC handlers |
| apps/server/src/serverRuntimeStartup.test.ts | Updates projection snapshot query seam shape |
| apps/server/src/server.test.ts | Tests incremental preference patch streaming behavior |
| apps/server/src/provider/Layers/ProviderSessionReaper.test.ts | Updates projection snapshot query seam shape |
| apps/server/src/project/ProjectSetupScriptRunner.test.ts | Updates projection snapshot query seam shape |
| apps/server/src/persistence/Services/OrchestrationCommandReceipts.ts | Expands receipt aggregateId union for preferences |
| apps/server/src/persistence/Migrations/041_ProjectionSyncedClientPreferences.ts | New projection table + projector cursor seed |
| apps/server/src/persistence/Migrations/041_ProjectionSyncedClientPreferences.test.ts | Tests migration creates projection + seeds cursor |
| apps/server/src/persistence/Migrations.ts | Registers migration 041 |
| apps/server/src/persistence/Layers/OrchestrationEventStore.ts | Expands event store streamId/aggregateId unions |
| apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts | Adds getSyncedClientPreferences to query interface |
| apps/server/src/orchestration/projector.ts | Projects LWW preferences into read model |
| apps/server/src/orchestration/Normalizer.ts | Clamps future-skewed preference stamps |
| apps/server/src/orchestration/Normalizer.test.ts | Tests future-skew clamp logic |
| apps/server/src/orchestration/Layers/ProjectionPipeline.ts | Adds SQL projector for preferences singleton |
| apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts | Tests deterministic rebuild/out-of-order handling |
| apps/server/src/orchestration/Layers/OrchestrationEngine.ts | Routes preference commands to singleton aggregate |
| apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts | End-to-end receipts/projection behavior for preferences |
| apps/server/src/orchestration/decider.ts | Emits client-preferences.patched events |
| apps/server/src/checkpointing/CheckpointDiffQuery.test.ts | Updates projection snapshot query seam shape |
| apps/server/src/auth/RpcAuthorization.ts | Adds auth scopes for new preference RPCs |
| apps/mobile/src/state/use-thread-outbox-drain.ts | Reads live plan-mode preference during outbox drain |
| apps/mobile/src/state/use-thread-composer-state.ts | Forces interactionMode default when plan mode disabled |
| apps/mobile/src/state/thread-outbox.test.ts | Updates tests for plan-mode-aware queued sends |
| apps/mobile/src/state/thread-outbox-model.ts | Adds plan-mode-aware queued settings + send decision helper |
| apps/mobile/src/state/synced-client-preferences.ts | Mobile worker to reconcile/fan-out synced preferences |
| apps/mobile/src/state/synced-client-preferences.test.ts | Tests reconciliation + fan-out + normalization behaviors |
| apps/mobile/src/state/synced-client-preferences-model.ts | Pure reconciliation/write helpers for mobile preferences |
| apps/mobile/src/Stack.tsx | Adds synced-preferences worker; simplifies header styling |
| apps/mobile/src/persistence/mobile-preferences.ts | Persists appearance/theme/imported themes + LWW stamp |
| apps/mobile/src/lib/storage.test.ts | Tests preference persistence + imported theme removal fallback |
| apps/mobile/src/lib/splashConfig.ts | Scheme-neutral splash config helper |
| apps/mobile/src/lib/splashConfig.test.ts | Tests scheme-neutral splash config |
| apps/mobile/src/lib/mobileThemeFile.test.ts | Tests ThemeFile parsing/caps/normalization |
| apps/mobile/src/lib/mobileTheme.test.ts | Tests token mapping + built-in parity with global.css |
| apps/mobile/src/features/threads/use-plan-mode-enabled.ts | Hook for resolved plan-mode preference state |
| apps/mobile/src/features/threads/ThreadFeed.tsx | Switches markdown/review theming to appearance preferences |
| apps/mobile/src/features/threads/ThreadComposer.tsx | Plan-mode slash gating + submit-time parsing + safer replacement |
| apps/mobile/src/features/threads/plan-mode.ts | Shared plan-mode helpers (slash/menu/submit/interactionMode) |
| apps/mobile/src/features/threads/plan-mode.test.ts | Tests plan-mode helper behaviors |
| apps/mobile/src/features/threads/NewTaskDraftScreen.tsx | Adds plan-mode slash popover + submit parsing + in-flight guard |
| apps/mobile/src/features/threads/new-task-submit.ts | Extracts new-task submit eligibility/selection helpers |
| apps/mobile/src/features/threads/new-task-submit.test.ts | Tests new-task submit helpers |
| apps/mobile/src/features/threads/new-task-flow-provider.tsx | Uses new plan-mode preference hook + interactionMode resolver |
| apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx | Themes terminal via nativeSurfaceColors + correct alpha |
| apps/mobile/src/features/terminal/terminalTheme.ts | Adds optional native-surface overrides to terminal theme |
| apps/mobile/src/features/terminal/terminalTheme.test.ts | Tests terminal overrides + alpha ordering stability |
| apps/mobile/src/features/terminal/NativeTerminalSurface.tsx | Allows appearanceScheme override for fallback/native surface |
| apps/mobile/src/features/settings/SettingsRouteScreen.tsx | Plan mode toggle now uses synced preference updates |
| apps/mobile/src/features/settings/SettingsAppearanceRouteScreen.tsx | Adds ColorSchemeAppearanceSection to Appearance screen |
| apps/mobile/src/features/settings/appearance/sections/ColorSchemeAppearanceSection.tsx | New UI: mode picker, theme cards, import/remove flows |
| apps/mobile/src/features/settings/appearance/AppearancePreferencesProvider.tsx | Applies theme variables + supports import/remove + syncing |
| apps/mobile/src/features/review/useNativeReviewDiffBridge.ts | Themes native review diff via nativeSurfaceColors |
| apps/mobile/src/features/review/ReviewSheet.tsx | Uses effectiveColorScheme instead of system scheme |
| apps/mobile/src/features/review/nativeReviewDiffAdapter.ts | Adds native theme overrides application helper |
| apps/mobile/src/features/review/nativeReviewDiffAdapter.test.ts | Tests stable defaults + override behavior + alpha serialization |
| apps/mobile/src/features/files/SourceFileSurface.tsx | Themes native source surface via shared theme helper |
| apps/mobile/src/features/files/nativeSourceFileAdapter.ts | Reuses review theme creation for source surfaces |
| apps/mobile/src/features/files/nativeSourceFileAdapter.test.ts | Tests stable defaults + override passthrough |
| apps/mobile/src/components/AppSymbol.tsx | Adds Android mapping for “hammer” symbol |
| apps/mobile/src/App.tsx | Navigation theme + status bar driven by appearance preferences |
| apps/mobile/plugins/withIosSplashScreenInlineBackground.cjs | Inlines storyboard splash background color on iOS |
| apps/mobile/modules/t3-terminal/ios/T3TerminalView.swift | Fixes CSS #RRGGBBAA parsing for iOS terminal colors |
| apps/mobile/modules/t3-terminal/android/.../T3TerminalView.kt | Fixes CSS #RRGGBBAA parsing for Android terminal colors |
| apps/mobile/modules/t3-review-diff/android/.../T3ReviewDiffView.kt | Fixes CSS #RRGGBBAA parsing for Android review colors |
| apps/mobile/global.css | Adds missing markdown inline-code token variables |
| apps/mobile/app.config.ts | Switches to scheme-neutral splash + iOS storyboard mod |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
ApprovabilityVerdict: Needs human review 1 blocking correctness issue found. Diff is too large for automated approval analysis. A human reviewer should evaluate this PR. You can customize Macroscope's approvability policy. Learn more. |
- Preserve offline web plan-mode writes - Reject impossible preference timestamps - Retry failed web preference patches - Order rapid web toggles monotonically - Re-read plan mode before queued mobile delivery - Route environment-scoped preference writes correctly - Ignore stale mobile preference acknowledgements - Document the mobile Legacy settings location - Use Hermes-compatible reconciliation sorting
- Bound mobile synced-preference stamps to the canonical future-skew window. - Gate thread-composer enqueue mode selection on preference hydration. - Preserve newer local Plan Mode intent in read-only mobile and web sessions. - Add Plan Mode to the user-docs index.
981cc64 to
625182a
Compare
625182a to
c299dad
Compare
c299dad to
d6c5438
Compare
d6c5438 to
bff15c1
Compare
ebc004b to
28a6e3c
Compare
9ab6ea0 to
3ac9f80
Compare
3ac9f80 to
ad661a1
Compare
ad661a1 to
09f8810
Compare
09f8810 to
0db7db6
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0db7db6. Configure here.
Adds a Color scheme section to mobile Appearance: a System/Light/Dark override and a six-theme picker (T3 Code default plus T3 Chat, Grove, Ocean, Ember, Iris) applied across Uniwind tokens, navigation chrome, markdown, and the native terminal/review/source surfaces. The default theme is byte-for-byte identical to the shipped palette (pinned by a global.css parity test). Supports pasting ThemeFile v1 JSON with the full web-accepted color syntax (hex, named, rgb, hsl, hwb, lab, lch, oklab, oklch, color()) normalized to RN-safe hex, with size/count caps and clean removal fallback. Fixes CSS hex alpha byte order in the Android review/terminal and iOS terminal decoders and ships a scheme-neutral splash. Closes pingdotgg#6405
Extends synced client preferences to reconcile and write through appearance mode and selected theme IDs on mobile and web. Keeps custom theme definitions and theme halves local, with a deterministic T3 Code fallback for unavailable IDs.
- Compose sequential synced-theme writes from the latest pending snapshot - Document every supported mobile theme color syntax - Remove a stale Hermes-test lint suppression
0db7db6 to
3df7474
Compare
| }); | ||
| } | ||
| if (clientPatch.planModeEnabled !== undefined) { | ||
| syncedPlanModeHydrationController.write({ |
There was a problem hiding this comment.
🟡 Medium hooks/useSettings.ts:639
A failed planModeEnabled sync from a secondary environment is never retried, so the UI remains optimistically updated while the server preference is lost on reload. useUpdateSettingsTarget records the write for every environmentId, but useSynchronizeSyncedClientPreferences deactivates the controller when that ID differs from the current primary ID; only the primary environment can provide the synchronization owner. Restrict synced-preference writes to the primary environment (while retaining the local persistence path) so secondary-environment failures cannot leave an unreachable pending write.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/hooks/useSettings.ts around line 639:
A failed `planModeEnabled` sync from a secondary environment is never retried, so the UI remains optimistically updated while the server preference is lost on reload. `useUpdateSettingsTarget` records the write for every `environmentId`, but `useSynchronizeSyncedClientPreferences` deactivates the controller when that ID differs from the current primary ID; only the primary environment can provide the synchronization owner. Restrict synced-preference writes to the primary environment (while retaining the local persistence path) so secondary-environment failures cannot leave an unreachable pending write.

Mobile's Appearance screen was typography-only: no theme picker, no light/dark override (the app hard-followed the OS), no custom themes. This adds a Color scheme section — System/Light/Dark override plus the six built-in themes (T3 Code default, T3 Chat, Grove, Ocean, Ember, Iris) — applied across Uniwind tokens, navigation chrome, markdown, and the native terminal/review/source surfaces. The default theme is byte-for-byte identical to the shipped palette, pinned by a test that parses
global.cssand asserts all 62 tokens per mode. Custom themes import via pasted ThemeFile v1 JSON with the full web-accepted color syntax (hex, named, rgb, hsl, hwb, lab, lch, oklab, oklch, color()) normalized to RN-safe hex with size/count caps and clean removal fallback. Also fixes CSS hex alpha byte order in the Android review/terminal and iOS terminal decoders, ships a scheme-neutral splash, and syncs appearance mode + theme id across clients via the synced-preferences mechanism.Depends on #6614 (stacked: this branch contains its commits — the synced-preferences mechanism lands there). Custom theme libraries stay device-local by design.
Closes #6405
Applied themes and import: Ocean dark thread · themed native terminal · paste-import sheet · imported theme applied · neutral splash. Verified end-to-end on the iOS simulator with a fresh native build (mode persistence across relaunch, native alpha decoding, import/removal fallback, OS-Light/app-Dark diff coherence, cold-launch splash).
Implemented by GPT-5.6 Sol (xhigh) via the Codex CLI, orchestrated and reviewed by Claude Fable 5 (Claude Code), with Claude Opus 5 run monitoring.
Note
Medium Risk
Broad UI/theming surface plus cross-device synced preferences (last-writer-wins) and native splash/build-plugin changes; default theme is regression-tested.
Overview
Adds a Color scheme experience on mobile: System/Light/Dark override, six built-in themes, and paste-import of ThemeFile v1 JSON (device-local).
AppearancePreferencesProvidernow drives Uniwind CSS variables, React NativeAppearance, navigation chrome (including form-sheet backgrounds), markdown tokens, and native terminal/review/source theming via optional surface-color overrides. Appearance mode and theme id persist through synced client preferences (wired fromStackand settings).Fixes 8-digit CSS hex alpha decoding on Android review/terminal and iOS terminal, and switches to a scheme-neutral splash (
createSchemeNeutralSplashConfigplus an iOS config plugin that inlines the splash storyboard background).Plan Mode (legacy) moves to synced prefs with stricter gating:
/planand/defaultslash commands and submit-time mode switches only when enabled and preferences are reconciled; new-task and thread composers shareplan-modehelpers and safer trigger replacement.Default T3 Code palette stays pinned to
global.cssvia tests; imported themes normalize colors to RN-safe hex.Reviewed by Cursor Bugbot for commit 3df7474. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add server-synced appearance themes and plan mode preferences to mobile
syncedClientPreferencessystem that syncsplanModeEnabled,appearanceMode, andthemeIdbetween mobile clients and the server via new WebSocket RPCs (syncedClientPreferencesGet/syncedClientPreferencesPatch) and aclient-preferences.patchorchestration command.nativeSurfaceColorsoverrides derived from the active theme, and markdown/composer styling uses CSS theme variables instead of hardcoded light/dark maps.defaultwhen plan mode is disabled.projection_synced_client_preferenceswith per-field last-writer-wins semantics and a 5-minute future-skew clamp on client timestamps.clientPreferencesStreamItem: truewill not receive the newclient-preferences-updatedshell stream events; updated clients always opt in.Macroscope summarized 3df7474.