feat: default sidebar v2 on for nightly and dev builds#4491
Conversation
Sidebar v2 stays off by default in production, but nightly and dev builds now opt in automatically so the beta gets exercised where regressions are cheap. Web/desktop resolve the default from `APP_STAGE_LABEL`, which already covers hosted nightly, packaged nightly desktop, and local dev. Mobile resolves from the app variant, where `development` and `preview` are the nightly equivalents. Client settings persist as a whole blob, so a stored `sidebarV2Enabled: false` could not be told apart from "never touched it". Adds a companion `sidebarV2ConfiguredByUser` flag, mirroring `updateChannelConfiguredByUser`, so the channel default only applies until the user picks a side. Mobile preferences persist sparse patches, so `undefined` already carries that meaning there and needs no new field. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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 |
| // any setting already has `sidebarV2Enabled: false` stored — without this bit | ||
| // there is no way to tell that apart from "left alone", and a channel-derived | ||
| // default could never reach them. Mirrors `updateChannelConfiguredByUser`. | ||
| sidebarV2ConfiguredByUser: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), |
There was a problem hiding this comment.
🟡 Medium src/settings.ts:123
Existing users who explicitly enabled sidebar v2 have persisted sidebarV2Enabled: true but no sidebarV2ConfiguredByUser field. After this change, the missing field decodes to false, so those users are silently switched back to v1. The decoding default for sidebarV2ConfiguredByUser should infer true when sidebarV2Enabled is already true in the persisted record, rather than unconditionally defaulting to false.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/contracts/src/settings.ts around line 123:
Existing users who explicitly enabled sidebar v2 have persisted `sidebarV2Enabled: true` but no `sidebarV2ConfiguredByUser` field. After this change, the missing field decodes to `false`, so those users are silently switched back to v1. The decoding default for `sidebarV2ConfiguredByUser` should infer `true` when `sidebarV2Enabled` is already `true` in the persisted record, rather than unconditionally defaulting to `false`.
There was a problem hiding this comment.
Confirmed and fixed in 6713291 — this was a real regression.
Rather than inferring in the schema decoder, the inference lives in resolveSidebarV2Enabled (apps/web/src/branding.logic.ts): a stored enabled: true counts as an explicit choice even without the companion flag, since true was never the schema default and can only come from the Settings → Beta toggle. Keeping it in the resolver rather than the decoder means the raw persisted values stay untouched and the rule is covered by pure tests.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 08d8f88. Configure here.
ApprovabilityVerdict: Approved This PR defaults sidebar v2 on for nightly/dev builds while keeping production unchanged. The logic correctly preserves existing user opt-ins via the You can customize Macroscope's approvability policy. Learn more. |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
apps/web/src/hooks/useSettings.ts (1)
223-237: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd focused tests for the resolved hook precedence.
The resolver and schema are tested separately, but not
useSidebarV2Enabled()itself. Add cases for configuredfalse, configuredtrue, and unconfigured nightly/production values; this is the shared behavior consumed by the sidebar, settings, and shortcut paths.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/src/hooks/useSettings.ts` around lines 223 - 237, Add focused tests for useSidebarV2Enabled covering configured users with sidebarV2Enabled set to false and true, plus unconfigured users resolving the nightly default and production default. Mock or provide useClientSettings inputs and APP_STAGE_LABEL as needed, and assert the hook prioritizes the explicit configured value while using resolveSidebarV2Default for unconfigured values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/mobile/src/features/threads/use-thread-list-v2-enabled.ts`:
- Around line 18-23: Update the preference handling in the thread-list v2
enablement flow so loading or failed mobilePreferencesAtom results do not pass
undefined as an unresolved choice to resolveThreadListV2Enabled. Keep
successfully loaded undefined distinct from unavailable preferences, and fail
closed or otherwise preserve readiness until preferences load, while retaining
explicit persisted true/false overrides over appVariant defaults.
In `@packages/contracts/src/settings.ts`:
- Line 613: Add focused decoder coverage for sidebarV2ConfiguredByUser in the
ClientSettingsPatch tests by decoding { sidebarV2Enabled: false,
sidebarV2ConfiguredByUser: true } through decodeClientSettingsPatch and
asserting the field is preserved, covering the persistence path used by
BetaSettingsPanel.
---
Nitpick comments:
In `@apps/web/src/hooks/useSettings.ts`:
- Around line 223-237: Add focused tests for useSidebarV2Enabled covering
configured users with sidebarV2Enabled set to false and true, plus unconfigured
users resolving the nightly default and production default. Mock or provide
useClientSettings inputs and APP_STAGE_LABEL as needed, and assert the hook
prioritizes the explicit configured value while using resolveSidebarV2Default
for unconfigured values.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 69426b69-76e5-4cbe-a68e-b5a301b95db3
📒 Files selected for processing (17)
apps/desktop/src/settings/DesktopClientSettings.test.tsapps/mobile/src/features/home/HomeHeader.tsxapps/mobile/src/features/home/HomeScreen.tsxapps/mobile/src/features/settings/SettingsRouteScreen.tsxapps/mobile/src/features/threads/ThreadNavigationSidebar.tsxapps/mobile/src/features/threads/threadListV2.test.tsapps/mobile/src/features/threads/threadListV2.tsapps/mobile/src/features/threads/use-thread-list-v2-enabled.tsapps/mobile/src/persistence/mobile-preferences.tsapps/web/src/branding.logic.tsapps/web/src/branding.test.tsapps/web/src/components/AppSidebarLayout.tsxapps/web/src/components/settings/BetaSettingsPanel.tsxapps/web/src/hooks/useSettings.tsapps/web/src/routes/_chat.tsxpackages/contracts/src/settings.test.tspackages/contracts/src/settings.ts
Review caught two real regressions in the per-channel default. Legacy opt-in was silently discarded: users who had turned sidebar v2 on before `sidebarV2ConfiguredByUser` existed have `sidebarV2Enabled: true` with no companion flag, so they fell back to the channel default and lost the beta on production. `true` was never the schema default, so it can only come from the toggle — treat it as an explicit choice, matching how `normalizeDesktopSettingsDocument` reads a legacy stored `updateChannel`. Both platforms also resolved against pre-load state, mounting one sidebar and swapping it a tick later. Web now gates on client-settings hydration and mobile on the preferences atom having loaded; both hold v1 until then. The web flash predates this PR — the pre-hydration snapshot was always `false`, so opted-in users already saw a v1→v2 flip. Resolution moves into `resolveSidebarV2Enabled` alongside the mobile counterpart, so both are covered by pure tests. Also adds patch-decoder coverage for the field the beta toggle actually writes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

What Changed
Sidebar v2 now defaults on for nightly and dev builds, and stays off for production. It remains a single toggle in Settings → Beta — only the default moves.
The default is resolved per surface from signals that already exist:
VITE_HOSTED_APP_CHANNEL→APP_STAGE_LABELAPP_STAGE_LABELexpoConfig.extra.appVariantdevelopment,previewTwo small pure resolvers do the work —
resolveSidebarV2Default(web) andresolveThreadListV2Default(mobile) — each read through a single hook so no consumer touches the raw setting.Why
The beta only gets exercised if it's on where regressions are cheap. Production users should keep v1 until we flip it deliberately.
The wrinkle is that client settings persist as a whole blob, so any user who has ever opened Settings already has
sidebarV2Enabled: falsestored — indistinguishable from "never touched it". This adds a companionsidebarV2ConfiguredByUserflag, mirroring the existingupdateChannelConfiguredByUserprecedent in desktop settings: the channel default applies until the user picks a side, and touching the switch pins the choice for good.Mobile needs no equivalent — its preferences persist as sparse patches, so
undefinedalready means "never chosen".Notes
Resolution is client-side only. A nightly client pointed at a stable server gets v2; a stable client on a nightly server does not. That's correct today since v2 lives entirely in the client, and
resolveSidebarV2Defaultis the single place to add a server-version input if that changes.Checklist
🤖 Generated with Claude Code
Note
Medium Risk
Changes default UI for many internal/nightly users and relies on migration logic for persisted client settings; risk is mitigated by tests, legacy opt-in handling, and hydration/loading guards.
Overview
Sidebar v2 (web/desktop) and Thread List v2 (mobile) now default on for pre-release builds (web: Nightly/Dev via
APP_STAGE_LABEL; mobile:development/previewapp variants) and stay off on stable/production unless the user has chosen otherwise.Web adds
sidebarV2ConfiguredByUserso a storedsidebarV2Enabled: falsefrom the full client-settings blob is not treated as an explicit opt-out when the user never touched the beta toggle. Toggling Settings → Beta sets that flag and pins the choice.useSidebarV2Enabled/resolveSidebarV2Enabledapply stage defaults, preserve legacyenabled: trueopt-ins, and keep v1 until settings hydrate to avoid remounting the sidebar.Mobile mirrors the pattern with
resolveThreadListV2EnabledanduseThreadListV2Enabled(sparse preferences:undefined= never chosen), including holding v1 while preferences load.Reviewed by Cursor Bugbot for commit 6713291. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Enable sidebar v2 by default on nightly and dev builds
resolveSidebarV2Enabledin branding.logic.ts anduseSidebarV2Enabledin useSettings.ts to derive sidebar v2 state from stage label, user preference, and settings hydration.nightlyanddevstage labels; explicit user opt-in or opt-out is preserved via the newsidebarV2ConfiguredByUserfield inClientSettingsSchema.useThreadListV2Enabledhook on mobile that defaults thread list v2 on fordevelopmentandpreviewapp variants, using the same load-guarded pattern.sidebarV2ConfiguredByUserto lock in their choice.Macroscope summarized 6713291.
Summary by CodeRabbit
New Features
Bug Fixes