toga-ai 1.0.778 β 1.0.779
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.
- package/knowledge/2.0/apps/_underscore/features/surface-resolver.md +34 -1
- package/knowledge/2.0/apps/dbchanges2/workflows/targeting-the-right-database.md +76 -14
- package/knowledge/2.0/apps/toga2-commerce/INDEX.md +1 -1
- package/knowledge/2.0/apps/toga2-commerce/features/catalog-cache-freshness.md +167 -21
- package/knowledge/2.0/apps/toga25-supply/INDEX.md +2 -1
- package/knowledge/2.0/apps/toga25-supply/features/persisted-query-cache.md +211 -0
- package/knowledge/2.0/apps/toga25-supply/features/transfer-orders-page.md +14 -1
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/nychh/profile.md +8 -1
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-03
|
|
10
10
|
owners: [jcardinal, apeterson, bala]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Client/Language.php
|
|
@@ -155,6 +155,32 @@ boolean rows; the item-record modal (the first boolean consumer) exposed the gap
|
|
|
155
155
|
`_underscore`, so it takes effect only once the framework branch is deployed (framework is pulled at
|
|
156
156
|
deploy).
|
|
157
157
|
|
|
158
|
+
### π¨ A message key inside `config` JSON is NEVER resolved β copy must ride a `*MessageId` column
|
|
159
|
+
|
|
160
|
+
`_buildBundle`/`_serializeElement` build `bundle.messages` by scanning **exactly six columns** on
|
|
161
|
+
`SurfaceElements` β `labelMessageId`, `tooltipMessageId`, `placeholderMessageId`,
|
|
162
|
+
`disabledTooltipMessageId`, `trueMessageId`, `falseMessageId` (`Surface.php` ~L600). **`config` is
|
|
163
|
+
passed through untouched and is never scanned for message keys.** A key written into `config`
|
|
164
|
+
therefore ships to the frontend with **no value behind it**, and the screen renders the raw key.
|
|
165
|
+
|
|
166
|
+
This is a load-bearing constraint on how *any* surface-driven copy is modelled: **one string = one
|
|
167
|
+
`SurfaceElement` message column.** There is no way to attach copy to a nested `config` blob.
|
|
168
|
+
|
|
169
|
+
**Real case (2026-09-03).** The Columns modal rendered `common.columns.modal.apply` as its Apply
|
|
170
|
+
button label. `Core/2026-08-28b` had put the seven `common.columns.modal.*` keys inside the
|
|
171
|
+
`columnsButton` element's `config.modal` β data that looks right and can never resolve. The chosen
|
|
172
|
+
fix was **not** to teach the resolver to scan `config` (that widens the message-resolution contract
|
|
173
|
+
for every surface); it was to give the modal **its own MODAL Surface** so every string rides a real
|
|
174
|
+
`*MessageId` on its own element and the existing collector resolves it with **zero `_underscore`
|
|
175
|
+
change**. See
|
|
176
|
+
[column-visibility](../../toga25-supply/features/column-visibility.md).
|
|
177
|
+
|
|
178
|
+
**Diagnostic order.** When the FE shows a raw message key, first check whether the key rides a
|
|
179
|
+
`*MessageId` column β *before* suspecting an unrun seed. The resolved bundle in the API response is
|
|
180
|
+
the proof: if the element is present but `bundle.messages` has no entry for the key, the seed ran and
|
|
181
|
+
the modelling is wrong. (This session's first diagnosis β "the seed hasn't run" β was wrong, and the
|
|
182
|
+
pasted API response is what disproved it.)
|
|
183
|
+
|
|
158
184
|
## FIELD elements bound to a RecordField β see the dedicated doc
|
|
159
185
|
|
|
160
186
|
A `renderType = FIELD` element with a non-null `recordFieldId` gets `Core.RecordFields` metadata and
|
|
@@ -755,6 +781,13 @@ full stop. See
|
|
|
755
781
|
match Compass, a follow-up migration aligning both `meta` and `meta-group` to roles 1,3,4 is needed.
|
|
756
782
|
|
|
757
783
|
## Change history
|
|
784
|
+
- 2026-09-03 β Recorded a **hard constraint on message resolution**: the resolver collects
|
|
785
|
+
`bundle.messages` from **only** the six `*MessageId` columns on `SurfaceElements`; a message key
|
|
786
|
+
placed inside `config` JSON is **never** resolved and renders raw on screen. Found when the Columns
|
|
787
|
+
modal displayed `common.columns.modal.apply`; `Core/2026-08-28b` had seeded those keys into
|
|
788
|
+
`config.modal`. Fixed in data, not in the framework β the modal became its own MODAL Surface
|
|
789
|
+
(Core 60) so each string rides a real `*MessageId`. Also recorded the diagnostic order: a raw key
|
|
790
|
+
means "check the modelling" before "check whether the seed ran". (apeterson)
|
|
758
791
|
- 2026-09-01 β Split the new **FIELD β RecordField binding** capability out into its own doc
|
|
759
792
|
([surface-field-recordfield-binding](surface-field-recordfield-binding.md)) rather than growing this
|
|
760
793
|
one: for a `renderType=FIELD` element with a `recordFieldId`, `_buildBundle` now folds on
|
|
@@ -6,7 +6,7 @@ project: Database Changes
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-04
|
|
10
10
|
owners: [apeterson]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Config/production.ini
|
|
@@ -65,6 +65,20 @@ setpw() {
|
|
|
65
65
|
`MYSQL_PWD` keeps the password out of `ps` and history the same way a defaults file does β use it
|
|
66
66
|
when the client rejects `--defaults-extra-file` (some builds do).
|
|
67
67
|
|
|
68
|
+
**Check the password's line SHAPE without ever seeing the value.** Mask every character that is not
|
|
69
|
+
a space, tab, `;`, or `"` β you learn whether the value is quote-wrapped and whether an inline `;`
|
|
70
|
+
comment is in play, and the secret never reaches your screen or scrollback:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
awk -v S=database '$0=="["S"]"{f=1;next} /^\[/{f=0} f && /^password/{
|
|
74
|
+
k=$0; sub(/=.*/,"=",k); v=$0; sub(/^[^=]*=/,"",v); gsub(/[^ \t;"]/,"x",v);
|
|
75
|
+
print "[" k v "]"; exit}' Config/production.ini
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
On `production.ini` `[database]` this printed `[password = "xxxxxxxx"]` (checked 2026-09-04): the
|
|
79
|
+
double-quote wrap is real, and there is **no inline `;` comment** on password lines β so `setpw`
|
|
80
|
+
above, which strips quotes only, is correct as written and needs no comment handling.
|
|
81
|
+
|
|
68
82
|
**Keep the password out of your shell history and out of `ps`** by building a throwaway
|
|
69
83
|
credentials file from the ini instead of passing `-p` on the command line:
|
|
70
84
|
|
|
@@ -93,10 +107,25 @@ Delete the file when you are done.
|
|
|
93
107
|
|
|
94
108
|
## Resetting a shared environment from production (dump β drop β reload)
|
|
95
109
|
|
|
96
|
-
Used 2026-09-03 to bring sandbox-client's stale `Client_Nychh` TableViews back in line with prod
|
|
97
|
-
The developer runs every one of these; do not
|
|
98
|
-
|
|
99
|
-
|
|
110
|
+
Used 2026-09-03 to bring sandbox-client's stale `Client_Nychh` TableViews back in line with prod,
|
|
111
|
+
and again 2026-09-04 (`Core` + `Client_Nychh`). The developer runs every one of these; do not
|
|
112
|
+
execute them for them.
|
|
113
|
+
|
|
114
|
+
1. π¨ **Inventory what the reset will DESTROY β before you drop anything.** A shared non-prod
|
|
115
|
+
environment is normally *ahead* of prod, because that is where changes get tried first, so a
|
|
116
|
+
prodβsandbox reload silently rolls that lead back. List every `dbchanges2` file dated after the
|
|
117
|
+
last prod release, plus anything parked in `git stash`, and decide which ones must be re-run.
|
|
118
|
+
Candidates from the 2026-09-04 sandbox-client reset, all *potentially* sandbox-only:
|
|
119
|
+
- `Core/` β 2026-09-01a, -01c, -01d, -02a, -02b, -03a, -03b, -03c, -03d
|
|
120
|
+
- `Client/` β 2026-09-01b, -01c, -02a
|
|
121
|
+
- `Client_Nychh/` β 2026-09-02a, -03a, -03b
|
|
122
|
+
- `dbchanges2` `stash@{0}` ("TO qty fulfilled core seed"), which is applied nowhere
|
|
123
|
+
|
|
124
|
+
**Verify every candidate against the environment β never assume the list.** After this reset
|
|
125
|
+
sandbox read `Core.Surfaces` = 54, which means some of those Core surface files were **already in
|
|
126
|
+
prod** and must not be re-run. The repo records intent, not deployed state β see
|
|
127
|
+
[Verifying whether a migration actually ran](./verifying-a-migration-ran.md).
|
|
128
|
+
2. **Dump each source database**, one file each β prod `Core` from
|
|
100
129
|
`reader1.core.database.togahub.com`, prod tenant from `reader1.client.database.togahub.com`:
|
|
101
130
|
```
|
|
102
131
|
mysqldump --single-transaction --quick --routines --triggers --events \
|
|
@@ -104,14 +133,17 @@ The developer runs every one of these; do not execute them for them.
|
|
|
104
133
|
```
|
|
105
134
|
`--set-gtid-purged=OFF` because RDS has GTID on; `--no-tablespaces` because the `admin` user
|
|
106
135
|
lacks `PROCESS` (otherwise **error 1227**).
|
|
107
|
-
|
|
136
|
+
3. π¨ **`tail -1` every dump file and confirm it ends with `-- Dump completed` BEFORE you drop
|
|
108
137
|
anything.** A truncated dump loads without complaining. DNS dropped mid-session on a host that
|
|
109
138
|
had resolved seconds earlier (`ERROR 2005 Unknown MySQL server host`) β the dump just stops.
|
|
110
|
-
|
|
139
|
+
4. **Prove which environment each connection actually reached.** Better than `@@hostname`: check the
|
|
111
140
|
resolved IP β production resolves into **`10.201.x`**, sandbox into **`10.200.x`**.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
you intended to copy.
|
|
141
|
+
5. **Drop + create the target schema, then pipe the dump in.**
|
|
142
|
+
6. **Verify with a row count known to differ** (step 1 of *before you trust ANY tenant dump*,
|
|
143
|
+
above), against the schema you intended to copy.
|
|
144
|
+
7. **Re-run the step-1 migrations that prod did not already have, in `Core/` β `Client/` fan-out β
|
|
145
|
+
`Client_<Tenant>/` order.** Client files reference Core record ids, and the client work needs the
|
|
146
|
+
columns the `Client/` fan-out adds β any other order fails or half-applies.
|
|
115
147
|
|
|
116
148
|
### β Which `mysql` / `mysqldump` binary you are running is load-bearing
|
|
117
149
|
|
|
@@ -120,10 +152,23 @@ The developer runs every one of these; do not execute them for them.
|
|
|
120
152
|
`xamppfiles/bin/mysqldump` is **MariaDB 10.4** (rejects the flag);
|
|
121
153
|
`xamppfiles/mysql/bin/mysqldump` is **MySQL 8.0.44** (correct). Run `mysqldump --version` before
|
|
122
154
|
you trust it.
|
|
123
|
-
- **A shell alias
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
155
|
+
- π¨ **A shell alias hijacks a remote connection β and the error names the wrong problem.** On an
|
|
156
|
+
XAMPP machine `~/.zshrc:31` defines
|
|
157
|
+
`alias mysql='/Applications/XAMPP/xamppfiles/mysql/bin/mysql -u root -p<pw> --socket=β¦'`. A later
|
|
158
|
+
`-u admin` on the command line wins for the **user**, but the alias's `-p<pw>` **overrides
|
|
159
|
+
`MYSQL_PWD`** β so every `mysql -h <remote-host> -u admin β¦` dies with
|
|
160
|
+
`ERROR 1045 (28000): Access denied for user 'admin'@'<your ip>' (using password: YES)`, *against
|
|
161
|
+
the correct remote host*. That reads as a rotated credential, a missing grant, a VPN problem, or
|
|
162
|
+
the double-quote trap above, and it is none of them. (The same alias can also silently talk to
|
|
163
|
+
localhost when its `--socket` wins β two symptoms, one cause.)
|
|
164
|
+
- **The tell: `mysqldump` to the SAME host with the SAME `MYSQL_PWD` succeeds while `mysql`
|
|
165
|
+
1045s.** Only `mysql` is aliased, not `mysqldump`. "Dump works, `mysql` denied" identifies the
|
|
166
|
+
alias immediately β check that before you go near the password extractor or the ini quoting.
|
|
167
|
+
- **Fix:** call the binary by full path
|
|
168
|
+
(`MYSQL=/Applications/XAMPP/xamppfiles/mysql/bin/mysql`). `\mysql` or `command mysql` also skip
|
|
169
|
+
alias expansion.
|
|
170
|
+
- An exact match with your local DB is evidence of a bad restore, not of a good one (see the
|
|
171
|
+
gotcha below).
|
|
127
172
|
|
|
128
173
|
## Gotchas
|
|
129
174
|
|
|
@@ -137,6 +182,12 @@ The developer runs every one of these; do not execute them for them.
|
|
|
137
182
|
makes the dump a point-in-time snapshot, so a live table keeps moving after it: `Units` read 8,627
|
|
138
183
|
on prod and 8,618 on the restored sandbox. Do not chase that; only a *category* difference
|
|
139
184
|
(missing tables, order-of-magnitude counts) means the restore was wrong.
|
|
185
|
+
- **The reference counts written down here go stale too.** After the 2026-09-04 reset,
|
|
186
|
+
sandbox-client read `Core.Surfaces` = **54** and `Client_Nychh.TransferOrders` = **1,932**,
|
|
187
|
+
against the prod figures previously recorded here β 53 (2026-09-03) and 1,927 (2026-08-31).
|
|
188
|
+
Prod itself moved on. Restored counts landing slightly **above** an older recorded prod number
|
|
189
|
+
is the expected pattern, not evidence of a bad restore. Re-read the count from the source at
|
|
190
|
+
reset time instead of comparing to a number in this doc.
|
|
140
191
|
- π¨ **The `dbchanges2` folder name IS the target database β pipe each folder to its own DB.**
|
|
141
192
|
`Core/` β `Core`, `Client_<Name>/` β that one tenant, `Client/` β **every** tenant. Files reference
|
|
142
193
|
tables **unqualified** by design (the cluster-isolation rule), so running a `Core/` file against a
|
|
@@ -157,6 +208,17 @@ The developer runs every one of these; do not execute them for them.
|
|
|
157
208
|
[Verifying whether a migration actually ran](./verifying-a-migration-ran.md).
|
|
158
209
|
|
|
159
210
|
## Change history
|
|
211
|
+
- 2026-09-04 β Second sandbox-client reset (`Core` + `Client_Nychh`) from prod; the procedure held,
|
|
212
|
+
three gaps closed. Added a mandatory **step 1 β inventory what the reset will destroy** (a shared
|
|
213
|
+
non-prod environment is normally ahead of prod, so the reload rolls that lead back), with the
|
|
214
|
+
2026-09-04 candidate file list and the unapplied stash, plus a **step 7** to re-run them in
|
|
215
|
+
`Core/` β `Client/` β `Client_<Tenant>/` order. Sharpened the shell-alias bullet: the real symptom
|
|
216
|
+
is **`ERROR 1045` against the CORRECT remote host** (the alias's `-p<pw>` beats `MYSQL_PWD`), not a
|
|
217
|
+
silent localhost connection β and the tell is that `mysqldump` works while `mysql` is denied,
|
|
218
|
+
because only `mysql` is aliased. Added a value-safe way to inspect an ini password's line shape
|
|
219
|
+
(confirms the double-quote wrap and that there is no inline `;` comment, so `setpw` needs no
|
|
220
|
+
change). Refreshed the expected-drift numbers and noted that recorded reference counts age.
|
|
221
|
+
(apeterson)
|
|
160
222
|
- 2026-09-03 β Added the **reset-a-shared-environment-from-production** procedure (the `mysqldump`
|
|
161
223
|
flag set, `--set-gtid-purged=OFF` for RDS GTID, `--no-tablespaces` for the missing `PROCESS`
|
|
162
224
|
grant, the mandatory `tail -1` "`-- Dump completed`" check because a truncated dump loads
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
| [Cart Notification Emails β duplicate prevention](features/cart-notification-emails.md) | On the cart "Notifications" section a user can add CC email addresses to an order. | src/pages/Cart/CartPage.tsx, src/pages/Cart/view/cartForm/CartForm.tsx, src/stores/useEmailOptionsStore.ts, src/stores/useCartSalesQuoteZu.ts, src/pages/Cart/viewModel/FIELDS/*/*/*/CARTPAGE.ts |
|
|
9
9
|
| [Cart Order-Total & Shipping Computation](features/cart-order-total-computation.md) | The Cart summary section (subtotal / shipping / tax / total) is **data-driven** from `cartData`. | toga2-commerce/src/pages/Cart/viewModel/useCartViewModel.ts, toga2-commerce/src/pages/Cart/CartPage.tsx, toga2-commerce/src/pages/Cart/view/cartForm/CartForm.tsx, toga2-commerce/src/pages/Cart/helpers/shippingOptionGates.ts, toga2-commerce/src/pages/Cart/api/CartApi.ts |
|
|
10
10
|
| [Cart Page β config-driven form architecture (current state + planned refactor)](features/cart-page-config-architecture.md) | The Cart page (`src/pages/Cart/`) is the most config-heavy page in `toga2-commerce`. | src/pages/Cart/CartPage.tsx, src/pages/Cart/view/cartForm/CartForm.tsx, src/pages/Cart/view/cartForm/CartFormSection.tsx, src/pages/Cart/view/cartForm/CartFormRenderer.tsx, src/pages/Cart/view/EditCart.tsx, src/pages/Cart/view/EditOrder.tsx, src/pages/Cart/viewModel/useEditOrderOrEditCartViewModel.ts, src/pages/Cart/viewModel/FIELDS/*/*/*/CARTPAGE.ts, src/hooks/useAssignClientFields.ts |
|
|
11
|
-
| [Catalog cache freshness β the 24h persisted query cache, and how to opt a query out of it](features/catalog-cache-freshness.md) | TOGa Commerce runs a **single `QueryClient` with a 24-hour default `staleTime`**, and persists it to **`localStorage["commerce"]`** through `PersistQueryClientP | toga2-commerce/src/App.tsx, toga2-commerce/src/contexts/AuthContext.tsx, toga2-commerce/src/pages/ItemsView/viewModel/useItemDetailsViewModel.ts |
|
|
11
|
+
| [Catalog cache freshness β the 24h persisted query cache, and how to opt a query out of it](features/catalog-cache-freshness.md) | TOGa Commerce runs a **single `QueryClient` with a 24-hour default `staleTime`**, and persists it to **`localStorage["commerce"]`** through `PersistQueryClientP | toga2-commerce/src/App.tsx, toga2-commerce/src/contexts/AuthContext.tsx, toga2-commerce/src/api/axiosInstance.ts, toga2-commerce/src/hooks/useAuthenticationFlow.ts, toga2-commerce/src/components/NavIcons/NavIconList.tsx, toga2-commerce/src/pages/BundleView/hooks/useBundleQueryData.ts, toga2-commerce/src/pages/Home/viewModel/useHomeViewModel.ts, toga2-commerce/src/pages/ItemsView/viewModel/useItemDetailsViewModel.ts |
|
|
12
12
|
| [Category Tile Order (AssortmentItems.sortOrder) β merchandising a storefront category](features/category-tile-sort-order.md) | **"Move item X to the front of category Y" is a DATA change, not a code change.** The order of item tiles on a storefront category page is driven by exactly one | src/pages/Filter/api/FilterApi.ts, src/pages/Filter/viewModel/useFilterViewModel.ts, api2/Component/Api/V2/V2.php, toga2-supply/src/pages/Items/api/itemsApi.ts |
|
|
13
13
|
| [Client Fields β per-tenant / language / role content & config](features/client-fields.md) | Almost no user-facing text, field layout, or page config is hard-coded in `toga2-commerce`. | src/pages/Account/view/MySettingsView.tsx, src/contexts/helpers/getLoginSettings.ts, src/pages/Account/viewModel/useAccountViewModel.ts, src/fieldsConfig/index.ts, src/fieldsConfig/getClientLoginFields.ts, src/fieldsConfig/clientFields/COMPASS.json, src/fieldsConfig/clientFields/COMPASSCANADA.json, src/fieldsConfig/clientFields/QUAD.json, src/pages/Cart/api/CartApi.ts, src/hooks/useAuthenticationFlow.ts, src/contexts/AuthContext.tsx, src/pages/Login/viewModel/useLoginPageViewModel.ts, src/hooks/useAssignClientFields.ts, src/hooks/useDynamicConditionalFieldOptions.ts, src/stores/useFieldsStore.ts, src/components/BaseDetailField/BaseDetailField.tsx, src/components/NavIcons/NavIconItem.tsx, src/components/Submenus/AlertSubmenu.tsx, src/components/Submenus/types.ts, src/pages/Account/AccountPage.tsx, src/pages/Account/view/MyOrdersView.tsx, src/pages/GetSupport/GetSupportPage.tsx, src/pages/GetSupport/viewModel/useGetSupportViewModel.ts, src/queries/queries.ts, src/App.tsx, src/pages/Filter/FilterPage.tsx, src/pages/Filter/viewModel/FIELDS/COMPASS/ENGLISH/USER/FILTERPAGEFIELDS.json, src/pages/Home/viewModel/FIELDS/COMPASS/ENGLISH/USER/HEADERFIELDS.json, src/components/AuthLayout/AuthLayout.tsx |
|
|
14
14
|
| [Config-Driven Expedited Shipping Gating (Cart)](features/expedited-shipping-gating.md) | On the toga2-commerce **Cart** page, expedited shipping options (**"2nd Day EOB"** and **"Next Day Air"**) are only offered in the *Shipping Method* dropdown wh | toga2-commerce/src/pages/Cart/helpers/shippingOptionGates.ts, toga2-commerce/src/pages/Cart/viewModel/FIELDS/shared/shippingOptionGates.ts, toga2-commerce/src/pages/Cart/view/cartForm/CartForm.tsx, toga2-commerce/src/pages/Cart/CartPage.tsx |
|
|
@@ -6,15 +6,21 @@ project: TOGa Commerce
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: ["bala"]
|
|
9
|
+
updated: 2026-09-03
|
|
10
|
+
owners: ["bala", "apeterson"]
|
|
11
11
|
files:
|
|
12
12
|
- toga2-commerce/src/App.tsx
|
|
13
13
|
- toga2-commerce/src/contexts/AuthContext.tsx
|
|
14
|
+
- toga2-commerce/src/api/axiosInstance.ts
|
|
15
|
+
- toga2-commerce/src/hooks/useAuthenticationFlow.ts
|
|
16
|
+
- toga2-commerce/src/components/NavIcons/NavIconList.tsx
|
|
17
|
+
- toga2-commerce/src/pages/BundleView/hooks/useBundleQueryData.ts
|
|
18
|
+
- toga2-commerce/src/pages/Home/viewModel/useHomeViewModel.ts
|
|
14
19
|
- toga2-commerce/src/pages/ItemsView/viewModel/useItemDetailsViewModel.ts
|
|
15
20
|
related:
|
|
16
21
|
- ./inactive-item-purchase-gating.md
|
|
17
22
|
- ../architecture.md
|
|
23
|
+
- ../../toga25-supply/features/persisted-query-cache.md
|
|
18
24
|
- ../../toga25-supply/features/force-logout-on-deployment.md
|
|
19
25
|
---
|
|
20
26
|
|
|
@@ -30,26 +36,37 @@ switched back on, it stays hidden.
|
|
|
30
36
|
Any query whose answer **gates an action** (can this be bought? is this still available?) must be
|
|
31
37
|
opted out of *both* mechanisms. Opting out of one is not enough.
|
|
32
38
|
|
|
39
|
+
**Three long-running production complaints all trace back to this file set**, and were pinned to
|
|
40
|
+
`file:line` on 2026-09-03 (read-only, branch `TRUE-81610`): "users don't see updated data",
|
|
41
|
+
"clearing their cache doesn't work", and "users get stuck on a broken login page after a forced
|
|
42
|
+
logout". They are **four separate causes**, listed below. Nothing has been fixed yet.
|
|
43
|
+
|
|
33
44
|
## How it works
|
|
34
45
|
|
|
35
46
|
### The default: cached and persisted
|
|
36
47
|
|
|
37
|
-
`src/App.tsx` creates the client with `staleTime: 1000 * 60 * 60 * 24` and a
|
|
38
|
-
`createSyncStoragePersister` on `localStorage` key `"commerce"
|
|
39
|
-
overrides it β including the `["clientFields", β¦]` query that carries the
|
|
40
|
-
content, which is why copy/config deploys also look stale for returning users
|
|
41
|
-
[architecture gotchas](../architecture.md#gotchas)).
|
|
48
|
+
`src/App.tsx:26-31` creates the client with `staleTime: 1000 * 60 * 60 * 24` and a
|
|
49
|
+
`createSyncStoragePersister` on `localStorage` key `"commerce"` (`src/App.tsx:34-37`). Everything
|
|
50
|
+
inherits that unless it overrides it β including the `["clientFields", β¦]` query that carries the
|
|
51
|
+
per-tenant `FIELDS` content, which is why copy/config deploys also look stale for returning users
|
|
52
|
+
(see the [architecture gotchas](../architecture.md#gotchas)).
|
|
53
|
+
|
|
54
|
+
For comparison, **TOGa 2.5 Supply's default is `staleTime: 0`** *because* its cache is persisted.
|
|
55
|
+
Same library, same persister, opposite default. See
|
|
56
|
+
[supply's persisted query cache](../../toga25-supply/features/persisted-query-cache.md).
|
|
42
57
|
|
|
43
|
-
### The platform's cache-bust:
|
|
58
|
+
### The platform's cache-bust: the Core parameters blob
|
|
44
59
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
60
|
+
`src/contexts/AuthContext.tsx:192-252` runs a `["fetchCoreParameters"]` query with `staleTime: 0`
|
|
61
|
+
and `refetchOnWindowFocus: true`, plus a refetch on every route change. On the first fetch after
|
|
62
|
+
login it stores the whole response as a baseline; on any later fetch it **string-compares the
|
|
63
|
+
entire core-parameters JSON** against that baseline and, on **any** difference, calls `logout()` and
|
|
64
|
+
`window.location.href = "/"`.
|
|
49
65
|
|
|
50
|
-
> **β
|
|
51
|
-
>
|
|
52
|
-
>
|
|
66
|
+
> **β It is not keyed on `META_LAST_REFRESH_DATETIME` alone β it compares the whole blob.** Any
|
|
67
|
+
> Core parameter change on that environment logs out **every** user. It is a deployment-grade
|
|
68
|
+
> lever, not a way to refresh one catalogue change. The narrower, notice-first sibling in TOGa 2.5
|
|
69
|
+
> Supply is documented in
|
|
53
70
|
> [force logout on deployment](../../toga25-supply/features/force-logout-on-deployment.md).
|
|
54
71
|
|
|
55
72
|
### Opting a single query out β `staleTime: 0` + `gcTime: 0` is NOT sufficient on its own
|
|
@@ -68,6 +85,13 @@ const shouldDehydrateQuery = (query) => {
|
|
|
68
85
|
// β¦ persistOptions: { dehydrateOptions: { shouldDehydrateQuery } }
|
|
69
86
|
```
|
|
70
87
|
|
|
88
|
+
> **β CORRECTION (verified 2026-09-03): this exclusion is NOT in production.** `shouldDehydrateQuery`
|
|
89
|
+
> exists only on `origin/_beta` and `origin/#sprint84`. `origin/_production`, `origin/_sandbox`,
|
|
90
|
+
> `origin/_dev` and `TRUE-81610` all pass `persistOptions: { persister: localStoragePersister }`
|
|
91
|
+
> with **no `dehydrateOptions` at all** β so **nothing is excluded from persistence in production**,
|
|
92
|
+
> and the inactive-item purchase gate it was built for is not actually protected there. Confirm the
|
|
93
|
+
> branch before relying on this section.
|
|
94
|
+
|
|
71
95
|
**Why the exclusion is required:** `gcTime: 0` only evicts a query once it becomes **inactive**. An
|
|
72
96
|
**active** query β exactly the state the item page is in while the user is looking at it β is still
|
|
73
97
|
dehydrated and written to `localStorage`, and is rehydrated on the next load. So `gcTime: 0` alone
|
|
@@ -80,6 +104,104 @@ every other query, so this is an allowlist-shaped opt-out with no app-wide side
|
|
|
80
104
|
decision; making every listing uncached would trade a real performance characteristic for no
|
|
81
105
|
correctness gain.
|
|
82
106
|
|
|
107
|
+
## The four stacked causes behind the production complaints
|
|
108
|
+
|
|
109
|
+
Verified 2026-09-03 on branch `TRUE-81610`. Read-only; none of these are fixed.
|
|
110
|
+
|
|
111
|
+
### 1. App-wide 24h `staleTime` + persisted cache with nothing excluded β daily stale data
|
|
112
|
+
|
|
113
|
+
`src/App.tsx:26-31` (24h default) plus a persisted cache and no `dehydrateOptions` means
|
|
114
|
+
**yesterday's queries are served today with no network call**. This is the "users not seeing
|
|
115
|
+
updated data" report, and it happens **daily** β not only after a deploy.
|
|
116
|
+
|
|
117
|
+
The same 24h value is **also hardcoded per-hook**, so changing only the default is not enough:
|
|
118
|
+
|
|
119
|
+
- `src/pages/BundleView/hooks/useBundleQueryData.ts:26,40`
|
|
120
|
+
- `src/pages/Home/viewModel/useHomeViewModel.ts:150,173,197`
|
|
121
|
+
|
|
122
|
+
### 2. Cache clears are not gated on restoration β they are silently undone
|
|
123
|
+
|
|
124
|
+
`PersistQueryClientProvider` renders children **while still restoring**
|
|
125
|
+
(`react-query-persist-client/build/modern/PersistQueryClientProvider.js`: `isRestoring` starts
|
|
126
|
+
`true`, `persistQueryClientRestore` resolves later). A `queryClient.clear()` +
|
|
127
|
+
`localStorage.removeItem("commerce")` that runs before restore finishes is **undone** β the pending
|
|
128
|
+
restore hydrates the old cache on top, and the persist subscription writes it back.
|
|
129
|
+
|
|
130
|
+
All three clearing sites are unguarded:
|
|
131
|
+
|
|
132
|
+
| Site | What it clears |
|
|
133
|
+
|---|---|
|
|
134
|
+
| `src/contexts/AuthContext.tsx:180-183` | logout |
|
|
135
|
+
| `src/components/NavIcons/NavIconList.tsx:162-170` | view-as / persona switch (language-specific data) |
|
|
136
|
+
| `src/api/axiosInstance.ts:46` | 401 forced logout |
|
|
137
|
+
|
|
138
|
+
**This is the "it's not successfully clearing their caches" report.** Supply already solved it with
|
|
139
|
+
an `awaitRestoration()` promise built from `useIsRestoring()`
|
|
140
|
+
(`toga25-supply/src/contexts/AuthContext.tsx:56-77`) and gates every clear behind it.
|
|
141
|
+
|
|
142
|
+
### 3. Two auth gates read two different localStorage keys β stuck on a broken login page
|
|
143
|
+
|
|
144
|
+
- The axios request interceptor gates on **`user`** (`src/api/axiosInstance.ts:112-118`). With no
|
|
145
|
+
`user` it calls `fetchPublicToken()`, which POSTs `/auth/public` and **writes the public token
|
|
146
|
+
back as `accessToken`** (`src/api/axiosInstance.ts:53-64`).
|
|
147
|
+
- But `src/hooks/useAuthenticationFlow.ts:213-217` gates on **`accessToken`** and returns early if
|
|
148
|
+
it exists.
|
|
149
|
+
|
|
150
|
+
So after a forced logout: the first API call writes a *public* `accessToken`, the auth flow sees a
|
|
151
|
+
token, skips **both** the SSO redirect and `/login`, and the user is logged out while the app
|
|
152
|
+
believes they are not.
|
|
153
|
+
|
|
154
|
+
Supply gates on `user` β with an explicit comment at
|
|
155
|
+
`toga25-supply/src/contexts/AuthContext.tsx:47-51`. This is the app-side twin of the blox
|
|
156
|
+
`fetchPublicToken` gotcha already recorded in `2.0/standards/frontend.md` Β§22.
|
|
157
|
+
|
|
158
|
+
### 4. The forced logout is too aggressive, and login never clears the cache
|
|
159
|
+
|
|
160
|
+
`src/contexts/AuthContext.tsx:192-252`:
|
|
161
|
+
|
|
162
|
+
- On **any** core-parameter change it calls `logout()` + `window.location.href = "/"` with **no
|
|
163
|
+
notice** to the user. On an SSO client `/` re-triggers the IdP.
|
|
164
|
+
- It **monkey-patches `window.history.pushState` and `replaceState`** (`:216-226`) to refetch on
|
|
165
|
+
every navigation. react-router uses those same methods.
|
|
166
|
+
|
|
167
|
+
Separately, commerce's `login()` (`:100-148`) **never clears the cache**, so signing in as a
|
|
168
|
+
different user inherits the prior user's data for up to 24h. Supply clears on login.
|
|
169
|
+
|
|
170
|
+
## Decision β `gcTime` is the speed knob, `staleTime` is the correctness knob
|
|
171
|
+
|
|
172
|
+
Decided 2026-09-03. The 24h `staleTime` was reached for to make heavy item/bundle pages feel fast.
|
|
173
|
+
**It is the wrong knob, and it buys zero perceived speed here.**
|
|
174
|
+
|
|
175
|
+
- React Query **paints cached data instantly regardless of `staleTime`**. `staleTime` only controls
|
|
176
|
+
whether it *also* refetches in the background. `gcTime` controls how long the cached copy is kept
|
|
177
|
+
at all.
|
|
178
|
+
- Commerce gates **every** loading state on `isLoading`, never `isFetching`
|
|
179
|
+
(`useBundleQueryData.ts:22,35`; `useHomeViewModel.ts:136,157,177`). `isLoading` is `false`
|
|
180
|
+
whenever cached data exists. So **lowering `staleTime` shows no extra spinner** β the 24h value
|
|
181
|
+
only suppresses the refetch.
|
|
182
|
+
- **`gcTime` is set nowhere in commerce**, so it is the 5-minute default. Navigating away from Home
|
|
183
|
+
for 6+ minutes and back drops the query from memory and **does** show a real spinner today,
|
|
184
|
+
despite the 24h `staleTime`. The persisted cache only helps a full reload, not in-session
|
|
185
|
+
navigation. **Fixing `gcTime` is the actual speed win.**
|
|
186
|
+
|
|
187
|
+
**Agreed shape for heavy queries:**
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
staleTime: 30_000, // stops a refetch storm on Home β bundle β Home clicks;
|
|
191
|
+
// never survives a session or a deploy
|
|
192
|
+
gcTime: 1000 * 60 * 60, // the real perceived-speed knob
|
|
193
|
+
refetchOnWindowFocus: false,
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Keep the **app default at `0`**, like supply.
|
|
197
|
+
|
|
198
|
+
**Caveat still open:** if a payload drives a **price or a stock count**, that query must gate its
|
|
199
|
+
spinner on **`isFetching`**, not `isLoading` β a wrong price shown for one second is a real
|
|
200
|
+
problem, unlike a stale item list. Which commerce queries those are is **not yet answered**.
|
|
201
|
+
|
|
202
|
+
**Rejected:** keeping the 24h `staleTime` "for speed" (verified it buys none), and `staleTime: 0`
|
|
203
|
+
on the heavy hooks (too many repeats of a large payload).
|
|
204
|
+
|
|
83
205
|
## Gotchas / known issues
|
|
84
206
|
|
|
85
207
|
- **β Never verify a catalogue or FIELDS change on a warm browser.** Clear
|
|
@@ -87,18 +209,42 @@ correctness gain.
|
|
|
87
209
|
rehydrated cache.
|
|
88
210
|
- **β `gcTime: 0` does not stop persistence.** Active queries are still dehydrated. Exclude the key
|
|
89
211
|
via `dehydrateOptions.shouldDehydrateQuery` as well.
|
|
90
|
-
- **β
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
-
|
|
94
|
-
|
|
95
|
-
|
|
212
|
+
- **β Nothing is excluded from persistence on `_production`** β the `shouldDehydrateQuery`
|
|
213
|
+
allowlist lives only on `_beta`/`#sprint84`. Check the branch.
|
|
214
|
+
- **β The core-parameters watcher is not a targeted invalidation** β any Core parameter change
|
|
215
|
+
force-logs-out every user on the environment. Use per-query opt-out or
|
|
216
|
+
`queryClient.invalidateQueries` for anything narrower.
|
|
217
|
+
- **β A `queryClient.clear()` that is not awaited behind cache restoration is undone.** All three
|
|
218
|
+
of commerce's clear sites have this bug.
|
|
219
|
+
- **β Gate auth on `user`, never `accessToken`** β `fetchPublicToken` writes a public token into
|
|
220
|
+
`accessToken`.
|
|
221
|
+
- **A `persistOptions.buster` keyed on the build version** would fix stale data at deploy time.
|
|
222
|
+
Commerce has a committed `amplify.yml`, so injecting `AWS_COMMIT_ID` as a build id is
|
|
223
|
+
straightforward here (`package.json` `version` is `0.0.0` and `vite.config.ts` has no `define`
|
|
224
|
+
block, so the id must be created first). Mechanics and the scope limit β a buster runs inside the
|
|
225
|
+
app and can never fix a browser-cached JS bundle β are written up in
|
|
226
|
+
[supply's persisted query cache](../../toga25-supply/features/persisted-query-cache.md#the-buster-gap).
|
|
227
|
+
Still **not implemented**.
|
|
96
228
|
- **`AuthLayout` never remounts**, so `refetchOnMount` fires once per session for queries it owns β
|
|
97
229
|
a route-change refresh has to be wired explicitly. That interacts with everything above: a query
|
|
98
230
|
can be both stale *and* never refetched during a session.
|
|
99
231
|
|
|
100
232
|
## Change history
|
|
101
233
|
|
|
234
|
+
- 2026-09-03 β Read-only diagnosis (branch `TRUE-81610`). **Corrected** this doc: the
|
|
235
|
+
`shouldDehydrateQuery` opt-out it described exists only on `origin/_beta` / `origin/#sprint84` β
|
|
236
|
+
`_production`, `_sandbox`, `_dev` have **no `dehydrateOptions`**, so nothing is excluded from
|
|
237
|
+
persistence in production. Also corrected the cache-bust: `AuthContext` string-compares the
|
|
238
|
+
**whole core-parameters blob**, not just `META_LAST_REFRESH_DATETIME`, so any Core parameter
|
|
239
|
+
change logs out every user. Added the four stacked causes behind the standing production
|
|
240
|
+
complaints (app-wide 24h `staleTime` also hardcoded in `useBundleQueryData`/`useHomeViewModel`;
|
|
241
|
+
all three cache clears unguarded against the `PersistQueryClientProvider` restore race; the
|
|
242
|
+
`user`-vs-`accessToken` gate split that leaves a user logged out while the app thinks otherwise;
|
|
243
|
+
the notice-less forced logout that monkey-patches `history.pushState`, plus `login()` never
|
|
244
|
+
clearing the cache). Recorded the decision that **`gcTime` is the speed knob and `staleTime` is
|
|
245
|
+
the correctness knob** β commerce gates every spinner on `isLoading`, so the 24h `staleTime` buys
|
|
246
|
+
no perceived speed, while `gcTime` is at its 5-minute default and *is* the real cause of slow
|
|
247
|
+
in-session navigation. No code changed. (apeterson)
|
|
102
248
|
- 2026-08-14 β Created while building the
|
|
103
249
|
[inactive-item purchase gate](./inactive-item-purchase-gating.md). Recorded that the 24h
|
|
104
250
|
persisted cache masks catalogue state **in both directions** (a switched-off item kept rendering
|
|
@@ -9,12 +9,13 @@
|
|
|
9
9
|
| [Column Visibility (URL-driven show/hide columns)](features/column-visibility.md) | A "Columns" header button that opens a modal listing every column from the table meta, lets the user show/hide columns, adjusts the table live, and persists the | toga25-supply/src/components/ColumnVisibilityModal/, toga25-supply/src/surface/useColumnVisibilityModalConfig.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableData.tsx, dbchanges2/Core/2026-08-28b - TransferOrderListActionsSurfaceSeed.sql |
|
|
10
10
|
| [Force Logout on Deployment (useDeploymentGuard)](features/force-logout-on-deployment.md) | On large deployments the backend bumps the Core parameter `META_LAST_REFRESH_DATETIME`. | toga25-supply/src/hooks/useDeploymentGuard.tsx, toga25-supply/src/App.tsx |
|
|
11
11
|
| [Meta-Driven Page & Table Setup](features/meta-driven-table-data.md) | A page in this app is **meta-driven end to end**: the page view model fetches *page meta* (labels, sections, ACL) and *table meta* (the columns/fields + table s | toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/TransferOrders/viewModel/useTransferOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableState.ts, toga25-supply/src/hooks/useTablePageMeta.ts, toga-blox-npm/dist/hooks/useFetchPageMeta.d.ts, toga-blox-npm/dist/hooks/useFetchTablePageMeta.d.ts, toga-blox-npm/dist/hooks/useAssignTableFieldLabels.d.ts, toga-blox-npm/dist/components/Table/hooks/useTableData.d.ts |
|
|
12
|
+
| [Persisted React Query cache (localStorage `supply-chain-query-cache`)](features/persisted-query-cache.md) | `localStorage["supply-chain-query-cache"]` is **not a hand-written cache**. | toga25-supply/src/App.tsx, toga25-supply/src/contexts/AuthContext.tsx, toga25-supply/src/hooks/useDeploymentGuard.tsx, toga25-supply/src/fieldsConfig/useClientFields.ts, toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/hooks/useCurrentUser.ts, toga25-supply/src/hooks/useStatusTypeValues.ts, toga25-supply/src/surface/useStatusColors.ts, toga25-supply/vite.config.ts |
|
|
12
13
|
| [Record Modals & Nested Tables](features/record-modals-and-nested-tables.md) | The repo's family of modal + nested-table patterns layered over toga-blox `TableRecordModal` and `PrimaryTable*Layout`. | toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/hooks/usePurchaseOrderDetails.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/FIELDS/apiFields.json, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/layout/PrimaryTableServerLayout/PrimaryTableServerLayout.tsx, toga25-supply/src/layout/PrimaryTableServerLayout/types.ts, toga25-supply/src/layout/ItemRecordModalLayout/, toga25-supply/src/layout/SalesOrderRecordModalLayout/, toga25-supply/src/layout/SalesOrderItemsTableLayout/, toga25-supply/src/layout/ItemFulfillmentModal/, toga25-supply/src/layout/GenericNestedTables/GenericNestedTables.tsx, toga25-supply/src/layout/GenericNestedTables/GenericTableLayout.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/DEFAULT/inventoryGroupings.json, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/TransferOrderRecordModalLayout.tsx, toga25-supply/src/pages/TransferOrders/helpers/buildInventoryPurchaseOrderUrl.ts, toga25-supply/src/hooks/useTableCellInteractions.ts, toga25-supply/src/hooks/useServerTableUrlState.ts, toga25-supply/src/layout/RecordApprovalModal/helpers/handleFormatApprovalWorkflowPayload.ts, toga25-supply/src/layout/RecordApprovalModal/api/approvalDecisionsApi.ts, toga25-supply/src/layout/RecordApprovalModal/ApprovalModal.module.css |
|
|
13
14
|
| [Side navigation & default route β an empty nav renders a BLANK PAGE and gets reported as "cannot log in"](features/side-navigation-and-default-route.md) | The 2.5 side nav is **100% backend-driven** by the `navigation` surface bundle, and the same list also decides **which routes exist** and **where `/` lands**. | toga25-supply/src/routes.tsx, toga25-supply/src/layout/AppLayout/viewModel/useAppLayoutViewModel.ts, api2/Component/Api/V2/V2.php, _underscore/Model/Core/Surface.php |
|
|
14
15
|
| [SSO redirect & public-vs-user session gating (useAuthenticationFlow)](features/sso-redirect-and-session-gating.md) | How 2.5 Supply decides, on every navigation, whether an anonymous visitor should be bounced to their client's SSO IdP instead of the local `/login` form. | toga25-supply/src/hooks/useAuthenticationFlow.ts, toga25-supply/src/routes.tsx, toga25-supply/src/contexts/AuthContext.tsx, toga25-supply/src/api/api.ts |
|
|
15
16
|
| [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The frontend consumer of the platform-wide Surface layer β DB-driven UI config fetched from `GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported J | toga25-supply/src/layout/RecordApprovalModal/helpers/stackedCurrencyJoiner.ts, toga25-supply/src/layout/RecordApprovalModal/helpers/stackedCurrencyJoiner.test.ts, toga25-supply/src/App.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/DenialBanner.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/SalesOrderRecordModal.module.css, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/SalesOrderApprovalModalsLayout.tsx, toga25-supply/src/layout/RecordApprovalModal/RecordApprovalModalLayout.tsx, toga25-supply/src/layout/RecordApprovalModal/view/ApprovalTimelineView.tsx, toga25-supply/src/layout/RecordApprovalModal/ApprovalModal.module.css, toga25-supply/src/utils/formatDateTime.ts, toga25-supply/src/utils/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/layout/RecordApprovalModal/, toga25-supply/src/contexts/AuthContext.tsx, toga25-supply/src/surface/applyColSpan.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/sectionRenderers.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getVisibleSections.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderApprovalSummaryGrid.tsx, toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Bundles/viewModel/useBundlesPageViewModel.tsx, toga25-supply/src/layout/ItemFulfillmentModal/useItemFulfillmentModalViewModel.tsx, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/ServiceRequestRecordModalLayout.tsx, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/view/ServiceRequestsView.tsx, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/viewModel/useServiceRequestRecordModalLayoutModel.tsx, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/viewModel/FIELDS/apiFields.json, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/SalesOrderView.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderSummaryGrid.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getDetailSections.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderNotesSection.tsx, toga25-supply/src/pages/SalesOrders/helpers/cleanOrder.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/AdminNotesSection.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getAdminNotes.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/surface/resolveElementState.ts, toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderRowRecordState.ts, toga25-supply/src/surface/actionRegistry.ts, toga25-supply/src/surface/componentRegistry.tsx, toga25-supply/src/surface/SurfaceActionBar.tsx, toga25-supply/src/surface/SurfaceSection.tsx, toga25-supply/src/surface/resolve.ts, toga25-supply/src/surface/types.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/Login/LoginPage.tsx, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/view/SurfaceRowActions.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderVip.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/Items/ItemsPage.tsx, toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx, toga25-supply/src/pages/VendorItems/VendorItemsPage.tsx, toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx, toga25-supply/src/pages/Inventory/Inventory.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/index.ts, toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/layout/ItemRecordModalLayout/helpers/surfaceBundleToItemFields.ts, toga25-supply/src/layout/ItemRecordModalLayout/helpers/index.ts, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/useItemRecordModalViewModel.tsx, toga25-supply/src/layout/ItemRecordModalLayout/ItemRecordModalLayout.tsx, toga25-supply/src/layout/ItemRecordModalLayout/components/ItemRecordView.tsx, toga25-supply/src/surface/useStatusColors.ts, toga25-supply/src/surface/SurfaceHeader.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/helpers/surfaceBundlesToDecisionFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/useApprovalModalViewModel.tsx |
|
|
16
17
|
| [Talos Integration (AppLayout host, live Aegra streaming, LangGraphβblox mapper)](features/talos-integration.md) | toga25-supply is the first host of the shared blox [Talos assistant](../../toga-blox/features/talos-assistant.md). | toga25-supply/src/api/talos.ts, toga25-supply/src/hooks/useTalosSession.ts, toga25-supply/src/hooks/useTalosThreads.ts, toga25-supply/src/hooks/useTalosThreads.test.ts, toga25-supply/src/hooks/useTalosSurface.ts, toga25-supply/src/hooks/useTalosSurface.test.ts, toga25-supply/src/App.tsx, toga25-supply/db-migrations/PLAYBOOK.md, dbchanges2/Core/2026-09-02a - TalosAssistantSurfaceSeed.sql, dbchanges2/Client_Nychh/2026-09-02a - TalosAssistantEnable.sql, toga25-supply/src/providers/TalosStreamProvider.tsx, toga25-supply/src/utils/talosMessages.ts, toga25-supply/src/utils/talosMessages.test.ts, toga25-supply/src/layout/AppLayout/AppLayout.tsx, toga25-supply/src/components/Header/Header.tsx, toga25-supply/src/components/Header/Header.module.css, toga25-supply/src/index.css, toga25-supply/src/assets/talos-owl.png |
|
|
17
|
-
| [Transfer Orders page (TableView β Core surfaces β React page + record modal)](features/transfer-orders-page.md) | The Transfer Orders screen β list + read-only record modal β built end to end on 2026-08-28 from a Claude Design prototype. | toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useCreateTransferOrder.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/PanelSelect.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/ContactSelect.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/SelectTargetLocationModal.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/TransferItemPickerTable.tsx, _underscore/Model/Client/TransferOrder.php, _underscore/Model/Client/TransferOrderNote.php, _underscore/Model/Client/TransferOrderNoteType.php, dbchanges2/Client/2026-09-01b - TransferOrderNotesTables.sql, dbchanges2/Client/2026-09-01c - TransferOrderContactCreatedByAndDescription.sql, dbchanges2/Core/2026-09-01c - TransferOrderNotesRecords.sql, dbchanges2/Core/2026-09-01d - TransferOrderContactCreatedByAndDescriptionRecordFields.sql, toga25-supply/src/pages/TransferOrders/TransferOrders.tsx, toga25-supply/src/pages/TransferOrders/hooks/useTransferOrdersTableState.ts, toga25-supply/src/pages/TransferOrders/viewModel/useTransferOrdersPageViewModel.tsx, toga25-supply/src/pages/TransferOrders/view/TransferOrdersTableLayout/TransferOrdersTableLayout.tsx, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/TransferOrderRecordModalLayout.tsx, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/hooks/useTransferOrderRecord.ts, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/viewModel/useTransferOrderRecordModalLayoutModel.ts, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/view/TransferOrderRecordView.tsx, toga25-supply/src/pages/TransferOrders/helpers/buildInventoryPurchaseOrderUrl.ts, toga25-supply/src/pages/TransferOrders/TransferOrder.module.css, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/CreateTransferOrderModal.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/index.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/usePurchaseOrderItemRows.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useTargetLocationOptions.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useTransferSourceLocation.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useLocationContacts.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useCreateLocationContact.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/AddContactModal.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/TransferNotesField.tsx, dbchanges2/Client/2026-09-01a - PurchaseOrderItemQtyFieldsApiRoleRead.sql, toga25-supply/src/surface/useColumnVisibilityModalConfig.ts, toga25-supply/src/routes.tsx, dbchanges2/Core/2026-08-28a - TransferOrderSurfaceSeed.sql, dbchanges2/Core/2026-08-28b - TransferOrderListActionsSurfaceSeed.sql, dbchanges2/Client/2026-08-28a - TransferOrderStageThemeTokens.sql, dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestamps.sql, dbchanges2/Client_Nychh/2026-08-28a - TransferOrdersTableView.sql, dbchanges2/Client_Nychh/2026-08-28b - TransferOrdersNavigationEnable.sql, dbchanges2/Client_Nychh/2026-08-28c - TransferOrderRecordAcl.sql |
|
|
18
|
+
| [Transfer Orders page (TableView β Core surfaces β React page + record modal)](features/transfer-orders-page.md) | The Transfer Orders screen β list + read-only record modal β built end to end on 2026-08-28 from a Claude Design prototype. | toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useCreateTransferOrder.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/PanelSelect.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/ContactSelect.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/SelectTargetLocationModal.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/TransferItemPickerTable.tsx, _underscore/Model/Client/TransferOrder.php, _underscore/Model/Client/TransferOrderNote.php, _underscore/Model/Client/TransferOrderNoteType.php, dbchanges2/Client/2026-09-01b - TransferOrderNotesTables.sql, dbchanges2/Client/2026-09-01c - TransferOrderContactCreatedByAndDescription.sql, dbchanges2/Core/2026-09-01c - TransferOrderNotesRecords.sql, dbchanges2/Core/2026-09-01d - TransferOrderContactCreatedByAndDescriptionRecordFields.sql, toga25-supply/src/pages/TransferOrders/TransferOrders.tsx, toga25-supply/src/pages/TransferOrders/hooks/useTransferOrdersTableState.ts, toga25-supply/src/pages/TransferOrders/viewModel/useTransferOrdersPageViewModel.tsx, toga25-supply/src/pages/TransferOrders/view/TransferOrdersTableLayout/TransferOrdersTableLayout.tsx, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/TransferOrderRecordModalLayout.tsx, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/hooks/useTransferOrderRecord.ts, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/viewModel/useTransferOrderRecordModalLayoutModel.ts, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/view/TransferOrderRecordView.tsx, toga25-supply/src/pages/TransferOrders/helpers/buildInventoryPurchaseOrderUrl.ts, toga25-supply/src/pages/TransferOrders/TransferOrder.module.css, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/CreateTransferOrderModal.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/index.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/usePurchaseOrderItemRows.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useTargetLocationOptions.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useTransferSourceLocation.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useLocationContacts.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useCreateLocationContact.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/AddContactModal.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/TransferNotesField.tsx, dbchanges2/Client/2026-09-01a - PurchaseOrderItemQtyFieldsApiRoleRead.sql, toga25-supply/src/surface/useColumnVisibilityModalConfig.ts, toga25-supply/src/routes.tsx, dbchanges2/Core/2026-08-28a - TransferOrderSurfaceSeed.sql, dbchanges2/Core/2026-08-28b - TransferOrderListActionsSurfaceSeed.sql, dbchanges2/Client/2026-08-28a - TransferOrderStageThemeTokens.sql, dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestamps.sql, dbchanges2/Client_Nychh/2026-08-28a - TransferOrdersTableView.sql, dbchanges2/Client_Nychh/2026-08-28b - TransferOrdersNavigationEnable.sql, dbchanges2/Client_Nychh/2026-08-28c - TransferOrderRecordAcl.sql, dbchanges2/Core/2026-09-03a - TransferOrderStatusBadgeRecordFields.sql, dbchanges2/Client_Nychh/2026-09-03a - TransferOrdersStatusBadge.sql, dbchanges2/Core/2026-09-03b - InventoryCreateTransferOrderAction.sql, dbchanges2/Client_Nychh/2026-09-03b - InventoryCreateTransferOrderShow.sql, toga25-supply/src/surface/useStatusColors.ts, toga25-supply/src/pages/Inventory/Inventory.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/layout/ItemFulfillmentModal/useItemFulfillmentModalViewModel.tsx, _underscore/Model/Client/TransferOrderItem.php |
|
|
18
19
|
| [AWS Amplify Multi-Environment Deployment](workflows/amplify-deployment.md) | How `toga25-supply` deploys to **all** of its environments on AWS Amplify from a **single shared `amplify.yml`**. | toga25-supply/amplify.yml, toga25-supply/src/api/api.ts, toga25-supply/src/hooks/useAuthenticationFlow.ts, toga25-supply/vite.config.ts, toga25-supply/package.json, toga25-supply/.env.development |
|
|
19
20
|
| [Cypress Testing Harness (multi-tenant, fully stubbed)](workflows/cypress-testing.md) | The Cypress harness for `toga25-supply`: an **e2e** project and a **component** project, plus a stub layer that answers **every** `/v2` call so no test touches | toga25-supply/cypress.config.ts, toga25-supply/cypress/README.md, toga25-supply/cypress/tsconfig.json, toga25-supply/cypress/support/e2e.ts, toga25-supply/cypress/support/commands.ts, toga25-supply/cypress/support/component.tsx, toga25-supply/cypress/support/component-index.html, toga25-supply/cypress/support/tenants.ts, toga25-supply/cypress/support/api/stubApi.ts, toga25-supply/cypress/support/api/envelope.ts, toga25-supply/cypress/support/api/users.ts, toga25-supply/cypress/support/api/surfaces.ts, toga25-supply/cypress/support/api/session.ts, toga25-supply/cypress/e2e/login.cy.ts, toga25-supply/cypress/e2e/emptyNavigation.cy.ts, toga25-supply/cypress/component/harness.cy.tsx, toga25-supply/cypress/fixtures/tenants/COMPASS/surfaces/navigation.json, toga25-supply/cypress/fixtures/tenants/COMPASSCANADA/surfaces/navigation.json, toga25-supply/cypress/fixtures/tenants/NYCHH/surfaces/navigation.json, toga25-supply/cypress/fixtures/tenants/QUAD/surfaces/navigation.json, toga25-supply/scripts/cypress.mjs, toga25-supply/tsconfig.node.json, toga25-supply/package.json |
|
|
20
21
|
| [Porting a page (or query) from toga2-supply to toga25-supply](workflows/porting-a-page-from-toga2-supply.md) | `toga25-supply` re-implements pages that already exist in `toga2-supply`. | toga25-supply/src/pages/SalesOrders/viewModel/FIELDS/PRUDENTIAL/exportApiFields.json, toga25-supply/src/pages/SalesOrders/api/prudentialExportApi.ts, toga2-supply/src/pages/Orders/api/OrdersApi.ts, toga2-supply/src/components/ui/Tables/hooks/useExportableData.tsx |
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Persisted React Query cache (localStorage `supply-chain-query-cache`)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga25-supply
|
|
5
|
+
project: TOGa 2.5 Supply
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-09-03
|
|
10
|
+
owners: ["apeterson"]
|
|
11
|
+
files:
|
|
12
|
+
- toga25-supply/src/App.tsx
|
|
13
|
+
- toga25-supply/src/contexts/AuthContext.tsx
|
|
14
|
+
- toga25-supply/src/hooks/useDeploymentGuard.tsx
|
|
15
|
+
- toga25-supply/src/fieldsConfig/useClientFields.ts
|
|
16
|
+
- toga25-supply/src/surface/useFetchSurfaceMeta.ts
|
|
17
|
+
- toga25-supply/src/hooks/useCurrentUser.ts
|
|
18
|
+
- toga25-supply/src/hooks/useStatusTypeValues.ts
|
|
19
|
+
- toga25-supply/src/surface/useStatusColors.ts
|
|
20
|
+
- toga25-supply/vite.config.ts
|
|
21
|
+
related:
|
|
22
|
+
- ./force-logout-on-deployment.md
|
|
23
|
+
- ./surface-frontend.md
|
|
24
|
+
- ./transfer-orders-page.md
|
|
25
|
+
- ../architecture.md
|
|
26
|
+
- ../../toga2-commerce/features/catalog-cache-freshness.md
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Summary
|
|
30
|
+
|
|
31
|
+
`localStorage["supply-chain-query-cache"]` is **not a hand-written cache**. It is the **whole
|
|
32
|
+
React Query cache**, dehydrated to localStorage by `PersistQueryClientProvider`. Every successful
|
|
33
|
+
query in the app lands there and survives reloads *and* sessions.
|
|
34
|
+
|
|
35
|
+
Two consequences drive most of the "it didn't deploy" / "I'm seeing yesterday's data" reports:
|
|
36
|
+
|
|
37
|
+
1. The app default is **`staleTime: 0`** precisely because the cache is persisted. Any hook that
|
|
38
|
+
sets its own non-zero `staleTime` can serve **a previous session's answer and never fetch**.
|
|
39
|
+
2. Nothing in the app invalidates the blob on deploy. There is **no `buster`**. Deploys are
|
|
40
|
+
covered separately, and only indirectly, by `DeploymentGuard` forcing a logout.
|
|
41
|
+
|
|
42
|
+
Scattered warnings about this exist in several docs. This is the owning doc: mechanics, what is
|
|
43
|
+
persisted, every reset path, and the `buster` gap.
|
|
44
|
+
|
|
45
|
+
## How it works
|
|
46
|
+
|
|
47
|
+
### Wiring
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// src/App.tsx:27-30
|
|
51
|
+
const localStoragePersister = createSyncStoragePersister({
|
|
52
|
+
storage: window.localStorage,
|
|
53
|
+
key: "supply-chain-query-cache",
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`PersistQueryClientProvider` (`src/App.tsx:33`) wraps the app. The `QueryClient` default is
|
|
58
|
+
`staleTime: 0` (`src/App.tsx:18`).
|
|
59
|
+
|
|
60
|
+
### What is persisted β everything except three namespaces
|
|
61
|
+
|
|
62
|
+
`shouldDehydrateQuery` (`src/App.tsx:63-70`) keeps TanStack's default (persist successful queries)
|
|
63
|
+
**minus** three query-key namespaces:
|
|
64
|
+
|
|
65
|
+
| Excluded namespace | Why |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `talos` | chat titles / assistant ids are per-session user data; must not sit on disk |
|
|
68
|
+
| `surface-meta` | the surface bundle carries **permission and feature-gate** state |
|
|
69
|
+
| `surface-meta-group` | same bundle, grouped endpoint |
|
|
70
|
+
|
|
71
|
+
The surface exclusion is the important one. `useFetchSurfaceMeta` runs at `staleTime: Infinity`, so
|
|
72
|
+
a *persisted* copy would never refetch β a permission **grant**, or worse a **revoke**, would not
|
|
73
|
+
reach a browser that already had one until the persister's 24h `maxAge` expired. Excluding it costs
|
|
74
|
+
one request per page load; the in-memory cache still covers every route change.
|
|
75
|
+
`staleTime: Infinity` is safe there **only because** it is excluded from persisting.
|
|
76
|
+
|
|
77
|
+
### Query key β API map
|
|
78
|
+
|
|
79
|
+
| Query key | Request |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `["table-data", slug, β¦]` | `getDataTableData` (blox) |
|
|
82
|
+
| `["table-meta", slug]` | `GET /table-views/meta` (blox `components/Table/hooks/useFetchTablePageMeta.js:8`) |
|
|
83
|
+
| `["status-colors", route]` | `GET /{route}?fields=slug,colorHex&sort=sortOrder` |
|
|
84
|
+
| `["currentUser"]` | `GET /users` |
|
|
85
|
+
| `["clientFields", slug, role]` | client `FIELDS` config |
|
|
86
|
+
| `["status-type-values", routes]` | status enum values |
|
|
87
|
+
| `["deploymentVersion"]` | `GET /core-parameters` |
|
|
88
|
+
|
|
89
|
+
### The 24h `maxAge` is ONE timestamp for the whole blob
|
|
90
|
+
|
|
91
|
+
The persister's default `maxAge` is 24h β confirmed at
|
|
92
|
+
`node_modules/@tanstack/query-persist-client-core/build/modern/persist.js:10`. It is compared
|
|
93
|
+
against a single `persistedClient.timestamp`, **not per query**. So the cache is either fully
|
|
94
|
+
hydrated or **fully dropped** at restore. There is no partial expiry.
|
|
95
|
+
|
|
96
|
+
### Reset paths β the only things that clear it
|
|
97
|
+
|
|
98
|
+
1. **Login** β `src/contexts/AuthContext.tsx:113-114` (`await awaitRestoration()` then
|
|
99
|
+
`queryClient.clear()`), so signing in as a different user cannot inherit the prior user's data.
|
|
100
|
+
2. **Logout** β `src/contexts/AuthContext.tsx:141-146` (same gate, plus `localStorage.clear()`).
|
|
101
|
+
3. **A list page's Refresh button** β `queryClient.clear()` + `resetQueries(["table-data"])` on
|
|
102
|
+
SalesOrders, TransferOrders, ServiceRequests, Items, VendorItems, Bundles, Inventory.
|
|
103
|
+
4. **The 24h `maxAge`** expiring at restore.
|
|
104
|
+
|
|
105
|
+
That is all. A deploy is **not** on the list.
|
|
106
|
+
|
|
107
|
+
### The restoration race, and `awaitRestoration()`
|
|
108
|
+
|
|
109
|
+
`PersistQueryClientProvider` renders children **while still restoring** (`isRestoring` starts
|
|
110
|
+
`true`; `persistQueryClientRestore` resolves later). A `queryClient.clear()` that runs before
|
|
111
|
+
restore finishes is **silently undone** β the pending restore hydrates the old cache on top, and
|
|
112
|
+
the persist subscription writes it straight back.
|
|
113
|
+
|
|
114
|
+
Supply solves this at `src/contexts/AuthContext.tsx:56-77`: it turns `useIsRestoring()` into an
|
|
115
|
+
awaitable promise and gates **every** clearing path behind it.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const isRestoring = useIsRestoring();
|
|
119
|
+
// β¦resolve a promise once isRestoring flips falseβ¦
|
|
120
|
+
const awaitRestoration = (): Promise<void> =>
|
|
121
|
+
restorationCompleteRef.current ?? Promise.resolve();
|
|
122
|
+
|
|
123
|
+
// then, in login() and logout():
|
|
124
|
+
await awaitRestoration();
|
|
125
|
+
queryClient.clear();
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Any new cache-clearing code path must await this first.** TOGa Commerce does not, and its cache
|
|
129
|
+
clears are undone β see
|
|
130
|
+
[catalog cache freshness](../../toga2-commerce/features/catalog-cache-freshness.md).
|
|
131
|
+
|
|
132
|
+
### Deploys are covered by DeploymentGuard, not by the cache
|
|
133
|
+
|
|
134
|
+
`src/hooks/useDeploymentGuard.tsx` polls the Core parameter `META_LAST_REFRESH_DATETIME`
|
|
135
|
+
(`GET /core-parameters`, `staleTime: 0`) every 5 minutes and on window focus. On a change it shows
|
|
136
|
+
a toast for 2.5s (`LOGOUT_NOTICE_DELAY_MS`) and then signs the user out β which clears the cache
|
|
137
|
+
via path 2 above. Details in
|
|
138
|
+
[force logout on deployment](./force-logout-on-deployment.md).
|
|
139
|
+
|
|
140
|
+
## The `buster` gap
|
|
141
|
+
|
|
142
|
+
A `persistOptions.buster` is the standard way to drop a persisted cache on deploy. Mechanics
|
|
143
|
+
(verified at `query-persist-client-core/build/modern/persist.js:20-24`):
|
|
144
|
+
|
|
145
|
+
- `buster` is **one string, compared once at restore, before hydrate**.
|
|
146
|
+
- If it differs from the stored one, the whole persisted cache is **deleted** (`removeClient()`)
|
|
147
|
+
instead of hydrated.
|
|
148
|
+
- It must be **stable within a build and change per build**. A `Date.now()` buster would wipe the
|
|
149
|
+
cache on every page load.
|
|
150
|
+
|
|
151
|
+
**Blocker: this repo has no build id.** `package.json` `version` is `0.0.0` (never bumped),
|
|
152
|
+
`vite.config.ts` has **no `define` block**, and there is **no `amplify.yml`** β supply's Amplify
|
|
153
|
+
build is console-configured. Amplify does set `AWS_COMMIT_ID` in the build environment, so the
|
|
154
|
+
build id has to be created first:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
// vite.config.ts
|
|
158
|
+
define: { __BUILD_ID__: JSON.stringify(process.env.AWS_COMMIT_ID ?? "dev") }
|
|
159
|
+
// src/vite-env.d.ts
|
|
160
|
+
declare const __BUILD_ID__: string;
|
|
161
|
+
// src/App.tsx
|
|
162
|
+
persistOptions: { persister: localStoragePersister, buster: __BUILD_ID__, /* β¦ */ }
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Type-check **both** `-p tsconfig.app.json` and `-p tsconfig.node.json` β `vite.config.ts` is only
|
|
166
|
+
covered by the node config.
|
|
167
|
+
|
|
168
|
+
**Scope limit β a buster cannot fix a stale JS bundle.** It runs *inside* the app, so if the
|
|
169
|
+
browser never downloaded the new app, the buster never runs. Diagnostic to tell the two caches
|
|
170
|
+
apart:
|
|
171
|
+
|
|
172
|
+
| Symptom | Cause | Does a buster help? |
|
|
173
|
+
|---|---|---|
|
|
174
|
+
| Hard refresh (Cmd+Shift+R) fixes it | bundle / CDN cache headers | No β irrelevant |
|
|
175
|
+
| Only logging out fixes it | persisted query data | Yes |
|
|
176
|
+
|
|
177
|
+
**Rejected alternative:** versioning the storage key (`β¦-v2`). It works, but leaves the old blob in
|
|
178
|
+
localStorage forever; `buster` calls `removeClient()` and cleans up.
|
|
179
|
+
|
|
180
|
+
## Gotchas / known issues
|
|
181
|
+
|
|
182
|
+
- **β A non-zero `staleTime` on a persisted query serves yesterday's data and never fetches.** Data
|
|
183
|
+
hooks in this app use `staleTime: 0`. Hooks that currently break this rule **and are persisted**:
|
|
184
|
+
`useClientFields`, `useCurrentUser`, `useStatusTypeValues`, `useStatusColors`,
|
|
185
|
+
`useSalesOrderUuidForServiceRequest` (all `Infinity`), and the three VendorItem form hooks
|
|
186
|
+
(`5 * 60_000`). Each can serve a previous session's answer.
|
|
187
|
+
- `useTalosSession` and `useTalosThreads` also set a non-zero `staleTime`, but their keys start with
|
|
188
|
+
`talos`, so they are **not** persisted β in-memory only, safe.
|
|
189
|
+
- **β Never verify a config, surface, or ACL change on a warm browser.** Log out, or delete the
|
|
190
|
+
`supply-chain-query-cache` key in devtools. Most "it didn't deploy" reports are a rehydrated
|
|
191
|
+
cache.
|
|
192
|
+
- **β `gcTime: 0` does not stop persistence.** It only evicts a query once it becomes *inactive*;
|
|
193
|
+
an active query is still dehydrated. To keep something off disk, exclude its namespace in
|
|
194
|
+
`shouldDehydrateQuery`.
|
|
195
|
+
- **β Any new `queryClient.clear()` must `await awaitRestoration()` first**, or the restore race
|
|
196
|
+
undoes it.
|
|
197
|
+
- The `maxAge` is one timestamp for the entire blob β you cannot expire a single query through it.
|
|
198
|
+
|
|
199
|
+
## Change history
|
|
200
|
+
|
|
201
|
+
- 2026-09-03 β Created. Consolidated the persisted-cache mechanics that were scattered across the
|
|
202
|
+
architecture doc and the surface/transfer-orders/talos features into one owning doc: the
|
|
203
|
+
three excluded namespaces and *why* surface-meta is excluded (permission/feature-gate state +
|
|
204
|
+
`staleTime: Infinity`), the query-keyβAPI map, the four reset paths, that the 24h `maxAge` is a
|
|
205
|
+
**single timestamp for the whole blob** (all-or-nothing at restore), the `awaitRestoration()`
|
|
206
|
+
gate for the `PersistQueryClientProvider` restore race, and the full list of hooks that still set
|
|
207
|
+
a non-zero `staleTime` while being persisted. Also recorded the `buster` mechanics
|
|
208
|
+
(one string, compared before hydrate, deletes the whole cache on mismatch) and the blocker β
|
|
209
|
+
no build id exists in the repo (`version 0.0.0`, no `define`, no `amplify.yml`) β plus the fact
|
|
210
|
+
that a buster can never fix a browser-cached JS bundle. Read-only investigation; no code changed.
|
|
211
|
+
(apeterson)
|
|
@@ -6,7 +6,7 @@ project: TOGa 2.5 Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-04
|
|
10
10
|
owners: [apeterson, bala]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useCreateTransferOrder.ts
|
|
@@ -50,6 +50,15 @@ files:
|
|
|
50
50
|
- dbchanges2/Client_Nychh/2026-08-28a - TransferOrdersTableView.sql
|
|
51
51
|
- dbchanges2/Client_Nychh/2026-08-28b - TransferOrdersNavigationEnable.sql
|
|
52
52
|
- dbchanges2/Client_Nychh/2026-08-28c - TransferOrderRecordAcl.sql
|
|
53
|
+
- dbchanges2/Core/2026-09-03a - TransferOrderStatusBadgeRecordFields.sql
|
|
54
|
+
- dbchanges2/Client_Nychh/2026-09-03a - TransferOrdersStatusBadge.sql
|
|
55
|
+
- dbchanges2/Core/2026-09-03b - InventoryCreateTransferOrderAction.sql
|
|
56
|
+
- dbchanges2/Client_Nychh/2026-09-03b - InventoryCreateTransferOrderShow.sql
|
|
57
|
+
- toga25-supply/src/surface/useStatusColors.ts
|
|
58
|
+
- toga25-supply/src/pages/Inventory/Inventory.tsx
|
|
59
|
+
- toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx
|
|
60
|
+
- toga25-supply/src/layout/ItemFulfillmentModal/useItemFulfillmentModalViewModel.tsx
|
|
61
|
+
- _underscore/Model/Client/TransferOrderItem.php
|
|
53
62
|
related:
|
|
54
63
|
- ./surface-frontend.md
|
|
55
64
|
- ./meta-driven-table-data.md
|
|
@@ -109,6 +118,10 @@ tenant.
|
|
|
109
118
|
| `Core/2026-09-01c - TransferOrderNotesRecords.sql` | Core | Records **357**/**358**, RecordFields **2550β2560**, `InherentRecordChildren` 312β357 β **also unused, and 357/358 have NO NYCHH ACL grant** |
|
|
110
119
|
| `Client/2026-09-01c - TransferOrderContactCreatedByAndDescription.sql` | all tenants (template) | `TransferOrders.contactId` (FK Contacts), `createdByUserId` (FK Users), `description varchar(255)` |
|
|
111
120
|
| `Core/2026-09-01d - TransferOrderContactCreatedByAndDescriptionRecordFields.sql` | Core | RecordFields **2561** `contactId` / **2562** `createdByUserId` / **2563** `description` on record 312, + `AclFieldPermissions` for roles **1** and **3** |
|
|
121
|
+
| `Core/2026-09-03a - TransferOrderStatusBadgeRecordFields.sql` | Core | flips the stage RecordField to `type = 'STATUS'` + points `Records.statusRecordId` at the stage record β the badge chain |
|
|
122
|
+
| `Client_Nychh/2026-09-03a - TransferOrdersStatusBadge.sql` | one tenant | the ACL grants the badge's vocabulary fetch needs (incl. the **`uuid`** RecordField β see the gotcha) |
|
|
123
|
+
| `Core/2026-09-03b - InventoryCreateTransferOrderAction.sql` | Core | the Inventory header **Create Transfer Order** action button, seeded `isVisible = 0` |
|
|
124
|
+
| `Client_Nychh/2026-09-03b - InventoryCreateTransferOrderShow.sql` | one tenant | the `SurfaceOverrides` row that reveals it |
|
|
112
125
|
|
|
113
126
|
All of them were audited against the cluster-isolation HARD RULE: **no `Core.*` reference in any
|
|
114
127
|
`Client_*` file** (the only such strings are inside comments), no `UUID()`, all table references
|
package/knowledge/INDEX.md
CHANGED
|
@@ -31,7 +31,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
31
31
|
- **voice-to-voice** (TOGa Voice) β 4 doc(s) β [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
|
|
32
32
|
- **ai-bdr** (AI-BDR) β 13 doc(s) β [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
|
|
33
33
|
- **toga2-commerce** (TOGa Commerce) β 21 doc(s) β [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
|
|
34
|
-
- **toga25-supply** (TOGa 2.5 Supply) β
|
|
34
|
+
- **toga25-supply** (TOGa 2.5 Supply) β 19 doc(s) β [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
|
|
35
35
|
- **toga-blox** (TOGa Blox) β 14 doc(s) β [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
|
|
36
36
|
- **bdr** (BDR) β 0 doc(s) β [2.0/apps/bdr/INDEX.md](2.0/apps/bdr/INDEX.md)
|
|
37
37
|
|
|
@@ -18,7 +18,7 @@ project: _Underscore
|
|
|
18
18
|
client: nychh
|
|
19
19
|
type: profile
|
|
20
20
|
status: active
|
|
21
|
-
updated: 2026-09-
|
|
21
|
+
updated: 2026-09-04
|
|
22
22
|
owners: ["jcardinal", "apeterson", "bala", "akhokhani"]
|
|
23
23
|
files:
|
|
24
24
|
- dbchanges2/Client_Nychh/2026-09-02a - TransferOrderNetsuitePushInterceptor.sql
|
|
@@ -58,6 +58,13 @@ table views. Client-specific DB change-sets live in `dbchanges2/Client_Nychh/`.
|
|
|
58
58
|
run the NetSuiteβTOGa Supply sync and the asset-tag verification/backfill diagnostics.
|
|
59
59
|
|
|
60
60
|
## Key features (this client)
|
|
61
|
+
- π¨ **sandbox-client (`Core` + `Client_Nychh`) was reset from PRODUCTION again on 2026-09-04** β
|
|
62
|
+
the second reset in two days (the first, 2026-09-03, was over the stale `purchase-orders`
|
|
63
|
+
TableView). Every "verified on <date>" sandbox-client claim in this profile dated **before
|
|
64
|
+
2026-09-04 now describes whatever prod had**, not what is there. The 2026-09-01 β 2026-09-03
|
|
65
|
+
`dbchanges2` migrations may also need re-running on sandbox-client. Re-read the environment before
|
|
66
|
+
trusting any sandbox state below; procedure and the re-run order are in
|
|
67
|
+
[Targeting the right database](../../2.0/apps/dbchanges2/workflows/targeting-the-right-database.md).
|
|
61
68
|
- **Transfer orders now push OUT to NetSuite as $0 sales orders (2026-09-02, built not yet run).**
|
|
62
69
|
NYCHH is the first and only client on that path; it is switched on purely by its
|
|
63
70
|
`transfer-orders` (record 312) `POST`/`POST` interceptor row. Destination location = NetSuite
|
package/package.json
CHANGED