toga-ai 1.0.579 → 1.0.580
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-
|
|
9
|
+
updated: 2026-08-14
|
|
10
10
|
owners: [jcardinal, apeterson]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Core/Surface.php
|
|
@@ -322,6 +322,18 @@ Core record grants + their logic-group expressions all evaluate `all`/`"1"`. The
|
|
|
322
322
|
|
|
323
323
|
## Gotchas
|
|
324
324
|
|
|
325
|
+
- **An empty bundle (`{surface:null, elements:[], vocabularies:{}, messages:{}, theme:{}}`) means the
|
|
326
|
+
Core.Surfaces row for that slug isn't seeded — NOT an ACL denial and NOT a client/scoping mismatch.**
|
|
327
|
+
`_buildBundle()` (`Surface.php` ~L516-532) sets `surface=null` and short-circuits when `$surface->load()`
|
|
328
|
+
fails (`metaGroup` ~L191 calls `resolve` ~L50 per slug). The lookup is **SLUG-ONLY** — it does NOT
|
|
329
|
+
filter by `appId`, `isVisible`, client, role, persona, or language, and `Surfaces` is a Core/shared
|
|
330
|
+
(not client-partitioned) table. Consequences when diagnosing an empty/blank surface: (a) a null surface
|
|
331
|
+
is **not** an ACL denial (that returns **403 EZ-1**, not a 200 with a null bundle) and **not** a
|
|
332
|
+
client/scoping mismatch (those only filter *overrides* on an already-found surface); it **always** means
|
|
333
|
+
the `Core.Surfaces` row for that slug isn't seeded **in that environment**; (b) `isVisible=0` does NOT
|
|
334
|
+
hide a surface at this layer. Verify with `SELECT slug FROM Core.Surfaces WHERE slug LIKE '...'`. (For
|
|
335
|
+
the FE-side crash when a summary surface IS seeded but its action siblings are NOT, see the partial-seed
|
|
336
|
+
gotcha in [surface-frontend](../../toga25-supply/features/surface-frontend.md).)
|
|
325
337
|
- **Per-client surface message overrides silently never apply when `languageId` resolves to `0`.**
|
|
326
338
|
`_Model_Core_Surface::_loadMessages` overlays `Core.Messages.defaultValue` with
|
|
327
339
|
`Client_<X>.MessageTranslations` rows **only when `languageId > 0`**. The JWT carries the language as
|
|
@@ -420,6 +432,12 @@ Core record grants + their logic-group expressions all evaluate `all`/`"1"`. The
|
|
|
420
432
|
match Compass, a follow-up migration aligning both `meta` and `meta-group` to roles 1,3,4 is needed.
|
|
421
433
|
|
|
422
434
|
## Change history
|
|
435
|
+
- 2026-08-14 — **Discovered/documented** why a surface meta request returns an empty bundle
|
|
436
|
+
(`surface:null`, empty elements/vocabularies/messages/theme): `_buildBundle()` short-circuits when the
|
|
437
|
+
slug-only `Surfaces` load fails, and the lookup filters by slug ONLY (no appId/isVisible/client/role/
|
|
438
|
+
persona/language; `Surfaces` is Core/shared). So a null bundle always = the `Core.Surfaces` row for
|
|
439
|
+
that slug isn't seeded in that environment — it is NOT a 403 ACL denial and NOT a client/scoping
|
|
440
|
+
mismatch, and `isVisible=0` does not hide a surface at this layer. Added as a gotcha. (apeterson)
|
|
423
441
|
- 2026-07-21 — Recorded **Option B**: the Approve step-two/PO gate is now evaluated **live on the FE**
|
|
424
442
|
(FE `evaluateSurfaceRule` gained a `SURFACE_NAMED_RULES` escape hatch) rather than via the no-op
|
|
425
443
|
backend Tier-2 — an intentional deviation from the frozen-grammar/escalate note (updated the
|
|
@@ -10,6 +10,6 @@
|
|
|
10
10
|
| [Force Logout on Deployment (useDeploymentGuard)](features/force-logout-on-deployment.md) | On large deployments the backend bumps the Core parameter `META_LAST_REFRESH_DATETIME`. | toga25-supply/src/hooks/useDeploymentGuard.tsx, toga25-supply/src/App.tsx |
|
|
11
11
|
| [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 |
|
|
12
12
|
| [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/GenericNestedTables.tsx, toga25-supply/src/layout/GenericNestedTables/GenericTableLayout.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/DEFAULT/inventoryGroupings.json, toga25-supply/src/hooks/useTableCellInteractions.ts, toga25-supply/src/hooks/useServerTableUrlState.ts |
|
|
13
|
-
| [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/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/sectionRenderers.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getVisibleSections.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderApprovalSummaryGrid.tsx, 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/resolveElementState.ts, toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderRowRecordState.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, toga25-supply/src/surface/useStatusColors.ts, toga25-supply/src/surface/SurfaceHeader.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/helpers/surfaceBundlesToDecisionFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/useApprovalModalViewModel.tsx |
|
|
13
|
+
| [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/applyColSpan.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/sectionRenderers.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getVisibleSections.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderApprovalSummaryGrid.tsx, 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/resolveElementState.ts, toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderRowRecordState.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, toga25-supply/src/surface/useStatusColors.ts, toga25-supply/src/surface/SurfaceHeader.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/helpers/surfaceBundlesToDecisionFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/useApprovalModalViewModel.tsx |
|
|
14
14
|
| [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 |
|
|
15
15
|
| [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,9 +6,10 @@ project: TOGa 2.5 Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-14
|
|
10
10
|
owners: [jcardinal, apeterson]
|
|
11
11
|
files:
|
|
12
|
+
- toga25-supply/src/surface/applyColSpan.ts
|
|
12
13
|
- toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/sectionRenderers.tsx
|
|
13
14
|
- toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getVisibleSections.ts
|
|
14
15
|
- toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderApprovalSummaryGrid.tsx
|
|
@@ -458,6 +459,29 @@ machinery for "this box is two columns wide", which is exactly the signal that i
|
|
|
458
459
|
FE. Use this as the config/logic line for surface geometry questions: **presentation geometry →
|
|
459
460
|
FE/CSS; content/visibility/order/ACL → Surface.**
|
|
460
461
|
|
|
462
|
+
#### Where per-field `colSpan` lives in the FE (CTO-reviewed, AGREE 2026-08-14)
|
|
463
|
+
Once colSpan is actually assigned FE-side, the durable rule is: **span VALUES stay per-consumer/adapter,
|
|
464
|
+
keyed by `valueKey` only — do NOT centralize colSpan as a universal per-element value, and do NOT store
|
|
465
|
+
it as SurfaceElement DB metadata (both rejected).** The reasoning is that colSpan is inherently
|
|
466
|
+
**(field × grid)-scoped**: a span number only means something relative to a *specific* grid's column
|
|
467
|
+
count, and the SAME surface element (e.g. `valueKey "reasonForRequest"`) is rendered by multiple
|
|
468
|
+
consumers into different grids — the SalesOrders modal `.approvalFieldsGrid` is 2-col, while
|
|
469
|
+
`RecordApprovalModal`'s `.approvalDetailsGrid` / `.approvalWorkflowDetailsGrid` differ. Storing one
|
|
470
|
+
per-element span is therefore semantically impossible across differing column counts (and violates the
|
|
471
|
+
"layout is FE presentation, not surface metadata" boundary above).
|
|
472
|
+
- Each adapter renders into exactly one grid, so **the adapter IS the grid scope** — no surface-slug
|
|
473
|
+
layer is introduced (slug ≠ grid). Value tables stay **local to each adapter**
|
|
474
|
+
(`surfaceBundleToTenantFields.ts`, `surfaceBundlesToDecisionFields.ts`).
|
|
475
|
+
- Share ONLY the trivial assignment plumbing: **`applyColSpan(field, span)`** helper in `@/surface`
|
|
476
|
+
(`src/surface/applyColSpan.ts`, exported from `src/surface/index.ts`). `BaseDetailField` (in
|
|
477
|
+
`@agilant/toga-blox`) reads `field.colSpan` → CSS var `--field-col-span`; it is grid-agnostic.
|
|
478
|
+
- This deliberately prevents a future "consolidate the span maps into one" regression (which would be a
|
|
479
|
+
bug generator).
|
|
480
|
+
- **Caveat (in code):** the decision adapter feeds BOTH the single-action and workflow grids from the
|
|
481
|
+
same field array (`overlayDecisionSurfaceFields` points `orderContentDetails` and
|
|
482
|
+
`orderContentDetailsWorkflow` at one array), so those two grids currently **share one span value**;
|
|
483
|
+
splitting them requires two arrays.
|
|
484
|
+
|
|
461
485
|
## Gotchas
|
|
462
486
|
|
|
463
487
|
- **A named predicate that FAILS OPEN silently enables a button when its data isn't fetched.**
|
|
@@ -548,6 +572,17 @@ FE/CSS; content/visibility/order/ACL → Surface.**
|
|
|
548
572
|
`extractBundle()` by structural fingerprint (the `elements` array). Consumers (`SurfaceSection`,
|
|
549
573
|
`SurfaceActionBar`) are hardened to bail to `null` unless `elements` is a real array, so a malformed
|
|
550
574
|
payload never crashes the page. (Root cause of the "s.elements is not iterable" runtime crash.)
|
|
575
|
+
- **A PARTIAL decision-surface seed CRASHES the approval modal (a TOTAL miss falls back cleanly).**
|
|
576
|
+
The SalesOrders approve/deny decision modal gates on the **SUMMARY surface only**
|
|
577
|
+
(`isDecisionSurfaceEnabled` = the summary bundle has any visible element). If the summary surface
|
|
578
|
+
(dbchanges2 seed `Core/2026-07-21a`) is seeded but the approve/deny **ACTION** surfaces
|
|
579
|
+
(`Core/2026-07-21b`) are NOT, the gate flips `true` while the action bundles are empty →
|
|
580
|
+
`buildActionChrome` yields an undefined icon/label → runtime crash `"Cannot read properties of
|
|
581
|
+
undefined (reading 'charAt')"` (undefined Font Awesome icon), then `reading 'label'`. A **total**
|
|
582
|
+
seed miss falls back cleanly to JSON; a **partial** miss crashes. Fix = ensure the whole
|
|
583
|
+
`2026-07-21a`+`b` seed set ran in that environment. (Both seeds are additive + `NOT EXISTS`-guarded
|
|
584
|
+
and inherit `appId`/`recordId` from the existing `order-details` sibling surface — so if
|
|
585
|
+
`order-details` isn't seeded the surface INSERT silently inserts nothing.)
|
|
551
586
|
- **No feature flag, no fallback.** Surface is the enforced path; a missing/broken surface bundle
|
|
552
587
|
does not silently fall back to legacy JSON — that path is deleted per screen as it migrates.
|
|
553
588
|
- **Endpoint is query-string form** — `GET /v2/surfaces/meta?slug=...`, not `/surfaces/<slug>/meta`
|
|
@@ -571,6 +606,19 @@ FE/CSS; content/visibility/order/ACL → Surface.**
|
|
|
571
606
|
treat type-checking as pending. Runtime `GET /v2/surfaces/{slug}/meta` also not yet exercised.
|
|
572
607
|
|
|
573
608
|
## Change history
|
|
609
|
+
- 2026-08-14 — **DECIDED (CTO-reviewed, AGREE)** where per-field grid `colSpan` lives in the FE Surface
|
|
610
|
+
layer: span VALUES stay per-consumer/adapter keyed by `valueKey` (the adapter IS the grid scope, since
|
|
611
|
+
each adapter renders into exactly one grid), NOT centralized as a universal per-element value and NOT
|
|
612
|
+
stored as SurfaceElement DB metadata (both rejected — colSpan is (field × grid)-scoped, and the same
|
|
613
|
+
element reuses across grids of differing column counts). Shared only the trivial `applyColSpan(field,
|
|
614
|
+
span)` plumbing in `@/surface` (`src/surface/applyColSpan.ts`); `BaseDetailField` reads
|
|
615
|
+
`field.colSpan`→`--field-col-span`. Prevents a future "consolidate the span maps" regression. Caveat:
|
|
616
|
+
the decision adapter feeds both the single-action and workflow grids from one field array
|
|
617
|
+
(`overlayDecisionSurfaceFields`), so they currently share one span value until split into two arrays.
|
|
618
|
+
Also documented a FE gotcha: a **PARTIAL** decision-surface seed (summary `2026-07-21a` seeded but
|
|
619
|
+
action `2026-07-21b` not) flips `isDecisionSurfaceEnabled` true with empty action bundles →
|
|
620
|
+
`buildActionChrome` crash (`charAt`/`label` of undefined); a total miss falls back cleanly to JSON.
|
|
621
|
+
(apeterson)
|
|
574
622
|
- 2026-08-05 — **Built/Fixed** staged approval-chain value resolution on the record-modal
|
|
575
623
|
**approval-details** card. Ported the decision adapter's staged resolver (`resolvePathInsensitive`
|
|
576
624
|
+ `makePreferredStageResolver`) into `surfaceBundleToTenantFields.ts`: a surface `config.preferredStage`
|
package/package.json
CHANGED