toga-ai 1.0.229 → 1.0.231
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.
|
@@ -46,7 +46,11 @@ feature flag — see below). Backend: [surface-resolver](../../_underscore/featu
|
|
|
46
46
|
- **`useFetchSurfaceMeta`** — the single fetch-once/React-Query-cached choke point for a surface's
|
|
47
47
|
meta. Loading a different *record* into the same surface reuses cached meta. Builds the URL as
|
|
48
48
|
`/surfaces/meta?slug=...` (query string), **not** `/surfaces/<slug>/meta` (the path form makes the
|
|
49
|
-
engine parse the slug as a record uuid → 404 EV-6).
|
|
49
|
+
engine parse the slug as a record uuid → 404 EV-6). Unwraps the bundle via **`extractBundle()`**,
|
|
50
|
+
which locates the bundle by its **structural fingerprint** (the `elements` array) across
|
|
51
|
+
`raw` / `raw.data` / `raw.data.<route-key>` — because V2 nests a scripted-API return value one
|
|
52
|
+
level deeper, under a route-keyed slot of `data` (e.g. `data.meta`, `data.surfaces`). On a malformed
|
|
53
|
+
payload it falls back to an empty-elements bundle so the page never crashes.
|
|
50
54
|
- **`evaluateSurfaceRule`** — the Tier-1 rule evaluator: a **frozen `all/any/none` + `{field,op,value}`
|
|
51
55
|
grammar** that **throws on an unknown op** (generalized from the SalesOrders
|
|
52
56
|
`buildPatchedTenantFields`/`evaluateEnableRule`/`resolveFlag` helpers). Evaluated client-side against
|
|
@@ -66,13 +70,14 @@ legacy pre-Surface fallback branches at the wired screens removed). Legacy per-s
|
|
|
66
70
|
|
|
67
71
|
## What's wired
|
|
68
72
|
|
|
69
|
-
Login + sales-orders list + the sales-order modal + the **Items
|
|
73
|
+
Login + sales-orders list + the sales-order modal + the **Items**, **VendorItems**, and
|
|
74
|
+
**Inventory** LIST screens. The SalesOrder action
|
|
70
75
|
bar reproduces `orderViewFields.json` approve/deny/approvalWorkflow/viewLog/editOrder by delegating
|
|
71
76
|
to the existing `onActionClick`/`onOpenLog` handlers. TABLE-type surfaces stay on the existing
|
|
72
77
|
TableView pipeline via `tableViewSlug` (see [meta-driven-table-data](meta-driven-table-data.md)) —
|
|
73
78
|
the Surface layer references the TableView, never replaces it.
|
|
74
79
|
|
|
75
|
-
## Per-screen migration recipe (proven on SalesOrders + Items)
|
|
80
|
+
## Per-screen migration recipe (proven on SalesOrders + Items + VendorItems + Inventory)
|
|
76
81
|
|
|
77
82
|
The screen-by-screen full-app refactor follows one recipe:
|
|
78
83
|
1. Seed the screen's surface(s) in a `Core/*SurfaceSeed.sql` — a TABLE surface that references the
|
|
@@ -87,8 +92,51 @@ Keep TABLE rendering on the existing TableView pipeline; only the chrome (action
|
|
|
87
92
|
moves to Surface. A screen with no client/role/lang variance ships a single DEFAULT bundle (no
|
|
88
93
|
Client overrides), as Items did.
|
|
89
94
|
|
|
95
|
+
**VendorItems (screen 2)** followed the recipe exactly: `vendor-items-list` (TABLE → TableView 10,
|
|
96
|
+
recordId 19) + `vendor-items-list-actions` (Refresh / Columns[hidden+disabled] / New Vendor Item),
|
|
97
|
+
page+view-model rewired to resolve title+buttons from the bundle, dropped the
|
|
98
|
+
`useClientFields`/`vendorItemsPageFields` dependency, deleted `vendorItemsPageFields.json` + its
|
|
99
|
+
`fieldsConfig/index.ts` entry.
|
|
100
|
+
|
|
101
|
+
**Inventory (screen 3) — presentation-only, by decision.** Only the page title, header buttons
|
|
102
|
+
(Group by / Refresh), the Group-by modal copy, and the entity icon/color tag map moved to Surface.
|
|
103
|
+
The `inventoryGroupings` `groupings[].levels[]` topology
|
|
104
|
+
(`fetchSlug`/`role`/`additionalDataSlug`/`urlParamKey` driving `GenericNestedTables`) is **data-fetch
|
|
105
|
+
wiring, not presentation**, so per the config/logic boundary it **stays as JSON**. The
|
|
106
|
+
`inventory-list` surface carries `groupByLabels` + `entityTags` in its surface `config`, with
|
|
107
|
+
**recordId+tableViewId NULL** (the page has no single backing Record — it composes
|
|
108
|
+
purchase-orders/items/units via per-level fetchSlugs). Labels + `entityTags` were stripped from both
|
|
109
|
+
DEFAULT and NYCHH `inventoryGroupings.json` (groupings kept) and marked `@deprecated`/optional in the
|
|
110
|
+
`InventoryGroupingsConfig` type.
|
|
111
|
+
|
|
112
|
+
**The config/logic line for migration:** move presentation (titles, button labels, icons, colors,
|
|
113
|
+
modal copy, visibility/enable) to Surface; **leave data-fetch wiring (fetch slugs, roles, URL params,
|
|
114
|
+
nested-table topology) in JSON/code.** Not every screen reduces to a generic `SurfaceSection` swap —
|
|
115
|
+
see SalesOrders below.
|
|
116
|
+
|
|
117
|
+
## SalesOrders remaining migration — bespoke, NOT a generic SurfaceSection swap (deferred)
|
|
118
|
+
|
|
119
|
+
The SalesOrders detail migration is the plan's explicitly-deferred "blessed escape hatch"/approval
|
|
120
|
+
work — it is bespoke-component + business-logic work, not a generic swap. `SalesOrderSummaryGrid` is
|
|
121
|
+
fully tenant-data-driven through three **bespoke renderers**: `detailSection` (blox `DetailSection` +
|
|
122
|
+
custom `renderField`), `locationCard` (`OrderSectionCard`, structured contact data), and `totalsCard`
|
|
123
|
+
(blox `BaseTotals` **with client-specific recurring-lease business logic**:
|
|
124
|
+
`_totalLease !== 0 → recurringKey` fields). The generic `SurfaceSection` (label/value) cannot
|
|
125
|
+
represent `BaseTotals`/`OrderSectionCard`, and the early-seeded order-details/ship-to/bill-to/
|
|
126
|
+
order-summary SECTION surfaces do **not** cleanly map onto the grid's arbitrary client-keyed section
|
|
127
|
+
model. A faithful migration needs a **registered-renderer approach** keyed off surface element config
|
|
128
|
+
(`detailSection`/`locationCard`/`totalsCard` → their components), with the lease logic staying in
|
|
129
|
+
code/`_underscore`, plus reconciling those seeds and the Compass approval-modal state machine.
|
|
130
|
+
**Deferred to a dedicated design-first session.**
|
|
131
|
+
|
|
90
132
|
## Gotchas
|
|
91
133
|
|
|
134
|
+
- **Scripted-API GET return values land under `data.<scriptRoute>`, not `data` directly.** V2 nests a
|
|
135
|
+
scripted-API return value inside the envelope under a route-keyed slot of `data` (e.g. `data.meta`,
|
|
136
|
+
`data.surfaces`), one level deeper than a normal data fetch. The bundle hook unwraps this with
|
|
137
|
+
`extractBundle()` by structural fingerprint (the `elements` array). Consumers (`SurfaceSection`,
|
|
138
|
+
`SurfaceActionBar`) are hardened to bail to `null` unless `elements` is a real array, so a malformed
|
|
139
|
+
payload never crashes the page. (Root cause of the "s.elements is not iterable" runtime crash.)
|
|
92
140
|
- **No feature flag, no fallback.** Surface is the enforced path; a missing/broken surface bundle
|
|
93
141
|
does not silently fall back to legacy JSON — that path is deleted per screen as it migrates.
|
|
94
142
|
- **Endpoint is query-string form** — `GET /v2/surfaces/meta?slug=...`, not `/surfaces/<slug>/meta`
|
|
@@ -99,6 +147,13 @@ Client overrides), as Items did.
|
|
|
99
147
|
treat type-checking as pending. Runtime `GET /v2/surfaces/{slug}/meta` also not yet exercised.
|
|
100
148
|
|
|
101
149
|
## Change history
|
|
150
|
+
- 2026-06-29 — Migrated VendorItems (screen 2, recipe-exact) and Inventory (screen 3,
|
|
151
|
+
**presentation-only by decision** — fetch topology stays JSON; only title/buttons/modal copy/entity
|
|
152
|
+
tags moved). Fixed the "s.elements is not iterable" crash: `extractBundle()` now finds the bundle by
|
|
153
|
+
its `elements`-array fingerprint across `raw`/`raw.data`/`raw.data.<route-key>` (V2 nests
|
|
154
|
+
scripted-API returns under `data.<scriptRoute>`), with an empty-bundle fallback + hardened
|
|
155
|
+
SurfaceSection/SurfaceActionBar guards. Recorded SalesOrders detail as deferred bespoke
|
|
156
|
+
(registered-renderer + lease logic + approval-modal) work. (jcardinal)
|
|
102
157
|
- 2026-06-29 — DECISION: Surface is now enforced, not flagged — removed `VITE_SURFACE_ENABLED`/
|
|
103
158
|
`SURFACE_ENABLED` (deleted `featureFlag.ts` + all gating/legacy-fallback branches). Fixed
|
|
104
159
|
`useFetchSurfaceMeta` to the query-string endpoint `/surfaces/meta?slug=` (path form 404'd EV-6).
|
package/knowledge/INDEX.md
CHANGED
|
@@ -17,7 +17,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
17
17
|
## 2.0 framework
|
|
18
18
|
|
|
19
19
|
- **_underscore** (_Underscore) _(framework core)_ — 17 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
20
|
-
- **worker2** (Worker) —
|
|
20
|
+
- **worker2** (Worker) — 22 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
21
21
|
- **api2** (API) — 7 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
22
22
|
- **dbchanges2** (Database Changes) _(framework core)_ — 3 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
23
23
|
- **toga2-supply** (TOGa Supply) — 3 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: session
|
|
3
|
+
slug: surface-ui-config-layer
|
|
4
|
+
title: Surface UI config layer — deploy fixes + 3 screen migrations
|
|
5
|
+
author: jcardinal
|
|
6
|
+
repos: [dbchanges2, _underscore, api2, toga25-supply]
|
|
7
|
+
framework: "2.0"
|
|
8
|
+
client: shared
|
|
9
|
+
status: active
|
|
10
|
+
created: 2026-06-29
|
|
11
|
+
updated: 2026-06-29
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Session: surface-ui-config-layer
|
|
15
|
+
**Date:** 2026-06-29
|
|
16
|
+
**Project/Repo:** dbchanges2 / _underscore / api2 / toga25-supply (2.0)
|
|
17
|
+
**Task:** Continue the platform-wide "Surface" UI presentation/config layer: fix the deployed layer's 403 + FE crash on dev.sandbox, then refactor toga25-supply screens onto Surface (Items done prior; VendorItems + Inventory this session; SalesOrders remaining).
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What WORKED
|
|
22
|
+
- **403 (EZ-1) on `GET /v2/surfaces/meta` fixed.** Root cause found by reading V2.php `getRecordScriptPhpMethod` (~line 5803): RecordScript dispatch is authorized by the **Client-DB `AclRecordScripts`** table (`WHERE recordScriptId=? AND roleId IN(...)`), NOT `AclRecordPermissions`. Confirmed on dev.sandbox: surfaces meta script (id 25) had ZERO `AclRecordScripts` rows, while working TableView/Page `meta` scripts (ids 1,2) grant roles 1,3,4. Fix file `dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql` grants meta→{1,3,4}, debug→{3}; id-agnostic (route-join, works on 333 or 2300), NOT EXISTS-guarded. Developer applied it on dev.sandbox → 403 cleared.
|
|
23
|
+
- **Latent fatal fixed** — `dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql`: idempotent UPDATE renaming surfaces `debug` RecordScript phpMethod `debug`→`metaDebug` on pre-rename envs.
|
|
24
|
+
- **FE "s.elements is not iterable" crash fixed.** Root cause: V2 nests a scripted-API return under a route-keyed slot of `data` (e.g. `data.meta`/`data.surfaces`), one level deeper than the hook unwrapped. `useFetchSurfaceMeta.ts` `extractBundle()` now finds the bundle by structural fingerprint (the `elements` array) across `raw`/`raw.data`/`raw.data.<key>`, with empty-elements fallback. Hardened `SurfaceSection.tsx` + `SurfaceActionBar.tsx` to bail to null unless `elements` is a real array.
|
|
25
|
+
- **VendorItems screen migrated** (screen 2/5). Seed `dbchanges2/Core/2026-06-29d - VendorItemsSurfaceSeed.sql` (vendor-items-list TABLE→TableView 10, vendor-items-list-actions BUTTON_BAR). FE rewired to bundle; legacy `vendorItemsPageFields.json` deleted + removed from fieldsConfig registry. Mirrors the prior Items migration exactly.
|
|
26
|
+
- **Inventory screen migrated presentation-only** (screen 3/5). Seed `dbchanges2/Core/2026-06-29e - InventorySurfaceSeed.sql` (inventory-list TABLE recordId/tableViewId NULL, carries groupByLabels+entityTags in config; inventory-list-actions BUTTON_BAR; Actions anchored to items(21)). FE sources title/buttons/entityTags/groupByLabels from the surface; grouping topology stays in JSON.
|
|
27
|
+
- **/capture PUSHED** — 4 surface feature docs updated on team `_main`.
|
|
28
|
+
|
|
29
|
+
## What did NOT work — DO NOT RETRY THESE
|
|
30
|
+
- **Granting only `AclRecordPermissions` (record-level READ) does NOT authorize a RecordScript call.** The earlier fix `dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql` granted Public(1) READ (perm 313, complete logic chain) and the 403 PERSISTED — because dispatch checks `AclRecordScripts`, a different table. Record perms are necessary but NOT sufficient. Do not re-investigate the AclRecordPermissions chain for script 403s.
|
|
31
|
+
- **Unwrapping only `raw.data` in the meta hook** left the FE reading `.elements` on the `{meta:{...}}` wrapper → "not iterable". Hard-coding a single nesting key (`data.meta` vs `data.surfaces`) is brittle — the structural-fingerprint extractor is the right approach.
|
|
32
|
+
- **Swapping `SurfaceSection` into `SalesOrderSummaryGrid` would DESTROY bespoke rendering.** The grid renders via blox `DetailSection`/`OrderSectionCard`/`BaseTotals` (the last with client recurring-lease logic `_totalLease!==0`). Generic label/value `SurfaceSection` cannot represent totals or location cards. Do not attempt a generic-section swap there.
|
|
33
|
+
|
|
34
|
+
## Not tried yet (candidates for next session)
|
|
35
|
+
- **SalesOrders detail sections via a REGISTERED-RENDERER approach**: map surface element `config.kind` → bespoke component (detailSection→DetailSection, locationCard→OrderSectionCard, totalsCard→BaseTotals), keeping the recurring-lease logic in code/_underscore. Reconcile the early-seeded order-details/ship-to/bill-to/order-summary SECTION surfaces with the grid's actual client-keyed, data-driven section model (they don't currently map cleanly).
|
|
36
|
+
- **add-notes (adminNotes) migration** — `getDetailSections.tsx` + `AdminNotesSection`; currently gated by `tenantFields[key].isVisible`. Move the section visibility gating to Surface while keeping the bespoke component.
|
|
37
|
+
- **Compass approval modals** (`SalesOrderApprovalModalsLayout`) — the plan's explicitly-deferred bespoke surface; needs the blessed-component registry + `_underscore` capability methods, not generic config.
|
|
38
|
+
- **Confirm BLANK_CLIENT_DATABASE provisioning** carries the `AclRecordScripts` grants for surface scripts (else a newly-provisioned client repeats the 403). Determine whether BLANK is a snapshot or replays dated migrations.
|
|
39
|
+
|
|
40
|
+
## Current file state
|
|
41
|
+
| File | Status | Notes |
|
|
42
|
+
|------|--------|-------|
|
|
43
|
+
| dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql | created | The 403 fix; applied on dev.sandbox. Fan out to other client DBs + BLANK. |
|
|
44
|
+
| dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql | created | debug→metaDebug phpMethod fix for pre-rename envs. |
|
|
45
|
+
| dbchanges2/Core/2026-06-29d - VendorItemsSurfaceSeed.sql | created | VendorItems surface seed. |
|
|
46
|
+
| dbchanges2/Core/2026-06-29e - InventorySurfaceSeed.sql | created | Inventory presentation-only seed. |
|
|
47
|
+
| toga25-supply/src/surface/useFetchSurfaceMeta.ts | modified | extractBundle() structural-fingerprint unwrap. |
|
|
48
|
+
| toga25-supply/src/surface/SurfaceSection.tsx | modified | Array.isArray(elements) guard. |
|
|
49
|
+
| toga25-supply/src/surface/SurfaceActionBar.tsx | modified | Array.isArray(elements) guard. |
|
|
50
|
+
| toga25-supply/src/pages/VendorItems/VendorItemsPage.tsx | modified | Title + button bar from bundle. |
|
|
51
|
+
| toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx | modified | Dropped useClientFields/vendorItemsPageFields; in-memory columnsButton default. |
|
|
52
|
+
| toga25-supply/src/pages/VendorItems/viewModel/FIELDS/DEFAULT/vendorItemsPageFields.json | deleted | Migrated to Surface. |
|
|
53
|
+
| toga25-supply/src/fieldsConfig/index.ts | modified | Removed vendorItemsPageFields (import/type/Omit/const/2 injections). |
|
|
54
|
+
| toga25-supply/src/pages/Inventory/Inventory.tsx | modified | Title + Refresh/GroupBy icon+label from bundle. |
|
|
55
|
+
| toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx | modified | entityTags/groupByLabels/title/buttons from surface; groupings still from JSON. |
|
|
56
|
+
| toga25-supply/src/pages/Inventory/viewModel/FIELDS/index.ts | modified | entityTags/labels @deprecated optional on InventoryGroupingsConfig. |
|
|
57
|
+
| toga25-supply/.../Inventory/.../DEFAULT/inventoryGroupings.json | modified | Stripped labels+entityTags; groupings only. |
|
|
58
|
+
| toga25-supply/.../Inventory/.../NYCHH/inventoryGroupings.json | modified | Same strip (file is byte-identical to DEFAULT). |
|
|
59
|
+
|
|
60
|
+
## Decisions made
|
|
61
|
+
- **Inventory = presentation-only migration** (developer-approved). The `groupings[].levels[]` topology (fetchSlug/role/additionalDataSlug/urlParamKey) is data-fetch wiring → stays JSON; only title/buttons/modal-copy/entityTags moved to Surface. Rejected: migrating the whole groupings blob into surface config (would erode the config/logic boundary — the plan's #1 risk).
|
|
62
|
+
- **SalesOrders deferred to a design-first session** (developer chose to stop here after the bespoke nature was discovered). Rejected: rushing a generic-section swap on this expensive thread (would destroy bespoke totals/location rendering and produce unverifiable output).
|
|
63
|
+
- **Surface RecordScripts must be granted in `AclRecordScripts` (Client DB) per role** — this is the canonical dispatch-authorization mechanism; encode it in every surface ACL seed and in BLANK.
|
|
64
|
+
|
|
65
|
+
## Blockers
|
|
66
|
+
- **Cannot run `tsc`/build locally** — private `@agilant/toga-blox` registry needs auth (npm E401); FE changes verified by reading, built/deployed by the developer on dev.sandbox.
|
|
67
|
+
- **File-vs-DB id drift** — seed files use renumbered ids (surfaces record 333), but dev.sandbox holds the originally-provisioned ids (2300). All fixes were made id-agnostic (match by route) to span both. Not blocking, but keep using route-based lookups.
|
|
68
|
+
|
|
69
|
+
## Exact next step
|
|
70
|
+
> In a fresh session: open `toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderSummaryGrid.tsx` and design a **registered-renderer** that maps a surface element's `config.kind` (detailSection / locationCard / totalsCard) to its existing bespoke component (DetailSection / OrderSectionCard / BaseTotals), sourcing section title + visibility + field bindings from the `order-details`/`ship-to`/`bill-to`/`order-summary` surface bundles while keeping the recurring-lease logic in code. First reconcile those 4 seeded surfaces against the grid's actual client-keyed section model (query their SurfaceElements: order-details=4, ship-to=2, bill-to=1, order-summary=4).
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
_Saved by /session-save on 2026-06-29_
|
package/package.json
CHANGED