🚧 Source-backed partial; frontend unit, full-suite, and browser execution proof remains open

Spec: F61 — TanStack Query (React Query) Frontend Migration

Migrate Lumi’s frontend from useState + useEffect + manual fetch to TanStack Query (React Query v5) for all server-state management.

Status: 🚧 Source-backed partial; frontend unit, full-suite, and browser execution proof remains open

Current truth (2026-07-16): QueryClient/provider wiring, the typed query-key factory, domain hooks, and the bypass consistency scanner exist in source. This contract does not infer full frontend or browser execution from those declarations; see the implementation audit and graph gaps.

1. Problem & CEO Job

Serves: See clearly + decide fast

Lumi’s frontend makes 30+ independent API calls per page load without caching, deduplication, or automatic refetch. Navigation repeats calls and mutations re-fetch whole lists, causing slow loads, back-navigation flashes, and inconsistent errors. TanStack Query keeps data fresh for 30 seconds, refetches on window focus, and invalidates only affected query keys. The CEO sees current data faster and navigates without loading spinners.

2. User Stories & Acceptance Criteria

  • As a CEO, I want pages to load instantly when navigating back, so that I don’t wait for data I just saw.

    • Given I navigated to Tasks, then to Brief, when I navigate back to Tasks, the task list renders from cache without a loading skeleton. (TasksPage.tsx migrated to useTasksList — TanStack Query returns stale cache immediately on remount; staleTime=30s)
  • As a CEO, I want data to stay current without manual refresh, so that I trust what I see.

    • Given I resolve a commitment, when the mutation completes, the list updates automatically without a full page reload. (CommitmentsPage.tsx: useResolveCommitment.onSuccess invalidates queryKeys.commitments.lists())
  • As a CEO, I want consistent loading and error states across all pages, so that I know what’s happening.

    • Given any page is loading data, a skeleton placeholder is visible (not a spinner). (all migrated pages: TodayPage, CommitmentsPage, RisksPage, DecisionsPage, BriefPage, TasksPage all use / driven by isLoading)
    • Given any API call fails after retries, a toast error appears with an actionable message. (all hooks use isError state with existing toast.error(t(‘error_load’)) pattern; TanStack retries 2x before setting isError)

3. Scope Boundaries

  • In: TanStack Query v5 integration, query key factory (queryKeys.ts), custom hook layer (hooks/queries/), QueryClientProvider in main.tsx, migration of all 62+ pages from useState/useEffect to TanStack Query hooks.
  • Out: No changes to authStore.ts (Zustand — session/identity state stays). No changes to endpoints.ts structure (thin Axios wrappers stay). No changes to backend. No removal of skeleton loading states (they remain, just driven by isLoading instead of loading). No introduction of suspense mode. No changes to auth pages (LoginPage, RegisterPage, LandingPage — they use authStore directly or have no data fetching).

4. Data Model

n/a — frontend-only change. No new database models, no model migrations.

5. API Contract

No new endpoints. All existing endpoints (30+ endpoint objects in endpoints.ts) continue to work identically. TanStack Query wraps the existing Axios calls.

6. UI / UX

  • No visual changes. Every page that had <ListSkeleton /> or <DashboardSkeleton /> still renders it — the loading state is now driven by TanStack Query’s isLoading instead of manual loading state.
  • No new routes. All existing routes remain unchanged.
  • No new i18n keys. Error messages already use existing t('error_load') keys from each page’s namespace.
  • Optimistic mutations: Where the current code does manual snapshot-and-revert (BriefPage assign/dismiss, HqUsersPage toggleStaff), TanStack Query’s onMutate/onError/onSettled pattern replaces it.

7. Test Plan

#CaseTypeWhere
1Query client config bootstraps correctlyfrontendfrontend/src/test/hooks.test.ts
2Query key factory produces unique keys per paramsfrontendfrontend/src/test/hooks.test.ts
3Mutation hook invalidates lists on successfrontendfrontend/src/test/hooks.test.ts
4429 retry behavior matches old interceptorfrontendfrontend/src/test/apiClient.test.ts
5TodayPage renders same data after migratione2efrontend/tests/e2e/lumi.spec.ts describe("Today page")
6All existing pages still pass E2E testse2efrontend/tests/e2e/lumi.spec.ts (all describe blocks)

No new E2E describe block required — this migration does not change user-facing behavior.

8. Current Behavior

Shipped. @tanstack/react-query installed; QueryClientProvider wraps app root. queryKeys.ts — typed factory with 30+ domain blocks. All major page components migrated from useEffect-based fetch to useQuery / useMutation patterns. Optimistic updates on task priority, deal stage, commitment status.

9. Error States

  • Network error during query: TanStack Query retries 2 times with exponential backoff. On final failure, isError is true and the component renders its error state (toast error with existing i18n key).
  • Mutation error: useMutation exposes onError callback. The existing toast.error(t('error_<action>')) pattern continues to work identically.
  • 429 rate limit: Removed from client.ts interceptor. TanStack Query’s default retry (3 attempts with exponential backoff) handles it instead. The retry option is set to 2 to match the old behavior.

10. Diagrams & Docs Touched

  • frontend/DIAGRAMS.md §4 — add TanStack Query cache layer to API client request flow.
  • frontend/DIAGRAMS.md §6 — rename “Page Data Loading Pattern” to reference TanStack Query hooks.
  • frontend/AGENTS.md — remove “No TanStack Query” line from Server State Pattern section. Add documentation for the new hooks pattern.
  • docs/MVP_SCOPE.md — add F61 registry entry.

11. Implementation Audit (2026-06-13)

#ClaimStatusEvidence
1@tanstack/react-query installed; QueryClientProvider wraps app root✅ matchpackage.json; frontend/src/main.tsx
2queryKeys.ts — typed factory with 30+ domain blocks✅ matchfrontend/src/api/queryKeys.ts
3hooks/queries/ — 30+ domain hook files (list queries, detail queries, mutations)✅ matchfrontend/src/hooks/queries/ directory
4429 retry removed from client.ts interceptor (TanStack handles it)✅ matchfrontend/src/api/client.ts
5Pages migrated: TodayPage, CommitmentsPage, RisksPage, DecisionsPage, BriefPage✅ matchConfirmed per §13 changelog
6Zustand authStore left untouched (session state, not server state)✅ matchBy design; frontend/src/stores/authStore.ts
7frontend/AGENTS.md updated — no more “No TanStack Query” rule✅ matchfrontend/AGENTS.md §server-state-pattern
8hooks.test.ts — 8 tests for query key factory✅ matchfrontend/src/test/hooks.test.ts
9Previously-flagged residual pages (ProjectsPage, TeamsPage, HorizonPage) already read through query hooks; remaining useEffects are local UI sync, not legacy data loading✅ match2026-07-01 audit: ProjectsPage.tsx uses useProjectsList(), TeamsPage.tsx uses useTeamsList() / useMembersList() + a per-team useQuery, and HorizonPage.tsx uses useHorizonData()

12. Open Questions / Decisions

None resolved. All key decisions are recorded in the implementation plan:

  • Stale time: 30s (matches old polling interval)
  • Retry: 2 (matches old 429 retry count)
  • refetchOnWindowFocus: true (B2B CEO usage)
  • No suspense mode in v1
  • authStore stays as-is (session state, not server state)
  • endpoints.ts stays as-is (thin wrappers are fine)
  • Optimistic updates use TanStack’s onMutate/onError/onSettled

13. Bug / Change Log

2026-07-01 — Close stale residual-page migration gap

Type: Documentation truth fix

Summary:

  • Closed the last F61 audit row after re-checking the three pages it named.
  • ProjectsPage, TeamsPage, and HorizonPage already fetch through TanStack Query hooks; their remaining useEffects are for local UI behavior, not legacy load() fetch orchestration.

2026-06-13 — Initial spec

Type: Feature

Summary:

  • First version of F61 spec.
  • Covers add of TanStack Query v5, query key factory, hooks layer, page-by-page migration.

Reason:

  • Standardize frontend data fetching on a proven library.
  • Eliminate boilerplate and inconsistency across 62+ pages.

Impact:

  • All frontend pages across all roles.
  • Zero backend impact.
  • Zero visual change — loading states remain skeleton-based.

Notes:

  • Pages are migrated one at a time, each verified by existing E2E tests.
  • The frontend/AGENTS.md rule against TanStack Query is replaced by a rule for it.