mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
# Description of Changes ## The problem The editor has no query client. ~295 `apiClient` call sites, each mount refetching what the last one just got, and three module-level caches reimplementing dedupe, retry and invalidation by hand — each shaped differently. The Processor (`frontend/editor/src/portal`) has run on TanStack Query since #7135. The editor never got it. ## End state The editor has a query client, and the three read-only fetch sites that convert safely now use it. `@tanstack/react-query` is already a dependency — no new package. **Foundation** | File | | |---|---| | `core/query/queryClient.ts` | `baseQueryOptions` + client factory. The portal now builds its client from the same options. `networkMode: "always"` — `navigator.onLine` describes internet reachability, which says nothing about a bundled backend on 127.0.0.1 or a self-hosted server on the LAN. | | `core/query/keys.ts` | `["editor", resource, ...params]` | | `core/query/staleTime.ts` + `desktop/query/staleTime.ts` | Config staleTime: `Infinity` on web, 5 min on desktop | | `core/api/config.ts`, `core/api/users.ts` | Fetch functions, mirroring `portal/api/*` | | `core/tests/utils/TestQueryProvider.tsx` | | | `desktop/components/DesktopQueryCacheReset.tsx` | | `QueryClientProvider` mounts at the top of `core/components/AppProviders.tsx`. That diff looks large but is one wrapper plus the reindent underneath it. **Converted.** All three keep their existing return shape, so no consumer changes. | | Before | |---|---| | `useFooterInfo` | Fetched twice — Footer and admin legal section | | `useGroupEnabled` | Refetched on every mount | | `UserSelector` | Refetched the whole roster on each of two mount sites, and again whenever `t` or `user` changed identity | **Desktop needs more than the provider.** `operationRouter` resolves the same relative path to the local bundled backend, a self-hosted server, or the SaaS backend. Query caches by key, not by resolved URL, so a cached entry can outlive the backend that filled it. `group-enabled` routes this way, so this PR introduces the hazard and carries the fix: `DesktopQueryCacheReset` calls `resetQueries()` when the connection mode changes or the self-hosted server goes up or down, and `CONFIG_STALE_TIME` is finite on desktop as a backstop. **Behaviour changes** - All three sites now retry once on failure (client default). None retried before, so a failing request sits in `loading` for one extra attempt plus backoff. - `staleTime: Infinity` on web means admin edits to legal links no longer appear on remount within a session. Saving those already prompts a restart, so this is accepted rather than incidental. - Desktop `useGroupEnabled` shows the *translated* offline reason on first render. The old code showed raw English for one render. - `UserSelector` drops three `console.log`s that were dumping user records to the console. ## Decisions **1. The foundation doesn't ship alone.** A provider nothing consumes gives a reviewer nothing to react to and rots if the follow-up stalls, so it lands with the cheapest safe conversions. **2. Hooks keep their existing return shape.** The alternative is switching to `{ data, isPending, error }` and updating consumers now. Cost of my choice: we carry a `loading`-shaped façade indefinitely, and consumers don't get `isFetching`/`refetch` without a second pass. Taken because it's what keeps each later migration a one-file diff. **3. Shared defaults, separate instances.** The editor and the Processor mount as *sibling* routes, not nested — they never coexist in one tree. Both clients now come from the same `baseQueryOptions`, so behaviour can't drift. A single shared instance would only buy cache surviving navigation between the two products, which is worth little while they share no keys, and it breaks the contract three portal tests rely on (`createPortalQueryClient()` returning a fresh client per test). That belongs in the collapse PR. Consequence meanwhile: the desktop reset covers the editor client only — harmless, since the portal isn't in desktop builds. **4. The desktop reset is wholesale.** A mode switch already remounts the SaaS provider tree, so there's nothing to preserve, and an allowlist of "mode-sensitive" keys would be a trap every new query has to remember to join. ## Coming next Ordered by consumers per line changed. | PR | Scope | |---|---| | 2 | `AppConfigContext` + `useEndpointConfig` — ~80 consumers, deletes ~200 lines of hand-rolled cache, retry and dedupe | | 3 | `useAdminSettings` (20 consumers) and the config sections | | 4 | Polling loops → `refetchInterval` | | 5 | Finish the Processor's remaining files, collapse to one client | | 6 | Tool execution — mutation state only, narrowly scoped | Not in scope, deliberately: `usePdfLibLinks` (its cache is a refcounted ArrayBuffer lifetime manager), thumbnail hooks, watched-folder IndexedDB reads, the desktop health monitors. Unifying `endpointAvailabilityService` / `saasAppConfigService` with the query cache would mean handing `operationRouter` a query client — its own PR if a second reason appears. ## Testing `task frontend:check` green: 1666 tests across 191 files, typecheck on all five flavours, eslint `--max-warnings=0`, dpdm, prettier. New tests cover request de-duplication, per-group key isolation, the desktop offline short-circuit, and the cache reset. The reset test was verified to fail against the `clear()` implementation it replaced. `UserSelector` has no test beyond its existing stories. One existing test needed a wrapper: `Login.test.tsx` renders `<Login />` in isolation, and `AuthLayout` → `Footer` → `useFooterInfo` now needs a client. The real `/login` route is already inside `AppProviders`, so this is test isolation, not a runtime gap. Rollback is a clean revert — nothing persists outside the React tree.