refactor: the rest of the frontend on TanStack Query, with toasts in the query cache

Merged
HA
4d ago
+1313-2451

Part of #326. Every page now reads and writes through the generated query and mutation options, so no hand-written loading, error, polling, cache or retry state is left around SDK calls. The only direct SDK calls are the three SSE streams.

The editor, the user settings page, notifications (the header center and the page), login, register, logout and the auth check moved over. The notification and user settings stores, lib/user-settings.ts, unwrap/unwrapOr and the auth store's own 30-second cache are gone. verifyAuth() now asks queryClient.query() for the profile with a 30 second stale time, which gives the same sharing for free. User settings are one query: the root layout applies the theme from it, the editor reads its editor settings from it, and saves put the response back with setQueryData. New notifications over SSE refetch the notification queries, so the unread count comes from the server.

The toasts moved out of the fetch interceptor. With throwOnError the generated options throw only the response body, so the interceptor now wraps each failure in an ApiError that keeps the status. The query client's cache handlers then call showApiError. That's what lets retries on: a query retries a network error or a 5xx twice, 1 and 2 seconds apart, then toasts once. Mutations don't retry. Login and register opt out of the toasts their forms show inline with meta: { inlineErrors: true } instead of the hard-coded endpoint list. Clearing auth also empties the query cache, so one user's data never shows to the next.

Vitest passes 1066 tests with coverage at 95.5/79.6/95.0/95.9, and check, lint, prettier and build are clean. Against a local backend the Playwright suite passes 149 tests; the 3 failures are the execution tests, which need k3s to run a pod. In Chrome, logged in, every route loads with no CSP violation or console error. A wrong password shows inline with no toast, and clearing the cookies sends the next request's 401 to /login with "Session expired" and the page remembered. With routed failures, a connection refused or a 503 on a polled list makes 3 attempts and one toast, and a 404 makes one attempt and shows its detail.

One backend bug showed up during that check and needs its own PR: the user settings service keeps a per-process 5-minute cache, and the API runs two gunicorn workers, so a read that lands on the other worker returns the old settings. Since the root layout now applies the theme from the settings query, a refetch could flip a just-toggled theme back until that's fixed.

Dates arriving as Date through @hey-api/transformers is the last bullet of #326 and comes next.


Summary by cubic

Moves the editor, settings, notifications, login, register, logout, and the auth check to TanStack Query, so no page keeps its own loading, error, polling, cache, or retry state around SDK calls. The only direct SDK calls left are the three SSE streams.

  • Removes the notification and user settings stores; user settings are now one shared query that the root layout applies for the theme and the editor reads for its settings, with saves written back via setQueryData.
  • Error toasts moved from the fetch interceptor to the query client's cache handlers, so a query retries a network failure or a 5xx twice before showing one toast; mutations don't retry.
  • Login and register opt out of the toasts their forms show inline via meta: { inlineErrors: true } instead of a hard-coded endpoint list.
  • verifyAuth() now reads the profile through the query client with a 30-second stale time, and clearing auth empties the query cache so stale data never shows to the next user.

Known issue

  • The user settings service keeps a per-process 5-minute cache and the API runs two gunicorn workers, so a refetch can flip a just-toggled theme back; a separate backend PR is needed to fix that.