228 lines
7.8 KiB
Markdown
228 lines
7.8 KiB
Markdown
# 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-control` scope 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-board` scope for this phase: migrate board/task/project/sprint client data flows off store-driven sync
|
|
|
|
## Current Problems To Fix
|
|
|
|
### `gantt-board`
|
|
|
|
- `src/stores/useTaskStore.ts` mixes server data + UI state + network side effects
|
|
- frequent full `syncFromServer()` calls
|
|
- repetitive `fetch("/api/auth/users")` and `fetch("/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.ts`
|
|
- `hooks/useDocuments.ts`
|
|
- `components/calendar/TaskCalendarIntegration.tsx`
|
|
- `components/layout/quick-search.tsx`
|
|
- duplicated activity fetch work across multiple widgets/components
|
|
- documents refresh currently uses full page reload
|
|
|
|
## Target Architecture
|
|
|
|
1. Per-repo query layer
|
|
|
|
- `lib/query/client.ts` (or `src/lib/query/client.ts`)
|
|
- `lib/query/keys.ts`
|
|
- `lib/query/fetcher.ts`
|
|
- domain hooks in `lib/query/hooks/*`
|
|
|
|
2. Global defaults
|
|
|
|
- `gcTime`: 10 minutes
|
|
- `retry`: 1 for idempotent GET queries
|
|
- `retry`: 0 for auth/session checks and mutation-like flows
|
|
- `refetchOnReconnect`: `true`
|
|
- `refetchOnWindowFocus`: `true` for board/activity/session data, `false` for quick-search query
|
|
|
|
3. 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
|
|
|
|
4. 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:
|
|
|
|
```ts
|
|
["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:
|
|
|
|
1. Ensure root Query provider with shared query-client defaults
|
|
2. Add typed fetcher helper with abort/error normalization
|
|
3. Add query key factory and domain API wrappers
|
|
4. Add cache patch + invalidation utility helpers
|
|
|
|
## Phase 2: `gantt-board` Main Board Route
|
|
|
|
1. Replace `syncFromServer`/auth `useEffect` flow in `src/app/page.tsx` with queries
|
|
2. Load active-sprint snapshot first; fetch full snapshot when entering backlog/search
|
|
3. Replace direct users fetch with shared users query
|
|
4. Move task/project/sprint writes to TanStack mutation hooks
|
|
5. Apply optimistic update + rollback + targeted invalidation
|
|
|
|
## Phase 3: `gantt-board` Secondary Routes
|
|
|
|
1. Task detail route -> detail query (`include=detail`, `taskId`) + mutation hooks
|
|
2. Projects list and project detail routes -> query-based data + no manual `syncFromServer`
|
|
3. Sprint archive route -> query-based sprints/tasks and derived stats
|
|
4. Search/backlog user lookups -> shared cached users query
|
|
5. Reduce `useTaskStore` to UI-only store with compatibility layer during migration
|
|
|
|
## Phase 4: `mission-control` Client Data Paths
|
|
|
|
1. Add Query provider in app layout
|
|
2. Replace `use-activity-feed` internal polling/state with `useQuery` + `refetchInterval: 30000`
|
|
3. Replace `useDocuments` mount fetch + reload refresh with query + invalidation/refetch
|
|
4. Replace due-dates widget `fetch` with query hook
|
|
5. Replace quick-search effect `fetch` with debounced query (`enabled: q.length >= 2`)
|
|
6. Replace login session precheck `useEffect` with session query
|
|
7. Keep server-rendered dashboard/task/project overviews unchanged for this phase
|
|
|
|
## Phase 5: Cleanup
|
|
|
|
1. Remove dead network/sync paths from Zustand stores
|
|
2. Remove hardcoded/default mock payloads in stores where no longer used
|
|
3. Remove unused `swr` dependency in `gantt-board` if still unused
|
|
4. Normalize loading/error/empty states from query status
|
|
5. 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
|
|
|
|
1. Query key stability tests for parameterized keys
|
|
2. Optimistic mutation tests:
|
|
- success keeps optimistic value and reconciles
|
|
- error rolls back
|
|
3. Hook integration tests:
|
|
- active-sprint initial load
|
|
- full-scope lazy load when switching view
|
|
- deduped users lookup fetches across mounts
|
|
4. Keep existing `gantt-board` API/CLI contract tests passing
|
|
|
|
## Manual Acceptance
|
|
|
|
1. `gantt-board` initial load should issue one session query, one active-sprint query, one users query
|
|
2. Backlog/search switch should not refetch full snapshot on every render
|
|
3. Drag/drop should update instantly and rollback correctly on error
|
|
4. Task detail should load detail payload without forcing global full sync
|
|
5. `mission-control` activity widgets should share cached activity data instead of duplicate requests
|
|
6. Documents refresh should no longer reload the page
|
|
7. Quick search should debounce and reuse cache for repeat queries
|
|
|
|
## Performance Exit Criteria
|
|
|
|
1. Warm route visits should render from cache first with background refetch
|
|
2. Redundant network requests should drop materially on board/activity/document-heavy flows
|
|
3. No hard refresh required to see server-backed updates
|
|
|
|
## Migration Touchpoints (Primary)
|
|
|
|
`mission-control`:
|
|
|
|
- `app/layout.tsx`
|
|
- `hooks/use-activity-feed.ts`
|
|
- `hooks/useDocuments.ts`
|
|
- `components/layout/quick-search.tsx`
|
|
- `components/calendar/TaskCalendarIntegration.tsx`
|
|
- `app/login/page.tsx`
|
|
|
|
`gantt-board`:
|
|
|
|
- `src/components/QueryProvider.tsx`
|
|
- `src/stores/useTaskStore.ts`
|
|
- `src/app/page.tsx`
|
|
- `src/app/tasks/[taskId]/page.tsx`
|
|
- `src/app/projects/page.tsx`
|
|
- `src/app/projects/[id]/page.tsx`
|
|
- `src/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
|