You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(web): legacy collapsed project folders stay collapsed after a save - #35659
parsePersistedState migrates a legacy blob that has expandedProjectCwds but no collapsedProjectCwds by adding a LEGACY_PROJECT_EXPANSION_DEFAULT_KEY=false entry to projectExpandedById, so projects missing from the expanded list resolve as collapsed. persistState filtered that entry out before writing. The saved blob then had projectExpandedById, so the next parse took the already-migrated branch and resolveProjectExpanded defaulted those projects to expanded. The fix makes persistState write projectExpandedById unchanged, keeping the default entry. sanitizeBooleanRecord already reads it back and resolveProjectExpanded already uses it as the fallback. Legacy semantics are kept: v0.0.20's syncProjects also treated any project missing from a non-empty expandedProjectCwds as collapsed, new projects included, and the current store already behaves that way during the first session after the upgrade. No new regex or parser.
Commit d8f3fef (origin/main). In apps/web I ran a vitest file that imported the unchanged apps/web/src/uiStateStore.ts from tag v0.0.20 as a temporary module, with a stubbed window.localStorage and fake timers. With the old store I called syncProjects([A:/repo/a, B:/repo/b]), then setProjectExpanded('physical-b', false), and advanced 500 ms. The legacy blob it wrote was {"expandedProjectCwds":["/repo/a"],"projectOrderCwds":[...]}, with no collapsedProjectCwds. I put that blob under t3code:ui-state:v1 (and in a second pass under t3code:renderer-state:v8) and imported the current store. resolveProjectExpanded gave [A=true, B=false]. I called useUiStateStore.getState().setPullRequestMergeMethod('squash'), advanced 500 ms, reset modules and re-imported. The persisted projectExpandedById was only {"legacy-project-cwd:/repo/a":true}, and resolution became [true, true]: B reopened. Both keys behaved the same. I then added a focused failing test to uiStateStore.test.ts (parse legacy {expandedProjectCwds:['/repo/a']}, persistState, parse again, expect B collapsed). It fails on main with 'expected true to be false'. I did not drive the Boring-sidebar UI in a browser; the reproduction runs the real web store modules.
Evidence
Real web app, Chromium 1280×800, light theme, Boring sidebar, fresh synthetic data on both commits. Seed legacy expanded-only state with A expanded and B collapsed; reload; toggle A to save UI state; wait for persistence; reload again without touching B.
Proven: scenarios:prove exited 0. B reopened on base and stayed collapsed on head.
AI-assisted; verified with the tests and tools listed above.
Not shown:
The renderer-state:v8 fallback key was not separately exercised in the browser; this proof used t3code:ui-state:v1.
Other UI-state write triggers were not separately exercised; the captured trigger was toggling Project A.
Tests
cd apps/web && vp test run src/uiStateStore.test.ts: fails without the fix (1 failed, 'expected true to be false'), passes with it (14/14). vp run check:changed: format, lint, knip, typecheck (2 packages), test (1 file), react compiler all pass.
Not in this change
For migrated users the legacy 'collapsed unless listed' default now persists, so projects they add later also start collapsed. This matches v0.0.20 and the current first-session behaviour; whether to retire the default after migration is a separate decision.
AI-assisted; verified with the tests and tools listed above.
The legacy migration marks projects missing from expandedProjectCwds as
collapsed through a default-collapsed entry, but persistState dropped that
entry, so the next load defaulted those projects to expanded. Persist it.
Fixes#35591
AI-assisted; verified with the tests and tools listed above.
Macroscope's review found this PR requires human approval — The fix preserves legacy collapsed folders across saves and reloads. It also makes newly added projects start collapsed for affected migrated users, so that persistent default needs human confirmation.
No code changes detected at ec50c7f. Prior analysis still applies.
You can add or adjust custom eligibility rules. Learn more.
Reviewing files that changed from the base of the PR and between 02db518 and ec50c7f.
You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.
Use the checkbox below for a quick retry:
Trigger review
No actionable comments were generated in the recent review.
Walkthrough
Persistence now retains the legacy project expansion entry. A test verifies that migrated expanded and collapsed states remain unchanged after saving and reloading.
Changes: Legacy Project Expansion Persistence
Layer / File(s)
Summary
Persist and verify legacy expansion state apps/web/src/uiStateStore.ts, apps/web/src/uiStateStore.test.ts
persistState writes projectExpandedById without filtering the legacy default entry. The test verifies migrated expanded and collapsed project states before and after saving and reloading.
This change keeps legacy collapsed-folder preferences across reloads after migration. No actionable merge-blocking risk was identified.
Pre-merge checks: 4
Check name
Status
Explanation
Title check
The title "fix(web): legacy collapsed project folders stay collapsed after a save" matches the main change. The PR keeps the legacy default entry in projectExpandedById when persistState saves, so pro…
Linked Issues check
[#35591] The root cause is in persistState. Before this change, it removed LEGACY_PROJECT_EXPANSION_DEFAULT_KEY from projectExpandedById before writing. The next parse then took the already-migrated b…
Out of Scope Changes check
The whole-PR diff against the merge base touches two files. apps/web/src/uiStateStore.ts removes the filter in persistState, a single write-path change that directly implements #35591. apps/web/src/ui…
Description check
The description explains the bug, the root cause, the fix, and the legacy-semantics rationale. It links the issue (#35591) and gives a reproduction on main, with a failing-then-passing focused test. V…
Full details: Title check
Explanation
The title "fix(web): legacy collapsed project folders stay collapsed after a save" matches the main change. The PR keeps the legacy default entry in projectExpandedById when persistState saves, so projects omitted from a legacy expanded list stay collapsed after a save and reload. The title is specific, uses a conventional commit prefix, and names the affected area and behavior.
Full details: Linked Issues check
Explanation
[#35591] The root cause is in persistState. Before this change, it removed LEGACY_PROJECT_EXPANSION_DEFAULT_KEY from projectExpandedById before writing. The next parse then took the already-migrated branch, and projects omitted from the legacy list resolved as expanded. The diff now writes state.projectExpandedById unchanged, so the default=false entry survives a save. The added test in uiStateStore.test.ts migrates {expandedProjectCwds:['/repo/a']}, persists, re-parses the stored blob, and asserts that /repo/a stays expanded and /repo/b stays collapsed. This covers the legacy-preservation-across-write-and-reload requirement. Existing behavior is preserved: the diff does not change the empty-list or new-install defaults, and explicit collapsed or expanded entries still persist as before. The v8 fallback (t3code:renderer-state:v8) is not exercised by the new test. Coverage for it relies on the shared persist/parse path and the author's manual reproduction on both keys. I did not verify the v8 read path directly. The gap is minor and does not change the verdict.
Full details: Out of Scope Changes check
Explanation
The whole-PR diff against the merge base touches two files. apps/web/src/uiStateStore.ts removes the filter in persistState, a single write-path change that directly implements #35591. apps/web/src/uiStateStore.test.ts adds one regression test for that same path. No unrelated code, refactors, or new parsers appear in the summary or the inspected diffs. The PR description states the retained legacy default for later-added projects is intentionally not retired, which matches the issue's requirement that new installations and empty lists must not collapse projects.
Full details: Description check
Explanation
The description explains the bug, the root cause, the fix, and the legacy-semantics rationale. It links the issue (#35591) and gives a reproduction on main, with a failing-then-passing focused test. Verification names the commands run and the results, plus browser evidence with before/after screenshots and recordings. It also states what was not shown and lists a follow-up scope note. It follows the template's Problem, Change, Scope, and Verification intent. Scope is weaker: it links the issue but does not explicitly justify it as a bug fix beyond the issue link. The template's AI-agent model/harness line is also not clearly provided, though the text says AI-assisted. These gaps are minor.
Finishing Touches
Generate unit tests (beta)
Commit to this branch
Create a new PR
Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
size:XS0-9 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.
1 participant
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
parsePersistedState migrates a legacy blob that has expandedProjectCwds but no collapsedProjectCwds by adding a LEGACY_PROJECT_EXPANSION_DEFAULT_KEY=false entry to projectExpandedById, so projects missing from the expanded list resolve as collapsed. persistState filtered that entry out before writing. The saved blob then had projectExpandedById, so the next parse took the already-migrated branch and resolveProjectExpanded defaulted those projects to expanded. The fix makes persistState write projectExpandedById unchanged, keeping the default entry. sanitizeBooleanRecord already reads it back and resolveProjectExpanded already uses it as the fallback. Legacy semantics are kept: v0.0.20's syncProjects also treated any project missing from a non-empty expandedProjectCwds as collapsed, new projects included, and the current store already behaves that way during the first session after the upgrade. No new regex or parser.
Fixes #35591
Reproduction on main
Commit d8f3fef (origin/main). In apps/web I ran a vitest file that imported the unchanged apps/web/src/uiStateStore.ts from tag v0.0.20 as a temporary module, with a stubbed window.localStorage and fake timers. With the old store I called syncProjects([A:/repo/a, B:/repo/b]), then setProjectExpanded('physical-b', false), and advanced 500 ms. The legacy blob it wrote was {"expandedProjectCwds":["/repo/a"],"projectOrderCwds":[...]}, with no collapsedProjectCwds. I put that blob under t3code:ui-state:v1 (and in a second pass under t3code:renderer-state:v8) and imported the current store. resolveProjectExpanded gave [A=true, B=false]. I called useUiStateStore.getState().setPullRequestMergeMethod('squash'), advanced 500 ms, reset modules and re-imported. The persisted projectExpandedById was only {"legacy-project-cwd:/repo/a":true}, and resolution became [true, true]: B reopened. Both keys behaved the same. I then added a focused failing test to uiStateStore.test.ts (parse legacy {expandedProjectCwds:['/repo/a']}, persistState, parse again, expect B collapsed). It fails on main with 'expected true to be false'. I did not drive the Boring-sidebar UI in a browser; the reproduction runs the real web store modules.
Evidence
Real web app, Chromium 1280×800, light theme, Boring sidebar, fresh synthetic data on both commits. Seed legacy expanded-only state with A expanded and B collapsed; reload; toggle A to save UI state; wait for persistence; reload again without touching B.
Proven: scenarios:prove exited 0. B reopened on base and stayed collapsed on head.
Scenario source
AI-assisted; verified with the tests and tools listed above.
Not shown:
Tests
cd apps/web && vp test run src/uiStateStore.test.ts: fails without the fix (1 failed, 'expected true to be false'), passes with it (14/14). vp run check:changed: format, lint, knip, typecheck (2 packages), test (1 file), react compiler all pass.
Not in this change
AI-assisted; verified with the tests and tools listed above.