toga-ai 1.0.387 → 1.0.389
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/features/surface-resolver.md +52 -3
- package/knowledge/2.0/apps/api2/features/surface-meta-option.md +23 -0
- package/knowledge/2.0/apps/dbchanges2/INDEX.md +1 -1
- package/knowledge/2.0/apps/dbchanges2/features/surface-layer-schema.md +40 -0
- package/knowledge/2.0/apps/toga25-supply/INDEX.md +1 -1
- package/knowledge/2.0/apps/toga25-supply/features/surface-frontend.md +52 -0
- package/knowledge/sessions/2026-07-21-surface-approve-steptwo-apeterson.md +70 -0
- package/package.json +1 -1
|
@@ -160,6 +160,19 @@ breaks logic.
|
|
|
160
160
|
`_Model_<Client>_*` overrides + `Client_ApiPayloadInterceptor` hooks — the Surface layer does
|
|
161
161
|
**not** absorb business logic.
|
|
162
162
|
|
|
163
|
+
### How the backend Tier-2 gate ACTUALLY dispatches (and `config.tier2` is DEAD)
|
|
164
|
+
The Tier-2 gate in `resolveRecordState → _resolveTier2State` (`Surface.php` ~line 280) dispatches
|
|
165
|
+
**by the element's ACTION SLUG** to a model method named by `const TIER2_CAPABILITY_METHOD`
|
|
166
|
+
(`resolveSurfaceActionState()`) on the resolved `_Model_<Client>_*` — it does **NOT** read
|
|
167
|
+
`config.tier2`. **`config.tier2` on a SurfaceElement is a NON-FUNCTIONAL marker: nothing —
|
|
168
|
+
backend or frontend — reads it** (the FE evaluator `resolveElementState.ts` never reads it
|
|
169
|
+
either), so do **not** spend effort "moving" it. As of 2026-07-20 the surface Tier-2 path is
|
|
170
|
+
effectively a **no-op**: **no model implements `resolveSurfaceActionState()`** (only `Surface.php`
|
|
171
|
+
references it), and **`stepTwoAssigned` does not exist anywhere in the backend** — so a Tier-2
|
|
172
|
+
predicate like the Approve `stepTwoAssigned` gate below cannot be resolved server-side today. It is
|
|
173
|
+
currently evaluable only by the **legacy** FE rule engine (see
|
|
174
|
+
[surface-frontend](../../toga25-supply/features/surface-frontend.md) — the two FE engines).
|
|
175
|
+
|
|
163
176
|
## Per-client override of the Tier-1 gating RULES (not just static flags)
|
|
164
177
|
|
|
165
178
|
Originally only static flags (`isVisible`/`isEnabled`) and `config` were per-client overridable
|
|
@@ -219,9 +232,34 @@ semantics). Concrete per-client diffs, all via `SurfaceOverrides` (rule JSON is
|
|
|
219
232
|
- **Compass USA / Compass Canada** (mirror each other) — two-stage approval:
|
|
220
233
|
- **ADMIN**: opts into all four gated record-action buttons (approve/deny/approvalWorkflow/editOrder)
|
|
221
234
|
`IS_VISIBLE=1`, and widens **both** `VISIBILITY_RULE` **and** `ENABLED_RULE` to
|
|
222
|
-
`_status in [pendingApproval, pendingInitialApproval]`. (For approve,
|
|
223
|
-
predicate
|
|
224
|
-
`CONFIG` `additionalData._status = [pendingApproval, pendingInitialApproval]`.
|
|
235
|
+
`_status in [pendingApproval, pendingInitialApproval]`. (For approve, a Tier-2 `stepTwoAssigned`
|
|
236
|
+
predicate further gates *enabled* — see the corrected Approve rule below.) On the approvals filter
|
|
237
|
+
button: `IS_VISIBLE=1` + `CONFIG` `additionalData._status = [pendingApproval, pendingInitialApproval]`.
|
|
238
|
+
|
|
239
|
+
### Corrected Approve-button enable rule (Compass / Compass Canada ADMIN) — decision 2026-07-20
|
|
240
|
+
The correct enable predicate for the Approve button is
|
|
241
|
+
**`(status pendingInitialApproval AND stepTwoAssigned) OR (status pendingApproval)`** — i.e. a
|
|
242
|
+
first-stage order is approvable only once step two is assigned, but a second-stage order is always
|
|
243
|
+
approvable. As DSL:
|
|
244
|
+
```json
|
|
245
|
+
{"any":[{"all":[{"field":"order._status","op":"in","value":["pendingInitialApproval"]},{"type":"stepTwoAssigned"}]},{"field":"order._status","op":"in","value":["pendingApproval"]}]}
|
|
246
|
+
```
|
|
247
|
+
**Decision:** this rule lives **ONLY in the per-client `SurfaceOverride` row** as attribute
|
|
248
|
+
`ENABLED_RULE`, encoded in the **`c_longValue`** column (with `value` NULL) — it is **NOT** baked
|
|
249
|
+
into Core's element `enabledRule`, keeping Core client-neutral (client/role-specific gating stays in
|
|
250
|
+
overrides). This works because `_castOverride` reads `value` first and **falls back to `c_longValue`**
|
|
251
|
+
(`Surface.php` ~line 871), and `c_longValue mediumtext` avoids the `varchar(255)` truncation the JSON
|
|
252
|
+
rule would hit. **Scope:** the Approve element on **surface 8** (`sales-order-record-actions`, el 17)
|
|
253
|
+
and **surface 3** (`sales-order-listing-row-actions`, el 29). The approval-workflow-modal Approve
|
|
254
|
+
element does not exist yet and is **deferred**.
|
|
255
|
+
|
|
256
|
+
> ⚠ **Not resolvable server-side today.** The rule carries a `{"type":"stepTwoAssigned"}` named node,
|
|
257
|
+
> which the backend Tier-2 path cannot resolve (see the Tier-2 dispatch note above — no
|
|
258
|
+
> `resolveSurfaceActionState`, no backend `stepTwoAssigned`) **and** the frozen Tier-1 FE evaluator
|
|
259
|
+
> `evaluateSurfaceRule` **throws** on `{type}` nodes by design. Only the **legacy** FE engine
|
|
260
|
+
> (`evaluateEnableRule`) can evaluate it today; enabling the surface path needs both (a) teaching
|
|
261
|
+
> `evaluateSurfaceRule` the named check and (b) feeding it live approval-stage data. See
|
|
262
|
+
> [surface-frontend](../../toga25-supply/features/surface-frontend.md).
|
|
225
263
|
- **MANAGER**: opts into **approve + deny only** (`IS_VISIBLE=1`); rules left at the Core base
|
|
226
264
|
(`pendingApproval` only, single-stage). No approvals-filter override → inherits Core `isVisible=0`
|
|
227
265
|
(hidden).
|
|
@@ -353,6 +391,17 @@ Core record grants + their logic-group expressions all evaluate `all`/`"1"`. The
|
|
|
353
391
|
match Compass, a follow-up migration aligning both `meta` and `meta-group` to roles 1,3,4 is needed.
|
|
354
392
|
|
|
355
393
|
## Change history
|
|
394
|
+
- 2026-07-20 — Clarified the **backend Tier-2 gate reality**: `_resolveTier2State` (Surface.php ~280)
|
|
395
|
+
dispatches by **action slug** to `resolveSurfaceActionState()` (`TIER2_CAPABILITY_METHOD`), **not**
|
|
396
|
+
by `config.tier2` — `config.tier2` is a **dead marker nothing reads** (backend or FE). The surface
|
|
397
|
+
Tier-2 path is currently a **no-op**: no model implements `resolveSurfaceActionState`, and
|
|
398
|
+
`stepTwoAssigned` doesn't exist in the backend. Recorded the **decision** on the corrected
|
|
399
|
+
Compass/CompassCanada ADMIN Approve rule — `(pendingInitialApproval AND stepTwoAssigned) OR
|
|
400
|
+
pendingApproval` — encoded as an `ENABLED_RULE` `SurfaceOverride` in `c_longValue` (value NULL;
|
|
401
|
+
`_castOverride` value→c_longValue fallback, ~line 871), scoped to surface 8 el 17 + surface 3 el 29,
|
|
402
|
+
NOT in Core; approval-workflow-modal Approve deferred. Flagged the rule's `{type:stepTwoAssigned}`
|
|
403
|
+
node is evaluable only by the legacy FE engine today (backend Tier-2 no-op + frozen `evaluateSurfaceRule`
|
|
404
|
+
throws on `{type}`). (apeterson)
|
|
356
405
|
- 2026-07-20 — Extended the resolver to emit `messages.trueLabel`/`messages.falseLabel` per element,
|
|
357
406
|
sourced from `trueMessageId`/`falseMessageId` (collected into the same message batch + id→key remap);
|
|
358
407
|
additive + backward-compatible (unset → null). Closes a latent gap: the serializer historically emitted
|
|
@@ -33,6 +33,23 @@ resolved in one batched pass, per row in `meta`). When the option is **absent**
|
|
|
33
33
|
traffic), behavior is **byte-for-byte unchanged** — pure data, zero extra work. The path is
|
|
34
34
|
**defensive**: it catches `Throwable` and omits `meta.surface` on error rather than failing the call.
|
|
35
35
|
|
|
36
|
+
### `meta.surface` payload shapes (V2.php ~5688-5717)
|
|
37
|
+
The state pass runs **only** when the request carries the `surface` option **AND** the action is
|
|
38
|
+
**READ** — no other method computes it. The attached shapes:
|
|
39
|
+
- **Single GET:** `{ slug, state: { <elementUuid>: { isVisible, isEnabled } } }`.
|
|
40
|
+
- **LIST:** `{ slug, records: { <recordUuid>: { elements: { <elementUuid>: { isVisible, isEnabled } } } } }`.
|
|
41
|
+
|
|
42
|
+
`recordSurfaceState` is the channel a **server-computed Tier-2 gate** rides down to the FE (see the
|
|
43
|
+
Tier-2 dispatch note in [surface-resolver](../../_underscore/features/surface-resolver.md)). On the
|
|
44
|
+
FE, the type `RecordSurfaceState` (`src/surface/types.ts`) is
|
|
45
|
+
`Record<string, { isVisible?, isEnabled?, isComplete? }>`, and `resolveElementState` **ANDs**
|
|
46
|
+
`elementState.isEnabled`/`isVisible` onto the static + Tier-1 result.
|
|
47
|
+
|
|
48
|
+
> ⚠ **Keying caveat (unverified — confirm before relying on the Tier-2 record-state path).** The
|
|
49
|
+
> backend keys `state`/`elements` by **element UUID**, but the FE `resolveElementState` looks the
|
|
50
|
+
> state up by **`element.action.key`**. Confirm those two key spaces line up (or add a UUID→action-key
|
|
51
|
+
> bridge) before a server-computed per-record gate can actually take effect on the FE.
|
|
52
|
+
|
|
36
53
|
## Key rules
|
|
37
54
|
|
|
38
55
|
- **Presentation state lives in `meta.surface`, never `data`.** Integrations get pure business data.
|
|
@@ -77,6 +94,12 @@ traffic), behavior is **byte-for-byte unchanged** — pure data, zero extra work
|
|
|
77
94
|
silently lack it.
|
|
78
95
|
|
|
79
96
|
## Change history
|
|
97
|
+
- 2026-07-20 — Documented the `meta.surface`/`recordSurfaceState` payload flow: computed only when the
|
|
98
|
+
`surface` option is present AND action is READ (V2.php ~5688-5717); single-GET shape
|
|
99
|
+
`{slug, state:{<elementUuid>:{isVisible,isEnabled}}}`, LIST shape
|
|
100
|
+
`{slug, records:{<recordUuid>:{elements:{…}}}}`; FE type `RecordSurfaceState` + `resolveElementState`
|
|
101
|
+
ANDs it onto the static+Tier-1 result. Flagged a keying caveat: backend keys by element UUID, FE
|
|
102
|
+
looks up by `element.action.key` — alignment unverified. (apeterson)
|
|
80
103
|
- 2026-07-15 — Documented the diagnostic tell (`identifiers.script` present on the 403 EZ-1 → it is
|
|
81
104
|
the `AclRecordScripts` dispatch gate, not record READ); clarified the dispatch check reads the
|
|
82
105
|
caller's **CORE** roles because `surfaces.aclDatabase = CORE` (contradicts the earlier "client
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [Database Changes (dbchanges2) Repository Architecture](architecture.md) | `dbchanges2` is the **schema-migration / SQL change-set repository** for the entire 2.0 platform. | Core/, Client/, Client_<Tenant>/, Logs/, Logs_Client/, _modules/ |
|
|
6
|
-
| [Surface Layer Schema (UI presentation/config tables)](features/surface-layer-schema.md) | The persistent schema for the platform-wide **Surface** UI presentation/configuration layer (see the `_underscore` [surface-resolver](../../_underscore/features | dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client_Compass/2026-06-25d - SalesOrderSurfaceClientSeed.sql, _underscore/Model/Client/ThemeToken.php, toga25-supply/src/themeConfig.json, dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql, dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql, dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql, dbchanges2/Core/2026-06-29a - ItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql, dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql, dbchanges2/Core/2026-06-29d - VendorItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29e - InventorySurfaceSeed.sql, dbchanges2/Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql, dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql, dbchanges2/Client_Compass/2026-06-30a - SalesOrderDisplaySectionManagerOverrides.sql, dbchanges2/Client_CompassCanada/2026-06-30a - SalesOrderSurfaceManagerOverrides.sql, dbchanges2/Client_Quad/2026-06-30a - SalesOrderSurfaceClientOverrides.sql, dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql, dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Core/2026-07-17h - Update - ClearApprovalsFilterButtonConfig.sql, dbchanges2/Client_Compass/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Compass/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_Quad/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Quad/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Core/2026-07-20a - Update - HideAdminNotesSectionByDefault.sql, dbchanges2/Core/2026-07-20b - Update - NotesSectionFieldElements.sql, dbchanges2/Client_Compass/2026-07-20a - AdminNotesSectionVisibilityOverride.sql, dbchanges2/Client_Compass/2026-07-20b - NotesSectionFieldsOverride.sql, dbchanges2/Client_CompassCanada/2026-07-20a - AdminNotesSectionVisibilityOverride.sql, dbchanges2/Client_CompassCanada/2026-07-20b - NotesSectionFieldsOverride.sql, dbchanges2/Client_Quad/2026-07-20a - NotesSectionFieldsOverride.sql, dbchanges2/Core/2026-07-20c - Update - VendorItemsToggleSurfaceSeed.sql, dbchanges2/Client_Compass/2026-07-20c - ItemRecordEditButtonEnable.sql, dbchanges2/Client_Compass/2026-07-20d - ItemRecordVendorItemsEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20c - ItemRecordEditButtonEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20d - ItemRecordVendorItemsEnable.sql, dbchanges2/Core/2026-07-17 - README - RUN ORDER.md |
|
|
6
|
+
| [Surface Layer Schema (UI presentation/config tables)](features/surface-layer-schema.md) | The persistent schema for the platform-wide **Surface** UI presentation/configuration layer (see the `_underscore` [surface-resolver](../../_underscore/features | dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client_Compass/2026-06-25d - SalesOrderSurfaceClientSeed.sql, _underscore/Model/Client/ThemeToken.php, toga25-supply/src/themeConfig.json, dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql, dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql, dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql, dbchanges2/Core/2026-06-29a - ItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql, dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql, dbchanges2/Core/2026-06-29d - VendorItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29e - InventorySurfaceSeed.sql, dbchanges2/Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql, dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql, dbchanges2/Client_Compass/2026-06-30a - SalesOrderDisplaySectionManagerOverrides.sql, dbchanges2/Client_CompassCanada/2026-06-30a - SalesOrderSurfaceManagerOverrides.sql, dbchanges2/Client_Quad/2026-06-30a - SalesOrderSurfaceClientOverrides.sql, dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql, dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Core/2026-07-17h - Update - ClearApprovalsFilterButtonConfig.sql, dbchanges2/Client_Compass/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Compass/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_Quad/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Quad/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Core/2026-07-20a - Update - HideAdminNotesSectionByDefault.sql, dbchanges2/Core/2026-07-20b - Update - NotesSectionFieldElements.sql, dbchanges2/Client_Compass/2026-07-20a - AdminNotesSectionVisibilityOverride.sql, dbchanges2/Client_Compass/2026-07-20b - NotesSectionFieldsOverride.sql, dbchanges2/Client_CompassCanada/2026-07-20a - AdminNotesSectionVisibilityOverride.sql, dbchanges2/Client_CompassCanada/2026-07-20b - NotesSectionFieldsOverride.sql, dbchanges2/Client_Quad/2026-07-20a - NotesSectionFieldsOverride.sql, dbchanges2/Core/2026-07-20c - Update - VendorItemsToggleSurfaceSeed.sql, dbchanges2/Client_Compass/2026-07-20c - ItemRecordEditButtonEnable.sql, dbchanges2/Client_Compass/2026-07-20d - ItemRecordVendorItemsEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20c - ItemRecordEditButtonEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20d - ItemRecordVendorItemsEnable.sql, dbchanges2/Core/2026-07-20e - RestoreApproveDenyRowActions.sql, dbchanges2/Client_Compass/2026-07-20e - RowActionsApprovalWorkflowAdminEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20e - RowActionsApprovalWorkflowAdminEnable.sql, dbchanges2/Core/2026-07-17 - README - RUN ORDER.md |
|
|
7
7
|
| [2.0 New-Client Onboarding (manual process)](workflows/client-onboarding.md) | > **A local browser wizard now automates this.** Steps 2–9 below (create DBs, generate Core/API > inserts, append to `Clients_Db.txt`) — plus the dbchanges2 bla | Client/, Client_<Tenant>/, Core/, Logs_Client/ |
|
|
@@ -50,6 +50,9 @@ files:
|
|
|
50
50
|
- dbchanges2/Client_Compass/2026-07-20d - ItemRecordVendorItemsEnable.sql
|
|
51
51
|
- dbchanges2/Client_CompassCanada/2026-07-20c - ItemRecordEditButtonEnable.sql
|
|
52
52
|
- dbchanges2/Client_CompassCanada/2026-07-20d - ItemRecordVendorItemsEnable.sql
|
|
53
|
+
- dbchanges2/Core/2026-07-20e - RestoreApproveDenyRowActions.sql
|
|
54
|
+
- dbchanges2/Client_Compass/2026-07-20e - RowActionsApprovalWorkflowAdminEnable.sql
|
|
55
|
+
- dbchanges2/Client_CompassCanada/2026-07-20e - RowActionsApprovalWorkflowAdminEnable.sql
|
|
53
56
|
- dbchanges2/Core/2026-07-17 - README - RUN ORDER.md
|
|
54
57
|
related:
|
|
55
58
|
- ../../_underscore/features/surface-resolver.md
|
|
@@ -180,6 +183,33 @@ to `{}` so Core carries **no** `_status` default. Every client — even Quad, wh
|
|
|
180
183
|
old default — now declares the full filter config via its own `CONFIG` override. This makes the Core
|
|
181
184
|
seed a true neutral default for the filter button, matching the action-button rule above.
|
|
182
185
|
|
|
186
|
+
### Row-actions surface (`sales-order-listing-row-actions`, Core surface id 3) — do not confuse with surface 9
|
|
187
|
+
The sales-order listing has **two distinct** action surfaces: `sales-order-listing-actions` (id 9,
|
|
188
|
+
the listing **header** buttons — approvals filter / columns) and `sales-order-listing-row-actions`
|
|
189
|
+
(id 3, the **per-row dropdown menu**). Row-actions surface 3 elements:
|
|
190
|
+
|
|
191
|
+
| Element | id | Discriminator |
|
|
192
|
+
|---|---|---|
|
|
193
|
+
| View | 5 | — |
|
|
194
|
+
| approve | 29 | action `salesOrder.approve` |
|
|
195
|
+
| deny | 30 | action `salesOrder.deny` |
|
|
196
|
+
| approvalWorkflow | 31 | action `salesOrder.approvalWorkflow` |
|
|
197
|
+
|
|
198
|
+
**Default-visibility correction + client opt-in (2026-07-20e):**
|
|
199
|
+
- `Core/2026-07-20e - RestoreApproveDenyRowActions.sql` **narrows** an earlier **over-broad
|
|
200
|
+
`2026-07-20d` hide**: it re-enables **Approve (29)** and **Deny (30)** `isVisible=1`, leaving only
|
|
201
|
+
**View (5)** and **Approval Workflow (31)** hidden by default. Per an explicit developer instruction
|
|
202
|
+
("create new, don't edit"), the correction is a **new dated file** and `2026-07-20d` was left
|
|
203
|
+
unchanged (an in-place edit was reverted) — a deliberate departure from the local-only fold-back
|
|
204
|
+
practice below, for this change.
|
|
205
|
+
- `Client_Compass/2026-07-20e` + `Client_CompassCanada/2026-07-20e -
|
|
206
|
+
RowActionsApprovalWorkflowAdminEnable.sql` (mirror) opt the **ADMIN** role (resolved id-agnostically
|
|
207
|
+
by `Roles.name='Admin'`) into the **Approval Workflow** row-action (el 31) with **two override
|
|
208
|
+
rows** — `IS_VISIBLE=1` and `VISIBILITY_RULE` (`pendingApproval OR pendingInitialApproval`) —
|
|
209
|
+
resolved by surface slug + `Core.Actions.slug='salesOrder.approvalWorkflow'`, `NOT EXISTS`-guarded.
|
|
210
|
+
This extends the established Surface-override opt-in house style (mirrors the `2026-07-17a`
|
|
211
|
+
`SalesOrderApprovalActionsOverride` files) to the listing row-action menu.
|
|
212
|
+
|
|
183
213
|
## Display-section visibility — two patterns (marker vs field-driven)
|
|
184
214
|
|
|
185
215
|
A record-modal **display SECTION**'s own `Surfaces.isVisible` can never be client-overridden (the
|
|
@@ -395,6 +425,16 @@ rule resumes.
|
|
|
395
425
|
override is added). **Open follow-up.**
|
|
396
426
|
|
|
397
427
|
## Change history
|
|
428
|
+
- 2026-07-20 — Corrected the **row-actions surface** (`sales-order-listing-row-actions`, Core id 3;
|
|
429
|
+
distinct from the header-actions surface 9) default visibility and added the client opt-in.
|
|
430
|
+
`Core/2026-07-20e` narrows an earlier over-broad `2026-07-20d` hide — re-enables Approve (el 29) +
|
|
431
|
+
Deny (el 30) `isVisible=1`, leaving only View (el 5) + Approval Workflow (el 31) hidden by default
|
|
432
|
+
(a **new** dated file per explicit developer instruction, `20d` left unchanged — a deliberate
|
|
433
|
+
departure from the local-only fold-back rule for this change). `Client_Compass`/`Client_CompassCanada`
|
|
434
|
+
`2026-07-20e` opt the ADMIN role (by `Roles.name='Admin'`) into the Approval Workflow row-action (el
|
|
435
|
+
31) via `IS_VISIBLE=1` + `VISIBILITY_RULE` (pendingApproval OR pendingInitialApproval), resolved by
|
|
436
|
+
surface slug + `Actions.slug='salesOrder.approvalWorkflow'`, NOT EXISTS-guarded. Recorded the surface-3
|
|
437
|
+
element-identity map and added the three files to the RUN ORDER README. (apeterson)
|
|
398
438
|
- 2026-07-20 — Applied the Core-neutral-default → client-opt-in pattern to the **item-record modal**.
|
|
399
439
|
Header Edit button seeded OFF in Core; Compass + Compass Canada opt in via `Client_*/2026-07-20c`,
|
|
400
440
|
resolving the element id-agnostically by `renderType='BUTTON'`+`config.kind='itemEdit'` (NOT EXISTS,
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
| [Column Visibility (URL-driven show/hide columns)](features/column-visibility.md) | A "Columns" header button that opens a modal listing every column from the table meta, lets the user show/hide columns, adjusts the table live, and persists the | toga25-supply/src/components/ColumnVisibilityModal/, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableData.tsx |
|
|
10
10
|
| [Meta-Driven Page & Table Setup](features/meta-driven-table-data.md) | A page in this app is **meta-driven end to end**: the page view model fetches *page meta* (labels, sections, ACL) and *table meta* (the columns/fields + table s | toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableState.ts, toga25-supply/src/hooks/useTablePageMeta.ts, toga-blox-npm/dist/hooks/useFetchPageMeta.d.ts, toga-blox-npm/dist/hooks/useFetchTablePageMeta.d.ts, toga-blox-npm/dist/hooks/useAssignTableFieldLabels.d.ts, toga-blox-npm/dist/components/Table/hooks/useTableData.d.ts |
|
|
11
11
|
| [Record Modals & Nested Tables](features/record-modals-and-nested-tables.md) | The repo's family of modal + nested-table patterns layered over toga-blox `TableRecordModal` and `PrimaryTable*Layout`. | toga25-supply/src/layout/ItemRecordModalLayout/, toga25-supply/src/layout/SalesOrderRecordModalLayout/, toga25-supply/src/layout/SalesOrderItemsTableLayout/, toga25-supply/src/layout/ItemFulfillmentModal/, toga25-supply/src/layout/GenericNestedTables/GenericNestedTables.tsx, toga25-supply/src/layout/GenericNestedTables/GenericTableLayout.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/DEFAULT/inventoryGroupings.json, toga25-supply/src/hooks/useTableCellInteractions.ts, toga25-supply/src/hooks/useServerTableUrlState.ts |
|
|
12
|
-
| [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from `GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported J | toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/SalesOrderView.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderSummaryGrid.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getDetailSections.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderNotesSection.tsx, toga25-supply/src/pages/SalesOrders/helpers/cleanOrder.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/surface/actionRegistry.ts, toga25-supply/src/surface/componentRegistry.tsx, toga25-supply/src/surface/SurfaceActionBar.tsx, toga25-supply/src/surface/SurfaceSection.tsx, toga25-supply/src/surface/resolve.ts, toga25-supply/src/surface/types.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/Login/LoginPage.tsx, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/view/SurfaceRowActions.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/Items/ItemsPage.tsx, toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx, toga25-supply/src/pages/VendorItems/VendorItemsPage.tsx, toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx, toga25-supply/src/pages/Inventory/Inventory.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/index.ts, toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/layout/ItemRecordModalLayout/helpers/surfaceBundleToItemFields.ts, toga25-supply/src/layout/ItemRecordModalLayout/helpers/index.ts, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/useItemRecordModalViewModel.tsx, toga25-supply/src/layout/ItemRecordModalLayout/ItemRecordModalLayout.tsx, toga25-supply/src/layout/ItemRecordModalLayout/components/ItemRecordView.tsx |
|
|
12
|
+
| [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from `GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported J | toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/SalesOrderView.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderSummaryGrid.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getDetailSections.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderNotesSection.tsx, toga25-supply/src/pages/SalesOrders/helpers/cleanOrder.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/surface/resolveElementState.ts, toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderRowRecordState.ts, toga25-supply/src/surface/actionRegistry.ts, toga25-supply/src/surface/componentRegistry.tsx, toga25-supply/src/surface/SurfaceActionBar.tsx, toga25-supply/src/surface/SurfaceSection.tsx, toga25-supply/src/surface/resolve.ts, toga25-supply/src/surface/types.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/Login/LoginPage.tsx, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/view/SurfaceRowActions.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/Items/ItemsPage.tsx, toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx, toga25-supply/src/pages/VendorItems/VendorItemsPage.tsx, toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx, toga25-supply/src/pages/Inventory/Inventory.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/index.ts, toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/layout/ItemRecordModalLayout/helpers/surfaceBundleToItemFields.ts, toga25-supply/src/layout/ItemRecordModalLayout/helpers/index.ts, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/useItemRecordModalViewModel.tsx, toga25-supply/src/layout/ItemRecordModalLayout/ItemRecordModalLayout.tsx, toga25-supply/src/layout/ItemRecordModalLayout/components/ItemRecordView.tsx |
|
|
13
13
|
| [AWS Amplify Multi-Environment Deployment](workflows/amplify-deployment.md) | How `toga25-supply` deploys to **all** of its environments on AWS Amplify from a **single shared `amplify.yml`**. | toga25-supply/amplify.yml, toga25-supply/src/api/api.ts, toga25-supply/src/hooks/useAuthenticationFlow.ts, toga25-supply/vite.config.ts, toga25-supply/package.json |
|
|
14
14
|
| [Cypress Testing Harness (component + e2e)](workflows/cypress-testing.md) | The Cypress test harness for the `toga25-supply` frontend, bootstrapped from scratch (`cypress` was already a dependency but there was no config, no `cypress/` | toga25-supply/cypress.config.ts, toga25-supply/cypress/tsconfig.json, toga25-supply/cypress/support/component.tsx, toga25-supply/cypress/support/component-index.html, toga25-supply/cypress/support/e2e.ts, toga25-supply/cypress/support/commands.ts, toga25-supply/cypress/support/fixtures.ts, toga25-supply/cypress/support/mocks/useApprovalModalViewModel.ts, toga25-supply/cypress/component/SalesOrderApprovalModalsLayout.cy.tsx, toga25-supply/cypress/component/RecordApprovalModalLayout.cy.tsx, toga25-supply/cypress/component/EnterPoNumberModal.cy.tsx, toga25-supply/cypress/e2e/salesOrderApproval.cy.ts |
|
|
@@ -20,6 +20,9 @@ files:
|
|
|
20
20
|
- toga25-supply/src/pages/SalesOrders/helpers/cleanOrder.ts
|
|
21
21
|
- toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/index.ts
|
|
22
22
|
- toga25-supply/src/surface/evaluateSurfaceRule.ts
|
|
23
|
+
- toga25-supply/src/surface/resolveElementState.ts
|
|
24
|
+
- toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts
|
|
25
|
+
- toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderRowRecordState.ts
|
|
23
26
|
- toga25-supply/src/surface/actionRegistry.ts
|
|
24
27
|
- toga25-supply/src/surface/componentRegistry.tsx
|
|
25
28
|
- toga25-supply/src/surface/SurfaceActionBar.tsx
|
|
@@ -257,6 +260,40 @@ in [surface-layer-schema](../../dbchanges2/features/surface-layer-schema.md):
|
|
|
257
260
|
> shows **`leasePrice`** for all clients, whereas the old DEFAULT JSON showed only unit price. Confirm
|
|
258
261
|
> that is the intended seed shape.
|
|
259
262
|
|
|
263
|
+
## Two FE rule engines (surface Tier-1 vs legacy named-rule) — do not conflate
|
|
264
|
+
|
|
265
|
+
There are **two** rule evaluators in this app, and a business predicate like the corrected Approve
|
|
266
|
+
rule can only run on the legacy one today:
|
|
267
|
+
|
|
268
|
+
- **`src/surface/evaluateSurfaceRule.ts`** — the **surface Tier-1** evaluator. Grammar is
|
|
269
|
+
**deliberately FROZEN**: `all`/`any`/`none` + leaf field ops (`eq`/`ne`/`in`/`nin`/`gt`/`gte`/`lt`/`lte`).
|
|
270
|
+
It **THROWS on any `{type:…}` named node by design** ("new logic does NOT go here — escalate to a
|
|
271
|
+
Tier-2 PHP capability method"). Evaluated client-side against the loaded record.
|
|
272
|
+
- **`src/pages/SalesOrders/helpers/evaluateEnableRule.ts`** (`evaluateRule`) — the **legacy** engine:
|
|
273
|
+
`all`/`any`/`not` + `field` + **`{type}` NAMED_RULES**, including **`stepTwoAssigned`** (which reads
|
|
274
|
+
`ctx.currentStage`/`ctx.stages`).
|
|
275
|
+
|
|
276
|
+
**Consequence:** the corrected Approve enable rule
|
|
277
|
+
(`(pendingInitialApproval AND stepTwoAssigned) OR pendingApproval` — see
|
|
278
|
+
[surface-resolver](../../_underscore/features/surface-resolver.md)) carries a `{type:stepTwoAssigned}`
|
|
279
|
+
node, so **only the legacy engine can evaluate it today**. Moving the Approve button fully onto the
|
|
280
|
+
surface path requires **both** (a) teaching `evaluateSurfaceRule` the named `stepTwoAssigned` check and
|
|
281
|
+
(b) feeding it live approval-stage data — neither is done. (The backend Tier-2 path is also a no-op:
|
|
282
|
+
no `resolveSurfaceActionState`, no backend `stepTwoAssigned`.)
|
|
283
|
+
|
|
284
|
+
### Approval-stage data plumbing for `stepTwoAssigned`
|
|
285
|
+
`stepTwoAssigned` needs the order's approval-stage data on the surface record. The surface record is
|
|
286
|
+
shaped `{ order }`, so the predicate reads **`order.currentStage` / `order.stages`**.
|
|
287
|
+
|
|
288
|
+
- **Record-modal action bar (surface 8) — DONE.** `useSalesOrderRecordModalLayoutModel.tsx` already
|
|
289
|
+
fetches `approvalCurrentStage`/`approvalStages`; they are now **merged onto the order (`orderWithPo`)
|
|
290
|
+
as `currentStage`/`stages`** (parallel to the existing `purchaseOrderDetails` merge). Purely additive.
|
|
291
|
+
- **Listing row dropdown (surface 3) — PENDING.** `src/pages/SalesOrders/hooks/useSalesOrderRowRecordState.ts`
|
|
292
|
+
currently fetches `order` + `purchaseOrderDetails` but **NOT** approval stages, so `stepTwoAssigned`
|
|
293
|
+
cannot resolve on the row path yet. It needs a `useFetchApprovalStages` call + the same
|
|
294
|
+
`currentStage`/`stages` merge (a per-open fetch cost tradeoff on the row path), **and**
|
|
295
|
+
`evaluateSurfaceRule` still needs the named-check support above.
|
|
296
|
+
|
|
260
297
|
## Gotchas
|
|
261
298
|
|
|
262
299
|
- **The single-meta envelope can hide the real bundle behind a DECOY `elements: []`.** The V2 envelope
|
|
@@ -319,6 +356,11 @@ in [surface-layer-schema](../../dbchanges2/features/surface-layer-schema.md):
|
|
|
319
356
|
(the path form 404s EV-6).
|
|
320
357
|
- **Tier-1 only on the client.** Business-logic gates (Tier-2) arrive as resolved booleans from the
|
|
321
358
|
backend (`meta.surface`); never re-encode business rules in the FE evaluator.
|
|
359
|
+
- **`recordSurfaceState` keying mismatch (unverified).** The backend keys the per-record state by
|
|
360
|
+
**element UUID** (single: `state[<elementUuid>]`; list: `records[<recordUuid>].elements[<elementUuid>]`
|
|
361
|
+
— see [surface-meta-option](../../api2/features/surface-meta-option.md)), but `resolveElementState.ts`
|
|
362
|
+
looks the state up by **`element.action.key`**. Confirm the two key spaces line up (or bridge them)
|
|
363
|
+
before a server-computed Tier-2 gate can actually AND onto the FE result.
|
|
322
364
|
- **A React portal overlay's click bubbles through the COMPONENT tree, not the DOM tree.** The
|
|
323
365
|
SalesOrders row-actions menu (`SurfaceRowActions.tsx`) renders its dismiss overlay via a portal;
|
|
324
366
|
an outside-click on that fixed overlay propagated up the React component tree to the row's
|
|
@@ -331,6 +373,16 @@ in [surface-layer-schema](../../dbchanges2/features/surface-layer-schema.md):
|
|
|
331
373
|
treat type-checking as pending. Runtime `GET /v2/surfaces/{slug}/meta` also not yet exercised.
|
|
332
374
|
|
|
333
375
|
## Change history
|
|
376
|
+
- 2026-07-20 — Documented the **two FE rule engines** (frozen surface Tier-1 `evaluateSurfaceRule`,
|
|
377
|
+
which THROWS on `{type}` nodes, vs legacy `evaluateEnableRule` with `{type}` NAMED_RULES incl
|
|
378
|
+
`stepTwoAssigned` reading `ctx.currentStage`/`ctx.stages`) and why the corrected Approve rule is
|
|
379
|
+
evaluable only by the legacy engine today. **Built** the record-modal (surface 8) stage plumbing:
|
|
380
|
+
merged `approvalCurrentStage`/`approvalStages` onto the order (`orderWithPo`) as `currentStage`/`stages`
|
|
381
|
+
(additive) so the surface `stepTwoAssigned` predicate can read `order.currentStage`/`order.stages`.
|
|
382
|
+
Recorded remaining work: the listing row dropdown (surface 3, `useSalesOrderRowRecordState.ts`)
|
|
383
|
+
fetches order+PO but not approval stages — still needs `useFetchApprovalStages`+merge plus
|
|
384
|
+
`evaluateSurfaceRule` named-check support. Added a gotcha: `recordSurfaceState` keys by element UUID
|
|
385
|
+
server-side but `resolveElementState` looks up by `action.key` (alignment unverified). (apeterson)
|
|
334
386
|
- 2026-07-20 — Migrated the **item-record modal VIEW** onto Surface for **ALL clients (no roster)** via
|
|
335
387
|
the adapter seam: new `surfaceBundleToItemViewFields()` adapts the `item-record-*` surface group
|
|
336
388
|
(`useFetchSurfaceMetaGroup`) into the existing `{header, editItem, sections, vendorItemsEnabled}`
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: session
|
|
3
|
+
slug: surface-approve-steptwo
|
|
4
|
+
title: Surface-drive the Approve step-two-assigned enable gate for Compass/Compass Canada
|
|
5
|
+
author: apeterson
|
|
6
|
+
repos: [toga25-supply, dbchanges2, _underscore, api2]
|
|
7
|
+
framework: "2.0"
|
|
8
|
+
client: shared
|
|
9
|
+
status: active
|
|
10
|
+
created: 2026-07-21
|
|
11
|
+
updated: 2026-07-21
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Session: surface-approve-steptwo
|
|
15
|
+
**Date:** 2026-07-21
|
|
16
|
+
**Project/Repo:** toga25-supply + dbchanges2 (2.0)
|
|
17
|
+
**Task:** Move the Approve-button "step-two assigned" enable gate off the legacy per-client FE JSON and into the Surface layer (role-scoped SurfaceOverride ENABLED_RULE), for Compass/Compass Canada admins, on the record-actions bar (surface 8) and listing row-actions (surface 3).
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What WORKED
|
|
22
|
+
<!-- Include specific file paths and evidence -->
|
|
23
|
+
- **Record-actions (modal) data merge — DONE & verified.** In `toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx`, merged `currentStage: approvalCurrentStage ?? null` and `stages: approvalStages ?? []` onto the returned `orderWithPo` object (lines ~148-149), and added `approvalCurrentStage, approvalStages` to the useMemo deps (line ~172). Purely additive — parallel to the existing `purchaseOrderDetails` merge. `approvalCurrentStage/approvalStages` were already fetched in that hook via `useFetchApprovalStages` (lines ~105-113). Verified present with grep post-edit.
|
|
24
|
+
- **DB — row-actions default-visibility correction (Core).** Created `dbchanges2/Core/2026-07-20e - Update - RestoreApproveDenyRowActions.sql` — narrows the earlier over-broad `2026-07-20d` hide: re-enables Approve (el 29) + Deny (el 30) `isVisible=1` on surface `sales-order-listing-row-actions` (id 3), leaving only View (el 5) and Approval Workflow (el 31) hidden by default. Scoped by surface slug + `e.id IN (29,30)`, idempotent.
|
|
25
|
+
- **DB — Approval Workflow row-item ADMIN opt-in (Compass + Compass Canada).** Created `dbchanges2/Client_Compass/2026-07-20e - RowActionsApprovalWorkflowAdminEnable.sql` and the mirrored `dbchanges2/Client_CompassCanada/2026-07-20e - RowActionsApprovalWorkflowAdminEnable.sql`. Each opts the `Admin` role (resolved id-agnostically by `Roles.name='Admin'`) into element 31 with two override rows — `IS_VISIBLE='1'` and `VISIBILITY_RULE` (`{"all":[{"field":"order._status","op":"in","value":["pendingApproval","pendingInitialApproval"]}]}`) — element resolved by surface slug + `Core.Actions.slug='salesOrder.approvalWorkflow'`. NOT EXISTS-guarded. Mirrors the 2026-07-17a `SalesOrderApprovalActionsOverride` precedent. NOTE: these are for the Approval WORKFLOW item's *visibility* (el 31), NOT the Approve-button ENABLED_RULE work (still TODO — see below).
|
|
26
|
+
- **DB — RUN ORDER updated.** `dbchanges2/Core/2026-07-17 - README - RUN ORDER.md` lists the three new `2026-07-20e` files.
|
|
27
|
+
- **Knowledge captured.** `/capture` updated 4 docs (surface-resolver, surface-meta-option, surface-frontend, surface-layer-schema) and pushed to `_main`.
|
|
28
|
+
|
|
29
|
+
## What did NOT work — DO NOT RETRY THESE
|
|
30
|
+
<!-- Exact failure reasons — do not vague-ify -->
|
|
31
|
+
- **DO NOT "move `config.tier2` from Core into a SurfaceOverride" to control the disabled state.** `config.tier2 = "stepTwoAssigned"` on Core element 17 is a NON-FUNCTIONAL marker. Nothing reads it: the backend Tier-2 gate (`_underscore/Model/Core/Surface.php::resolveRecordState` → `_resolveTier2State`, ~line 280) dispatches by ACTION SLUG to a model method `resolveSurfaceActionState()` (const `TIER2_CAPABILITY_METHOD`), never by `config.tier2`; and the FE (`toga25-supply/src/surface/resolveElementState.ts`) never reads `config.tier2`. Moving/overriding it changes zero behavior.
|
|
32
|
+
- **DO NOT rely on the backend Tier-2 path to gate this today.** No model implements `resolveSurfaceActionState` (only `Surface.php` references it), and `stepTwoAssigned` does not exist anywhere in the backend. So `_resolveTier2State` always returns null; `meta.surface` carries only Tier-1 results.
|
|
33
|
+
- **DO NOT put `{"type":"stepTwoAssigned"}` into a rule and expect `evaluateSurfaceRule` to run it as-is.** `toga25-supply/src/surface/evaluateSurfaceRule.ts` has a DELIBERATELY FROZEN grammar (all/any/none + leaf field ops eq/ne/in/nin/gt/gte/lt/lte) and THROWS `"[surface] Unknown rule node"` on any `{type:...}` node. It needs named-check support added first (piece #3 below).
|
|
34
|
+
- **DO NOT bake the Approve rule into Core's element `enabledRule`.** Per developer decision, the rule lives ONLY in the per-client SurfaceOverride `c_longValue` column (attribute `ENABLED_RULE`, `value` NULL). Core stays client-neutral.
|
|
35
|
+
- **DO NOT edit an already-run migration file in place.** Developer instruction: create a NEW forward migration instead. (An in-place edit to `2026-07-20d` was made then reverted for this reason; the `2026-07-20e` Core file is the forward fix.)
|
|
36
|
+
|
|
37
|
+
## Not tried yet (candidates for next session)
|
|
38
|
+
- **[Piece 2 — row-actions data merge]** Add a `useFetchApprovalStages(enabled ? uuid : null, userUuid, {})` call to `toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderRowRecordState.ts` (gated on `enabled`, like the order fetch) and merge `currentStage`/`stages` into its `order` object (currently `{ order: {...cleanedOrder, purchaseOrderDetails} }`). This is the heaviest piece — adds a per-row approval-stages fetch when a dropdown opens.
|
|
39
|
+
- **[Piece 3 — evaluator]** Teach `toga25-supply/src/surface/evaluateSurfaceRule.ts` the `{"type":"stepTwoAssigned"}` named check, reusing the existing logic from `toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts` (`NAMED_RULES.stepTwoAssigned`). Surface record is shaped `{ order }`, so the surface predicate must read `order.currentStage` / `order.stages` (NOT top-level `ctx.currentStage` like the legacy engine). Add a code comment noting this intentionally deviates from the "frozen grammar / escalate to Tier-2" note.
|
|
40
|
+
- **[Piece 4 — DB ENABLED_RULE overrides]** Write per-client SurfaceOverride rows (attribute `ENABLED_RULE`, `value` NULL, rule JSON in `c_longValue`, scoped to `@adminRoleId` via `Roles.name='Admin'`) on the Approve element for surface 8 el 17 (`sales-order-record-actions`) AND surface 3 el 29 (`sales-order-listing-row-actions`), for Compass + Compass Canada. Rule = `{"any":[{"all":[{"field":"order._status","op":"in","value":["pendingInitialApproval"]},{"type":"stepTwoAssigned"}]},{"field":"order._status","op":"in","value":["pendingApproval"]}]}`. Works because `_castOverride` (Surface.php ~line 871) reads `value` then falls back to `c_longValue`. Resolve element id-agnostically by surface slug + `Core.Actions.slug='salesOrder.approve'`. NOT EXISTS-guard; add to RUN ORDER.
|
|
41
|
+
- **Verify keying caveat:** backend keys `meta.surface.state` by element UUID, but `resolveElementState` looks up `recordSurfaceState` by `element.action.key`. Confirm these line up (or Tier-2 state silently never matches). Not blocking the Tier-1 ENABLED_RULE plan, but relevant if backend Tier-2 is ever wired.
|
|
42
|
+
|
|
43
|
+
## Current file state
|
|
44
|
+
| File | Status | Notes |
|
|
45
|
+
|------|--------|-------|
|
|
46
|
+
| toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx | Modified (DONE) | `currentStage`/`stages` merged onto `orderWithPo` + deps updated. Additive. |
|
|
47
|
+
| toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderRowRecordState.ts | Unchanged (TODO piece 2) | Still fetches order + PO only; no approval-stages fetch/merge. |
|
|
48
|
+
| toga25-supply/src/surface/evaluateSurfaceRule.ts | Unchanged (TODO piece 3) | Frozen grammar; throws on `{type}`. Needs `stepTwoAssigned` named check. |
|
|
49
|
+
| dbchanges2/Core/2026-07-20e - Update - RestoreApproveDenyRowActions.sql | Created | Re-enables Approve(29)+Deny(30) on surface 3; narrows 2026-07-20d. |
|
|
50
|
+
| dbchanges2/Core/2026-07-20d - Update - HideSalesOrderRowActionsByDefault.sql | Unchanged (in-place edit reverted) | Original all-elements hide preserved. |
|
|
51
|
+
| dbchanges2/Client_Compass/2026-07-20e - RowActionsApprovalWorkflowAdminEnable.sql | Created | Admin opt-in for Approval Workflow item (el 31): IS_VISIBLE + VISIBILITY_RULE. NOT the Approve ENABLED_RULE. |
|
|
52
|
+
| dbchanges2/Client_CompassCanada/2026-07-20e - RowActionsApprovalWorkflowAdminEnable.sql | Created | Mirror of Compass. |
|
|
53
|
+
| dbchanges2/Core/2026-07-17 - README - RUN ORDER.md | Modified | Lists the three new 2026-07-20e files. |
|
|
54
|
+
| (TODO) dbchanges2 Client_Compass/Client_CompassCanada Approve ENABLED_RULE override files | Not created | Piece 4 — Approve el 17 + el 29 ENABLED_RULE in c_longValue. |
|
|
55
|
+
|
|
56
|
+
## Decisions made
|
|
57
|
+
- **Approve step-two gate lives in the Surface layer, evaluated on the FE (Option B), not backend Tier-2 (Option C) and not legacy-only (Option A).** Rationale: the value changes live in-modal (user assigns a step-two approver right there), so instant FE evaluation beats a server snapshot that only refreshes on refetch; and the team is already creating separate Approve elements per surface. Rejected: A (legacy JSON only — can't reach the surface bar/row); C (backend `resolveSurfaceActionState` — snapshot-only, won't re-enable live after assignment).
|
|
58
|
+
- **The rule JSON goes in the SurfaceOverride `c_longValue` column, attribute `ENABLED_RULE`, `value` NULL — NOT in Core.** Rationale: keep Core client-neutral; client/role gating stays in overrides; `_castOverride` falls back to `c_longValue`. (Note: the earlier 2026-07-17a files put shorter rule JSON in `value`; this longer OR-rule uses `c_longValue` per developer instruction.)
|
|
59
|
+
- **Corrected Approve rule:** `(pendingInitialApproval AND stepTwoAssigned) OR (pendingApproval)` — only for the Approve button (not Deny / Approval Workflow).
|
|
60
|
+
- **Scope for now:** only `sales-order-record-actions` (surface 8, el 17) and `sales-order-listing-row-actions` (surface 3, el 29). The approval-workflow-modal Approve element does NOT exist as a surface yet — deferred.
|
|
61
|
+
- **Admin role resolved by `Roles.name='Admin'`** (id-agnostic; client role ids are not environment-stable), matching the 2026-07-17a precedent.
|
|
62
|
+
|
|
63
|
+
## Blockers
|
|
64
|
+
- None hard. Piece 2 (row-actions) requires an extra per-row approval-stages fetch — a performance consideration (fires on dropdown open only), not a blocker. Piece 3 intentionally deviates from the frozen-grammar design intent of `evaluateSurfaceRule` — acceptable per the Option B decision but worth a comment.
|
|
65
|
+
|
|
66
|
+
## Exact next step
|
|
67
|
+
> In `toga25-supply/src/surface/evaluateSurfaceRule.ts`, add support for a `{"type":"stepTwoAssigned"}` leaf node (a small named-predicate branch reusing the `stepTwoAssigned` logic from `src/pages/SalesOrders/helpers/evaluateEnableRule.ts`), reading `order.currentStage` / `order.stages` off the `{ order }`-shaped surface record. This is the smallest independent piece and unblocks testing the already-merged modal stage data (piece 1).
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
_Saved by /session-save on 2026-07-21_
|
package/package.json
CHANGED