toga-ai 1.0.240 → 1.0.242
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.
- package/knowledge/2.0/apps/_underscore/INDEX.md +1 -1
- package/knowledge/2.0/apps/_underscore/features/surface-resolver.md +39 -1
- package/knowledge/2.0/apps/api2/features/surface-meta-option.md +8 -1
- package/knowledge/2.0/apps/dbchanges2/INDEX.md +1 -1
- package/knowledge/2.0/apps/dbchanges2/features/surface-layer-schema.md +43 -1
- package/knowledge/2.0/apps/toga25-supply/INDEX.md +2 -1
- package/knowledge/2.0/apps/toga25-supply/features/record-modals-and-nested-tables.md +15 -0
- package/knowledge/2.0/apps/toga25-supply/features/surface-frontend.md +47 -15
- package/knowledge/2.0/apps/toga25-supply/workflows/cypress-testing.md +143 -0
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/compass-canada/profile.md +1 -0
- package/knowledge/clients/compass-usa/profile.md +1 -0
- package/knowledge/clients/quad/profile.md +2 -1
- package/package.json +1 -1
|
@@ -13,6 +13,6 @@
|
|
|
13
13
|
| [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 |
|
|
14
14
|
| [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 |
|
|
15
15
|
| [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 |
|
|
16
|
-
| [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, dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql, dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.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
|
+
| [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-30a - SurfaceMetaGroupAndSalesOrderSections.sql, dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql, dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.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 |
|
|
17
17
|
| [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 |
|
|
18
18
|
| [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,10 +6,12 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-30
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Core/Surface.php
|
|
13
|
+
- dbchanges2/Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql
|
|
14
|
+
- dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql
|
|
13
15
|
- dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql
|
|
14
16
|
- dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql
|
|
15
17
|
- dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql
|
|
@@ -69,6 +71,29 @@ Inputs come from `$api` (the same accessor `_Model_Client_TableView::meta` uses)
|
|
|
69
71
|
A `metaDebug()` inspector returns the resolved bundle plus the raw cascade layers (shipped with the
|
|
70
72
|
resolver, not "later"). It is **not** named `debug()` — see gotchas.
|
|
71
73
|
|
|
74
|
+
## Grouped resolution — `metaGroup()`
|
|
75
|
+
|
|
76
|
+
`_Model_Core_Surface::metaGroup(&$api, string $slugs): object` — a scripted-API method exposed at
|
|
77
|
+
`GET /v2/surfaces/meta-group?slugs=a,b,c` for screens (e.g. a record-detail modal) that need many
|
|
78
|
+
section surfaces at once: **one round trip instead of N**. It splits the comma-separated `slugs`
|
|
79
|
+
list (capped at `const META_GROUP_MAX_SLUGS = 25`, logging truncation), resolves **each slug via the
|
|
80
|
+
existing per-slug `resolve()` cache path**, and returns `{group, surfaces: {slug => bundle|null}}`.
|
|
81
|
+
It is **composition only** — no new cache layer; an unknown slug yields the structured **empty
|
|
82
|
+
bundle** so one missing surface never fails the whole group. (Chosen over a `parentSurfaceId` GROUP
|
|
83
|
+
row after CTO review — a slug list keeps the model schema-stateless and avoids id drift.)
|
|
84
|
+
|
|
85
|
+
`resolve()` now declares an explicit `: object` return type.
|
|
86
|
+
|
|
87
|
+
### Message/label resolution fixes (latent SECTION/BUTTON_BAR bugs)
|
|
88
|
+
The grouped SalesOrders section migration is the first real consumer of the SECTION/BUTTON_BAR path,
|
|
89
|
+
which surfaced latent resolution gaps in `_buildBundle`/`_resolveElement`:
|
|
90
|
+
- Element message slots (`label`/`tooltip`/`placeholder`/`disabledTooltip`) are now remapped from
|
|
91
|
+
**message-id → messageKey** before return, because `bundle.messages` is keyed by `messageKey` —
|
|
92
|
+
previously labels silently fell back to the element key.
|
|
93
|
+
- `disabledTooltipMessageId` is collected into the batch and emitted as a `disabledTooltip` slot.
|
|
94
|
+
- `bundle.surface.titleMessageKey` is set from `titleMessageId`.
|
|
95
|
+
- The 2nd `_loadMessages` return is merged into `$messageKeyById`; dangling message ids are logged.
|
|
96
|
+
|
|
72
97
|
## Caching & invalidation
|
|
73
98
|
|
|
74
99
|
- Disk cache keyed `surface:meta:{app}:{slug}:{client}:{sortedPersonaIds}:{sortedRoleIds}:{lang}`.
|
|
@@ -137,7 +162,20 @@ resolver, not "later"). It is **not** named `debug()` — see gotchas.
|
|
|
137
162
|
`record-fields` already expose Public READ. Only the `surfaces` record gets Public read — the
|
|
138
163
|
admin-CRUD sibling records stay Super-User-only (the resolver reads their tables server-side).
|
|
139
164
|
|
|
165
|
+
- **The meta-group dispatch grant is per-client-DB too.** A 403 EZ-1 on `meta-group` on dev-sandbox
|
|
166
|
+
was the same mechanism as the original `meta` 403: the `meta-group` RecordScript needs its own
|
|
167
|
+
per-role `Client.AclRecordScripts` dispatch grant. Verified via toga-db — `meta` script id 25 was
|
|
168
|
+
granted roles 1/3/4 but `meta-group` (id 27) had **none** until the grant ran. Any new surfaces
|
|
169
|
+
scripted-API method repeats this: record READ never authorizes dispatch.
|
|
170
|
+
|
|
140
171
|
## Change history
|
|
172
|
+
- 2026-06-30 — Added `metaGroup()` (`GET /v2/surfaces/meta-group?slugs=a,b,c`): batched composition
|
|
173
|
+
over the existing per-slug `resolve()` cache, capped at `META_GROUP_MAX_SLUGS=25`, unknown slug →
|
|
174
|
+
empty bundle (chosen over a `parentSurfaceId` GROUP row post-CTO-review). Fixed latent
|
|
175
|
+
SECTION/BUTTON_BAR resolution (message-id→messageKey remap for label/tooltip/placeholder/
|
|
176
|
+
disabledTooltip, titleMessageKey, dangling-id logging); added `: object` to `resolve()`. The
|
|
177
|
+
`meta-group` script needed its own per-client `AclRecordScripts` dispatch grant (403 EZ-1 until
|
|
178
|
+
granted). (jcardinal)
|
|
141
179
|
- 2026-06-29 — Deploy fixes (round 2): grant the surfaces `meta`/`debug` RecordScripts in
|
|
142
180
|
`Client.AclRecordScripts` per role (Public/SuperUser/Base) — record-level `AclRecordPermissions`
|
|
143
181
|
alone left every meta call 403 EZ-1 because V2 dispatch authorizes via `AclRecordScripts`
|
|
@@ -6,7 +6,7 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-30
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
@@ -56,7 +56,14 @@ traffic), behavior is **byte-for-byte unchanged** — pure data, zero extra work
|
|
|
56
56
|
meta 403'd until the `meta`/`debug` scripts were granted per role in `AclRecordScripts`. See
|
|
57
57
|
[surface-resolver](../../_underscore/features/surface-resolver.md) gotchas.
|
|
58
58
|
|
|
59
|
+
- **Grouped meta endpoint `GET /v2/surfaces/meta-group?slugs=a,b,c`** (one round trip for a
|
|
60
|
+
multi-section screen) is a distinct RecordScript and needs its **own** per-role
|
|
61
|
+
`Client.AclRecordScripts` dispatch grant — it 403'd EZ-1 on dev-sandbox until granted, exactly like
|
|
62
|
+
`meta` did. See [surface-resolver](../../_underscore/features/surface-resolver.md).
|
|
63
|
+
|
|
59
64
|
## Change history
|
|
65
|
+
- 2026-06-30 — Noted the grouped `meta-group` endpoint (batched multi-slug resolve) and that it needs
|
|
66
|
+
its own per-role `AclRecordScripts` dispatch grant (403 EZ-1 until granted). (jcardinal)
|
|
60
67
|
- 2026-06-29 — Documented the second ACL layer: scripted-API dispatch is authorized via
|
|
61
68
|
`Client.AclRecordScripts` (per `recordScriptId`+`roleId`), not record `AclRecordPermissions` —
|
|
62
69
|
record READ alone still 403'd meta until the dispatch grants were seeded. (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/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/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-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 |
|
|
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-06-
|
|
9
|
+
updated: 2026-06-30
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql
|
|
@@ -18,6 +18,11 @@ files:
|
|
|
18
18
|
- dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql
|
|
19
19
|
- dbchanges2/Core/2026-06-29d - VendorItemsSurfaceSeed.sql
|
|
20
20
|
- dbchanges2/Core/2026-06-29e - InventorySurfaceSeed.sql
|
|
21
|
+
- dbchanges2/Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql
|
|
22
|
+
- dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql
|
|
23
|
+
- dbchanges2/Client_Compass/2026-06-30a - SalesOrderDisplaySectionManagerOverrides.sql
|
|
24
|
+
- dbchanges2/Client_CompassCanada/2026-06-30a - SalesOrderSurfaceManagerOverrides.sql
|
|
25
|
+
- dbchanges2/Client_Quad/2026-06-30a - SalesOrderSurfaceClientOverrides.sql
|
|
21
26
|
- dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql
|
|
22
27
|
- dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql
|
|
23
28
|
- dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql
|
|
@@ -82,6 +87,19 @@ SurfaceElements`). Core migrations run first; Client after. The session built/se
|
|
|
82
87
|
purchase-orders/items/units via per-level fetchSlugs and has no single backing Record; carries
|
|
83
88
|
`groupByLabels` + `entityTags` in surface `config`) + `inventory-list-actions` (BUTTON_BAR:
|
|
84
89
|
Group by / Refresh). Actions anchored to `items(21)` because `Actions.recordId` is NOT NULL.
|
|
90
|
+
- `Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql` — registers the `meta-group`
|
|
91
|
+
RecordScript; seeds the previously-missing `order-recurring` SECTION surface (cardType
|
|
92
|
+
`recurringFields`, `isVisible 0`); seeds 5 display-toggle SECTION surfaces (invoices / shipments /
|
|
93
|
+
additional-order-details / notes / admin-notes), **each carrying a single `sectionVisibility`
|
|
94
|
+
marker element** (see gotcha — the override cascade only overrides ELEMENTS, never a surface's own
|
|
95
|
+
`isVisible`, so display-section visibility cannot be per-role-overridden without a marker element).
|
|
96
|
+
Core→Core only, so this file is prod-safe.
|
|
97
|
+
- `Client/2026-06-30a - SurfaceMetaGroupAcl.sql` — the `meta-group` `AclRecordScripts` dispatch grant
|
|
98
|
+
(roles 1/3/4), mirroring the `meta` grant.
|
|
99
|
+
- `Client_Compass/2026-06-30a` / `Client_CompassCanada/2026-06-30a` — per-client **MANAGER**-scope
|
|
100
|
+
SalesOrder section overrides; `Client_Quad/2026-06-30a` — Quad's **client-level** section deltas.
|
|
101
|
+
⚠ These three Client-DB seeds currently resolve **Core** ids via cross-DB subselect — see the
|
|
102
|
+
prod cross-cluster gotcha; they are paused before prod.
|
|
85
103
|
- `Client/2026-06-29c - SurfaceRecordScriptAcl.sql` — grants the surfaces `meta`/`debug` RecordScripts
|
|
86
104
|
in **`Client.AclRecordScripts`** per role (meta → Public(1)/SuperUser(3)/Base(4); debug →
|
|
87
105
|
SuperUser(3)). V2 authorizes scripted-API dispatch via `AclRecordScripts`, NOT record
|
|
@@ -122,7 +140,31 @@ SurfaceElements`). Core migrations run first; Client after. The session built/se
|
|
|
122
140
|
- **CLIENT-aclDatabase records** (`SurfaceOverrides`, `MessageTranslations`, `ThemeTokens`) need the
|
|
123
141
|
full 4-step ACL chain + field permissions in **each** client DB; resolve `roleId` by subselect.
|
|
124
142
|
|
|
143
|
+
- **⚠ PROD BLOCKER (ON HOLD) — Client-DB surface seeds must NOT resolve Core ids via cross-DB
|
|
144
|
+
subselect.** The `Client_Compass`/`Client_CompassCanada`/`Client_Quad` SalesOrder override seeds
|
|
145
|
+
resolve Core ids (`Core.RecordScripts`/`Core.Surfaces`/`Core.SurfaceElements`) with a cross-DB
|
|
146
|
+
subselect. This works only on the dev-sandbox **all-in-one** instance; in **production Core is a
|
|
147
|
+
separate cluster** and cross-DB queries require a single instance, so these seeds **break in prod**.
|
|
148
|
+
The established prod-safe pattern is `Client/2026-06-23b - ItemTranslationsAcl.sql`: **hardcode Core
|
|
149
|
+
ids as literals**, resolve only Client-local ids (e.g. `roleId` by name). The fix is paused; the
|
|
150
|
+
earlier `2026-06-25c` / `2026-06-29c` / `2026-06-25d` surface seeds **likely share the same
|
|
151
|
+
anti-pattern** and need auditing. Core→Core seeds are fine (same cluster). Also still open:
|
|
152
|
+
re-seeding NYCHH/Prudential/SPGlobal (deferred).
|
|
153
|
+
- **Display-section visibility cannot be per-role-overridden without an element.** The override
|
|
154
|
+
cascade overrides **ELEMENTS only**, never a surface's own `isVisible`. The 5 display-toggle SECTION
|
|
155
|
+
surfaces therefore each carry a single `sectionVisibility` marker element so a Client override can
|
|
156
|
+
flip a section per role/persona. Also discovered: `elementType` in `orderViewFields.json` is **dead
|
|
157
|
+
config** (no renderer reads it); and MANAGER/Quad summary tooltips can't be removed via the cascade
|
|
158
|
+
because `TOOLTIP_MESSAGE` is numeric-only (cosmetic, deferred).
|
|
159
|
+
|
|
125
160
|
## Change history
|
|
161
|
+
- 2026-06-30 — Added the SalesOrders SECTION-surface migration seeds: Core `meta-group` RecordScript +
|
|
162
|
+
`order-recurring` + 5 display-toggle SECTION surfaces (each with a `sectionVisibility` marker
|
|
163
|
+
element, since the cascade overrides elements not a surface's own isVisible), the `meta-group`
|
|
164
|
+
dispatch ACL grant, and per-client (Compass/CompassCanada MANAGER, Quad client-level) section
|
|
165
|
+
overrides. Recorded the ⚠ prod blocker: Client-DB seeds resolving Core ids via cross-DB subselect
|
|
166
|
+
break in prod (Core is a separate cluster) — must hardcode Core id literals per the
|
|
167
|
+
`ItemTranslationsAcl.sql` pattern; fix paused, earlier surface seeds likely share it. (jcardinal)
|
|
126
168
|
- 2026-06-29 — Added VendorItems + Inventory LIST seeds (`VendorItemsSurfaceSeed.sql` recordId 19 /
|
|
127
169
|
TableView 10; `InventorySurfaceSeed.sql` presentation-only, recordId+tableViewId NULL, Actions
|
|
128
170
|
anchored to items(21)). Added `SurfaceRecordScriptAcl.sql` (per-role `AclRecordScripts` dispatch
|
|
@@ -8,4 +8,5 @@
|
|
|
8
8
|
| [Column Visibility (URL-driven show/hide columns)](features/column-visibility.md) | A "Columns" header button that opens a modal listing every column from the table meta, lets the user show/hide columns, adjusts the table live, and persists the | toga25-supply/src/components/ColumnVisibilityModal/, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableData.tsx |
|
|
9
9
|
| [Meta-Driven Page & Table Setup](features/meta-driven-table-data.md) | A page in this app is **meta-driven end to end**: the page view model fetches *page meta* (labels, sections, ACL) and *table meta* (the columns/fields + table s | toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableState.ts, toga25-supply/src/hooks/useTablePageMeta.ts, toga-blox-npm/dist/hooks/useFetchPageMeta.d.ts, toga-blox-npm/dist/hooks/useFetchTablePageMeta.d.ts, toga-blox-npm/dist/hooks/useAssignTableFieldLabels.d.ts, toga-blox-npm/dist/components/Table/hooks/useTableData.d.ts |
|
|
10
10
|
| [Record Modals & Nested Tables](features/record-modals-and-nested-tables.md) | The repo's family of modal + nested-table patterns layered over toga-blox `TableRecordModal` and `PrimaryTable*Layout`. | toga25-supply/src/layout/ItemRecordModalLayout/, toga25-supply/src/layout/SalesOrderRecordModalLayout/, toga25-supply/src/layout/SalesOrderItemsTableLayout/, toga25-supply/src/layout/ItemFulfillmentModal/, toga25-supply/src/layout/GenericNestedTables/, toga25-supply/src/hooks/useTableCellInteractions.ts |
|
|
11
|
-
| [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from `GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported J | toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/surface/actionRegistry.ts, toga25-supply/src/surface/SurfaceActionBar.tsx, toga25-supply/src/surface/SurfaceSection.tsx, toga25-supply/src/surface/resolve.ts, toga25-supply/src/surface/types.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/Login/LoginPage.tsx, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/Items/ItemsPage.tsx, toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx, toga25-supply/src/pages/VendorItems/VendorItemsPage.tsx, toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx, toga25-supply/src/pages/Inventory/Inventory.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/index.ts, toga25-supply/src/fieldsConfig/index.ts |
|
|
11
|
+
| [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from `GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported J | toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/surface/actionRegistry.ts, toga25-supply/src/surface/SurfaceActionBar.tsx, toga25-supply/src/surface/SurfaceSection.tsx, toga25-supply/src/surface/resolve.ts, toga25-supply/src/surface/types.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/Login/LoginPage.tsx, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/Items/ItemsPage.tsx, toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx, toga25-supply/src/pages/VendorItems/VendorItemsPage.tsx, toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx, toga25-supply/src/pages/Inventory/Inventory.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/index.ts, toga25-supply/src/fieldsConfig/index.ts |
|
|
12
|
+
| [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 |
|
|
@@ -141,7 +141,22 @@ Scaffold per the `ItemRecordModalLayout` pattern (`src/layout/ItemRecordModalLay
|
|
|
141
141
|
configs must use a `scope` discriminator in the query key or they overwrite each other's cache
|
|
142
142
|
(record modal vs. approval modal: the approval modal omitting item-image fields blanked the
|
|
143
143
|
record modal's images until scoping isolated the entries).
|
|
144
|
+
- **`poNumber` does NOT go through the approval modal.** `SalesOrderView` routes
|
|
145
|
+
`activeModal === "poNumber"` to a **separate** `EnterPoNumberModal` and explicitly excludes it
|
|
146
|
+
from `SalesOrderApprovalModalsLayout` (`activeModal !== "poNumber"`). The approval-modal
|
|
147
|
+
`modalSelected` union still lists `"poNumber"`, but that path is handled by the PO modal, not
|
|
148
|
+
the approval flow — don't add PO-number handling to the approval layout.
|
|
149
|
+
- **Approval submit payload: workflow vs single decision differ.** A single approve/deny posts a
|
|
150
|
+
**boolean** `isApproved` (`true`/`false`). The workflow path
|
|
151
|
+
(`handleFormatApprovalWorkflowPayload`) posts a **string** `"1"`/`"0"`, emits a request **only**
|
|
152
|
+
for stages whose `note` / `assignedTo` / `decision` actually changed, and chooses verb by
|
|
153
|
+
`stage.ApprovalDecisions.uuid` — `null` → POST `/approval-decisions`, otherwise PUT
|
|
154
|
+
`/approval-decisions/{uuid}`.
|
|
144
155
|
|
|
145
156
|
## Change history
|
|
157
|
+
- 2026-06-30 — Recorded two behaviors confirmed while building approval-modal tests: `poNumber`
|
|
158
|
+
is handled by a separate `EnterPoNumberModal` (excluded from the approval layout), and the
|
|
159
|
+
workflow vs single-decision submit payloads differ (string `"1"`/`"0"` + changed-stages-only +
|
|
160
|
+
POST/PUT by decision uuid, vs boolean `isApproved`). (apeterson)
|
|
146
161
|
- 2026-06-23 — Documented from CLAUDE.md Modal Patterns 1–5 + the `create-record-modal` skill during
|
|
147
162
|
initial knowledge seed. (apeterson)
|
|
@@ -6,10 +6,13 @@ project: TOGa 2.5 Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-30
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/src/surface/useFetchSurfaceMeta.ts
|
|
13
|
+
- toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts
|
|
14
|
+
- toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx
|
|
15
|
+
- toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/index.ts
|
|
13
16
|
- toga25-supply/src/surface/evaluateSurfaceRule.ts
|
|
14
17
|
- toga25-supply/src/surface/actionRegistry.ts
|
|
15
18
|
- toga25-supply/src/surface/SurfaceActionBar.tsx
|
|
@@ -51,6 +54,12 @@ feature flag — see below). Backend: [surface-resolver](../../_underscore/featu
|
|
|
51
54
|
`raw` / `raw.data` / `raw.data.<route-key>` — because V2 nests a scripted-API return value one
|
|
52
55
|
level deeper, under a route-keyed slot of `data` (e.g. `data.meta`, `data.surfaces`). On a malformed
|
|
53
56
|
payload it falls back to an empty-elements bundle so the page never crashes.
|
|
57
|
+
- **`useFetchSurfaceMetaGroup(slugs[])`** — the grouped sibling of `useFetchSurfaceMeta`, for a screen
|
|
58
|
+
needing many section surfaces at once. **Order-insensitive** (the slug list is normalized so cache
|
|
59
|
+
hits don't depend on argument order), `staleTime: Infinity`, hits the grouped endpoint
|
|
60
|
+
`GET /v2/surfaces/meta-group?slugs=...`, and extracts the `{surfaces}` map via `extractGroup()` /
|
|
61
|
+
`looksLikeGroup()` (the same structural-fingerprint approach as `extractBundle`). Returns `{}` for
|
|
62
|
+
unmigrated screens (`enabled:false`), so a non-roster client does no fetch.
|
|
54
63
|
- **`evaluateSurfaceRule`** — the Tier-1 rule evaluator: a **frozen `all/any/none` + `{field,op,value}`
|
|
55
64
|
grammar** that **throws on an unknown op** (generalized from the SalesOrders
|
|
56
65
|
`buildPatchedTenantFields`/`evaluateEnableRule`/`resolveFlag` helpers). Evaluated client-side against
|
|
@@ -114,20 +123,35 @@ modal copy, visibility/enable) to Surface; **leave data-fetch wiring (fetch slug
|
|
|
114
123
|
nested-table topology) in JSON/code.** Not every screen reduces to a generic `SurfaceSection` swap —
|
|
115
124
|
see SalesOrders below.
|
|
116
125
|
|
|
117
|
-
## SalesOrders
|
|
118
|
-
|
|
119
|
-
The SalesOrders detail
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
code
|
|
130
|
-
|
|
126
|
+
## SalesOrders record-modal sections — migrated via the registered-renderer seam (roster-gated)
|
|
127
|
+
|
|
128
|
+
The SalesOrders detail sections are now migrated onto Surface for the **seeded roster clients only**,
|
|
129
|
+
via the registered-renderer approach (not a generic `SurfaceSection` swap — `SalesOrderSummaryGrid`'s
|
|
130
|
+
three bespoke renderers `detailSection`/`locationCard`/`totalsCard` are untouched).
|
|
131
|
+
|
|
132
|
+
- **`surfaceBundleToTenantFields()`** (`SalesOrders/helpers/`) adapts the grouped Surface bundle into
|
|
133
|
+
the existing **`TenantFields`** shape, so the existing `SalesOrderSummaryGrid` renderers and
|
|
134
|
+
`getDetailSections` are untouched — **only the SOURCE of section config moves JSON → Surface.**
|
|
135
|
+
Business logic stays in code: the recurring-lease `_totalLease` gate in the grid and approval
|
|
136
|
+
gating are **not** moved. (Registered-renderer seam: *config describes, code decides.*)
|
|
137
|
+
- **Roster gate — `SALES_ORDER_SURFACE_MIGRATED_CLIENTS = [COMPASS, COMPASSCANADA, QUAD]`** (an
|
|
138
|
+
in-code roster in `surfaceBundleToTenantFields.ts`; the view-model
|
|
139
|
+
`useSalesOrderRecordModalLayoutModel.tsx` consults it). The view-model resolves sections from
|
|
140
|
+
Surface **only** for roster clients; every other client (incl. never-migrated
|
|
141
|
+
NYCHH/Prudential/SPGlobal) skips the fetch and stays fully JSON-driven. Merge is **JSON-base +
|
|
142
|
+
Surface-wins.** ⚠ **Why a roster, not a flag:** the Core SECTION surfaces are **shared** and seeded
|
|
143
|
+
from Compass's shape — without the gate every client would inherit Compass's base and regress. A
|
|
144
|
+
**code** roster (not an env flag) is deliberate: a client is removed from the roster only once its
|
|
145
|
+
own seeds are verified.
|
|
146
|
+
- **JSON cleanup:** the migrated section blocks (orderDetails/shipTo/billTo/orderSummary/
|
|
147
|
+
orderRecurring + the 5 display sections) were **deleted** from the COMPASS / COMPASSCANADA / QUAD
|
|
148
|
+
`orderViewFields.json` files. Data-wiring (`salesOrderDetailsConfig`/`itemFulfillmentClickRule`/
|
|
149
|
+
`recordActionFields`/`recordApprovals`) was **kept**. The DEFAULT `orderViewFields.json` is left
|
|
150
|
+
**intact** — non-roster clients are still JSON-driven.
|
|
151
|
+
|
|
152
|
+
**Still JSON / deferred:** the SalesOrders **action-bar / approval-modal** migration remains on JSON;
|
|
153
|
+
seeding NYCHH/Prudential/SPGlobal is deferred; the Client-DB prod cross-cluster seed fix is on hold
|
|
154
|
+
(see [surface-layer-schema](../../dbchanges2/features/surface-layer-schema.md)).
|
|
131
155
|
|
|
132
156
|
## Gotchas
|
|
133
157
|
|
|
@@ -147,6 +171,14 @@ code/`_underscore`, plus reconciling those seeds and the Compass approval-modal
|
|
|
147
171
|
treat type-checking as pending. Runtime `GET /v2/surfaces/{slug}/meta` also not yet exercised.
|
|
148
172
|
|
|
149
173
|
## Change history
|
|
174
|
+
- 2026-06-30 — Migrated the SalesOrders record-modal sections onto Surface for the roster clients
|
|
175
|
+
[COMPASS, COMPASSCANADA, QUAD] via the registered-renderer seam: added `useFetchSurfaceMetaGroup`
|
|
176
|
+
(order-insensitive grouped `/surfaces/meta-group` fetch) + `surfaceBundleToTenantFields()` (adapts
|
|
177
|
+
the grouped bundle to the existing `TenantFields` shape; bespoke grid renderers + lease/approval
|
|
178
|
+
logic stay in code). Roster-gated (code roster, JSON-base + Surface-wins) so non-roster clients
|
|
179
|
+
don't inherit Compass's shared base. Deleted the migrated section blocks from COMPASS/COMPASSCANADA/
|
|
180
|
+
QUAD `orderViewFields.json` (data-wiring kept; DEFAULT intact). Action-bar/approval-modal still
|
|
181
|
+
JSON. (jcardinal)
|
|
150
182
|
- 2026-06-29 — Migrated VendorItems (screen 2, recipe-exact) and Inventory (screen 3,
|
|
151
183
|
**presentation-only by decision** — fetch topology stays JSON; only title/buttons/modal copy/entity
|
|
152
184
|
tags moved). Fixed the "s.elements is not iterable" crash: `extractBundle()` now finds the bundle by
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Cypress Testing Harness (component + e2e)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga25-supply
|
|
5
|
+
project: TOGa 2.5 Supply
|
|
6
|
+
client: shared
|
|
7
|
+
type: workflow
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-30
|
|
10
|
+
owners: [apeterson]
|
|
11
|
+
files:
|
|
12
|
+
- toga25-supply/cypress.config.ts
|
|
13
|
+
- toga25-supply/cypress/tsconfig.json
|
|
14
|
+
- toga25-supply/cypress/support/component.tsx
|
|
15
|
+
- toga25-supply/cypress/support/component-index.html
|
|
16
|
+
- toga25-supply/cypress/support/e2e.ts
|
|
17
|
+
- toga25-supply/cypress/support/commands.ts
|
|
18
|
+
- toga25-supply/cypress/support/fixtures.ts
|
|
19
|
+
- toga25-supply/cypress/support/mocks/useApprovalModalViewModel.ts
|
|
20
|
+
- toga25-supply/cypress/component/SalesOrderApprovalModalsLayout.cy.tsx
|
|
21
|
+
- toga25-supply/cypress/component/RecordApprovalModalLayout.cy.tsx
|
|
22
|
+
- toga25-supply/cypress/component/EnterPoNumberModal.cy.tsx
|
|
23
|
+
- toga25-supply/cypress/e2e/salesOrderApproval.cy.ts
|
|
24
|
+
related:
|
|
25
|
+
- ../features/record-modals-and-nested-tables.md
|
|
26
|
+
- ../features/action-button-rule-engine.md
|
|
27
|
+
- ../architecture.md
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## What it is
|
|
31
|
+
|
|
32
|
+
The Cypress test harness for the `toga25-supply` frontend, bootstrapped from scratch
|
|
33
|
+
(`cypress` was already a dependency but there was no config, no `cypress/` dir, and zero
|
|
34
|
+
tests anywhere in the repo). It defines **both** a `component` project (Vite devServer,
|
|
35
|
+
mounts a single component) and an `e2e` project, plus reusable support code for mounting
|
|
36
|
+
components with the app's providers, mocking a component's data hook without a network or
|
|
37
|
+
tenant config, and sharing fixtures. The first specs cover the Sales Order **approval
|
|
38
|
+
modal flow**. Use this doc when adding any new test to the repo.
|
|
39
|
+
|
|
40
|
+
## How the harness is wired
|
|
41
|
+
|
|
42
|
+
### `cypress.config.ts` — two projects
|
|
43
|
+
|
|
44
|
+
- **`component`** — `devServer: { framework: "react", bundler: "vite" }` with a `viteConfig`
|
|
45
|
+
that **mirrors the app's `vite.config.ts`**: the `@` → `src` alias, the regex aliases that
|
|
46
|
+
force a single React copy, `optimizeDeps.exclude: ["@agilant/toga-blox"]` +
|
|
47
|
+
`optimizeDeps.include: ["dayjs"]`. This is the load-bearing part — toga-blox must resolve
|
|
48
|
+
the same way it does under `npm run dev`, or mounted components fail to bundle or get a
|
|
49
|
+
second React copy.
|
|
50
|
+
- **`e2e`** — standard `baseUrl` + `supportFile` setup for full-journey specs.
|
|
51
|
+
|
|
52
|
+
### `cypress/support/component.tsx` + `commands.ts` — mounting with providers
|
|
53
|
+
|
|
54
|
+
`cy.mountWithProviders` is a custom command that wraps the mounted component in
|
|
55
|
+
`QueryClientProvider` + `ToasterProvider`. It accepts an **injectable `queryClient`** so a
|
|
56
|
+
spec can `cy.spy()` on `removeQueries` / `invalidateQueries` and assert the cache routine a
|
|
57
|
+
component runs on success. The default QueryClient is configured with `retry: false` for
|
|
58
|
+
determinism. Support also imports `@/index.css` (styling) and `@/api/api` — the latter so
|
|
59
|
+
toga-blox's `apiPost` / `apiPut` have a configured axios instance available during component
|
|
60
|
+
tests.
|
|
61
|
+
|
|
62
|
+
### Window-backed hook-mock pattern (key technique)
|
|
63
|
+
|
|
64
|
+
`cypress/support/mocks/useApprovalModalViewModel.ts` lets you unit-test a data-binding
|
|
65
|
+
component **without** a network or tenant config. The component devServer aliases the
|
|
66
|
+
component's **relative** import of its view model to the mock:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
// in the component viteConfig resolve.alias
|
|
70
|
+
{ find: /^\.\/viewModel\/useApprovalModalViewModel$/, replacement: "<abs>/cypress/support/mocks/useApprovalModalViewModel.ts" }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The mock returns whatever the spec parked on `window.__approvalModalViewModel`. In Cypress
|
|
74
|
+
component testing the spec and the component share the **same `window`**, so a plain
|
|
75
|
+
assignment before `cy.mount` is visible to the hook. Helpers `setApprovalModalViewModel` /
|
|
76
|
+
`resetApprovalModalViewModel` set and clear it. To mock a different component's hook, add a
|
|
77
|
+
parallel regex alias + a parallel `window.__<name>` mock.
|
|
78
|
+
|
|
79
|
+
### `cypress/support/fixtures.ts` — shared fixtures
|
|
80
|
+
|
|
81
|
+
Imports the **real shipped** tenant config
|
|
82
|
+
`src/pages/SalesOrders/.../viewModel/FIELDS/QUAD/approvalActionFields.json` as
|
|
83
|
+
`recordActionFields`, so tests track the actual field / label / guardrail config the app
|
|
84
|
+
ships (not a hand-rolled copy that drifts). Adds synthetic order / approval-stages / PO
|
|
85
|
+
fixtures alongside it. (The QUAD file is reused only as a representative real config — this
|
|
86
|
+
harness is generic test infra, not Quad-specific behavior.)
|
|
87
|
+
|
|
88
|
+
## What the specs cover
|
|
89
|
+
|
|
90
|
+
- **`SalesOrderApprovalModalsLayout.cy.tsx`** (component) — the sales-order binding:
|
|
91
|
+
null-guards, `isApproveButtonEnabled` derivation, and the documented `onSuccess` cache
|
|
92
|
+
routine asserted via the spied QueryClient.
|
|
93
|
+
- **`RecordApprovalModalLayout.cy.tsx`** (component) — the generic approval modal flow: deny
|
|
94
|
+
requires a reason (approve optional), submit fires the decision API + `onSuccess`/`onClose`,
|
|
95
|
+
discard-reason guardrail on Cancel/backdrop, workflow approve-enablement, deep workflow
|
|
96
|
+
stage editing (deny one stage with a required note → submits only that stage; an empty
|
|
97
|
+
required note blocks submit), and the **decision-failure path** (500 → modal stays open,
|
|
98
|
+
`onSuccess`/`onClose` not called).
|
|
99
|
+
- **`EnterPoNumberModal.cy.tsx`** (component) — the PO-number action: create requires a PO#,
|
|
100
|
+
POST `/purchase-orders/`, credit-card flag `0`/`1`, edit → PUT.
|
|
101
|
+
- **`salesOrderApproval.cy.ts`** (e2e) — full journey: seed auth via
|
|
102
|
+
`localStorage.accessToken`, row-action menu → Deny → submit, stubbing only the write.
|
|
103
|
+
|
|
104
|
+
## Conventions for writing tests here
|
|
105
|
+
|
|
106
|
+
- **Component test of a data-binding component** → use the window-backed hook mock so you
|
|
107
|
+
never need a real network/tenant config; park state on `window.__<hook>` before `cy.mount`.
|
|
108
|
+
- **Asserting a cache side-effect** (invalidate/remove queries on success) → inject a spied
|
|
109
|
+
`queryClient` into `cy.mountWithProviders`.
|
|
110
|
+
- **Testing an async error path** → use `cy.on("uncaught:exception", ...)` to swallow the
|
|
111
|
+
rejected `mutateAsync` so the rejection doesn't fail the spec, then assert the modal stayed
|
|
112
|
+
open and `onSuccess`/`onClose` were not called.
|
|
113
|
+
- **e2e auth** → seed `localStorage.accessToken`; stub only the write request, let reads hit
|
|
114
|
+
the configured backend (document any backend prerequisites in the spec).
|
|
115
|
+
- **Keep the component devServer config in sync with `vite.config.ts`** — when the app's
|
|
116
|
+
Vite aliases or `optimizeDeps` change, mirror the change into `cypress.config.ts` or
|
|
117
|
+
component mounts break.
|
|
118
|
+
|
|
119
|
+
## package.json scripts
|
|
120
|
+
|
|
121
|
+
`cy:open`, `cy:run:ct` (component), `cy:run:e2e`, `test:ct`.
|
|
122
|
+
`.gitignore` excludes `cypress/videos|screenshots|downloads`.
|
|
123
|
+
|
|
124
|
+
## Gotchas
|
|
125
|
+
|
|
126
|
+
- **Cypress's bundled Electron cannot launch in the sandboxed macOS dev environment** — it
|
|
127
|
+
fails the pre-run smoke test (`bad option: --no-sandbox / --smoke-test / --ping`), which
|
|
128
|
+
blocks **all** specs before any run. The specs in this harness were validated by
|
|
129
|
+
`tsc -p cypress/tsconfig.json` (0 errors) and `eslint cypress` (clean) but **could not be
|
|
130
|
+
executed in that sandbox** — run them locally with `npm run cy:run:ct`.
|
|
131
|
+
- **`eslint.config.js` is protected by a config-protection hook.** When a spec/support file
|
|
132
|
+
trips a lint rule (e.g. `no-explicit-any`), **fix the source to satisfy the linter** (use
|
|
133
|
+
`unknown` / typed shapes) — do **not** weaken the shared eslint config to make tests pass.
|
|
134
|
+
The override edit to `eslint.config.js` is blocked by the hook by design.
|
|
135
|
+
- **`@agilant/toga-blox` consumes `dist/`** — same as the app, the harness sees built
|
|
136
|
+
toga-blox, so a toga-blox source change needs a rebuild before tests observe it.
|
|
137
|
+
|
|
138
|
+
## Change history
|
|
139
|
+
- 2026-06-30 — Bootstrapped the Cypress harness from scratch (dual component + e2e config,
|
|
140
|
+
`cy.mountWithProviders`, window-backed hook-mock pattern, shared fixtures off the real
|
|
141
|
+
QUAD `approvalActionFields.json`) and added the first specs for the Sales Order approval
|
|
142
|
+
modal flow. Documented the sandbox-Electron blocker and the protected-eslint decision.
|
|
143
|
+
(apeterson)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -28,7 +28,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
28
28
|
- **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
|
|
29
29
|
- **ai-bdr** (AI-BDR) — 4 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
|
|
30
30
|
- **toga2-commerce** (TOGa Commerce) — 7 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
|
|
31
|
-
- **toga25-supply** (TOGa 2.5 Supply) —
|
|
31
|
+
- **toga25-supply** (TOGa 2.5 Supply) — 8 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
|
|
32
32
|
- **toga-blox** (TOGa Blox) — 7 doc(s) → [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
|
|
33
33
|
|
|
34
34
|
## standalone framework
|
|
@@ -5,6 +5,7 @@ apps:
|
|
|
5
5
|
- _underscore
|
|
6
6
|
- api2
|
|
7
7
|
- toga2-supply
|
|
8
|
+
- toga25-supply
|
|
8
9
|
- toga2-commerce
|
|
9
10
|
- worker2
|
|
10
11
|
- dbchanges2
|
|
@@ -12,7 +13,7 @@ project: _Underscore
|
|
|
12
13
|
client: quad
|
|
13
14
|
type: profile
|
|
14
15
|
status: active
|
|
15
|
-
updated: 2026-06-
|
|
16
|
+
updated: 2026-06-30
|
|
16
17
|
owners: ["jcardinal", "bala"]
|
|
17
18
|
files: []
|
|
18
19
|
related:
|
package/package.json
CHANGED