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.
@@ -6,7 +6,7 @@ project: Tools
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-07-16
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 now takes the index ARN).
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-06-23
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
- lowercase `en`/`fr`. See the client-fields doc.
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-23
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)
@@ -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) — 10 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.465",
3
+ "version": "1.0.467",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",