7.8 KiB
7.8 KiB
TanStack Query + Zustand Migration Plan
Last updated: February 25, 2026
Goal
Improve page-load performance and consistency across both projects by introducing a clear data-layer split:
- TanStack Query for all server state (fetching, cache, refetch, invalidation)
- Zustand for UI/client state only (selection, view mode, dialogs, preferences)
Projects in scope:
mission-control(/Users/mattbruce/Documents/Projects/OpenClaw/Web/mission-control)gantt-board(/Users/mattbruce/Documents/Projects/OpenClaw/Web/gantt-board)
Both apps use the same Supabase backend.
Locked Decisions
- Rollout strategy: incremental by route
- Mutation strategy: optimistic updates + rollback on error
- Freshness strategy: invalidate/refetch only (no Supabase Realtime in this phase)
mission-controlscope for this phase: client data paths only- activity, documents, quick search, task due-dates widget, auth session checks
- keep server-rendered dashboard/task/project overview pages unchanged
gantt-boardscope for this phase: migrate board/task/project/sprint client data flows off store-driven sync
Current Problems To Fix
gantt-board
src/stores/useTaskStore.tsmixes server data + UI state + network side effects- frequent full
syncFromServer()calls - repetitive
fetch("/api/auth/users")andfetch("/api/auth/session")across routes/components - mutation flow often does write then full refetch
mission-control
- isolated client hooks perform uncached fetches
hooks/use-activity-feed.tshooks/useDocuments.tscomponents/calendar/TaskCalendarIntegration.tsxcomponents/layout/quick-search.tsx
- duplicated activity fetch work across multiple widgets/components
- documents refresh currently uses full page reload
Target Architecture
- Per-repo query layer
lib/query/client.ts(orsrc/lib/query/client.ts)lib/query/keys.tslib/query/fetcher.ts- domain hooks in
lib/query/hooks/*
- Global defaults
gcTime: 10 minutesretry: 1 for idempotent GET queriesretry: 0 for auth/session checks and mutation-like flowsrefetchOnReconnect:truerefetchOnWindowFocus:truefor board/activity/session data,falsefor quick-search query
- Stale-time defaults
- session: 30s
- tasks snapshot (active-sprint): 15s
- tasks snapshot (all): 60s
- task detail snapshot: 30s
- users list: 5m
- projects/sprints lists: 60s
- documents list: 60s
- quick search: 20s
- due-dates widget: 60s
- Zustand boundary
- retain UI state only
- remove network fetch/sync logic from stores
- persist only stable UI preferences and selections
Query Key Contract
Use stable keys in both repos:
["auth", "session"]
["users", "directory"] // mission-control: /api/users/directory
["users", "auth-list"] // gantt-board: /api/auth/users
["tasks", "snapshot", { scope: "active-sprint" | "all", include?: "detail", taskId?: string }]
["projects", "list"]
["sprints", "list", { status?: "planning" | "active" | "completed" }]
["sprints", "current", { projectId?: string, includeCompletedFallback?: boolean }]
["documents", "list"]
["tasks", "due-dates", { limit: number }]
["search", { q: string }]
["activity", { limit: number, projectId?: string, filterType?: string }]
Planned Hook Interfaces
useSessionQuery()useBoardSnapshotQuery(params)(gantt-board)useUpsertTaskMutation()useDeleteTaskMutation()useProjectMutations()useSprintMutations()useActivityQuery(params)(mission-control)useDocumentsQuery()useTasksDueDatesQuery(limit)useQuickSearchQuery(q)
Phased Implementation
Phase 1: Shared Infrastructure
Both repos:
- Ensure root Query provider with shared query-client defaults
- Add typed fetcher helper with abort/error normalization
- Add query key factory and domain API wrappers
- Add cache patch + invalidation utility helpers
Phase 2: gantt-board Main Board Route
- Replace
syncFromServer/authuseEffectflow insrc/app/page.tsxwith queries - Load active-sprint snapshot first; fetch full snapshot when entering backlog/search
- Replace direct users fetch with shared users query
- Move task/project/sprint writes to TanStack mutation hooks
- Apply optimistic update + rollback + targeted invalidation
Phase 3: gantt-board Secondary Routes
- Task detail route -> detail query (
include=detail,taskId) + mutation hooks - Projects list and project detail routes -> query-based data + no manual
syncFromServer - Sprint archive route -> query-based sprints/tasks and derived stats
- Search/backlog user lookups -> shared cached users query
- Reduce
useTaskStoreto UI-only store with compatibility layer during migration
Phase 4: mission-control Client Data Paths
- Add Query provider in app layout
- Replace
use-activity-feedinternal polling/state withuseQuery+refetchInterval: 30000 - Replace
useDocumentsmount fetch + reload refresh with query + invalidation/refetch - Replace due-dates widget
fetchwith query hook - Replace quick-search effect
fetchwith debounced query (enabled: q.length >= 2) - Replace login session precheck
useEffectwith session query - Keep server-rendered dashboard/task/project overviews unchanged for this phase
Phase 5: Cleanup
- Remove dead network/sync paths from Zustand stores
- Remove hardcoded/default mock payloads in stores where no longer used
- Remove unused
swrdependency ingantt-boardif still unused - Normalize loading/error/empty states from query status
- Ensure no full-page reload is used for data refresh paths
Mutation Invalidation Matrix
- Task create/update/delete/comment/attachment:
- invalidate task snapshot keys (all relevant scopes), due-dates keys, and activity keys if impacted
- Project mutations:
- invalidate projects + task snapshots + sprint current/list keys
- Sprint mutations:
- invalidate sprint list/current + task snapshot keys
- Auth login/logout/account changes:
- invalidate session + users keys
Testing Plan
Automated
- Query key stability tests for parameterized keys
- Optimistic mutation tests:
- success keeps optimistic value and reconciles
- error rolls back
- Hook integration tests:
- active-sprint initial load
- full-scope lazy load when switching view
- deduped users lookup fetches across mounts
- Keep existing
gantt-boardAPI/CLI contract tests passing
Manual Acceptance
gantt-boardinitial load should issue one session query, one active-sprint query, one users query- Backlog/search switch should not refetch full snapshot on every render
- Drag/drop should update instantly and rollback correctly on error
- Task detail should load detail payload without forcing global full sync
mission-controlactivity widgets should share cached activity data instead of duplicate requests- Documents refresh should no longer reload the page
- Quick search should debounce and reuse cache for repeat queries
Performance Exit Criteria
- Warm route visits should render from cache first with background refetch
- Redundant network requests should drop materially on board/activity/document-heavy flows
- No hard refresh required to see server-backed updates
Migration Touchpoints (Primary)
mission-control:
app/layout.tsxhooks/use-activity-feed.tshooks/useDocuments.tscomponents/layout/quick-search.tsxcomponents/calendar/TaskCalendarIntegration.tsxapp/login/page.tsx
gantt-board:
src/components/QueryProvider.tsxsrc/stores/useTaskStore.tssrc/app/page.tsxsrc/app/tasks/[taskId]/page.tsxsrc/app/projects/page.tsxsrc/app/projects/[id]/page.tsxsrc/app/sprints/archive/page.tsx
Notes For Future Chats
If a new chat needs to continue this effort, reference:
- this file path directly
- "TanStack Query + Zustand migration plan (Feb 25, 2026)"
- the locked decisions section above