toga-ai 1.0.465 → 1.0.467
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/1.0/apps/tools/features/talos-kb-documents-admin.md +36 -3
- package/knowledge/2.0/apps/toga2-commerce/INDEX.md +2 -1
- package/knowledge/2.0/apps/toga2-commerce/architecture.md +17 -4
- package/knowledge/2.0/apps/toga2-commerce/features/client-fields.md +48 -1
- package/knowledge/2.0/apps/toga2-commerce/features/filter-search-results-page.md +150 -0
- package/knowledge/INDEX.md +1 -1
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ project: Tools
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-28
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- tools/mvc/talos/kb-documents/get.php
|
|
@@ -108,7 +108,7 @@ Vectors** (the canonical design — see the architecture note below), and the fl
|
|
|
108
108
|
3. **S3 Vectors store** — create a dedicated vector bucket + index via
|
|
109
109
|
`App_Talos_Bedrock::createVectorStore()`, capturing the authoritative **`indexArn`**.
|
|
110
110
|
4. **VECTOR KB** — `createKnowledgeBase($bedrock, $slug, $indexArn)` (VECTOR + `S3_VECTORS`
|
|
111
|
-
storage).
|
|
111
|
+
storage), **tagged** with the mandatory `App_Talos_Bedrock::KB_TAGS` pair (see below).
|
|
112
112
|
5. **wait for `ACTIVE`** (`waitForKnowledgeBaseActive`).
|
|
113
113
|
6. **S3 data source** — plain `S3` connector + `BEDROCK_DATA_AUTOMATION` parsing.
|
|
114
114
|
7. **`Team.KnowledgeBases`** (`db_team`, Team MySQL cluster).
|
|
@@ -163,8 +163,16 @@ orphaned KB).
|
|
|
163
163
|
float32, euclidean), then **polls `getIndex` for the authoritative index ARN** with a short
|
|
164
164
|
`NotFound` retry; returns an actionable error on `Conflict`/`AlreadyExists` from an orphaned
|
|
165
165
|
prior attempt.
|
|
166
|
+
- **`KB_TAGS`** — the **mandatory** tag pair stamped on every new KB:
|
|
167
|
+
`['tenant' => 'True', 'model_role' => 'knowledge_base']`. Passed as the top-level `'tags'`
|
|
168
|
+
parameter of `CreateKnowledgeBase`. Casing is load-bearing: capital-T **`True`**, snake_case
|
|
169
|
+
**`model_role`**. Verified against the live `development-team-toga-desk` KB (`ZOLCCQZZL1`) with
|
|
170
|
+
`aws bedrock-agent list-tags-for-resource --resource-arn
|
|
171
|
+
arn:aws:bedrock:us-east-1:654654170868:knowledge-base/ZOLCCQZZL1 --region us-east-1`. These tags
|
|
172
|
+
drive **cost allocation and model-inventory reporting** in the togaiq account — an untagged KB is
|
|
173
|
+
invisible to those reports.
|
|
166
174
|
- `createKnowledgeBase($bedrock, string $slug, string $indexArn)` — creates the VECTOR KB with
|
|
167
|
-
`S3_VECTORS` storage (signature
|
|
175
|
+
`S3_VECTORS` storage (signature takes the index ARN) and applies `KB_TAGS`.
|
|
168
176
|
- `waitForKnowledgeBaseActive()` polls `getKnowledgeBase` every `WAIT_INTERVAL_SECONDS` (3s)
|
|
169
177
|
up to **`WAIT_MAX_SECONDS` = 300**. Exits immediately on `ACTIVE`/`FAILED`. 300s sits well
|
|
170
178
|
under the 900s Apache/PHP ceiling.
|
|
@@ -191,6 +199,12 @@ orphaned KB).
|
|
|
191
199
|
`iam:PassRole` on the shared role. Its bedrock create actions were already covered by
|
|
192
200
|
`AmazonBedrockFullAccess` and S3 by `AmazonS3FullAccess`. **`s3vectors` is a SEPARATE service
|
|
193
201
|
NOT covered by `AmazonBedrockFullAccess`** — that was the real missing grant.
|
|
202
|
+
- **⚠ `bedrock:TagResource` (new, NOT yet verified as granted).** AWS authorizes
|
|
203
|
+
**tagging-on-create separately from the create action itself**. Now that `createKnowledgeBase()`
|
|
204
|
+
sends `tags`, the `bedrock-model-accessor` user must also hold **`bedrock:TagResource`** on the
|
|
205
|
+
knowledge-base resource, or `CreateKnowledgeBase` fails with `AccessDeniedException` — even
|
|
206
|
+
though the *identical call without tags* would have succeeded. Confirm this grant with devops
|
|
207
|
+
**before the next real KB creation** (see the orphaned-vector-bucket gotcha).
|
|
194
208
|
|
|
195
209
|
## Helpers
|
|
196
210
|
|
|
@@ -277,6 +291,17 @@ orphaned KB).
|
|
|
277
291
|
`Conflict`/`AlreadyExists` on the vector bucket (surfaced as an actionable error) or creates a
|
|
278
292
|
**duplicate** Bedrock KB (Bedrock allows same-name KBs with new ids). After a failed create:
|
|
279
293
|
delete the orphaned vector store + KB and re-run clean, or finish the DB rows manually.
|
|
294
|
+
- **Tagging on create needs its own IAM permission, and failing it orphans a vector bucket.**
|
|
295
|
+
`bedrock:TagResource` is authorized separately from `CreateKnowledgeBase`. Because the KB is
|
|
296
|
+
created at **step 4 — after** the S3 Vectors bucket + index (step 3) — a missing `TagResource`
|
|
297
|
+
grant aborts the flow leaving an **orphaned `bedrock-knowledge-base-{slug}` vector bucket with
|
|
298
|
+
no KB and no DB rows**, and retrying the same slug then hits `Conflict`/`AlreadyExists`. If a
|
|
299
|
+
create suddenly starts failing `AccessDeniedException` right after the vector store succeeds,
|
|
300
|
+
suspect this permission first — not the tag values.
|
|
301
|
+
- **Only the Bedrock KB is tagged — the per-KB S3 Vectors bucket is deliberately NOT.** Scope
|
|
302
|
+
decision (2026-07-28): `createVectorStore()` leaves `bedrock-knowledge-base-{slug}` untagged.
|
|
303
|
+
Known gap: the vector bucket is a **separate billable resource**, so vector-storage cost is not
|
|
304
|
+
attributed by `tenant`/`model_role`. Revisit if cost reporting needs it.
|
|
280
305
|
- **Two prior KBs use the WRONG (fully-MANAGED) type.** `development-team-craftex` and
|
|
281
306
|
`development-team-toga-desk` were created by the earlier MANAGED code and were **intentionally
|
|
282
307
|
left as-is** (this session's scope was code-only). They should be **recreated later** to match
|
|
@@ -296,6 +321,14 @@ orphaned KB).
|
|
|
296
321
|
|
|
297
322
|
## Change history
|
|
298
323
|
|
|
324
|
+
- 2026-07-28 — **Every new Bedrock KB is now tagged** `tenant=True` + `model_role=knowledge_base`
|
|
325
|
+
via the new `App_Talos_Bedrock::KB_TAGS` constant, passed as the `tags` parameter of
|
|
326
|
+
`CreateKnowledgeBase` (required for togaiq cost allocation / model-inventory reporting; values
|
|
327
|
+
and casing verified against the live `toga-desk` KB `ZOLCCQZZL1`). **New IAM prerequisite:**
|
|
328
|
+
tagging-on-create needs `bedrock:TagResource` on the `bedrock-model-accessor` user — *not yet
|
|
329
|
+
verified as granted*; without it `CreateKnowledgeBase` returns `AccessDeniedException` and, with
|
|
330
|
+
no rollback, orphans the already-created S3 Vectors bucket. Scoped decision: the per-KB S3
|
|
331
|
+
Vectors bucket is left **untagged**, so vector-storage cost is not attributed. (jcardinal)
|
|
299
332
|
- 2026-07-16 — **Corrected the KB-create architecture from MANAGED to VECTOR + S3 Vectors**
|
|
300
333
|
(the canonical design, from `development-team-dunn`; the earlier code was modeled on the WRONG
|
|
301
334
|
`craftex`/MANAGED KB). Rewrote `App_Talos_Bedrock`: KB is now `VECTOR` + `S3_VECTORS` storage;
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
| [Cart Bundle Submission & the bundleUuid Identity Contract](features/cart-bundle-submission-and-identity.md) | How cart **bundles** (kits) are turned into `SalesOrderItems` when a cart is submitted or an existing order is edited, and the **identity-field contract** every | src/api/syncSalesOrderItemsFromLocalStorageCartToApi.ts, src/utils/formatSalesOrderBundlesFromApi.ts, src/stores/useCartStoreZu.ts, src/pages/OrderDetails/helpers/formatSalesOrderDataFromLocalStorage.ts, src/pages/OrderDetails/view/components/OrderItems.tsx |
|
|
7
7
|
| [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 |
|
|
8
8
|
| [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 |
|
|
9
|
-
| [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/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 |
|
|
9
|
+
| [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/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 |
|
|
10
10
|
| [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 |
|
|
11
|
+
| [Filter / Search-Results Page & the Two Search Entry Points](features/filter-search-results-page.md) | The storefront has **two distinct search entry points that render the same card component through completely different code paths and different FIELDS files**. | src/pages/Filter/FilterPage.tsx, src/pages/Filter/viewModel/useFilterViewModel.ts, src/pages/Filter/viewModel/FIELDS/COMPASS/ENGLISH/USER/FILTERPAGEFIELDS.json, src/pages/Filter/viewModel/FIELDS/COMPASSCANADA/FRENCH/USER/FILTERPAGEFIELDS.json, src/pages/Filter/viewModel/FIELDS/QUAD/ENGLISH/BUYER/FILTERPAGEFIELDS.json, src/components/Header/Header.tsx, src/pages/Home/view/components/BundlesSection.tsx, src/pages/Home/viewModel/FIELDS/COMPASS/ENGLISH/USER/HOMEPAGEFIELDS.json, src/components/Cards/BundleViewCard.tsx, src/utils/renderBadge.tsx, src/hooks/useAssignClientFields.ts |
|
|
11
12
|
| [Multi-Tenant Resolution & Theming](features/multi-tenant-theming.md) | `toga2-commerce` serves multiple clients from one codebase. | src/themeConfig/themes.json, src/themeConfig/ThemeContext.tsx, src/themeConfig/types.ts, src/components/ThemeSwitcher/ThemeSwitcher.tsx, src/components/AuthLayout/AuthLayout.tsx, src/api/axiosInstance.ts, src/contexts/AuthContext.tsx, tailwind.config.js |
|
|
12
13
|
| [AWS Amplify Build & Deploy (non-prod environments)](workflows/amplify-build-and-deploy.md) | How `toga2-commerce` (React + Vite, "commerce2-react") builds and deploys on **AWS Amplify**. | toga2-commerce/amplify.yml, toga2-commerce/.gitattributes, toga2-commerce/package.json, toga2-commerce/.github/workflows/sync-stage-environments.yml |
|
|
13
14
|
| [Cart e2e — Cypress conventions & harness (toga2-commerce)](workflows/cypress-testing.md) | The Cypress **e2e** convention set for `toga2-commerce`, and the first **active** e2e coverage for the **Cart** page (`cartV2.cy.ts`, slice 1 — 12 tests, verifi | toga2-commerce/cypress/e2e/cartPage/cartV2.cy.ts, toga2-commerce/cypress/fixtures/cart/fetchSingleUserAdmin.json, toga2-commerce/cypress/fixtures/cart/fetchLocations.json, toga2-commerce/cypress/fixtures/cart/fetchUserShippingMethods.json, toga2-commerce/cypress/support/commands.ts, toga2-commerce/cypress/support/e2e.ts, toga2-commerce/src/pages/Cart/CartPage.tsx, toga2-commerce/src/pages/Cart/view/cartForm/CartForm.tsx, toga2-commerce/src/pages/Cart/view/cartForm/CartFormSection.tsx, toga2-commerce/src/pages/Cart/view/cartTable/CartContentsTable.tsx, toga2-commerce/src/pages/Cart/view/cartTable/CartTableItem.tsx, toga2-commerce/src/components/Inputs/AdvancedInput.tsx, toga2-commerce/src/components/BaseButton/BaseButton.tsx |
|
|
@@ -6,8 +6,8 @@ project: TOGa Commerce
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: ["apeterson"]
|
|
9
|
+
updated: 2026-07-28
|
|
10
|
+
owners: ["apeterson", "tcox"]
|
|
11
11
|
files:
|
|
12
12
|
- src/main.tsx
|
|
13
13
|
- src/App.tsx
|
|
@@ -26,6 +26,7 @@ related:
|
|
|
26
26
|
- 2.0/apps/toga2-commerce/features/client-fields.md
|
|
27
27
|
- 2.0/apps/toga2-commerce/workflows/amplify-build-and-deploy.md
|
|
28
28
|
- 2.0/apps/api2/architecture.md
|
|
29
|
+
- 2.0/apps/toga2-commerce/features/filter-search-results-page.md
|
|
29
30
|
---
|
|
30
31
|
|
|
31
32
|
## Summary
|
|
@@ -236,11 +237,23 @@ generic CRUD helpers live in `src/api/genericApi.ts` (paginated `getData`, `save
|
|
|
236
237
|
- **`AuthLayout` never remounts** → `refetchOnMount` fires once per session; wire explicit
|
|
237
238
|
invalidation for data that must refresh on navigation.
|
|
238
239
|
- **24h `staleTime` + persisted cache** → users can see stale catalog/pricing; invalidate on the
|
|
239
|
-
events that should bust it.
|
|
240
|
+
events that should bust it. **This also masks config/copy deploys:** `FIELDS` is served *through*
|
|
241
|
+
the `["clientFields", …]` query, so a returning user rehydrates the old labels from
|
|
242
|
+
`localStorage["commerce"]` and sees stale copy for up to 24h after a build that contains the new
|
|
243
|
+
JSON. Verify any FIELDS change after `localStorage.removeItem('commerce')` or a logout, never on a
|
|
244
|
+
warm browser. A build-version `buster` in `persistOptions` would fix this at deploy time but
|
|
245
|
+
invalidates every persisted query app-wide — **open team decision, not implemented.**
|
|
240
246
|
- **Tenant resolution depends on the hostname.** On `localhost` with no `*.togacommerce` host the
|
|
241
247
|
first DNS label won't match a tenant, so config falls back (`getClientLoginFields` → COMPASS,
|
|
242
248
|
theme → DEFAULT). Use the per-tenant dev scripts.
|
|
243
249
|
- **Theme vs. fields language keys differ** — theme/field *folder* names are uppercase
|
|
244
250
|
(`COMPASSCANADA`, `ENGLISH`/`FRENCH`) but the runtime `FIELDS` registry keys language as
|
|
245
|
-
|
|
251
|
+
`en` / **`fr-CA`** (not `fr`). Any language branch must use `startsWith("fr")` — a `=== "fr"`
|
|
252
|
+
comparison silently never matches. See the client-fields doc.
|
|
253
|
+
|
|
254
|
+
## Change history
|
|
255
|
+
- 2026-07-28 — Gotchas: recorded that the 24h persisted React Query cache also masks `FIELDS`
|
|
256
|
+
config/copy deploys (verify after clearing `localStorage["commerce"]`; `persistOptions.buster` is
|
|
257
|
+
an open team decision), and corrected the runtime language key to `fr-CA` requiring
|
|
258
|
+
`startsWith("fr")` (tcox)
|
|
246
259
|
</content>
|
|
@@ -6,7 +6,7 @@ project: TOGa Commerce
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-28
|
|
10
10
|
owners: ["apeterson", "tcox", "bala"]
|
|
11
11
|
files:
|
|
12
12
|
- src/pages/Account/view/MySettingsView.tsx
|
|
@@ -31,11 +31,15 @@ files:
|
|
|
31
31
|
- src/pages/GetSupport/GetSupportPage.tsx
|
|
32
32
|
- src/pages/GetSupport/viewModel/useGetSupportViewModel.ts
|
|
33
33
|
- src/queries/queries.ts
|
|
34
|
+
- src/App.tsx
|
|
35
|
+
- src/pages/Filter/FilterPage.tsx
|
|
36
|
+
- src/pages/Filter/viewModel/FIELDS/COMPASS/ENGLISH/USER/FILTERPAGEFIELDS.json
|
|
34
37
|
related:
|
|
35
38
|
- 2.0/apps/toga2-commerce/architecture.md
|
|
36
39
|
- 2.0/apps/toga2-commerce/features/multi-tenant-theming.md
|
|
37
40
|
- 2.0/apps/toga2-commerce/features/cart-notification-emails.md
|
|
38
41
|
- 2.0/apps/toga2-commerce/features/cart-page-config-architecture.md
|
|
42
|
+
- 2.0/apps/toga2-commerce/features/filter-search-results-page.md
|
|
39
43
|
- 2.0/apps/_underscore/features/persona-name-translation.md
|
|
40
44
|
---
|
|
41
45
|
|
|
@@ -226,6 +230,26 @@ Any code that branches on the current language must compare against **`"fr-CA"`*
|
|
|
226
230
|
`language.startsWith("fr")`, **never `language === "fr"`** (that comparison silently fails and was the
|
|
227
231
|
root cause of the Get Support image bug — see gotchas).
|
|
228
232
|
|
|
233
|
+
### FIELDS ride the persisted React Query cache — config fixes can appear not to deploy
|
|
234
|
+
|
|
235
|
+
`FIELDS` is statically bundled, but it is **served through a React Query query**
|
|
236
|
+
(`["clientFields", clientName, language, role]`). `src/App.tsx` sets the QueryClient default
|
|
237
|
+
`staleTime` to **24h** and persists the entire cache to `localStorage` under the key **`"commerce"`**
|
|
238
|
+
via `createSyncStoragePersister` + `PersistQueryClientProvider`. Consequence: a **returning** user
|
|
239
|
+
rehydrates the *old* fields object from localStorage and keeps seeing **stale labels for up to 24
|
|
240
|
+
hours after a deploy**, even though the new JSON is in the shipped bundle.
|
|
241
|
+
|
|
242
|
+
This is the single most common false negative when verifying a FIELDS/copy change — QA reports "your
|
|
243
|
+
fix didn't ship" on a browser that simply replayed the persisted cache.
|
|
244
|
+
|
|
245
|
+
- **Clear it manually:** `localStorage.removeItem('commerce')` then refresh.
|
|
246
|
+
- **Logging out also clears it** — both `src/contexts/AuthContext.tsx` (logout path) and
|
|
247
|
+
`src/api/axiosInstance.ts` call `localStorage.removeItem("commerce")`.
|
|
248
|
+
- **Proper deploy-time fix — not implemented, team decision pending:** pass a build-version `buster`
|
|
249
|
+
string in `persistOptions` in `App.tsx`. It is one line, but it invalidates **every** persisted query
|
|
250
|
+
app-wide on each deploy (losing all warm cache, not just fields), so it needs a deliberate call
|
|
251
|
+
rather than a drive-by change.
|
|
252
|
+
|
|
229
253
|
## What's inside a FIELDS JSON
|
|
230
254
|
|
|
231
255
|
Shape varies by page, but common forms:
|
|
@@ -322,6 +346,21 @@ on switch.
|
|
|
322
346
|
untranslated on purpose (the dropdown *contents* are translated). It is not a localization gap.
|
|
323
347
|
- **No fallback** in `FIELDS[client][language][role]` — a missing tenant/role/language combo returns
|
|
324
348
|
`null` and the page renders empty. Keep all role variants in sync.
|
|
349
|
+
- **No fallback for an individual *key* either — and that failure is silent.** The "no fallback" rule
|
|
350
|
+
applies one level deeper than the section lookup: a key missing from an otherwise-present page JSON
|
|
351
|
+
just yields `undefined`, which the consuming component happily interpolates. Real symptoms shipped
|
|
352
|
+
2026-07-28: `renderBadge(undefined)` rendered a structurally-present but **empty, uncolored,
|
|
353
|
+
icon-less** badge, and a card template `` `${count} ${label}s` `` rendered **"3 undefineds"**. Nothing
|
|
354
|
+
throws, nothing logs, TypeScript is clean (the lookups are optional-chained `any`). When adding a
|
|
355
|
+
card/label key, add it to **every** tenant × language × role file for that page — 12 for Compass
|
|
356
|
+
(COMPASS/ENGLISH × 4 roles + COMPASSCANADA/{ENGLISH,FRENCH} × 4), 3 for QUAD. See
|
|
357
|
+
[filter-search-results-page](filter-search-results-page.md).
|
|
358
|
+
- **Key names are per page, not global — the same UI element can have different key names in two
|
|
359
|
+
pages' JSON.** The kit badge is `bundleBadge` in `HOMEPAGEFIELDS` but `kitBadge` in
|
|
360
|
+
`FILTERPAGEFIELDS`. Never assume a label added for one page covers another; grep the consuming
|
|
361
|
+
component for the exact `fields?.<key>` it reads.
|
|
362
|
+
- **A stale persisted cache masks FIELDS deploys** — see the persisted-cache section above; verify
|
|
363
|
+
copy changes after `localStorage.removeItem('commerce')` or a logout, never on a warm browser.
|
|
325
364
|
- Role is derived from string `"1"` flags on the user (`user?._isAdmin === "1"`), not booleans.
|
|
326
365
|
- `getClientLoginFields` defaults to **COMPASS** for an unknown host, which can mask a
|
|
327
366
|
misconfigured tenant in local dev.
|
|
@@ -339,6 +378,14 @@ on switch.
|
|
|
339
378
|
slip. Do not "reconcile" the two lists.
|
|
340
379
|
|
|
341
380
|
## Change history
|
|
381
|
+
- 2026-07-28 — Documented two failure modes found while fixing the header-search kit card: (1) there is
|
|
382
|
+
**no fallback for an individual key** inside a present page JSON — a missing key silently yields
|
|
383
|
+
`undefined` (empty uncolored badge, `"3 undefineds"`), and key names differ per page
|
|
384
|
+
(`bundleBadge` in HOMEPAGEFIELDS vs `kitBadge` in FILTERPAGEFIELDS); (2) FIELDS are served *through*
|
|
385
|
+
the `["clientFields", …]` React Query, whose cache is persisted to `localStorage["commerce"]` with a
|
|
386
|
+
24h `staleTime`, so returning users see **stale labels for up to 24h after a deploy** — clear with
|
|
387
|
+
`localStorage.removeItem('commerce')` or a logout. Recorded the pending team decision on adding a
|
|
388
|
+
build-version `buster` to `persistOptions` in `App.tsx`. (tcox)
|
|
342
389
|
- 2026-07-23 — **Correction:** clarified that `LANGUAGE_SENSITIVE_QUERY_KEYS` alone does **not**
|
|
343
390
|
refresh the persona name on language switch — `user._personaOptions` lives in the persisted Zustand
|
|
344
391
|
`user` store (populated once at login), not a live React Query, so nothing in the allow-list owns it.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Filter / Search-Results Page & the Two Search Entry Points
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga2-commerce
|
|
5
|
+
project: TOGa Commerce
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-28
|
|
10
|
+
owners: ["tcox"]
|
|
11
|
+
files:
|
|
12
|
+
- src/pages/Filter/FilterPage.tsx
|
|
13
|
+
- src/pages/Filter/viewModel/useFilterViewModel.ts
|
|
14
|
+
- src/pages/Filter/viewModel/FIELDS/COMPASS/ENGLISH/USER/FILTERPAGEFIELDS.json
|
|
15
|
+
- src/pages/Filter/viewModel/FIELDS/COMPASSCANADA/FRENCH/USER/FILTERPAGEFIELDS.json
|
|
16
|
+
- src/pages/Filter/viewModel/FIELDS/QUAD/ENGLISH/BUYER/FILTERPAGEFIELDS.json
|
|
17
|
+
- src/components/Header/Header.tsx
|
|
18
|
+
- src/pages/Home/view/components/BundlesSection.tsx
|
|
19
|
+
- src/pages/Home/viewModel/FIELDS/COMPASS/ENGLISH/USER/HOMEPAGEFIELDS.json
|
|
20
|
+
- src/components/Cards/BundleViewCard.tsx
|
|
21
|
+
- src/utils/renderBadge.tsx
|
|
22
|
+
- src/hooks/useAssignClientFields.ts
|
|
23
|
+
related:
|
|
24
|
+
- 2.0/apps/toga2-commerce/architecture.md
|
|
25
|
+
- 2.0/apps/toga2-commerce/features/client-fields.md
|
|
26
|
+
- 2.0/apps/toga2-commerce/features/multi-tenant-theming.md
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Summary
|
|
30
|
+
|
|
31
|
+
The storefront has **two distinct search entry points that render the same card component through
|
|
32
|
+
completely different code paths and different FIELDS files**. That split is the single most important
|
|
33
|
+
fact about this page: a search-results defect almost always reproduces on **one** path only, and the
|
|
34
|
+
first diagnostic question is *"which search box did you use?"*
|
|
35
|
+
|
|
36
|
+
| Entry point | Component | Route | FIELDS section read |
|
|
37
|
+
|---|---|---|---|
|
|
38
|
+
| **Home page lower search bar** | `src/pages/Home/view/components/BundlesSection.tsx` | stays on `/home`, filters **in place** | `HOMEPAGEFIELDS` |
|
|
39
|
+
| **Header search bar** (global, every authenticated page) | `src/components/Header/Header.tsx` → `src/pages/Filter/FilterPage.tsx` | **navigates** to `/filter?search=<text>&category=all` | `FILTERPAGEFIELDS` |
|
|
40
|
+
|
|
41
|
+
Both then render the shared **`src/components/Cards/BundleViewCard.tsx`**. Because the two callers
|
|
42
|
+
populate that shared card's props from two different JSON files, the card can render correctly from
|
|
43
|
+
one entry point and be broken from the other — which is exactly the 2026-07-28 "3 undefineds" kit-card
|
|
44
|
+
bug (see Change history).
|
|
45
|
+
|
|
46
|
+
## How it works
|
|
47
|
+
|
|
48
|
+
### Header search → `/filter`
|
|
49
|
+
|
|
50
|
+
`Header.tsx` navigates to `` `/filter?search=${encodeURIComponent(inputValue)}` + "&category=all" ``.
|
|
51
|
+
`FilterPage.tsx` reads the query string, and `useFilterViewModel.ts` runs the catalog query and calls
|
|
52
|
+
`useAssignClientFields(fieldKey, language, user)` to resolve `FILTERPAGEFIELDS` for the current
|
|
53
|
+
tenant × language × role (see [client-fields](client-fields.md) for the resolver). `FilterPage`
|
|
54
|
+
`renderBundles` maps each result to a `BundleViewCard`.
|
|
55
|
+
|
|
56
|
+
### Home search → in-place filter
|
|
57
|
+
|
|
58
|
+
The Home page's own lower search bar never leaves `/home`. `BundlesSection.tsx` reads
|
|
59
|
+
`HOMEPAGEFIELDS` (already loaded by the Home ViewModel) and renders the same `BundleViewCard`.
|
|
60
|
+
|
|
61
|
+
### The shared card contract (`BundleViewCard`)
|
|
62
|
+
|
|
63
|
+
The card is a dumb presentational component — every label is a prop. Two props matter for kit cards:
|
|
64
|
+
|
|
65
|
+
- `includedItems` — the count.
|
|
66
|
+
- `includedItemsText` — the **singular** noun, e.g. `"included item"`.
|
|
67
|
+
|
|
68
|
+
The card composes them itself, in **two places** (mobile and desktop blocks, ~lines 112 and 191):
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
text={`${includedItems} ${includedItemsText}${includedItems == 1 ? "" : "s"}`}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
So a caller that forgets `includedItemsText` renders **"3 undefineds"** — the literal string
|
|
75
|
+
`undefined` plus the appended `s`. There is no default and no runtime guard.
|
|
76
|
+
|
|
77
|
+
### Badges are keyed off the badge *text*
|
|
78
|
+
|
|
79
|
+
`src/utils/renderBadge.tsx` takes the resolved label string and derives **everything** from it:
|
|
80
|
+
|
|
81
|
+
- `badgeTextMapping` / `badgeTextMappingFrench` — display text (French is looked up by the **English**
|
|
82
|
+
key, e.g. `Kit → "Trousse"`), falling back to the raw string.
|
|
83
|
+
- `getBadgeClasses(badgeText)` — a `switch` on the text for background/foreground colors (it accepts
|
|
84
|
+
both English and French spellings: `"Kit"`, `"Trousse"`, `"Ensemble"`).
|
|
85
|
+
- `getBadgeIcon(badgeText)` — a `switch` on the text for the FontAwesome icon; only `"Kit"` and
|
|
86
|
+
`"VIP"` match.
|
|
87
|
+
|
|
88
|
+
`renderBadge(undefined, "")` therefore renders a **structurally present but empty and uncolored
|
|
89
|
+
badge**: no text, no icon, and the `default:` class branch (no color). Nothing throws and nothing
|
|
90
|
+
logs — the bug looks like a CSS problem and is actually a missing config key.
|
|
91
|
+
|
|
92
|
+
### The badge key is named differently on each page
|
|
93
|
+
|
|
94
|
+
Same visual badge, two different FIELDS key names — do not assume one:
|
|
95
|
+
|
|
96
|
+
- Home / `HOMEPAGEFIELDS` → **`bundleBadge`** (`fields?.bundleBadge?.label`)
|
|
97
|
+
- Filter / `FILTERPAGEFIELDS` → **`kitBadge`** (`fields?.kitBadge?.label`)
|
|
98
|
+
|
|
99
|
+
## Gotchas
|
|
100
|
+
|
|
101
|
+
- **A missing *key* inside a present FIELDS file fails silently.** The known "no fallback" rule for
|
|
102
|
+
`FIELDS[tenant][language][role]` also applies one level deeper: an individual key absent from a
|
|
103
|
+
page's JSON yields `undefined`, which renders as an empty badge or `"N undefineds"`. Any card-label
|
|
104
|
+
key must exist in **every** tenant × language × role file for that page — 12 files for Compass
|
|
105
|
+
(COMPASS/ENGLISH × 4 roles, COMPASSCANADA/ENGLISH × 4, COMPASSCANADA/FRENCH × 4) and 3 for QUAD
|
|
106
|
+
(QUAD/ENGLISH/{GLOBALADMIN,BUYER,ITSHOPPER}).
|
|
107
|
+
- **Adding a label to one page's FIELDS does not cover the other page.** `HOMEPAGEFIELDS` and
|
|
108
|
+
`FILTERPAGEFIELDS` are independent files with independent key sets; a label added for the Home card
|
|
109
|
+
must be added again (possibly under a different key name) for the Filter card.
|
|
110
|
+
- **French pluralization is broken by design in the shared card.** `BundleViewCard` blindly appends
|
|
111
|
+
`"s"`, so `"articles inclus"` renders as **"3 articles incluss"** in fr-CA. This pre-dates the
|
|
112
|
+
Filter-page fix (the Compass Canada *home* page has always shown it) and the Filter page now
|
|
113
|
+
mirrors it for parity. The real fix is pre-pluralized per-language labels (or a singular/plural pair)
|
|
114
|
+
in the card contract — **follow-up ticket, not yet filed.**
|
|
115
|
+
- **French kit badge loses its icon.** The FRENCH JSONs store the label `"Trousse"`, so
|
|
116
|
+
`getBadgeClasses` still colors it but `getBadgeIcon` (which only matches `"Kit"`) returns `null` — no
|
|
117
|
+
BoxesStacked icon in French. Storing the English `"Kit"` in the French file would get both the icon
|
|
118
|
+
*and* auto-translated text via `badgeTextMappingFrench`. Current behavior matches the Home page, so
|
|
119
|
+
it was left alone; treat it as a known cosmetic gap, not a regression.
|
|
120
|
+
- **Persisted React Query cache hides FIELDS fixes for up to 24h after deploy.** FIELDS are served
|
|
121
|
+
*through* a query (`["clientFields", tenant, language, role]`) and the whole cache is persisted to
|
|
122
|
+
`localStorage["commerce"]` with a 24h `staleTime`, so a returning user rehydrates the **old** labels
|
|
123
|
+
even though the new JSON is in the bundle. When QA says "your config fix didn't ship", have them run
|
|
124
|
+
`localStorage.removeItem('commerce')` and refresh (logging out also clears it). See
|
|
125
|
+
[client-fields](client-fields.md).
|
|
126
|
+
|
|
127
|
+
## Known open items
|
|
128
|
+
|
|
129
|
+
- **QUAD has the same missing-key bug, unfixed.** All three
|
|
130
|
+
`src/pages/Filter/viewModel/FIELDS/QUAD/ENGLISH/{GLOBALADMIN,BUYER,ITSHOPPER}/FILTERPAGEFIELDS.json`
|
|
131
|
+
still lack `kitBadge` and `includedItemsText`, so a header-search kit card on QUAD renders the empty
|
|
132
|
+
badge + `"3 undefineds"`. The fix is the identical two-entry JSON addition; it was left out because
|
|
133
|
+
the 2026-07-28 session was scoped to Compass.
|
|
134
|
+
- French pluralization (`"3 articles incluss"`) — needs a card-contract change, see gotchas.
|
|
135
|
+
|
|
136
|
+
## Change history
|
|
137
|
+
- 2026-07-28 — Fixed header-search kit cards for Compass USA + Compass Canada. Two defects in the
|
|
138
|
+
Filter path: (1) `FilterPage.tsx` rendered `BundleViewCard` without passing `includedItemsText`
|
|
139
|
+
→ `"3 undefineds"` (added `const includedItemsText = fields?.includedItemsText?.label;` beside the
|
|
140
|
+
existing `kitBadgeLabel` read and passed it through in `renderBundles`); (2) the `kitBadge` key was
|
|
141
|
+
missing from every `FILTERPAGEFIELDS.json` except COMPASSCANADA/ENGLISH/ADMIN, so
|
|
142
|
+
`renderBadge(undefined)` produced an empty uncolored badge. Added `kitBadge` ("Kit"/"Trousse") and
|
|
143
|
+
`includedItemsText` ("included item"/"articles inclus") to all **12** Compass FILTERPAGEFIELDS
|
|
144
|
+
files, copied from the working HOMEPAGEFIELDS labels so both search paths render identically. The
|
|
145
|
+
code change is a no-op for tenants whose config lacks the keys, so QUAD is unchanged (still buggy —
|
|
146
|
+
see Known open items). `npx tsc --noEmit` clean. (tcox)
|
|
147
|
+
- 2026-07-28 — Initial doc: recorded the two search entry points (Home in-place vs. header →
|
|
148
|
+
`/filter`), the shared `BundleViewCard` label contract, text-keyed badge rendering in
|
|
149
|
+
`renderBadge.tsx`, the `bundleBadge` vs `kitBadge` key-name split, and the silent-`undefined`
|
|
150
|
+
failure mode for a missing per-key FIELDS entry. (tcox)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -28,7 +28,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
28
28
|
- **talos** (TOGa IQ) — 7 doc(s) → [2.0/apps/talos/INDEX.md](2.0/apps/talos/INDEX.md)
|
|
29
29
|
- **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
|
|
30
30
|
- **ai-bdr** (AI-BDR) — 8 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
|
|
31
|
-
- **toga2-commerce** (TOGa Commerce) —
|
|
31
|
+
- **toga2-commerce** (TOGa Commerce) — 11 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
|
|
32
32
|
- **toga25-supply** (TOGa 2.5 Supply) — 11 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
|
|
33
33
|
- **toga-blox** (TOGa Blox) — 8 doc(s) → [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
|
|
34
34
|
- **bdr** (BDR) — 0 doc(s) → [2.0/apps/bdr/INDEX.md](2.0/apps/bdr/INDEX.md)
|
package/package.json
CHANGED