Intent
Three autosave bugs were introduced and fixed in PR #66 — all living in a gap that types, lint, and the existing unit tests cannot see: React effects in the same render don't observe state set by sibling effects in that render. A single integration test with @testing-library/react would have caught every one of them before any data was lost.
The three bugs this test would have caught
| # |
What went wrong |
Observable symptom |
| 1 |
useProjectPersistence load effect had [projectId] as its only dep. When Clerk resolved and swapped localStore → apiStore, the load never re-ran. storeRef.current intentionally defeats react-hooks/exhaustive-deps. |
Existing project always loaded empty canvas |
| 2 |
hydrated.current never flipped true when initialState === null (new project). Logic error; types were correct. |
Autosave never fired; state lost on every reload |
| 3 |
Save effect fired with stale canvasNodes = [] in the same render where setCanvasNodes([...real nodes...]) was called by the hydration effect. State from setCanvasNodes isn't visible until the next render. exhaustive-deps only catches missing deps, not timing-wrong extras. |
0-node PUT overwrote a real row on hot-reload unmount |
What's needed
- Add
@testing-library/react (+ @testing-library/user-event) to apps/web devDependencies.
- Mock
useUser from @clerk/nextjs and IProjectStore so tests control Clerk's load timing and store responses.
- Write the core assertion test:
it("save never fires with fewer nodes than initialState after store swap", async () => {
// mount with localStore → returns null
// simulate Clerk resolving → swap to apiStore with 6-node project
// assert: store.save never called with canvasNodes.length < 6
// assert: store.save eventually called with canvasNodes.length === 6
})
- Add two supporting cases: (a) new project — save fires only after user adds first node, not on load; (b) reload round-trip — mount → load N nodes → unmount → remount → same N nodes loaded, no save in between.
- Wire into
pnpm test (already runs vitest; RTL tests belong in tests/seed-validation/ or a new tests/hooks/ directory).
Acceptance
pnpm test is green and includes at least the three cases above.
- No
eslint-disable react-hooks/exhaustive-deps comment is needed after the refactor (the hooks should be lint-clean by design).
- A future regression that re-introduces any of the three bugs causes at least one of these tests to fail red before merge.
Links
Originally tracked internally as Haptic-AI/festivus#67.
Intent
Three autosave bugs were introduced and fixed in PR #66 — all living in a gap that types, lint, and the existing unit tests cannot see: React effects in the same render don't observe state set by sibling effects in that render. A single integration test with
@testing-library/reactwould have caught every one of them before any data was lost.The three bugs this test would have caught
useProjectPersistenceload effect had[projectId]as its only dep. When Clerk resolved and swappedlocalStore → apiStore, the load never re-ran.storeRef.currentintentionally defeatsreact-hooks/exhaustive-deps.hydrated.currentnever flippedtruewheninitialState === null(new project). Logic error; types were correct.canvasNodes = []in the same render wheresetCanvasNodes([...real nodes...])was called by the hydration effect. State fromsetCanvasNodesisn't visible until the next render.exhaustive-depsonly catches missing deps, not timing-wrong extras.What's needed
@testing-library/react(+@testing-library/user-event) toapps/webdevDependencies.useUserfrom@clerk/nextjsandIProjectStoreso tests control Clerk's load timing and store responses.pnpm test(already runs vitest; RTL tests belong intests/seed-validation/or a newtests/hooks/directory).Acceptance
pnpm testis green and includes at least the three cases above.eslint-disable react-hooks/exhaustive-depscomment is needed after the refactor (the hooks should be lint-clean by design).Links
apps/web/src/lib/workbench/hooks/use-project-persistence.ts— hook under test; load effect deps are the core invariantapps/web/src/app/workbench/workbench-canvas.tsx— hydration + save effects; the timing interaction between them is what needs coveringtests/seed-validation/use-project-persistence.test.ts— existing unit tests (store + debounce logic only; no React rendering)specs/028-workbench-project-autosave.md— spec defining the persistence contract these tests should verifyOriginally tracked internally as Haptic-AI/festivus#67.