Skip to content

refactor(web): data through TanStack Query, no useEffect outside SSE subscriptions (CL-8438) - #883

Merged
TheGreatAxios merged 5 commits into
mainfrom
cl-8438-no-useeffect
Sep 18, 2026
Merged

TheGreatAxios merged 5 commits into
mainfrom
cl-8438-no-useeffect

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Contributor

Sweeps apps/web/src for useEffect, converting every remaining one that
can be converted safely. 45 effects down to 19.

What changed

  • Settings sections (account, credentials, roles, people, grants) read
    through useQuery on shared tenantKeys; writes invalidate instead of a
    reloadKey counter. The dead, uncached useTenancyAccess hook is deleted
    (settings-access.ts already replaced it).
  • Retired paths resolve in the router. redirectTargetFor in
    routes.tsx owns every old prefix (/mission-control, /inbox,
    /routines, /files, /library, /settings/agents,
    /settings/skills); the new router-store.ts canonicalizes the path
    before any screen mounts. The six route entries that existed only to
    bounce, and pages/legacy-settings-redirects.tsx, are gone.
  • History is an external store. Root reads the path with
    useSyncExternalStore; the popstate listener lives in the store, not in
    an effect.
  • Redirects hop during render. A new Redirect component schedules the
    navigate on a microtask, once per target — nothing is updated mid-render
    and no frame of the route being left is painted. Used by the login
    bounce, / → newest chat, and the settings section correction.
  • Shell: layout mode via useSyncExternalStore over the media queries;
    canvas scope/route resets and the artifact save state adjusted during
    render; scroll reset is a keyed ref callback (use-scroll-reset.ts
    deleted); stage search focus moves to ref callbacks and handlers.
  • Chat blocks: approve-block reads status with useQuery and decides
    with useMutation; connect-service reads with useQuery and its live
    fold writes into that cache (the allowed subscription exception).
  • Pages: skills roster, skill detail and palette entity search read
    through useQuery; library selection reconciliation and bench selection
    persistence happen during render.
  • Canvas animation (dither-background) and the artifact editor's
    debounce cleanup are ref callbacks with cleanups, not effects.

bunx tsc -p apps/web --noEmit, bun run fmt, bun run lint clean;
bun test ./src in apps/web is 586 pass / 0 fail. No tests were deleted —
none broke.

Leftovers, with reasons

SSE subscriptions are the sanctioned exception and were left as-is
(shell/sidebar-sections.ts, pages/chat-thread-page.tsx,
pages/workbench-room-page.tsx, chat/blocks/connect-service-block-container.tsx).
The rest:

  • main.tsx — the session probe and first-login provisioning. Both run
    above the QueryClientProvider, which lives inside Shell. Hoisting the
    client to Root would change its "one client per signed-in shell mount"
    lifetime (the cache is dropped on sign-out today), and provisioning
    navigates on resolution. Worth doing, but not as a side effect of this
    sweep.
  • pages/onboarding-page.tsx (3) — a multi-phase provisioning state
    machine whose effects carry their own StrictMode guard against deploying
    Myra twice. Converting it is a rewrite of the boot path, not a mechanical
    swap.
  • Pure UI timers with no query behind them: chat/loading-state.tsx (2 —
    mount delay and tip rotation), chat/turn-activity.tsx (2 — stale-clear
    and the 1s elapsed tick), pages/insights-page.tsx (the same 1s tick).
    refetchInterval has nothing to attach to; the CSS-animation equivalents
    would be a design change to the loading treatment.
  • command-palette/use-entity-search.ts — the debounce timer. The fetch it
    guarded is now a useQuery; the timer itself has no Query equivalent.
  • Document/window event listeners that are handlers, not stores, so
    useSyncExternalStore does not fit:
    shell/context-menu/lib/use-document-context-menu-trigger.ts,
    command-palette-provider.tsx (the workbench-not-found event) and
    pages/library-page.tsx (the off-route Upload event, which also clicks a
    file input).

@linear-code

linear-code Bot commented Sep 18, 2026

Copy link
Copy Markdown

CL-8438

@TheGreatAxios
TheGreatAxios merged commit 772da7f into main Sep 18, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant