fix: derive server-backed state instead of mirroring it (Category C, #200) - #369
Conversation
…200) All three Category C sites. Each held a copy of server state in local state and refilled it from an effect once the data landed. BranchContext (2 sites) `currentBranch` was seeded from the initialBranch prop / sessionStorage pin, then rewritten by one effect that re-validated it against the branch list and a second that applied `initialBranch` once behind an `initialBranchHandled` flag. Both are gone; `currentBranch` is now a useMemo over the query response and the visitor's explicit selection. An explicit switch is authoritative and deliberately not re-validated against the cached list — `switchBranch` already checks the branch exists, and `createBranch` selects one the server has just created but which the cached list does not include until the refetch lands. Only the *seeds* are validated, which is what the original check was really protecting against: a stale pin naming a deleted branch. Clearing that stale pin stays in an effect. It writes to sessionStorage and nothing else, so it is synchronisation, not derived state. `initialBranchHandled` disappears. It existed to stop the prop being re-applied after the user switched away; precedence (`selectedBranch ?? initialBranch ?? storedBranch`) expresses that directly. One behavioural nuance: if `initialBranch` *changes* before the user has switched, it is now applied, where the old flag would have ignored anything after the first application. That is the more intuitive reading of a resume link. suggestions page (1 site) Manual fetch-into-state replaced with a `useSuggestionSessions` React Query hook, matching useProject's shape. The hook reports isLoading as false when disabled, since a disabled query stays pending forever and would otherwise spin for signed-out visitors. `react-hooks/set-state-in-effect`: 9 warnings → 6. Tests: eight characterisation tests were written against the *previous* BranchContext implementation first — seed precedence, deleted-pin fallback to preferred_branch then current_branch, the unauthenticated lock, and survival of a switch across a refetch — and all still pass unchanged after the refactor. They caught one real difference along the way: the first derivation re-validated explicit selections and broke create-then-switch, which is why selections are now exempt. Five new tests cover useSuggestionSessions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults 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 |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Category C of #200 — server-data-derived state. All three sites.
BranchContext (2 sites)
currentBranchwas seeded from theinitialBranchprop / sessionStorage pin, then rewritten by two effects: one re-validating it against the branch list every time the query resolved, another applyinginitialBranchexactly once behind aninitialBranchHandledflag.Both are gone.
currentBranchis now auseMemoover the query response and the visitor's explicit selection:Two decisions worth calling out:
Explicit switches are authoritative and not re-validated against the cached list.
switchBranchalready checks the branch exists, andcreateBranchselects a branch the server has just created but which the cached list doesn't include until the refetch lands. Only the seeds get validated — which is what the original check was really guarding: a stale pin naming a deleted branch. I got this wrong on the first pass and the existingcreateBranchtest caught it.initialBranchHandleddisappears. It existed to stop the prop being re-applied after the user switched away; the precedence chain says that directly. One nuance: ifinitialBranchchanges before the user has switched, it's now applied, where the old flag ignored anything after the first application. That reads as the more intuitive behaviour for a resume link — flagging it as a deliberate change rather than an accident.Clearing the stale sessionStorage pin stays in an effect. It writes to sessionStorage and nothing else, so it's synchronisation with an external store, not derived state.
Suggestions page (1 site)
Manual fetch-into-state replaced with a
useSuggestionSessionsReact Query hook shaped likeuseProject. The hook reportsisLoading: falsewhen disabled — a disabled query stayspendingforever, which would otherwise spin the page indefinitely for signed-out visitors.This is the site #200 flagged as overlapping #89; #89's migration (PR #105) is merged, so this was the leftover.
How I verified the BranchContext change
Coverage for the branch-resolution logic was thin, so I wrote eight characterisation tests against the previous implementation first and confirmed they passed before touching anything:
initialBranchkept when the server confirms itpreferred_branch, and the stale pin clearedcurrent_branchpreferred_branchignored when it isn't in the list"main"before the server respondsAll eight still pass unchanged after the refactor, alongside the 17 pre-existing tests.
Verification against the #367 base
react-hooks/set-state-in-effect: 9 → 6 warnings.npm run type-checkclean,npm run lint0 errors,npm run buildsucceeds.npx vitest --run— 161 files / 2772 tests pass.Where this leaves #200
With this merged, the only remaining warnings are the four Category D sites in #362, the one #154 removes, and the re-filed
app/projects/[id]/editor/page.tsx:311— which still needs your call on latch-vs-live derivation before it can be done. Once those land the rule can graduate to"error".