toga-ai 1.0.377 → 1.0.379

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: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-07-17
9
+ updated: 2026-07-20
10
10
  owners: [jcardinal, apeterson]
11
11
  files:
12
12
  - _underscore/Model/Core/Surface.php
@@ -106,6 +106,21 @@ which surfaced latent resolution gaps in `_buildBundle`/`_resolveElement`:
106
106
  - `bundle.surface.titleMessageKey` is set from `titleMessageId`.
107
107
  - The 2nd `_loadMessages` return is merged into `$messageKeyById`; dangling message ids are logged.
108
108
 
109
+ ### Boolean labels — `messages.trueLabel` / `messages.falseLabel`
110
+ Each resolved element now also emits **`messages.trueLabel`** and **`messages.falseLabel`**, sourced
111
+ from the element's **`trueMessageId`** / **`falseMessageId`** (the unified boolean-label pair on
112
+ `SurfaceElements` — see the [surface-layer-schema](../../dbchanges2/features/surface-layer-schema.md)).
113
+ `_buildBundle`/`_serializeElement` collect those two ids into the same message batch and id→key remap
114
+ as the other slots. **Additive + backward-compatible:** an element that sets neither id emits `null`
115
+ for both, so existing surfaces are unchanged. This makes `Core.Messages` the single source of truth for
116
+ Yes/No + Active/Inactive labels (no Vocabulary needed) so **BOOLEAN-`valueType` toggles render their
117
+ caption and boolean status badges get a label**. Historically the serializer only emitted
118
+ `label`/`tooltip`/`placeholder`/`disabledTooltip` and **silently dropped** `trueMessageId`/
119
+ `falseMessageId` — SalesOrders never hit this because its status badge uses a Vocabulary and it has no
120
+ boolean rows; the item-record modal (the first boolean consumer) exposed the gap. Rides in
121
+ `_underscore`, so it takes effect only once the framework branch is deployed (framework is pulled at
122
+ deploy).
123
+
109
124
  ## Vocabulary / label per-client relabeling (slugs are internal, labels are messages)
110
125
 
111
126
  Clients **never rename a Vocabulary**. `Vocabularies.slug` (`sales-order-status`) and
@@ -338,6 +353,14 @@ Core record grants + their logic-group expressions all evaluate `all`/`"1"`. The
338
353
  match Compass, a follow-up migration aligning both `meta` and `meta-group` to roles 1,3,4 is needed.
339
354
 
340
355
  ## Change history
356
+ - 2026-07-20 — Extended the resolver to emit `messages.trueLabel`/`messages.falseLabel` per element,
357
+ sourced from `trueMessageId`/`falseMessageId` (collected into the same message batch + id→key remap);
358
+ additive + backward-compatible (unset → null). Closes a latent gap: the serializer historically emitted
359
+ only label/tooltip/placeholder/disabledTooltip and dropped the boolean-label ids, so BOOLEAN-valueType
360
+ toggles rendered blank and boolean status badges had no label — SalesOrders sidestepped it (Vocabulary
361
+ badge, no boolean rows); the item-record modal (first boolean consumer) surfaced it. Keeps Core.Messages
362
+ the single source of truth for Yes/No + Active/Inactive. Takes effect only once the `_underscore`
363
+ framework branch is deployed. (apeterson)
341
364
  - 2026-07-17 — Materialized the per-client sales-order button diffs (re-adding the overrides wiped in
342
365
  the earlier rebuild): Compass/CompassCanada ADMIN = all four record-action buttons visible + both
343
366
  `VISIBILITY_RULE`/`ENABLED_RULE` widened to `[pendingApproval, pendingInitialApproval]`, MANAGER =
@@ -3,5 +3,5 @@
3
3
  | Doc | Summary | Files |
4
4
  |-----|---------|-------|
5
5
  | [Database Changes (dbchanges2) Repository Architecture](architecture.md) | `dbchanges2` is the **schema-migration / SQL change-set repository** for the entire 2.0 platform. | Core/, Client/, Client_<Tenant>/, Logs/, Logs_Client/, _modules/ |
6
- | [Surface Layer Schema (UI presentation/config tables)](features/surface-layer-schema.md) | The persistent schema for the platform-wide **Surface** UI presentation/configuration layer (see the `_underscore` [surface-resolver](../../_underscore/features | dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client_Compass/2026-06-25d - SalesOrderSurfaceClientSeed.sql, _underscore/Model/Client/ThemeToken.php, toga25-supply/src/themeConfig.json, dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql, dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql, dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql, dbchanges2/Core/2026-06-29a - ItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql, dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql, dbchanges2/Core/2026-06-29d - VendorItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29e - InventorySurfaceSeed.sql, dbchanges2/Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql, dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql, dbchanges2/Client_Compass/2026-06-30a - SalesOrderDisplaySectionManagerOverrides.sql, dbchanges2/Client_CompassCanada/2026-06-30a - SalesOrderSurfaceManagerOverrides.sql, dbchanges2/Client_Quad/2026-06-30a - SalesOrderSurfaceClientOverrides.sql, dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql, dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Core/2026-07-17h - Update - ClearApprovalsFilterButtonConfig.sql, dbchanges2/Client_Compass/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Compass/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_Quad/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Quad/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql |
6
+ | [Surface Layer Schema (UI presentation/config tables)](features/surface-layer-schema.md) | The persistent schema for the platform-wide **Surface** UI presentation/configuration layer (see the `_underscore` [surface-resolver](../../_underscore/features | dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client_Compass/2026-06-25d - SalesOrderSurfaceClientSeed.sql, _underscore/Model/Client/ThemeToken.php, toga25-supply/src/themeConfig.json, dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql, dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql, dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql, dbchanges2/Core/2026-06-29a - ItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql, dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql, dbchanges2/Core/2026-06-29d - VendorItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29e - InventorySurfaceSeed.sql, dbchanges2/Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql, dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql, dbchanges2/Client_Compass/2026-06-30a - SalesOrderDisplaySectionManagerOverrides.sql, dbchanges2/Client_CompassCanada/2026-06-30a - SalesOrderSurfaceManagerOverrides.sql, dbchanges2/Client_Quad/2026-06-30a - SalesOrderSurfaceClientOverrides.sql, dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql, dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Core/2026-07-17h - Update - ClearApprovalsFilterButtonConfig.sql, dbchanges2/Client_Compass/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Compass/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_Quad/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Quad/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Core/2026-07-20a - Update - HideAdminNotesSectionByDefault.sql, dbchanges2/Core/2026-07-20b - Update - NotesSectionFieldElements.sql, dbchanges2/Client_Compass/2026-07-20a - AdminNotesSectionVisibilityOverride.sql, dbchanges2/Client_Compass/2026-07-20b - NotesSectionFieldsOverride.sql, dbchanges2/Client_CompassCanada/2026-07-20a - AdminNotesSectionVisibilityOverride.sql, dbchanges2/Client_CompassCanada/2026-07-20b - NotesSectionFieldsOverride.sql, dbchanges2/Client_Quad/2026-07-20a - NotesSectionFieldsOverride.sql, dbchanges2/Core/2026-07-20c - Update - VendorItemsToggleSurfaceSeed.sql, dbchanges2/Client_Compass/2026-07-20c - ItemRecordEditButtonEnable.sql, dbchanges2/Client_Compass/2026-07-20d - ItemRecordVendorItemsEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20c - ItemRecordEditButtonEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20d - ItemRecordVendorItemsEnable.sql, dbchanges2/Core/2026-07-17 - README - RUN ORDER.md |
7
7
  | [2.0 New-Client Onboarding (manual process)](workflows/client-onboarding.md) | How to manually stand up a new 2.0 client (tenant). | Client/, Client_<Tenant>/, Core/, Logs_Client/ |
@@ -6,7 +6,7 @@ project: Database Changes
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-07-17
9
+ updated: 2026-07-20
10
10
  owners: [jcardinal, apeterson]
11
11
  files:
12
12
  - dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql
@@ -38,6 +38,19 @@ files:
38
38
  - dbchanges2/Client_CompassCanada/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql
39
39
  - dbchanges2/Client_Quad/2026-07-17a - SalesOrderApprovalActionsOverride.sql
40
40
  - dbchanges2/Client_Quad/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql
41
+ - dbchanges2/Core/2026-07-20a - Update - HideAdminNotesSectionByDefault.sql
42
+ - dbchanges2/Core/2026-07-20b - Update - NotesSectionFieldElements.sql
43
+ - dbchanges2/Client_Compass/2026-07-20a - AdminNotesSectionVisibilityOverride.sql
44
+ - dbchanges2/Client_Compass/2026-07-20b - NotesSectionFieldsOverride.sql
45
+ - dbchanges2/Client_CompassCanada/2026-07-20a - AdminNotesSectionVisibilityOverride.sql
46
+ - dbchanges2/Client_CompassCanada/2026-07-20b - NotesSectionFieldsOverride.sql
47
+ - dbchanges2/Client_Quad/2026-07-20a - NotesSectionFieldsOverride.sql
48
+ - dbchanges2/Core/2026-07-20c - Update - VendorItemsToggleSurfaceSeed.sql
49
+ - dbchanges2/Client_Compass/2026-07-20c - ItemRecordEditButtonEnable.sql
50
+ - dbchanges2/Client_Compass/2026-07-20d - ItemRecordVendorItemsEnable.sql
51
+ - dbchanges2/Client_CompassCanada/2026-07-20c - ItemRecordEditButtonEnable.sql
52
+ - dbchanges2/Client_CompassCanada/2026-07-20d - ItemRecordVendorItemsEnable.sql
53
+ - dbchanges2/Core/2026-07-17 - README - RUN ORDER.md
41
54
  related:
42
55
  - ../../_underscore/features/surface-resolver.md
43
56
  ---
@@ -167,6 +180,68 @@ to `{}` so Core carries **no** `_status` default. Every client — even Quad, wh
167
180
  old default — now declares the full filter config via its own `CONFIG` override. This makes the Core
168
181
  seed a true neutral default for the filter button, matching the action-button rule above.
169
182
 
183
+ ## Display-section visibility — two patterns (marker vs field-driven)
184
+
185
+ A record-modal **display SECTION**'s own `Surfaces.isVisible` can never be client-overridden (the
186
+ cascade keys on `surfaceElementId`, never a Surface's own `isVisible` — see gotcha). Section
187
+ visibility is therefore always carried by an **element**, in one of two patterns depending on whether
188
+ the section has per-client field content:
189
+
190
+ - **Marker pattern (whole-section on/off)** — a section with fixed content (invoices / shipments /
191
+ additional-order-details / admin-notes) carries a single hidden TEXT **`sectionVisibility` marker
192
+ element** (`config.role='sectionVisibility'`); that element's `isVisible` *is* the section's
193
+ visibility. A client flips the section by overriding the marker's `IS_VISIBLE`.
194
+ - **Field-driven pattern (per-client field set)** — a section whose fields vary per client (notes)
195
+ models **each field as a real `FIELD` `SurfaceElement`** (the source of truth for that field's label
196
+ + visibility). No marker element: the FE **DERIVES** the section's visibility as "visible iff ≥1
197
+ field element is visible" (see
198
+ [surface-frontend](../../toga25-supply/features/surface-frontend.md)). This is the third section
199
+ pattern, alongside the card sections (`cardType` → grid renderer) and the marker toggles.
200
+
201
+ ### Core neutral default + client opt-in (admin-notes & notes, 2026-07-20)
202
+ Both sections follow the Core-neutral-default → client-opt-in rule (the same rule as the action/filter
203
+ buttons above): Core seeds the section **off**, each client opts in via a `SurfaceOverride`.
204
+
205
+ - **admin-notes (marker pattern).** `Core/2026-07-20a` flips the admin-notes section's
206
+ `sectionVisibility` marker `isVisible=0`, so the section is **hidden by default**. Resolved
207
+ **id-agnostically** by `Surfaces.slug='admin-notes'` + `config.role='sectionVisibility'` (never a
208
+ hardcoded element id). **Compass + Compass Canada** opt the section back on with a client-wide
209
+ `IS_VISIBLE='1'` override on that same element (`Client_Compass/2026-07-20a`,
210
+ `Client_CompassCanada/2026-07-20a`); every other client stays hidden.
211
+ - **notes (field-driven pattern).** `Core/2026-07-20b` migrates the two previously-hardcoded FE fields
212
+ (`reasonForRequest`, `shippingInstructions`) into two `FIELD` `SurfaceElements` under the `notes`
213
+ surface, both `isVisible=0` in Core (**default = neither field → the section is hidden**). Per-client
214
+ `IS_VISIBLE` overrides: **Quad → shippingInstructions only** (`Client_Quad/2026-07-20a`); **Compass +
215
+ Compass Canada → both** (`Client_Compass/2026-07-20b`, `Client_CompassCanada/2026-07-20b`).
216
+ - **A note field's value binding is a nested-array lookup, not a flat column.** Note text lives in
217
+ `order.salesOrderNotes[]` (each entry has `salesOrderNoteType.slug` + `note`), not a flat order
218
+ field. Each notes `FIELD` element names its note type in **`config.noteTypeSlug`** (`request` /
219
+ `shipping`); the FE resolves `salesOrderNotes[].note` by that slug (client-agnostic). Per-client slug
220
+ variance is handled by a **`CONFIG` override** on the element (REPLACE semantics) — no FE change.
221
+
222
+ ### Item-record modal gating (Edit button + vendor-items table, 2026-07-20)
223
+ The item-record modal's per-client gating uses the same Core-neutral-default → client-opt-in rule as
224
+ the SalesOrder buttons/sections above, applied via two mechanisms:
225
+
226
+ - **Header Edit button (existing element).** The `item-record-header` Edit button is seeded OFF in Core
227
+ (the neutral-default action-button rule — see the TRUE NEUTRAL DEFAULT section). Compass + Compass
228
+ Canada opt in with a client-wide `IS_VISIBLE='1'` override (`Client_*/2026-07-20c -
229
+ ItemRecordEditButtonEnable.sql`). The override resolves the element **id-agnostically** by
230
+ `renderType='BUTTON'` + `config.kind='itemEdit'` (never a hardcoded element id/uuid — those regenerate
231
+ per environment), `NOT EXISTS`-guarded, client-wide (`roleId`/`personaId` NULL).
232
+ - **Vendor-items table (new dedicated toggle surface).** `Core/2026-07-20c -
233
+ VendorItemsToggleSurfaceSeed.sql` seeds a dedicated **`item-record-vendor-items`** SECTION surface
234
+ carrying a single hidden marker element (`config.role='sectionVisibility'`, the **marker pattern**
235
+ above), OFF in Core. The FE reads that marker's resolved `isVisible` as `vendorItemsEnabled` and
236
+ conditionally renders the vendor table (see
237
+ [surface-frontend](../../toga25-supply/features/surface-frontend.md)). Compass + Compass Canada opt in
238
+ via `Client_*/2026-07-20d - ItemRecordVendorItemsEnable.sql` (client-wide `IS_VISIBLE='1'` on the
239
+ marker, resolved by `Surfaces.slug='item-record-vendor-items'` + `config.role='sectionVisibility'`).
240
+
241
+ Both clients' `c`/`d` overrides and the Core seed were added to `Core/2026-07-17 - README - RUN
242
+ ORDER.md`. Verified working in the UI for both clients. (Boolean toggle/badge labels in this modal
243
+ resolve through the resolver's new `trueLabel`/`falseLabel` emission — see the resolver doc.)
244
+
170
245
  ## ThemeTokens — per-tenant, physically isolated in each client DB
171
246
 
172
247
  `ThemeTokens` lives **inside each tenant's own database** (`Client_Compass.ThemeTokens`,
@@ -320,6 +395,26 @@ rule resumes.
320
395
  override is added). **Open follow-up.**
321
396
 
322
397
  ## Change history
398
+ - 2026-07-20 — Applied the Core-neutral-default → client-opt-in pattern to the **item-record modal**.
399
+ Header Edit button seeded OFF in Core; Compass + Compass Canada opt in via `Client_*/2026-07-20c`,
400
+ resolving the element id-agnostically by `renderType='BUTTON'`+`config.kind='itemEdit'` (NOT EXISTS,
401
+ client-wide). New dedicated `item-record-vendor-items` toggle surface (`Core/2026-07-20c`) carrying a
402
+ `config.role='sectionVisibility'` marker (marker pattern, OFF in Core) that the FE reads as
403
+ `vendorItemsEnabled`; Compass + Compass Canada opt in via `Client_*/2026-07-20d` (resolved by
404
+ `Surfaces.slug`+`config.role`). All added to the `Core/2026-07-17 - README - RUN ORDER.md`; verified
405
+ in the UI for both clients. (apeterson)
406
+ - 2026-07-20 — Applied the Core-neutral-default → client-opt-in pattern to two SalesOrder display
407
+ sections. **admin-notes** (marker pattern): `Core/2026-07-20a` hides it by default (marker
408
+ `isVisible=0`, resolved id-agnostically by `Surfaces.slug`+`config.role='sectionVisibility'`);
409
+ Compass + Compass Canada opt back in via a client-wide `IS_VISIBLE` override
410
+ (`Client_Compass`/`Client_CompassCanada/2026-07-20a`). **notes** (new field-driven pattern):
411
+ `Core/2026-07-20b` migrates the two hardcoded FE note fields into `FIELD` SurfaceElements (both
412
+ `isVisible=0` = neither by default), with per-client `IS_VISIBLE` overrides — Quad = shipping only
413
+ (`Client_Quad/2026-07-20a`), Compass + Compass Canada = both (`Client_*/2026-07-20b`). Documented the
414
+ two display-section visibility patterns (marker for whole-section on/off vs field-driven where the FE
415
+ derives section visibility from "≥1 field visible") and note-field value binding via
416
+ `config.noteTypeSlug` into `order.salesOrderNotes[]` (per-client slug variance via a `CONFIG`
417
+ override). (apeterson)
323
418
  - 2026-07-17 — Applied the Core-neutral-default → client-opt-in pattern to the sales-order action &
324
419
  filter buttons across Compass, CompassCanada, and Quad (`Client_*/2026-07-17a`+`b`). Cleared the
325
420
  Core approvals-filter `_status` default (`Core/2026-07-17h`) so every client declares its own filter
@@ -9,6 +9,6 @@
9
9
  | [Column Visibility (URL-driven show/hide columns)](features/column-visibility.md) | A "Columns" header button that opens a modal listing every column from the table meta, lets the user show/hide columns, adjusts the table live, and persists the | toga25-supply/src/components/ColumnVisibilityModal/, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableData.tsx |
10
10
  | [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 |
11
11
  | [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 |
12
- | [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/helpers/buildPatchedTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/SalesOrderView.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderSummaryGrid.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getDetailSections.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/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/SurfaceRowActions.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
+ | [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/helpers/buildPatchedTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/SalesOrderView.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderSummaryGrid.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getDetailSections.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderNotesSection.tsx, toga25-supply/src/pages/SalesOrders/helpers/cleanOrder.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/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/SurfaceRowActions.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, toga25-supply/src/layout/ItemRecordModalLayout/helpers/surfaceBundleToItemFields.ts, toga25-supply/src/layout/ItemRecordModalLayout/helpers/index.ts, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/useItemRecordModalViewModel.tsx, toga25-supply/src/layout/ItemRecordModalLayout/ItemRecordModalLayout.tsx, toga25-supply/src/layout/ItemRecordModalLayout/components/ItemRecordView.tsx |
13
13
  | [AWS Amplify Multi-Environment Deployment](workflows/amplify-deployment.md) | How `toga25-supply` deploys to **all** of its environments on AWS Amplify from a **single shared `amplify.yml`**. | toga25-supply/amplify.yml, toga25-supply/src/api/api.ts, toga25-supply/src/hooks/useAuthenticationFlow.ts, toga25-supply/vite.config.ts, toga25-supply/package.json |
14
14
  | [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 |
@@ -6,7 +6,7 @@ project: TOGa 2.5 Supply
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-07-13
9
+ updated: 2026-07-20
10
10
  owners: [jcardinal, apeterson]
11
11
  files:
12
12
  - toga25-supply/src/surface/useFetchSurfaceMeta.ts
@@ -16,6 +16,8 @@ files:
16
16
  - toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/SalesOrderView.tsx
17
17
  - toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderSummaryGrid.tsx
18
18
  - toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getDetailSections.tsx
19
+ - toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderNotesSection.tsx
20
+ - toga25-supply/src/pages/SalesOrders/helpers/cleanOrder.ts
19
21
  - toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/index.ts
20
22
  - toga25-supply/src/surface/evaluateSurfaceRule.ts
21
23
  - toga25-supply/src/surface/actionRegistry.ts
@@ -37,6 +39,11 @@ files:
37
39
  - toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx
38
40
  - toga25-supply/src/pages/Inventory/viewModel/FIELDS/index.ts
39
41
  - toga25-supply/src/fieldsConfig/index.ts
42
+ - toga25-supply/src/layout/ItemRecordModalLayout/helpers/surfaceBundleToItemFields.ts
43
+ - toga25-supply/src/layout/ItemRecordModalLayout/helpers/index.ts
44
+ - toga25-supply/src/layout/ItemRecordModalLayout/viewModel/useItemRecordModalViewModel.tsx
45
+ - toga25-supply/src/layout/ItemRecordModalLayout/ItemRecordModalLayout.tsx
46
+ - toga25-supply/src/layout/ItemRecordModalLayout/components/ItemRecordView.tsx
40
47
  related:
41
48
  - ../../_underscore/features/surface-resolver.md
42
49
  - meta-driven-table-data.md
@@ -107,8 +114,8 @@ legacy pre-Surface fallback branches at the wired screens removed). Legacy per-s
107
114
 
108
115
  ## What's wired
109
116
 
110
- Login + sales-orders list + the sales-order modal + the **Items**, **VendorItems**, and
111
- **Inventory** LIST screens. The SalesOrder action
117
+ Login + sales-orders list + the sales-order modal + the **item-record modal (VIEW, all clients)** +
118
+ the **Items**, **VendorItems**, and **Inventory** LIST screens. The SalesOrder action
112
119
  bar reproduces `orderViewFields.json` approve/deny/approvalWorkflow/viewLog/editOrder by delegating
113
120
  to the existing `onActionClick`/`onOpenLog` handlers. TABLE-type surfaces stay on the existing
114
121
  TableView pipeline via `tableViewSlug` (see [meta-driven-table-data](meta-driven-table-data.md)) —
@@ -173,6 +180,31 @@ three bespoke renderers `detailSection`/`locationCard`/`totalsCard` are untouche
173
180
  `sectionVisibility` marker element). **Decision (confirmed by the developer this session): KEEP the
174
181
  adapter approach — do NOT replace it with a direct `<SurfaceSection>` swap.** The bespoke grid
175
182
  renderers and the `TenantFields` seam are the deliberate integration point.
183
+
184
+ ### `displayToggle` sections — marker vs field-driven (the notes section)
185
+ `buildSection`'s `displayToggle` branch handles **two** shapes (mirroring the schema's two
186
+ display-section patterns — see
187
+ [surface-layer-schema](../../dbchanges2/features/surface-layer-schema.md)):
188
+ - **Field-carrying section (notes)** — the branch maps every `FIELD`-`renderType` element to a field
189
+ config via `elementToFieldConfig` (now carrying **`config.noteTypeSlug`**) and **DERIVES** the
190
+ section's `isVisible` as `fields.some(f => f.isVisible)` — no `sectionVisibility` marker needed. It
191
+ emits both `isVisible` and the `fields[]` array.
192
+ - **Pure-toggle section (invoices / shipments / additional-order-details / admin-notes)** — no `FIELD`
193
+ elements, so it falls back to the single `sectionVisibility` marker element's `isVisible`.
194
+
195
+ **`SalesOrderNotesSection`** consumes the field-driven shape: it renders **only the visible note
196
+ slots** and resolves each value from `order.salesOrderNotes[]` by
197
+ `salesOrderNoteType.slug === field.noteTypeSlug` (note text is a nested-array entry, not a flat order
198
+ column). When no surface `fields` are supplied it falls back to the **legacy flat props**
199
+ (`reasonForRequest` / `shippingInstructions`, flattened by `cleanOrder`) so non-migrated JSON clients
200
+ still render — see the `cleanOrder` slug gotcha below.
201
+
202
+ **Reusable pattern (field-driven display section):** for a section whose content varies per client,
203
+ model each field as a `FIELD` element (label + visibility source of truth), derive section visibility
204
+ from "≥1 field visible", and bind an array-derived value via element `config` (here `noteTypeSlug`)
205
+ resolved on the FE. This is the presentation counterpart of the schema's field-driven pattern and the
206
+ third section pattern alongside card sections (`cardType` → grid renderer) and marker toggles.
207
+
176
208
  - **Roster gate — `SALES_ORDER_SURFACE_MIGRATED_CLIENTS = [COMPASS, COMPASSCANADA, QUAD]`** (an
177
209
  in-code roster in `surfaceBundleToTenantFields.ts`; the view-model
178
210
  `useSalesOrderRecordModalLayoutModel.tsx` consults it). The view-model resolves sections from
@@ -192,6 +224,39 @@ three bespoke renderers `detailSection`/`locationCard`/`totalsCard` are untouche
192
224
  seeding NYCHH/Prudential/SPGlobal is deferred; the Client-DB prod cross-cluster seed fix is on hold
193
225
  (see [surface-layer-schema](../../dbchanges2/features/surface-layer-schema.md)).
194
226
 
227
+ ## Item-record modal (VIEW) — migrated via an adapter seam, ALL clients (no roster)
228
+
229
+ The item-record modal's **VIEW** sections are now resolved from Surface for **every client** — unlike
230
+ the SalesOrders modal, there is **no migration roster**. It mirrors the SalesOrders adapter-seam
231
+ approach (config source moves JSON → Surface; the existing view is untouched):
232
+
233
+ - **`surfaceBundleToItemViewFields()`** (`ItemRecordModalLayout/helpers/surfaceBundleToItemFields.ts`,
234
+ new; barrel `helpers/index.ts`) adapts the grouped Surface bundle into the
235
+ `{ header, editItem, sections, vendorItemsEnabled }` shape the view already consumed.
236
+ - **`useItemRecordModalViewModel`** fetches the `item-record-*` surface group via
237
+ `useFetchSurfaceMetaGroup`, adapts it, and threads it into **`ItemRecordView`** as a **`viewFields`**
238
+ prop. The per-client JSON (`itemRecordViewFields`) is now a **fallback only** — used when the group
239
+ fails to resolve. **Edit mode stays JSON-driven (deferred).**
240
+ - **Boolean labels** flow through `messages.trueLabel`/`messages.falseLabel` (added to
241
+ `SurfaceElementMessages` in `src/surface/types.ts`), which the resolver now emits from each element's
242
+ `trueMessageId`/`falseMessageId` (see
243
+ [surface-resolver](../../_underscore/features/surface-resolver.md)) — so BOOLEAN toggles/badges render
244
+ their caption instead of blank.
245
+
246
+ **Per-client gating rides the Core-default-OFF → SurfaceOverride opt-in pattern** (the same
247
+ "approvals-gate" shape used on SalesOrders — `elements.some(el => el.isVisible)`); the seed side lives
248
+ in [surface-layer-schema](../../dbchanges2/features/surface-layer-schema.md):
249
+
250
+ - **Header Edit button** — an element in `item-record-header`, matched **id-agnostically** by
251
+ `renderType='BUTTON'` + `config.kind='itemEdit'`. Core seeds it OFF; Compass / Compass Canada opt in.
252
+ - **Vendor-items table** — a dedicated `item-record-vendor-items` **toggle surface** carrying a
253
+ `config.role='sectionVisibility'` marker element; the FE reads it as **`vendorItemsEnabled`** and
254
+ conditionally renders the vendor table in `ItemRecordView`. Core-OFF; Compass / Compass Canada opt in.
255
+
256
+ > **Seed-intent to verify (flagged to the dev):** the Core item-record Pricing section seed now also
257
+ > shows **`leasePrice`** for all clients, whereas the old DEFAULT JSON showed only unit price. Confirm
258
+ > that is the intended seed shape.
259
+
195
260
  ## Gotchas
196
261
 
197
262
  - **The single-meta envelope can hide the real bundle behind a DECOY `elements: []`.** The V2 envelope
@@ -222,6 +287,14 @@ seeding NYCHH/Prudential/SPGlobal is deferred; the Client-DB prod cross-cluster
222
287
  intentionally gated in `getDetailSections`: `shipments` is **permanently hidden** (the view-model
223
288
  hardcodes `shipmentsData: []` and `useShipments` is commented out; the section gates on
224
289
  `shipmentsData.length > 0`), and `invoices` is data-gated on `invoiceData.length > 0`.
290
+ - **`cleanOrder.ts` flattens notes by the WRONG note-type slugs (latent blank-notes bug).**
291
+ `cleanOrder` builds the legacy flat `order.reasonForRequest` / `order.shippingInstructions` via
292
+ `getNoteBySlug(notes, "reasonForRequest")` / `("shippingInstructions")`, but the API's actual
293
+ `salesOrderNoteType.slug` values are **`request`** / **`shipping`**. So for any client whose notes
294
+ use the real slugs (e.g. Compass) those flat fields resolve to `""` and the legacy notes render
295
+ blank. The new field-driven notes path resolves by the element's `config.noteTypeSlug` (the correct
296
+ slug) so it is unaffected; the flat path only survives as the non-migrated-client fallback in
297
+ `SalesOrderNotesSection`. Fixing `cleanOrder`'s slugs is the durable follow-up for JSON clients.
225
298
  - **The surface `record` shape must match the rule field namespace — wrap as `{ order }`.** Seeded
226
299
  Tier-1 rules namespace their field as `order._status`, so `getByPath(record, "order._status")` only
227
300
  resolves if the host passes `record={{ order }}`. Passing the bare order object resolves to
@@ -258,6 +331,30 @@ seeding NYCHH/Prudential/SPGlobal is deferred; the Client-DB prod cross-cluster
258
331
  treat type-checking as pending. Runtime `GET /v2/surfaces/{slug}/meta` also not yet exercised.
259
332
 
260
333
  ## Change history
334
+ - 2026-07-20 — Migrated the **item-record modal VIEW** onto Surface for **ALL clients (no roster)** via
335
+ the adapter seam: new `surfaceBundleToItemViewFields()` adapts the `item-record-*` surface group
336
+ (`useFetchSurfaceMetaGroup`) into the existing `{header, editItem, sections, vendorItemsEnabled}`
337
+ shape, threaded into `ItemRecordView` as a `viewFields` prop with the per-client JSON
338
+ (`itemRecordViewFields`) as resolve-failure fallback only; edit mode stays JSON-driven (deferred).
339
+ Added `trueLabel`/`falseLabel` to `SurfaceElementMessages` (`src/surface/types.ts`) so BOOLEAN
340
+ toggles/badges render their caption (backend resolver now emits them). Per-client gating uses the
341
+ Core-default-OFF → SurfaceOverride opt-in pattern: header Edit button (`item-record-header`, matched
342
+ by `renderType='BUTTON'`+`config.kind='itemEdit'`) and a dedicated `item-record-vendor-items` toggle
343
+ surface (marker `config.role='sectionVisibility'` → FE `vendorItemsEnabled`), both opted into by
344
+ Compass / Compass Canada. Flagged: Core Pricing seed now also shows `leasePrice` for all clients
345
+ (verify seed intent). (apeterson)
346
+ - 2026-07-20 — Made the SalesOrders `notes` display section **field-driven** per client.
347
+ `buildSection`'s `displayToggle` branch now handles field-carrying sections: it maps each `FIELD`
348
+ element to a field config (`elementToFieldConfig` now carries `config.noteTypeSlug`) and **derives**
349
+ section visibility as "≥1 field visible" — no `sectionVisibility` marker needed (pure-toggle sections
350
+ invoices/shipments/additional-order-details/admin-notes still fall back to their marker element).
351
+ `SalesOrderNotesSection` renders only the visible note slots and resolves each value from
352
+ `order.salesOrderNotes[].note` by `salesOrderNoteType.slug === config.noteTypeSlug`, with a legacy
353
+ flat-prop fallback for non-migrated JSON clients. Recorded the reusable field-driven-display-section
354
+ pattern (third section pattern, alongside card sections and marker toggles). Discovered a latent
355
+ blank-notes bug in `cleanOrder.ts` (flattens by the wrong slugs
356
+ `reasonForRequest`/`shippingInstructions` vs the API's actual `request`/`shipping`) — the surface
357
+ resolver reads by the correct slug and bypasses it. (apeterson)
261
358
  - 2026-07-13 — Fixed two SalesOrders row-actions bugs: (1) the portal dismiss overlay in
262
359
  `SurfaceRowActions.tsx` propagated an outside-click up the React component tree to the row handler
263
360
  and opened the record modal — added `e.stopPropagation()` (+ close) on the overlay `onClick`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.377",
3
+ "version": "1.0.379",
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",