toga-ai 1.0.222 → 1.0.223

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.
@@ -12,6 +12,6 @@
12
12
  | [NetSuite REST Client (_Component_Api_Netsuite) — record writes & SuiteQL](features/netsuite-rest-client.md) | `_Component_Api_Netsuite` is the **2.0 `_underscore` NetSuite REST client** — the shared primitive every worker2/api2 NetSuite caller uses for record GETs, Suit | _underscore/Component/Api/Netsuite/Netsuite.php |
13
13
  | [Per-Client Database Connections & the Local Logs Trap](features/per-client-database-connections.md) | When `_underscore` serves a request for a client it opens **three distinct per-client database connections**, not one. | _underscore/Database.php, _underscore/ApiRequest.php, _underscore/Model/Client/Logs/Api.php |
14
14
  | [Recursive Item Fulfillments (upstream mirroring)](features/recursive-item-fulfillments.md) | In a multi-tier supply chain a sales order (SO) spawns a purchase order (PO) that becomes another SO downstream, and so on. | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemFulfillmentItem.php, _underscore/Model/Client/ItemFulfillmentItemUnit.php, _underscore/Model/Client/ItemFulfillmentPackage.php, _underscore/Model/Compass/AdvanceShippingNotice.php, dbchanges2/Core/2026-02-13 - 75601 - RecursiveItemFulfillmentCreation.sql, dbchanges2/Core/2026-06-04 - RecursiveItemFulfillmentPut.sql |
15
- | [Surface Resolver (_Model_Core_Surface::resolve — replaces Page::meta)](features/surface-resolver.md) | The runtime for the platform-wide **Surface** UI presentation layer: 9 `_underscore` models plus a cached resolver, `_Model_Core_Surface::resolve(&$api, string | _underscore/Model/Core/Surface.php, _underscore/Model/Core/SurfaceElement.php, _underscore/Model/Core/Action.php, _underscore/Model/Core/Vocabulary.php, _underscore/Model/Core/VocabularyTerm.php, _underscore/Model/Core/Message.php, _underscore/Model/Client/SurfaceOverride.php, _underscore/Model/Client/MessageTranslation.php, _underscore/Model/Client/ThemeToken.php, _underscore/Model/Core/Page.php |
15
+ | [Surface Resolver (_Model_Core_Surface::resolve — replaces Page::meta)](features/surface-resolver.md) | The runtime for the platform-wide **Surface** UI presentation layer: 9 `_underscore` models plus a cached resolver, `_Model_Core_Surface::resolve(&$api, string | _underscore/Model/Core/Surface.php, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, _underscore/Model/Core/SurfaceElement.php, _underscore/Model/Core/Action.php, _underscore/Model/Core/Vocabulary.php, _underscore/Model/Core/VocabularyTerm.php, _underscore/Model/Core/Message.php, _underscore/Model/Client/SurfaceOverride.php, _underscore/Model/Client/MessageTranslation.php, _underscore/Model/Client/ThemeToken.php, _underscore/Model/Core/Page.php |
16
16
  | [Tracking-Number Bridge Migration (ASN / Item Fulfillment / Item Receipt)](features/tracking-number-bridges.md) | Shipment tracking numbers used to live as **scalar FK columns** (`trackingNumberId`, `returnTrackingNumberId`) directly on the lowest-level "unit"/"item" tables | api2/Component/Api/V2/V2.php, _underscore/Model/Client/AdvanceShippingNoticeItemUnit.php, _underscore/Model/Client/AdvanceShippingNoticeItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillmentItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Prudential/AdvanceShippingNotice.php, _underscore/Model/Compass/AdvanceShippingNotice.php, _underscore/Trait/Netsuite/ItemFulfillment.php, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client/2026-06-10 - TrackingNumberBridges.sql, dbchanges2/Core/2026-06-10 - TrackingNumberBridges.sql, dbchanges2/Client_Prudential/2026-06-15 - ItemFulfillmentTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Quad/2026-06-18a - ItemFulfillmentItemReceiptTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Nychh/2026-06-19b - ItemFulfillmentItemReceiptTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Nychh/2026-06-19a - FixItemFulfillmentTableViewTrackingAndRoot.sql, dbchanges2/Client_Quad/2026-06-19a - FixItemFulfillmentTableViewTrackingAndRoot.sql |
17
17
  | [Units for Items for Purchase Orders — Data Structure](features/units-for-items-for-purchase-orders.md) | Describes how unit (serialized inventory) data is linked to sales-order and purchase-order line items behind the `units-for-items-for-purchase-orders` TableView | |
@@ -6,7 +6,7 @@ project: _Underscore
6
6
  client: shared
7
7
  type: architecture
8
8
  status: active
9
- updated: 2026-06-25
9
+ updated: 2026-06-29
10
10
  owners: ["jcardinal", "rgirish"]
11
11
  files:
12
12
  - _underscore/_underscore.php
@@ -71,7 +71,7 @@ Tier-1 trivial declarative rules (frozen `all/any/none + {field,op,value}` gramm
71
71
  client-side; Tier-2 business logic computed in PHP returning booleans (lives in
72
72
  `_Model_<Client>_*` overrides + interceptors). Config/logic boundary: **config describes,
73
73
  PHP decides.** M2M-safe delivery via opt-in `surface=<slug>` request option → response
74
- `meta.surface`, never `data`. Generic FE machinery destined for `@agilant/toga-blox`.
74
+ `meta.surface`, never `data`. Generic FE machinery destined for `@agilant/toga-blox`. **Surface is the enforced presentation path — no feature flag.** The `SURFACE_ENABLED`/`VITE_SURFACE_ENABLED` flag and all legacy pre-Surface fallback branches were removed; legacy per-screen FIELDS config is deleted as each screen migrates (no dual-path fallback). The meta endpoint is query-string form: `GET /v2/surfaces/meta?slug=<slug>`.
75
75
 
76
76
  **Rejected alternatives:** a generic EAV settings cascade (the old `*Settings` tables —
77
77
  proven near-empty in prod); raw-CSS-in-config (the old "Dummy Fields" anti-pattern).
@@ -80,7 +80,7 @@ proven near-empty in prod); raw-CSS-in-config (the old "Dummy Fields" anti-patte
80
80
  allowlist that throws on unknown ops, with a CI test asserting the op-set; defensive
81
81
  resolver with logged fallbacks; debug-bundle endpoint ships WITH the resolver; `c_longValue`
82
82
  on overrides with strict per-attribute casting; per-surface meta() cutover with a parity
83
- diff (do NOT delete meta() until every page is migrated); `BLANK_CLIENT_DATABASE` as a CI gate.
83
+ diff (do NOT delete meta() until every page is migrated); `BLANK_CLIENT_DATABASE` as a CI gate. Reserved Core id blocks: `Records` 333–341, `RecordFields` 2246–2433 (renumbered 2026-06-29, seed files only — a known file-vs-DB id drift in already-provisioned envs, to reconcile by re-provisioning). Anything spanning both id schemes must be id-agnostic (match by `route=`, not id).
84
84
 
85
85
  ## Naming convention & autoloader
86
86
 
@@ -324,3 +324,4 @@ multi-file UI components (`.php`/`.html`/`.css`/`.js`) invoked as `<_ComponentNa
324
324
  ## Change history
325
325
  - 2026-06-11 — Documented lazy transaction gotcha in `_Database::register()` (rgirish)
326
326
  - 2026-06-25 — Added the Surface platform UI presentation/configuration layer (DB-driven UI config replacing `Page::meta()`, CTO-reviewed AGREE-WITH-ADJUSTMENTS) (jcardinal)
327
+ - 2026-06-29 — Surface made the enforced (un-flagged) presentation path; reserved id blocks renumbered to Records 333–341 / RecordFields 2246–2433 (known seed-vs-provisioned drift). (jcardinal)
@@ -6,10 +6,11 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-25
9
+ updated: 2026-06-29
10
10
  owners: [jcardinal]
11
11
  files:
12
12
  - _underscore/Model/Core/Surface.php
13
+ - dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql
13
14
  - _underscore/Model/Core/SurfaceElement.php
14
15
  - _underscore/Model/Core/Action.php
15
16
  - _underscore/Model/Core/Vocabulary.php
@@ -30,8 +31,9 @@ related:
30
31
  The runtime for the platform-wide **Surface** UI presentation layer: 9 `_underscore` models plus a
31
32
  cached resolver, `_Model_Core_Surface::resolve(&$api, string $slug)`, that merges base config +
32
33
  cascade overrides + ACL + messages + theme tokens into one flat, serializable bundle. It is the
33
- **replacement for `_Model_Core_Page::meta()`** and is shipped as a scripted-API method behind
34
- `GET /v2/surfaces/{slug}/meta`. Platform/shared — not client-specific. Schema:
34
+ **replacement for `_Model_Core_Page::meta()`** and is shipped as a scripted-API method called as
35
+ `GET /v2/surfaces/meta?slug=<slug>` (RecordScript args come from the **query string**, not the
36
+ path — see gotchas). Platform/shared — not client-specific. Schema:
35
37
  [surface-layer-schema](../../dbchanges2/features/surface-layer-schema.md).
36
38
 
37
39
  ## The 9 models
@@ -62,8 +64,8 @@ Inputs come from `$api` (the same accessor `_Model_Client_TableView::meta` uses)
62
64
  table pipeline (referenced, never duplicated).
63
65
  7. Assemble the flat bundle. **Zero INSERTs.**
64
66
 
65
- A `debug()` inspector returns the resolved bundle plus the raw cascade layers (shipped with the
66
- resolver, not "later").
67
+ A `metaDebug()` inspector returns the resolved bundle plus the raw cascade layers (shipped with the
68
+ resolver, not "later"). It is **not** named `debug()` — see gotchas.
67
69
 
68
70
  ## Caching & invalidation
69
71
 
@@ -97,8 +99,29 @@ resolver, not "later").
97
99
  falls back to sane defaults and never crashes a page. Strict per-`attribute` casting with logged
98
100
  fallback on bad `SurfaceOverrides` data.
99
101
  - **Cross-DB reads are batched** (DB_CORE vs DB_CLIENT) and FKs are soft — tolerate dangling refs.
102
+ - **A scripted-API method MUST NOT collide with a non-static `_Model` base method.** Naming the
103
+ inspector `debug()` caused a fatal *"Cannot make non static method _Model::debug() static"* — the
104
+ framework base `_Model` defines a NON-static `debug()` (Model.php:167) and the scripted-API
105
+ dispatch calls the method statically. Renamed to `metaDebug` and remapped the RecordScript
106
+ (`route 'debug' → phpMethod 'metaDebug'`). Vet any new scripted-API method name against the
107
+ `_Model` base (e.g. `debug`, and check others) before mapping it.
108
+ - **The endpoint is `GET /v2/surfaces/meta?slug=<slug>`, not `/v2/surfaces/<slug>/meta`.** TOGA
109
+ RecordScripts read their args from the **query string**; the path form makes the engine parse the
110
+ slug as a record uuid and return 404 EV-6. Pattern for any RecordScript:
111
+ `/<route>/<scriptRoute>?<arg>=...`.
112
+ - **Core metadata records exposed to the app must grant CORE role Public (id 1) READ.** The meta
113
+ RecordScript is ACL-gated on the `surfaces` Core record, and authenticated app users carry CORE
114
+ role Public(1); a seed that grants only Super User(3)/Base(4) returns 403 EZ-1. Grant Public(1)
115
+ `allowRead` on the `surfaces` record (full AclRecordPermissions → AclLogicGroups →
116
+ AclLogicGroupExpressions → AclRecordExpressions 'all' chain), mirroring how `apps`/`records`/
117
+ `record-fields` already expose Public READ. Only the `surfaces` record gets Public read — the
118
+ admin-CRUD sibling records stay Super-User-only (the resolver reads their tables server-side).
100
119
 
101
120
  ## Change history
102
- - 2026-06-25 — Built the 9 models + `resolve()`/`debug()` as the cached, parameterized,
121
+ - 2026-06-29 — Deploy fixes: renamed the scripted inspector `debug()`→`metaDebug()` (name collided
122
+ with non-static `_Model::debug()`, fatal); corrected the endpoint to query-string form
123
+ `GET /v2/surfaces/meta?slug=` (path form 404'd EV-6); granted CORE Public(1) READ on the `surfaces`
124
+ record so app users stop getting 403 EZ-1 on meta (id-agnostic migration matches by `route='surfaces'`). (jcardinal)
125
+ - 2026-06-25 — Built the 9 models + `resolve()`/`metaDebug()` as the cached, parameterized,
103
126
  zero-write-on-read replacement for `_Model_Core_Page::meta()`; cache-bust interceptors on all 9
104
127
  models (outside the transaction). meta() retained until per-page cutover with parity diff. (jcardinal)
@@ -6,7 +6,7 @@ project: API
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-25
9
+ updated: 2026-06-29
10
10
  owners: [jcardinal]
11
11
  files:
12
12
  - api2/Component/Api/V2/V2.php
@@ -42,6 +42,17 @@ traffic), behavior is **byte-for-byte unchanged** — pure data, zero extra work
42
42
  the logic.
43
43
  - **One call, no extra round-trip** — the state pass rides the existing data fetch.
44
44
 
45
+ ## Gotchas
46
+
47
+ - **Meta endpoint 403 EZ-1 for normal app users → grant CORE Public(1) READ on the `surfaces`
48
+ record.** Authenticated app users carry CORE role Public (id 1); the meta RecordScript is
49
+ ACL-gated on the `surfaces` Core record, and a seed granting only Super User(3)/Base(4) rejects
50
+ them. Grant Public(1) `allowRead` on `surfaces` only (the admin-CRUD sibling records stay
51
+ Super-User-only — the resolver reads their tables server-side). See
52
+ [surface-resolver](../../_underscore/features/surface-resolver.md) gotchas for the full chain.
53
+
45
54
  ## Change history
55
+ - 2026-06-29 — Documented the meta-endpoint ACL: normal app users (CORE Public role 1) need
56
+ `allowRead` on the `surfaces` record or meta returns 403 EZ-1. (jcardinal)
46
57
  - 2026-06-25 — Added the `surface=<slug>` opt-in option to the V2 engine; attaches per-record action
47
58
  state under `meta.surface` (M2M-safe, defensive, absent = unchanged). (jcardinal)
@@ -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/Core/2026-06-25a - SurfaceCoreTables.sql, dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql, dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.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 |
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/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-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 |
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,12 +6,14 @@ project: Database Changes
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-25
9
+ updated: 2026-06-29
10
10
  owners: [jcardinal]
11
11
  files:
12
12
  - dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql
13
13
  - dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql
14
14
  - dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql
15
+ - dbchanges2/Core/2026-06-29a - ItemsSurfaceSeed.sql
16
+ - dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql
15
17
  - dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql
16
18
  - dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql
17
19
  - dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql
@@ -60,11 +62,13 @@ SurfaceElements`). Core migrations run first; Client after. The session built/se
60
62
  **SalesOrders + login** vertical (not Tickets) as the review proof:
61
63
 
62
64
  - `Core/2026-06-25a - SurfaceCoreTables.sql` — the 6 Core tables.
63
- - `Core/2026-06-25b - SurfaceRecordsAndFields.sql` — `Core.Records`+`RecordFields` for all 9 tables (reserved id block **2300–2399**; `recordId` via `model=` subselect).
65
+ - `Core/2026-06-25b - SurfaceRecordsAndFields.sql` — `Core.Records`+`RecordFields` for all 9 tables (`Records` ids **333–341**, `RecordFields` ids **2246–2433**; `recordId` via `model=` subselect). Also maps the meta `RecordScript` `route 'debug' → phpMethod 'metaDebug'` (the inspector cannot be named `debug` — see [surface-resolver](../../_underscore/features/surface-resolver.md)).
64
66
  - `Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql` — Core seed + the `RecordScripts` resolve row.
67
+ - `Core/2026-06-29a - ItemsSurfaceSeed.sql` — the Items LIST screen seed: surfaces `items-list` (TABLE → existing `items` TableView id 9, recordId 21) + `items-list-actions` (BUTTON_BAR: refresh/columns/newItem) + Actions + ~11 Messages. Single DEFAULT bundle, no Client overrides.
68
+ - `Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql` — grants CORE role Public(1) `allowRead` on the `surfaces` record (so app users stop getting 403 EZ-1 on meta). **Id-agnostic**: matches the record by `route='surfaces'`, so it works whether the record id is 333 or the legacy 2300.
65
69
  - `Client/2026-06-25a - SurfaceClientTables.sql` — the 3 Client tables + `INSERT IGNORE Languages('en','English')`.
66
70
  - `Client/2026-06-25b - SurfaceClientSeed.sql` — `ThemeTokens` + `MessageTranslations(en)`.
67
- - `Client/2026-06-25c - SurfaceClientAcl.sql` — full ACL chain for the 3 CLIENT-aclDatabase records.
71
+ - `Client/2026-06-25c - SurfaceClientAcl.sql` — full ACL chain for the 3 CLIENT-aclDatabase records (recordId refs updated to the 333-block renumber).
68
72
 
69
73
  ## Gotchas
70
74
 
@@ -79,12 +83,22 @@ SurfaceElements`). Core migrations run first; Client after. The session built/se
79
83
  - **`SurfaceOverrides.value` is a stringly-typed escape hatch** — a long `CONFIG`/label override
80
84
  would silently truncate at varchar(255); that is why `c_longValue mediumtext` exists, and the
81
85
  resolver MUST cast strictly per `attribute` (bool/int/string/json) with a logged fallback.
82
- - **Reserved id block 2300–2399** for the Surface `Records`/`RecordFields` rows (Core max was 331);
83
- use `model=` subselects for `recordId`, never hardcoded ids.
86
+ - **Reserved id blocks (renumbered 2026-06-29):** Surface `Core.Records` ids **333–341** (9 tables),
87
+ `Core.RecordFields` ids **2246–2433**. (Originally seeded at 2300–2308 / 2400+, renumbered at the
88
+ team's request.) `RecordScripts.recordId` and the Core+Client ACL `recordId` refs were updated to
89
+ match. Use `model=` subselects for `recordId`, never hardcoded ids.
90
+ - **Known file-vs-DB id drift.** The renumber changed the **seed files only** — already-provisioned
91
+ envs still hold the old 2300/2400 ids. This is intentional/known, to be reconciled by
92
+ re-provisioning later. Anything that must work across both (e.g. the Public-READ ACL grant) must
93
+ be **id-agnostic** — match the record by `route='surfaces'`, not by id.
84
94
  - **CLIENT-aclDatabase records** (`SurfaceOverrides`, `MessageTranslations`, `ThemeTokens`) need the
85
95
  full 4-step ACL chain + field permissions in **each** client DB; resolve `roleId` by subselect.
86
96
 
87
97
  ## Change history
88
- - 2026-06-25 — Initial schema for the Surface layer: 6 Core + 3 Client tables, `Records`/`RecordFields`
89
- (ids 2300–2399), SalesOrders+login seed, ACL chain, and `BLANK_CLIENT_DATABASE` append. Typed
98
+ - 2026-06-29 — Renumbered the reserved seed id blocks (Records 2300-2308→**333–341**, RecordFields
99
+ 2400+→**2246–2433**) in the seed files only — known file-vs-DB drift in provisioned envs. Added
100
+ the Items LIST seed (`ItemsSurfaceSeed.sql`) and the id-agnostic CORE Public(1) READ ACL grant on
101
+ `surfaces` (`SurfaceMetaPublicReadAcl.sql`, matches by `route='surfaces'`). (jcardinal)
102
+ - 2026-06-25 — Initial schema for the Surface layer: 6 Core + 3 Client tables, `Records`/`RecordFields`,
103
+ SalesOrders+login seed, ACL chain, and `BLANK_CLIENT_DATABASE` append. Typed
90
104
  columns chosen over EAV (the old `*Settings` cascade was empirically near-empty in prod). (jcardinal)
@@ -8,4 +8,4 @@
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/{slug}/meta` instead of statically-imported JSON f | toga25-supply/src/surface/useFetchSurfaceMeta.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/featureFlag.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 |
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/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/fieldsConfig/index.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-06-25
9
+ updated: 2026-06-29
10
10
  owners: [jcardinal]
11
11
  files:
12
12
  - toga25-supply/src/surface/useFetchSurfaceMeta.ts
@@ -16,11 +16,13 @@ files:
16
16
  - toga25-supply/src/surface/SurfaceSection.tsx
17
17
  - toga25-supply/src/surface/resolve.ts
18
18
  - toga25-supply/src/surface/types.ts
19
- - toga25-supply/src/surface/featureFlag.ts
20
19
  - toga25-supply/src/surface/index.ts
21
20
  - toga25-supply/src/pages/Login/LoginPage.tsx
22
21
  - toga25-supply/src/pages/SalesOrders/SalesOrders.tsx
23
22
  - toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx
23
+ - toga25-supply/src/pages/Items/ItemsPage.tsx
24
+ - toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx
25
+ - toga25-supply/src/fieldsConfig/index.ts
24
26
  related:
25
27
  - ../../_underscore/features/surface-resolver.md
26
28
  - meta-driven-table-data.md
@@ -29,14 +31,17 @@ related:
29
31
  ## What it is
30
32
 
31
33
  The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from
32
- `GET /v2/surfaces/{slug}/meta` instead of statically-imported JSON field bundles. Lives in
34
+ `GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported JSON field bundles. Lives in
33
35
  `src/surface/` for now with a `TODO(blox)` to extract the generic machinery into `@agilant/toga-blox`
34
- so all 2.0 apps inherit it. Backend: [surface-resolver](../../_underscore/features/surface-resolver.md).
36
+ so all 2.0 apps inherit it. Surface is now the **enforced, unconditional presentation path** (no
37
+ feature flag — see below). Backend: [surface-resolver](../../_underscore/features/surface-resolver.md).
35
38
 
36
39
  ## How it works
37
40
 
38
41
  - **`useFetchSurfaceMeta`** — the single fetch-once/React-Query-cached choke point for a surface's
39
- meta. Loading a different *record* into the same surface reuses cached meta.
42
+ meta. Loading a different *record* into the same surface reuses cached meta. Builds the URL as
43
+ `/surfaces/meta?slug=...` (query string), **not** `/surfaces/<slug>/meta` (the path form makes the
44
+ engine parse the slug as a record uuid → 404 EV-6).
40
45
  - **`evaluateSurfaceRule`** — the Tier-1 rule evaluator: a **frozen `all/any/none` + `{field,op,value}`
41
46
  grammar** that **throws on an unknown op** (generalized from the SalesOrders
42
47
  `buildPatchedTenantFields`/`evaluateEnableRule`/`resolveFlag` helpers). Evaluated client-side against
@@ -47,25 +52,53 @@ so all 2.0 apps inherit it. Backend: [surface-resolver](../../_underscore/featur
47
52
  - **theme/message resolvers** (`resolve.ts`) — token → CSS and ICU message rendering.
48
53
  - **`SurfaceActionBar`/`SurfaceActions` + `SurfaceSection`** — generic renderers.
49
54
 
50
- ## What's wired (review proof)
55
+ ## Enforced, not flagged
51
56
 
52
- Login + sales-orders list + the sales-order modal, all behind the **`SURFACE_ENABLED`** flag (env
53
- `VITE_SURFACE_ENABLED`, **default OFF**). The SalesOrder action bar reproduces
54
- `orderViewFields.json` approve/deny/approvalWorkflow/viewLog/editOrder by delegating to the existing
55
- `onActionClick`/`onOpenLog` handlers. The table stays on the existing TableView pipeline via
56
- `tableViewSlug` (see [meta-driven-table-data](meta-driven-table-data.md)) — the Surface layer
57
- references the TableView, never replaces it.
57
+ Surface is the **unconditional** presentation path. The `VITE_SURFACE_ENABLED` / `SURFACE_ENABLED`
58
+ feature flag was **removed entirely** (`featureFlag.ts` deleted, all gating conditionals and the
59
+ legacy pre-Surface fallback branches at the wired screens removed). Legacy per-screen `FIELDS` config
60
+ + rendering are deleted as each screen migrates — there is no dual-path fallback.
61
+
62
+ ## What's wired
63
+
64
+ Login + sales-orders list + the sales-order modal + the **Items LIST** screen. The SalesOrder action
65
+ bar reproduces `orderViewFields.json` approve/deny/approvalWorkflow/viewLog/editOrder by delegating
66
+ to the existing `onActionClick`/`onOpenLog` handlers. TABLE-type surfaces stay on the existing
67
+ TableView pipeline via `tableViewSlug` (see [meta-driven-table-data](meta-driven-table-data.md)) —
68
+ the Surface layer references the TableView, never replaces it.
69
+
70
+ ## Per-screen migration recipe (proven on SalesOrders + Items)
71
+
72
+ The screen-by-screen full-app refactor follows one recipe:
73
+ 1. Seed the screen's surface(s) in a `Core/*SurfaceSeed.sql` — a TABLE surface that references the
74
+ screen's existing `TableView` id via `tableViewSlug`, plus a BUTTON_BAR/actions surface + its
75
+ Actions + Messages (e.g. Items: `items-list` TABLE → TableView id 9 / recordId 21, and
76
+ `items-list-actions` refresh/columns/newItem).
77
+ 2. Rewire the page + its view-model to consume the surface bundle (`ItemsPage.tsx` /
78
+ `useItemsPageViewModel.tsx`).
79
+ 3. **Delete** the legacy `FIELDS` config: the page's `*Fields.json` and its `fieldsConfig/index.ts`
80
+ entries.
81
+ Keep TABLE rendering on the existing TableView pipeline; only the chrome (actions/labels/visibility)
82
+ moves to Surface. A screen with no client/role/lang variance ships a single DEFAULT bundle (no
83
+ Client overrides), as Items did.
58
84
 
59
85
  ## Gotchas
60
86
 
61
- - **Default OFF.** Nothing renders through the Surface path unless `VITE_SURFACE_ENABLED` is set —
62
- the existing JSON-bundle path remains live until per-surface cutover.
87
+ - **No feature flag, no fallback.** Surface is the enforced path; a missing/broken surface bundle
88
+ does not silently fall back to legacy JSON — that path is deleted per screen as it migrates.
89
+ - **Endpoint is query-string form** — `GET /v2/surfaces/meta?slug=...`, not `/surfaces/<slug>/meta`
90
+ (the path form 404s EV-6).
63
91
  - **Tier-1 only on the client.** Business-logic gates (Tier-2) arrive as resolved booleans from the
64
92
  backend (`meta.surface`); never re-encode business rules in the FE evaluator.
65
93
  - **`tsc` not yet run** this session (the private `@agilant/toga-blox` registry needs npm creds);
66
94
  treat type-checking as pending. Runtime `GET /v2/surfaces/{slug}/meta` also not yet exercised.
67
95
 
68
96
  ## Change history
97
+ - 2026-06-29 — DECISION: Surface is now enforced, not flagged — removed `VITE_SURFACE_ENABLED`/
98
+ `SURFACE_ENABLED` (deleted `featureFlag.ts` + all gating/legacy-fallback branches). Fixed
99
+ `useFetchSurfaceMeta` to the query-string endpoint `/surfaces/meta?slug=` (path form 404'd EV-6).
100
+ Migrated the Items LIST screen (second proof of the per-screen recipe; deleted `itemsPageFields.json`
101
+ + its fieldsConfig entries). (jcardinal)
69
102
  - 2026-06-25 — Built `src/surface/` (fetch hook, frozen Tier-1 rule evaluator, action registry, theme/
70
103
  message resolvers, action-bar + section renderers); wired login + SalesOrders list + SO modal behind
71
104
  the OFF-by-default `SURFACE_ENABLED` flag. Generic machinery destined for toga-blox. (jcardinal)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.222",
3
+ "version": "1.0.223",
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",