From 7de3adc5bbdb0552838116e147881a87e37a280f Mon Sep 17 00:00:00 2001 From: Arthur Bassi Date: Mon, 27 Jul 2026 14:29:23 -0300 Subject: [PATCH] docs(adr): record ADR 0001 for query-broadcast-client-experimental --- docs/adr/0001-query-broadcast-client.md | 73 +++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 docs/adr/0001-query-broadcast-client.md diff --git a/docs/adr/0001-query-broadcast-client.md b/docs/adr/0001-query-broadcast-client.md new file mode 100644 index 00000000..c990e584 --- /dev/null +++ b/docs/adr/0001-query-broadcast-client.md @@ -0,0 +1,73 @@ +# 0001. Cross-tab QueryClient sync via `@tanstack/query-broadcast-client-experimental` + +## Status + +Accepted + +## Context + +The SeaHaven admin SPA is frequently used with multiple browser tabs open +against the same work-order board (e.g. a dispatcher triaging the board in one +tab while editing a work order in another). Each tab owns its own TanStack +Query `QueryClient` cache, so a mutation performed in one tab (status change, +dispatch creation, comment, vendor patch, etc.) does not invalidate or update +the cache in sibling tabs. Users were seeing stale board/detail data until a +manual refresh or the next background refetch. + +We need a way to keep `QueryClient` caches roughly in sync across tabs of the +same origin, without introducing a new state-management layer (Redux is +disallowed by this repo's conventions) or a server-push mechanism. + +## Decision + +Use TanStack's own experimental broadcast client, +[`@tanstack/query-broadcast-client-experimental`](https://tanstack.com/query), +wired up in a single dedicated setup module, +`src/lib/query/setup-query-broadcast.ts`. The module: + +- Wraps `broadcastQueryClient({ queryClient, broadcastChannel })`, keyed to a + single named channel (`seahaven-admin-query`). +- No-ops outside the browser (SSR/build) and under Vitest (`import.meta.env.MODE +=== "test"`), so it never runs in unit tests or node-based tooling. +- Is invoked once from `src/lib/query/query-client.ts` against the app's + singleton `QueryClient`, so every tab that loads the SPA subscribes to the + same channel automatically — no per-feature wiring required. + +This piggybacks on the query cache we already have (no parallel store), uses +the library that owns the `QueryClient` we already depend on, and requires +close to zero application code (~20 lines) to adopt. + +### Alternatives considered + +- **Custom `BroadcastChannel` + manual `queryClient.invalidateQueries` calls** + — full control over payloads, but requires hand-rolling + serialization/versioning of query keys and mutation results, and keeping + every future mutation hook wired to broadcast. More code to own and more + surface area for subtle cache-desync bugs. +- **No cross-tab sync** — simplest option, but leaves the stale-tab UX problem + unresolved; users would need to manually refresh or wait for + `refetchOnWindowFocus`/`staleTime` to catch up, which is not reliable enough + for a live dispatch board. +- **Full WebSocket-based real-time sync** — solves both cross-tab and + cross-user staleness, but is a materially larger investment (server-side + push infra, connection lifecycle, auth over the socket) that is out of scope + for the current admin SPA and not justified by the actual problem (same + browser, same user, same origin). + +## Consequences + +- **Positive**: sibling tabs reflect mutations (status changes, dispatch + actions, comments, patches) without a manual refresh; the integration is + isolated to one setup file and does not touch domain/query-key code. +- **Risk — package lifecycle**: the dependency is explicitly "experimental" in + the TanStack ecosystem; its API may change or be deprecated between minor + versions. We pin a specific version in `package.json` and re-evaluate on + every TanStack Query upgrade. +- **Risk — same-origin only**: `BroadcastChannel` only syncs tabs on the same + origin; it does not sync across different users/sessions or devices. This is + acceptable for the current requirement (single user, multiple tabs). +- **Rollback plan**: remove the `@tanstack/query-broadcast-client-experimental` + dependency from `package.json` and delete the call to `setupQueryBroadcast` + in `src/lib/query/query-client.ts` (and the `setup-query-broadcast.ts` module + itself). No other code depends on it, so rollback is a single, isolated + change with no data migration.