toga-ai 1.0.769 → 1.0.771
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -709,17 +709,21 @@ non-deterministic, and **can write real data**. Two consequences worth internali
|
|
|
709
709
|
|
|
710
710
|
**MUST — every new test is SEEN TO FAIL before it is allowed to pass.** A test written straight to
|
|
711
711
|
green has proven nothing; on a stubbed harness a test can easily pass for the wrong reason. Break
|
|
712
|
-
the thing under test on purpose, watch the test go red, then restore it.
|
|
713
|
-
the reference implementation:
|
|
714
|
-
|
|
715
|
-
1. **
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
712
|
+
the thing under test on purpose, watch the test go red, then restore it. The two mutations actually
|
|
713
|
+
run against the reference implementation on 2026-09-03, both of which produced the expected red:
|
|
714
|
+
|
|
715
|
+
1. **Break the config the test reads.** The tenant registry was edited to claim `QUAD` is granted
|
|
716
|
+
`transfer-orders` while its surface fixture still withheld it. The nav test failed with
|
|
717
|
+
`Expected to find element: a[href="/transfer-orders"], but never found it.` — proving the test
|
|
718
|
+
reads the real resolved nav, not the registry it was seeded from.
|
|
719
|
+
2. **Break the app code the test covers.** `getUserFieldsForClient` was forced to always return the
|
|
720
|
+
`DEFAULT` field list, dropping every role flag. The user-fields test failed with
|
|
721
|
+
`expected 'firstName,lastName,email,uuid' to include '_isAdmin'` — proving it would catch a real
|
|
722
|
+
regression that silently demotes every admin to `USER`.
|
|
723
|
+
|
|
724
|
+
Mutate **the thing under test**, not the harness. Verifying a *guardrail* (e.g. reverting an
|
|
725
|
+
intercept to a URL glob to confirm the fall-through check fires) is a separate and also worthwhile
|
|
726
|
+
exercise, but it proves the guard works — not that any test does.
|
|
723
727
|
|
|
724
728
|
**MUST — every config file is type-checked.** A config that no `tsconfig` includes is compiled by
|
|
725
729
|
**nothing** and is green by default — the same false-green class as running bare `npx tsc --noEmit`
|
|
@@ -781,6 +785,11 @@ Copy from there rather than reinventing.
|
|
|
781
785
|
|
|
782
786
|
## Change history
|
|
783
787
|
|
|
788
|
+
- 2026-09-03 — §23 correction: replaced the two "seen to fail" worked examples. The first pass
|
|
789
|
+
listed a guardrail check plus an **unverified** component-config mutation; both are now the two
|
|
790
|
+
mutations that were actually run and observed red (a registry/fixture nav mismatch, and forcing
|
|
791
|
+
the `DEFAULT` user field list). Added the distinction that mutating the harness verifies a
|
|
792
|
+
**guard**, not a **test**. (apeterson)
|
|
784
793
|
- 2026-09-03 — **Front-end testing promoted from an open item to §23 (MUST/SHOULD).** Settles the
|
|
785
794
|
layering: Vitest + RTL for logic, Cypress **component** tests for the tenant x role x feature
|
|
786
795
|
matrix (**not** e2e), e2e for a few real journeys only, with the rule of thumb "no URL or page
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: session
|
|
3
|
+
slug: commerce-query-cache-diagnosis
|
|
4
|
+
title: Root-caused toga2-commerce stale-cache + stuck-login bugs against toga25-supply's fixed setup
|
|
5
|
+
author: apeterson
|
|
6
|
+
repos: [toga25-supply, toga2-commerce]
|
|
7
|
+
framework: "2.0"
|
|
8
|
+
client: shared
|
|
9
|
+
status: active
|
|
10
|
+
created: 2026-09-03
|
|
11
|
+
updated: 2026-09-03
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Session: commerce-query-cache-diagnosis
|
|
15
|
+
**Date:** 2026-09-03
|
|
16
|
+
**Project/Repo:** toga2-commerce + toga25-supply (2.0)
|
|
17
|
+
**Task:** Understand what `supply-chain-query-cache` does in toga25-supply, then use it as the reference to root-cause toga2-commerce's chronic stale-data, failed-cache-clear, and stuck-login-page bugs.
|
|
18
|
+
|
|
19
|
+
**No code was written this session.** It is a read-only investigation. Everything below is a
|
|
20
|
+
verified finding plus an agreed fix plan that has NOT been implemented.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## What WORKED
|
|
25
|
+
|
|
26
|
+
**Mapped `supply-chain-query-cache` in toga25-supply completely (verified in source):**
|
|
27
|
+
- It is not a hand-written cache. It is the whole React Query cache dumped to `localStorage`
|
|
28
|
+
under that key, via `createSyncStoragePersister` at `src/App.tsx:27-30`, wired by
|
|
29
|
+
`PersistQueryClientProvider` at `src/App.tsx:33`.
|
|
30
|
+
- Persists **every successful query** except three namespaces, excluded in
|
|
31
|
+
`shouldDehydrateQuery` (`src/App.tsx:63-70`): `talos`, `surface-meta`, `surface-meta-group`.
|
|
32
|
+
Surface-meta is excluded because it carries permission/feature-gate state, so a persisted
|
|
33
|
+
copy would hide a grant or a revoke.
|
|
34
|
+
- QueryClient default is `staleTime: 0` (`src/App.tsx:18`) precisely because the cache is
|
|
35
|
+
persisted.
|
|
36
|
+
- Resets on: login (`contexts/AuthContext.tsx:113-114`), logout (`:141-146`), each list page's
|
|
37
|
+
Refresh button (`queryClient.clear()` + `resetQueries(["table-data"])` — SalesOrders,
|
|
38
|
+
TransferOrders, ServiceRequests, Items, VendorItems, Bundles, Inventory), and a 24h
|
|
39
|
+
`maxAge`.
|
|
40
|
+
- `maxAge` default confirmed at `node_modules/@tanstack/query-persist-client-core/build/modern/persist.js:10`
|
|
41
|
+
— one timestamp for the **whole blob**, not per query.
|
|
42
|
+
- No `buster` is set, so a deploy does not invalidate it. `DeploymentGuard`
|
|
43
|
+
(`src/hooks/useDeploymentGuard.tsx`) handles deploys separately by watching the Core
|
|
44
|
+
parameter `META_LAST_REFRESH_DATETIME` and signing the user out.
|
|
45
|
+
- Query-key → endpoint map: `["table-data", slug, …]` → `getDataTableData`;
|
|
46
|
+
`["table-meta", slug]` → `GET /table-views/meta` (blox
|
|
47
|
+
`components/Table/hooks/useFetchTablePageMeta.js:8`); `["status-colors", route]` →
|
|
48
|
+
`GET /{route}?fields=slug,colorHex&sort=sortOrder`; current user → `GET /users`;
|
|
49
|
+
`["deploymentVersion"]` → `GET /core-parameters`.
|
|
50
|
+
- Remaining non-zero `staleTime` hooks in supply that ARE persisted (so each can serve a
|
|
51
|
+
previous session's data and never fetch): `useClientFields`, `useCurrentUser`,
|
|
52
|
+
`useStatusTypeValues`, `useStatusColors`, `useSalesOrderUuidForServiceRequest` (all
|
|
53
|
+
`Infinity`); the three VendorItem form hooks (`5 * 60_000`); `useTalosThreads` (`30_000`).
|
|
54
|
+
`useFetchSurfaceMeta`'s `Infinity` is safe only because it is excluded from persisting.
|
|
55
|
+
|
|
56
|
+
**Root-caused four stacked bugs in toga2-commerce (branch `TRUE-81610`), all verified:**
|
|
57
|
+
|
|
58
|
+
1. **App-wide 24-hour `staleTime`** — `src/App.tsx:26-31`. Supply's default is `0`. Combined
|
|
59
|
+
with a persisted cache (key `"commerce"`, `src/App.tsx:34-37`) and **no**
|
|
60
|
+
`dehydrateOptions` (nothing excluded), yesterday's queries are served today with no
|
|
61
|
+
network call. This is the "users not seeing updated data" report, and it happens daily —
|
|
62
|
+
not only after a deploy. The same 24h value is also hardcoded per-hook at
|
|
63
|
+
`pages/BundleView/hooks/useBundleQueryData.ts:26,40` and
|
|
64
|
+
`pages/Home/viewModel/useHomeViewModel.ts:150,173,197`, so changing only the default is
|
|
65
|
+
not enough.
|
|
66
|
+
|
|
67
|
+
2. **Cache clears are not gated on restoration** — `PersistQueryClientProvider` renders
|
|
68
|
+
children *during* restore (confirmed in
|
|
69
|
+
`node_modules/@tanstack/react-query-persist-client/build/modern/PersistQueryClientProvider.js`:
|
|
70
|
+
`isRestoring` starts `true`, `restoreClient` resolves later). A `clear()` +
|
|
71
|
+
`removeItem("commerce")` that runs before restore finishes is silently undone when the
|
|
72
|
+
pending restore hydrates the old cache on top and the subscription writes it back.
|
|
73
|
+
Unguarded in all three places: `contexts/AuthContext.tsx:180-183`,
|
|
74
|
+
`components/NavIcons/NavIconList.tsx:162-170`, `api/axiosInstance.ts:46`. Supply fixed
|
|
75
|
+
this with an `awaitRestoration()` promise at `contexts/AuthContext.tsx:56-77`. **This is
|
|
76
|
+
the "it's not successfully clearing their caches" report.**
|
|
77
|
+
|
|
78
|
+
3. **Stuck/broken login page — two gates check different keys.** The axios request
|
|
79
|
+
interceptor gates on **`user`** (`api/axiosInstance.ts:112-118`): with no `user` it calls
|
|
80
|
+
`fetchPublicToken()`, which POSTs `/auth/public` and **writes the public token to
|
|
81
|
+
`localStorage` as `accessToken`** (`:53-64`). But `hooks/useAuthenticationFlow.ts:213-217`
|
|
82
|
+
gates on **`accessToken`** and returns early if it exists. So after a forced logout: the
|
|
83
|
+
first API call writes a public `accessToken` back, the auth flow sees it, skips both the
|
|
84
|
+
SSO redirect and `/login`, and the user is logged out while the app thinks they are not.
|
|
85
|
+
Supply already gates on `user` — see the explicit comment at
|
|
86
|
+
`contexts/AuthContext.tsx:47-51`.
|
|
87
|
+
|
|
88
|
+
4. **The forced logout is too aggressive.** `contexts/AuthContext.tsx:192-252` watches
|
|
89
|
+
`META_LAST_REFRESH_DATETIME` and on any change calls `logout()` +
|
|
90
|
+
`window.location.href = "/"` with no notice. It also **monkey-patches
|
|
91
|
+
`window.history.pushState` and `replaceState`** (`:216-226`) to refetch on every
|
|
92
|
+
navigation — react-router uses those same methods. On an SSO client, `/` re-triggers the
|
|
93
|
+
IdP. Supply's DeploymentGuard at least shows a 2.5s toast first.
|
|
94
|
+
Separately, commerce's `login()` (`:100-148`) **never clears the cache**, so signing in as
|
|
95
|
+
a different user inherits the previous user's data for up to 24h. Supply clears on login.
|
|
96
|
+
|
|
97
|
+
**Resolved the "but caching is what makes items/bundles fast" objection — `staleTime` is the
|
|
98
|
+
wrong knob:**
|
|
99
|
+
- React Query paints cached data instantly regardless of `staleTime`. `staleTime` only
|
|
100
|
+
controls whether it *also* refetches in the background. `gcTime` is the knob that keeps the
|
|
101
|
+
cached copy around.
|
|
102
|
+
- Verified commerce gates **every** loading state on `isLoading`, never `isFetching`
|
|
103
|
+
(`useBundleQueryData.ts:22,35`; `useHomeViewModel.ts:136,157,177`). `isLoading` is false
|
|
104
|
+
whenever cached data exists. So dropping `staleTime` shows **no** spinner on cached data —
|
|
105
|
+
the 24h `staleTime` buys nothing on perceived speed, it only suppresses the refetch.
|
|
106
|
+
- **`gcTime` is set nowhere in commerce**, so it is the 5-minute default. Navigating away
|
|
107
|
+
from Home for 6+ minutes and back drops the query from memory and *does* show a real
|
|
108
|
+
spinner today, despite the 24h staleTime. The persisted cache only helps on a full reload,
|
|
109
|
+
not in-session navigation. Fixing `gcTime` is the actual speed win.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## What did NOT work — DO NOT RETRY THESE
|
|
114
|
+
|
|
115
|
+
- **Nothing was attempted and failed** — no code was written or run this session.
|
|
116
|
+
|
|
117
|
+
Two dead ends established by reasoning, worth not re-proposing:
|
|
118
|
+
|
|
119
|
+
- **A buster alone will not fix commerce.** `buster` is compared once on restore and deletes
|
|
120
|
+
the persisted cache when it differs (`persist.js:20-24`). It only fires on a **deploy**.
|
|
121
|
+
Commerce's stale data is a daily 24h-`staleTime` problem, so a buster leaves the main bug
|
|
122
|
+
untouched. It is step 5 of 5, not step 1.
|
|
123
|
+
- **Versioning the storage key instead of using `buster`** (e.g.
|
|
124
|
+
`supply-chain-query-cache-v2`) works but leaks the old blob in `localStorage` forever.
|
|
125
|
+
`buster` calls `removeClient()` and actually cleans up. Use `buster`.
|
|
126
|
+
- **Do not expect a buster to fix a cached JS bundle.** Buster runs *inside* the app; if the
|
|
127
|
+
browser never downloaded the new app, buster never runs. Diagnostic to separate the two:
|
|
128
|
+
hard-refresh (Cmd+Shift+R) fixes it → bundle/CDN cache-header problem, buster is
|
|
129
|
+
irrelevant; only logout fixes it → persisted query data, buster helps.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Not tried yet (candidates for next session)
|
|
134
|
+
|
|
135
|
+
1. Set `gcTime: 1000 * 60 * 60` + `staleTime: 30_000` + `refetchOnWindowFocus: false` on the
|
|
136
|
+
heavy commerce hooks (`pages/BundleView/hooks/useBundleQueryData.ts`,
|
|
137
|
+
`pages/Home/viewModel/useHomeViewModel.ts`). Keeps the instant paint, makes data fresh.
|
|
138
|
+
2. Change the commerce app default `staleTime` at `src/App.tsx:29` from
|
|
139
|
+
`1000 * 60 * 60 * 24` to `0`.
|
|
140
|
+
3. Port supply's `awaitRestoration()` gate (`toga25-supply/src/contexts/AuthContext.tsx:56-77`)
|
|
141
|
+
into commerce and put all three clear sites behind it.
|
|
142
|
+
4. Change `hooks/useAuthenticationFlow.ts:213` to gate on `localStorage.getItem("user")`
|
|
143
|
+
instead of `accessToken`.
|
|
144
|
+
5. Add a `clear()` to commerce's `login()`, gated on restoration.
|
|
145
|
+
6. Add the buster to **both** apps: a `define: { __BUILD_ID__: JSON.stringify(process.env.AWS_COMMIT_ID ?? "dev") }`
|
|
146
|
+
block in `vite.config.ts`, a `declare const __BUILD_ID__: string;` in `src/vite-env.d.ts`,
|
|
147
|
+
and `buster: __BUILD_ID__` in `persistOptions`. Note `package.json` version is `0.0.0` in
|
|
148
|
+
both repos (never bumped, unusable as a build id) and there is **no `amplify.yml` in
|
|
149
|
+
either repo** — Amplify is console-configured, so `AWS_COMMIT_ID` is the available value.
|
|
150
|
+
7. Check whether prices or stock counts ride in the bundle/item payloads. If they do, those
|
|
151
|
+
specific queries must gate their spinner on `isFetching`, not `isLoading` — a wrong price
|
|
152
|
+
shown for one second is a real problem, unlike a stale item list.
|
|
153
|
+
8. Check the Amplify/CloudFront cache headers on `index.html` for commerce — the second,
|
|
154
|
+
separate cache that a buster cannot touch.
|
|
155
|
+
9. Audit whether the commerce item/bundle requests are field-limited and what FK depth they
|
|
156
|
+
use (api2 default depth is 3). Caching is currently hiding an oversized payload.
|
|
157
|
+
10. Revisit supply's own persisted `staleTime: Infinity` hooks (`useClientFields`,
|
|
158
|
+
`useCurrentUser`, `useStatusTypeValues`, `useStatusColors`,
|
|
159
|
+
`useSalesOrderUuidForServiceRequest`) — each can serve a previous session's data with
|
|
160
|
+
nothing in the network tab.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Current file state
|
|
165
|
+
|
|
166
|
+
| File | Status | Notes |
|
|
167
|
+
|------|--------|-------|
|
|
168
|
+
| (none) | unchanged | Read-only investigation. No file in any repo was created, modified, or deleted. |
|
|
169
|
+
|
|
170
|
+
Working tree note: `toga2-commerce` was inspected on branch `TRUE-81610`; `api2` (the session's
|
|
171
|
+
primary dir) is on `_sandbox-client` with only the pre-existing `CLAUDE.md` / `.claude/` /
|
|
172
|
+
`.gstack/` changes from before this session.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Decisions made
|
|
177
|
+
|
|
178
|
+
- **`gcTime`, not `staleTime`, is the speed knob — fix both together.** Rationale: React Query
|
|
179
|
+
serves cached data immediately no matter what `staleTime` says, and commerce gates every
|
|
180
|
+
spinner on `isLoading` (false when cached data exists), so lowering `staleTime` costs
|
|
181
|
+
nothing visually. Raising `gcTime` is what actually removes the in-session spinner.
|
|
182
|
+
Rejected: keeping the 24h `staleTime` "for speed" — verified it buys no perceived speed and
|
|
183
|
+
is the direct cause of the stale-data reports.
|
|
184
|
+
- **Fix order is staleTime/gcTime → restoration gate → auth gate → buster.** Rationale: ranked
|
|
185
|
+
by how often each bug bites. The 24h `staleTime` fires daily, the restoration race is why
|
|
186
|
+
every attempted clear fails, the `accessToken`/`user` mismatch is the stuck login page, and
|
|
187
|
+
the buster only matters on deploy day. Rejected: leading with the buster (which is what the
|
|
188
|
+
conversation started from) — it would have left the daily bug in place.
|
|
189
|
+
- **Use `staleTime: 30_000` rather than `0` on the heavy commerce hooks.** Rationale: stops a
|
|
190
|
+
refetch storm when a user clicks Home → bundle → Home in a few seconds, while still never
|
|
191
|
+
surviving a session or a deploy. Rejected: `0` everywhere (too many repeats of a large
|
|
192
|
+
payload) and any value long enough to outlive a session.
|
|
193
|
+
- **toga25-supply is the reference implementation; port from it rather than inventing.**
|
|
194
|
+
Rationale: supply already hit and fixed the same three traps (restoration race, public-token
|
|
195
|
+
gate, persisted-cache staleTime) and the fixes carry explanatory comments. Rejected: writing
|
|
196
|
+
fresh solutions in commerce.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Blockers
|
|
201
|
+
|
|
202
|
+
- **Unanswered question that changes step 7:** do the commerce bundle/item payloads carry
|
|
203
|
+
prices or stock/availability counts? If yes, those queries need an `isFetching`-based
|
|
204
|
+
loading state, not just the `gcTime`/`staleTime` swap. Asked the developer; not yet answered.
|
|
205
|
+
- **No implementation authorization yet.** The plan was presented and the developer ran
|
|
206
|
+
`/session-save` instead of approving. Do not start writing code next session without
|
|
207
|
+
asking again.
|
|
208
|
+
- Not a blocker, but note: `toga2-commerce` is on branch `TRUE-81610` with uncommitted work
|
|
209
|
+
(per the standards doc, commerce's blox pin and `dedupe` were being changed on this branch).
|
|
210
|
+
Check the tree state before editing.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Exact next step
|
|
215
|
+
|
|
216
|
+
> Ask the developer two things before writing any code: (1) do the commerce bundle/item
|
|
217
|
+
> payloads include prices or stock counts, and (2) confirm authorization to implement. Then
|
|
218
|
+
> start with candidate #1 — edit `toga2-commerce/src/pages/BundleView/hooks/useBundleQueryData.ts`
|
|
219
|
+
> lines 26 and 40, replacing `staleTime: 1000 * 60 * 60 * 24` with
|
|
220
|
+
> `staleTime: 30_000, gcTime: 1000 * 60 * 60, refetchOnWindowFocus: false`, and make the same
|
|
221
|
+
> change at `src/pages/Home/viewModel/useHomeViewModel.ts:150,173,197`. Verify the instant
|
|
222
|
+
> paint is unchanged before touching anything auth-related.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
_Saved by /session-save on 2026-09-03_
|
package/package.json
CHANGED