Adopt TanStack Query/Form/Table + zod as the data layer (#43) #64
No reviewers
Labels
No labels
area:auth
area:ci
area:db
area:infra
area:native
area:pwa
area:service
epic
feature
foundation
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
james/carol!64
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "43-tanstack"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #43.
Summary
lib/dto/note.ts). Same schema validates the request body server-side (safeParsein the route handler) and powers TanStack Form'svalidators.onChangeclient-side. Existing hand-rolled parsers inlib/dto/{user,settings}.tsare slated for migration in a follow-up ticket — out of scope here.notestable (per-user, FK + cascade, index on(user_id, updated_at)). The existingexampleentity stays untouched — disturbing it would have been destructive (noADD CONSTRAINT FKin SQLite for additive ALTER) and unnecessary./notes: server component prefetches via the repo, dehydrates into<HydrationBoundary>; client component drives the create form + table + per-row inline edit + delete via Query / Form / Table together. Exercises the optimistic-update pattern (setQueryData) on update and delete, invalidation on create.app/providers.tsxmounts<QueryClientProvider>viauseState(createQueryClient)and renders<ReactQueryDevtools />only whenNODE_ENV !== "production". Wrapped around{children}in the root layout.lib/auth/session-server.tswithgetServerSession()— usesnext/headerscookies(), markedimport "server-only". Refactoredlib/auth/session.tsto extract a sharedvalidateSessionId(id)core so route handlers (getSession(req)) and server components (getServerSession()) share the validation path.What didn't change
/offlinestill○ (Static)in build output — ADR-0008'sforce-staticprecache is preserved./notes,/api/notes,/api/notes/[id]are all gated by the proxy default-deny.exampleentity, its DTOs, repos, and tests are untouched.Sharp edges worth flagging
zNoteCreateForm,zNoteUpdateForm) duplicatezNoteCreate/zNoteUpdateminus the"" → nulltransform. TanStack Form's Standard Schema integration wants schema input/output to match the formdefaultValuesexactly; transforms drift. Thebody || nullconversion happens inonSubmit. ADR-0010 calls this out as intentional.notes-client.tsxonuseReactTableforreact-hooks/incompatible-library. TanStack Table's return value relies on per-render identity changes; memoizing breaks it. The library's recommended call site triggers the React Compiler heuristic; the disable is targeted and commented.createQueryClient()returns a fresh instance per call. Reusing on the server would leak one user's cache into another's render.use(queryPromise)from RSCs — that's the TanStack Query anti-pattern with React 19. Noted in the ADR.Test plan
npm run typecheck— clean.npm run lint— clean (after the one targeted disable above).npm test— 119 passed / 32 skipped (151 total). 41 new tests:tests/db/notes.test.ts(16, dual-engine),tests/api/notes.test.ts(15),tests/dto/note.test.ts(10).npm run build— succeeds./notesshows asƒ(dynamic, expected);/api/notesand/api/notes/[id]asƒ;/offlinestill○ (Static).npm run dev— manual probes:GET /api/notesunauth → 401; authed empty →[]; authed after POST → contains created row.POST /api/notes {title:""}→ 400invalid_body"Title is required." (zod validating).PATCH /api/notes/[id]with another user's id → 404 (don't-leak-existence)./notesHTML contains the dehydrated["notes"]query state with prefetched data + the table row already rendered + React Query devtools container.🤖 Generated with Claude Code
aed4b9fc14aedfddebf1aedfddebf12fccdfe068