toga-ai 1.0.246 → 1.0.248
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.
|
@@ -70,20 +70,20 @@ This mirrors the toga2.5 / toga25-supply "named rule" / config-driven cart patte
|
|
|
70
70
|
behavior is expressed in config, not in code branches.
|
|
71
71
|
|
|
72
72
|
## Gotchas
|
|
73
|
-
- **
|
|
74
|
-
|
|
75
|
-
**
|
|
76
|
-
`
|
|
77
|
-
(
|
|
78
|
-
`
|
|
79
|
-
|
|
80
|
-
one-per-order limit
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
73
|
+
- **Computer-kit eligibility depends on consistent `COMPUTERS` categorization (was a data gap,
|
|
74
|
+
now resolved).** The gate matches the primary item's `itemCategory.name === "COMPUTERS"`
|
|
75
|
+
**only**. MacBook kits were initially mis-categorized: during this session
|
|
76
|
+
`Client_Compass.Items` showed MacBooks spread across **four** categories — `"COMPUTERS"`
|
|
77
|
+
(14"/16" MacBook Pro M4, `MW2W3LLA-S` / `MX2X3LLA-S`), `"APPLE LAPTOP"` (14" MacBook Pro M5,
|
|
78
|
+
`MDE54LL/A-S`), `"MAC & ACCESSORIES"` (~18 older M1/M2 models), and `"PDC NEW HIRE - APPLE"`.
|
|
79
|
+
A MacBook tagged anything other than `COMPUTERS` did **not** qualify for expedited (nor
|
|
80
|
+
trigger the one-per-order limit). **Resolved 2026-06-30:** the catalog/data team
|
|
81
|
+
re-categorized the computer/laptop kits (incl. MacBooks) to `COMPUTERS`; the tested
|
|
82
|
+
"Compass 14\" Macbook" was confirmed updated and now shows expedited. **Lesson:** the rule is
|
|
83
|
+
only as correct as the catalog — new computer/laptop kits must be filed under `COMPUTERS`
|
|
84
|
+
(the same category the bundle-page one-per-order limit keys off). If a future kit
|
|
85
|
+
legitimately needs a different category, extend the config `value` to a list (e.g.
|
|
86
|
+
`["COMPUTERS","APPLE LAPTOP"]`) rather than adding code.
|
|
87
87
|
- **The gate is UI-only, not server-enforced.** Shipping methods come from `/shipping-methods`
|
|
88
88
|
(UPS carrier, fetched in `useCartViewModel`) and are filtered client-side. The gate is a
|
|
89
89
|
selection restriction, not order validation. Follow-up recommended: enforce
|
|
@@ -104,5 +104,8 @@ behavior is expressed in config, not in code branches.
|
|
|
104
104
|
base-name compare via `getBaseShippingOptionName`; edit-order silent-downgrade/load race →
|
|
105
105
|
reset guarded on dirty + loaded options. Documented the Compass MacBook category
|
|
106
106
|
inconsistency as a known gotcha (data-normalization item, not a code gap). (tcox)
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
- 2026-06-30 — Catalog data normalized: the computer/laptop kits (incl. MacBooks) were
|
|
108
|
+
re-categorized to `COMPUTERS`, so MacBook kits now qualify for expedited; the tested
|
|
109
|
+
"Compass 14\" Macbook" was confirmed updated. Updated the MacBook-categorization gotcha to
|
|
110
|
+
reflect the resolution, and removed stray closing tags accidentally written into the doc.
|
|
111
|
+
(tcox)
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
| [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/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableData.tsx |
|
|
9
9
|
| [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/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 |
|
|
10
10
|
| [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/layout/ItemRecordModalLayout/, toga25-supply/src/layout/SalesOrderRecordModalLayout/, toga25-supply/src/layout/SalesOrderItemsTableLayout/, toga25-supply/src/layout/ItemFulfillmentModal/, toga25-supply/src/layout/GenericNestedTables/, toga25-supply/src/hooks/useTableCellInteractions.ts |
|
|
11
|
-
| [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/surface/useFetchSurfaceMeta.ts, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/surface/actionRegistry.ts, 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/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 |
|
|
11
|
+
| [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/surface/useFetchSurfaceMeta.ts, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.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/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 |
|
|
12
12
|
| [Cypress Testing Harness (component + e2e)](workflows/cypress-testing.md) | The Cypress test harness for the `toga25-supply` frontend, bootstrapped from scratch (`cypress` was already a dependency but there was no config, no `cypress/` | toga25-supply/cypress.config.ts, toga25-supply/cypress/tsconfig.json, toga25-supply/cypress/support/component.tsx, toga25-supply/cypress/support/component-index.html, toga25-supply/cypress/support/e2e.ts, toga25-supply/cypress/support/commands.ts, toga25-supply/cypress/support/fixtures.ts, toga25-supply/cypress/support/mocks/useApprovalModalViewModel.ts, toga25-supply/cypress/component/SalesOrderApprovalModalsLayout.cy.tsx, toga25-supply/cypress/component/RecordApprovalModalLayout.cy.tsx, toga25-supply/cypress/component/EnterPoNumberModal.cy.tsx, toga25-supply/cypress/e2e/salesOrderApproval.cy.ts |
|
|
@@ -7,7 +7,7 @@ client: shared
|
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
9
|
updated: 2026-06-30
|
|
10
|
-
owners: [jcardinal]
|
|
10
|
+
owners: [jcardinal, apeterson]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/src/surface/useFetchSurfaceMeta.ts
|
|
13
13
|
- toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts
|
|
@@ -15,6 +15,7 @@ files:
|
|
|
15
15
|
- toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/index.ts
|
|
16
16
|
- toga25-supply/src/surface/evaluateSurfaceRule.ts
|
|
17
17
|
- toga25-supply/src/surface/actionRegistry.ts
|
|
18
|
+
- toga25-supply/src/surface/componentRegistry.tsx
|
|
18
19
|
- toga25-supply/src/surface/SurfaceActionBar.tsx
|
|
19
20
|
- toga25-supply/src/surface/SurfaceSection.tsx
|
|
20
21
|
- toga25-supply/src/surface/resolve.ts
|
|
@@ -50,16 +51,35 @@ feature flag — see below). Backend: [surface-resolver](../../_underscore/featu
|
|
|
50
51
|
meta. Loading a different *record* into the same surface reuses cached meta. Builds the URL as
|
|
51
52
|
`/surfaces/meta?slug=...` (query string), **not** `/surfaces/<slug>/meta` (the path form makes the
|
|
52
53
|
engine parse the slug as a record uuid → 404 EV-6). Unwraps the bundle via **`extractBundle()`**,
|
|
53
|
-
which
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
which uses a recursive **`findBundle(raw, predicate, depth=4)`** to walk the envelope (replacing the
|
|
55
|
+
old hard-coded `raw`/`raw.data`/`raw.data.<key>` probing) and locates the bundle by its
|
|
56
|
+
**structural fingerprint**: `looksLikeBundle` now requires **both** an `elements` array **and** a
|
|
57
|
+
`surface` descriptor object (not the `elements` array alone — see the decoy gotcha below).
|
|
58
|
+
`extractBundle(raw, slug)` then **prefers** the bundle whose `surface.slug === slug`, falling back to
|
|
59
|
+
first-found, so a stray sibling bundle can't win. `looksLikeGroup` requires the documented `group`
|
|
60
|
+
discriminator key so a single-bundle envelope is not mistaken for a group. On a malformed payload it
|
|
61
|
+
falls back to a safe empty bundle (empty `elements` + empty `messages`/`theme`/`vocabularies` maps)
|
|
62
|
+
so the page never crashes.
|
|
57
63
|
- **`useFetchSurfaceMetaGroup(slugs[])`** — the grouped sibling of `useFetchSurfaceMeta`, for a screen
|
|
58
64
|
needing many section surfaces at once. **Order-insensitive** (the slug list is normalized so cache
|
|
59
65
|
hits don't depend on argument order), `staleTime: Infinity`, hits the grouped endpoint
|
|
60
66
|
`GET /v2/surfaces/meta-group?slugs=...`, and extracts the `{surfaces}` map via `extractGroup()` /
|
|
61
67
|
`looksLikeGroup()` (the same structural-fingerprint approach as `extractBundle`). Returns `{}` for
|
|
62
68
|
unmigrated screens (`enabled:false`), so a non-roster client does no fetch.
|
|
69
|
+
- **`componentRegistry`** (`src/surface/componentRegistry.tsx`) — the **presentation sibling** of
|
|
70
|
+
`actionRegistry`, both keyed by the surface meta's `action.key`. `actionRegistry` maps key→behavior
|
|
71
|
+
handler; `componentRegistry` maps key→custom React component. `SurfaceActionBar.renderElement` calls
|
|
72
|
+
`getSurfaceComponent(element.action?.key)` and renders the registered component (in a keyed Fragment)
|
|
73
|
+
**before** falling through to the default icon button — so `action.key` drives **both** which
|
|
74
|
+
component renders and its behavior, while `messages.label` supplies its text and clicks route through
|
|
75
|
+
the same `handle`→dispatch path. First entry: `"salesOrder.viewLog"` → `<ViewLogButton>`. Extending
|
|
76
|
+
is one surface element in meta + one registry entry (same model as `actionRegistry` and the Pattern-3
|
|
77
|
+
cell registry).
|
|
78
|
+
- **element `config` vs `action.payload`.** Per-element `config` (`Record<string,unknown>|null`) is the
|
|
79
|
+
opaque element-level seed bag (the SurfaceElements JSON long-tail). At runtime the **backend resolver
|
|
80
|
+
merges the Action's own config with the element `config` to produce `action.payload`**, which is what
|
|
81
|
+
the FE consumes — the FE renderer never reads `element.config` directly. A separate surface-level
|
|
82
|
+
`surface.config` lives on the descriptor (e.g. Inventory's `groupByLabels`/`entityTags`).
|
|
63
83
|
- **`evaluateSurfaceRule`** — the Tier-1 rule evaluator: a **frozen `all/any/none` + `{field,op,value}`
|
|
64
84
|
grammar** that **throws on an unknown op** (generalized from the SalesOrders
|
|
65
85
|
`buildPatchedTenantFields`/`evaluateEnableRule`/`resolveFlag` helpers). Evaluated client-side against
|
|
@@ -155,6 +175,25 @@ seeding NYCHH/Prudential/SPGlobal is deferred; the Client-DB prod cross-cluster
|
|
|
155
175
|
|
|
156
176
|
## Gotchas
|
|
157
177
|
|
|
178
|
+
- **The single-meta envelope can hide the real bundle behind a DECOY `elements: []`.** The V2 envelope
|
|
179
|
+
can arrive as `{ surfaces: { meta: <real-bundle> }, elements: [] }` — a decoy empty `elements: []`
|
|
180
|
+
sits at the top level with **no `surface` descriptor**. A `looksLikeBundle` check matching on
|
|
181
|
+
`Array.isArray(value.elements)` alone short-circuits on the decoy and never finds the real nested
|
|
182
|
+
bundle → the action bar renders nothing. Fix: `looksLikeBundle` must require **both** `elements` AND
|
|
183
|
+
a `surface` descriptor; use recursive `findBundle` + slug-preferring `extractBundle` (see How it
|
|
184
|
+
works). The durable backend follow-up is to confirm/stabilize the single-meta envelope contract.
|
|
185
|
+
- **The surface `record` shape must match the rule field namespace — wrap as `{ order }`.** Seeded
|
|
186
|
+
Tier-1 rules namespace their field as `order._status`, so `getByPath(record, "order._status")` only
|
|
187
|
+
resolves if the host passes `record={{ order }}`. Passing the bare order object resolves to
|
|
188
|
+
`order.order._status` = undefined → every `in`/`eq` op is false → buttons hidden. The fix is at the
|
|
189
|
+
**host** (`SalesOrderTopBar.tsx`: `record={order ? { order } : null}`), **not** in
|
|
190
|
+
`evaluateSurfaceRule.ts` (the evaluator was correct).
|
|
191
|
+
- **`tokens.icon` is a LITERAL FontAwesome icon name, not a theme-token slug.** Only `tokens.color` and
|
|
192
|
+
`tokens.bg` are theme slugs resolved via `resolveThemeToken(theme, ...)` (the `theme` map only holds
|
|
193
|
+
`color.status.*`/`bg.status.*` keys). `tokens.icon` is a direct name (e.g. `check`, `xmark`,
|
|
194
|
+
`clipboardListCheck`, `penToSquare`) and must be used as-is (`element.tokens?.icon ?? "check"`).
|
|
195
|
+
Running it through `resolveThemeToken` misses on every icon and silently falls back to `"check"` for
|
|
196
|
+
every button.
|
|
158
197
|
- **Scripted-API GET return values land under `data.<scriptRoute>`, not `data` directly.** V2 nests a
|
|
159
198
|
scripted-API return value inside the envelope under a route-keyed slot of `data` (e.g. `data.meta`,
|
|
160
199
|
`data.surfaces`), one level deeper than a normal data fetch. The bundle hook unwraps this with
|
|
@@ -179,6 +218,17 @@ seeding NYCHH/Prudential/SPGlobal is deferred; the Client-DB prod cross-cluster
|
|
|
179
218
|
don't inherit Compass's shared base. Deleted the migrated section blocks from COMPASS/COMPASSCANADA/
|
|
180
219
|
QUAD `orderViewFields.json` (data-wiring kept; DEFAULT intact). Action-bar/approval-modal still
|
|
181
220
|
JSON. (jcardinal)
|
|
221
|
+
- 2026-06-30 — Debugged + extended the client-side action-bar renderer (no client-specific work).
|
|
222
|
+
Fixed three rendering bugs: (1) the single-meta envelope's **decoy `elements: []`** masked the real
|
|
223
|
+
nested bundle — `looksLikeBundle` now requires `elements` AND a `surface` descriptor, with recursive
|
|
224
|
+
`findBundle(depth=4)` + slug-preferring `extractBundle` + `group`-discriminator `looksLikeGroup` +
|
|
225
|
+
safe empty `messages`/`theme`/`vocabularies` fallback (`useFetchSurfaceMeta.ts`); (2) Tier-1 rules
|
|
226
|
+
always false because the host passed the bare order object instead of the `{ order }` envelope the
|
|
227
|
+
`order.*` field namespace requires (`SalesOrderTopBar.tsx`); (3) every action icon rendered as
|
|
228
|
+
`check` because `tokens.icon` (a literal FA name) was wrongly run through `resolveThemeToken`
|
|
229
|
+
(`SurfaceActionBar.tsx`). Built `componentRegistry.tsx` — the presentation sibling of
|
|
230
|
+
`actionRegistry`, key→custom component (`salesOrder.viewLog` → `ViewLogButton`). Clarified element
|
|
231
|
+
`config` vs backend-merged `action.payload`. (apeterson)
|
|
182
232
|
- 2026-06-29 — Migrated VendorItems (screen 2, recipe-exact) and Inventory (screen 3,
|
|
183
233
|
**presentation-only by decision** — fetch topology stays JSON; only title/buttons/modal copy/entity
|
|
184
234
|
tags moved). Fixed the "s.elements is not iterable" crash: `extractBundle()` now finds the bundle by
|
package/package.json
CHANGED