toga-ai 1.0.247 → 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.
@@ -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 locates the bundle by its **structural fingerprint** (the `elements` array) across
54
- `raw` / `raw.data` / `raw.data.<route-key>` — because V2 nests a scripted-API return value one
55
- level deeper, under a route-keyed slot of `data` (e.g. `data.meta`, `data.surfaces`). On a malformed
56
- payload it falls back to an empty-elements bundle so the page never crashes.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.247",
3
+ "version": "1.0.248",
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",