toga-ai 1.0.321 → 1.0.322
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 -0
- package/knowledge/2.0/apps/_underscore/architecture/surface-authoring-proposal.md +67 -0
- package/knowledge/2.0/apps/_underscore/features/surface-resolver.md +76 -1
- package/knowledge/2.0/apps/toga25-supply/INDEX.md +1 -1
- package/knowledge/2.0/apps/toga25-supply/features/surface-frontend.md +16 -1
- package/knowledge/INDEX.md +1 -1
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
|
+
| [Proposed — git-sourced base+overlay JSON authoring for the Surface layer](architecture/surface-authoring-proposal.md) | A **proposal / handoff recommendation** (not implemented) that the Surface layer's *authoring* model move off hand-authored SQL against the `SurfaceOverrides` E | _underscore/Model/Core/Surface.php, _underscore/Model/Client/SurfaceOverride.php |
|
|
5
6
|
| [_underscore Framework Architecture](architecture.md) | `_underscore` is the shared PHP backend framework for **all 2.0 applications**. | _underscore/_underscore.php, _underscore/Loader.php, _underscore/Framework.php, _underscore/Model.php, _underscore/Database.php, _underscore/Query.php, _underscore/Route.php, _underscore/Component.php |
|
|
6
7
|
| [ACL Permission Chain (Record & Field Authorization)](features/acl-permission-chain.md) | Authorization in the 2.0 API is **metadata-driven**: whether a role may Create/Read/Update/Delete a record is decided by rows across **four linked tables**, not | api2/Component/Api/V2/V2.php, _underscore/Model/Core/Page.php, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql |
|
|
7
8
|
| [Address Validation (carrier waterfall + validateAddress scripted endpoint)](features/address-validation.md) | `_Model_Client_Address::validateAddress` verifies a US address against a **carrier waterfall (USPS → FedEx → UPS)** and returns a single canonical, carrier-norm | _underscore/Model/Client/Address.php |
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Proposed — git-sourced base+overlay JSON authoring for the Surface layer
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: _underscore
|
|
5
|
+
project: _Underscore
|
|
6
|
+
client: shared
|
|
7
|
+
type: architecture
|
|
8
|
+
status: draft
|
|
9
|
+
updated: 2026-07-13
|
|
10
|
+
owners: [apeterson]
|
|
11
|
+
files:
|
|
12
|
+
- _underscore/Model/Core/Surface.php
|
|
13
|
+
- _underscore/Model/Client/SurfaceOverride.php
|
|
14
|
+
related:
|
|
15
|
+
- ../features/surface-resolver.md
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Summary
|
|
19
|
+
|
|
20
|
+
A **proposal / handoff recommendation** (not implemented) that the Surface layer's *authoring* model
|
|
21
|
+
move off hand-authored SQL against the `SurfaceOverrides` EAV table onto **git-sourced JSON**: a full
|
|
22
|
+
neutral `_base/<surface>.json` per surface plus partial per-client overlay files holding only diffs,
|
|
23
|
+
merged by the resolver at read time. The runtime resolver is kept for the dynamic parts (per-record
|
|
24
|
+
Tier-2 evaluation + ACL element-drop). This doc records the motivation, the design, and interim
|
|
25
|
+
guardrails so the lead dev can decide.
|
|
26
|
+
|
|
27
|
+
**Critical rules:** This is a **proposal, status: draft — nothing here is implemented.** Do not treat
|
|
28
|
+
any of it as the current system. The live authoring path is still hand-written SQL against
|
|
29
|
+
`SurfaceOverrides`; if you stay on it, apply the interim guardrails below (unique index, strict SQL
|
|
30
|
+
mode, autocommit on, idempotent migrations) or you will hit the silent EAV failure modes.
|
|
31
|
+
|
|
32
|
+
## Problem
|
|
33
|
+
|
|
34
|
+
Surface config is authored as hand-written SQL against the `SurfaceOverrides` EAV table. This has:
|
|
35
|
+
- **No git diff / review / rollback** — config changes bypass code review and CI.
|
|
36
|
+
- **No team-merge story** — concurrent work on a shared surface collides with no merge tooling.
|
|
37
|
+
- **Silent DB failure modes** — ENUM coercion of unknown values to `''` (non-strict mode), a missing
|
|
38
|
+
unique constraint letting duplicate override rows pile up, and autocommit-off swallowing writes.
|
|
39
|
+
|
|
40
|
+
Cost signal: 2 of ~17 surfaces took ~5 days.
|
|
41
|
+
|
|
42
|
+
## Recommendation
|
|
43
|
+
|
|
44
|
+
**Source of truth = git.** For each surface author a full neutral `_base/<surface>.json`, plus partial
|
|
45
|
+
per-client overlay files holding **only diffs**, merged by the resolver at read time. Keep the
|
|
46
|
+
resolver for the dynamic parts it must still compute (per-record Tier-2 evaluation + ACL
|
|
47
|
+
element-drop — see [surface-resolver](../features/surface-resolver.md)).
|
|
48
|
+
|
|
49
|
+
**Framing: "source of truth = git; read substrate = a choice."** Three read substrates, in order of
|
|
50
|
+
preference:
|
|
51
|
+
1. Read the JSON files directly at read time.
|
|
52
|
+
2. Seed the JSON documents into a DB column at deploy (git still authors; DB is a materialized cache).
|
|
53
|
+
3. (Avoid) DB-as-master authored by hand — the status quo.
|
|
54
|
+
|
|
55
|
+
## Optional future — bounded live toggles
|
|
56
|
+
|
|
57
|
+
An admin UI (a screen + a validating API endpoint + an `admin_flags` DB table) that writes a
|
|
58
|
+
**bounded, whitelisted** set of per-client feature toggles, merged at **highest precedence**. This
|
|
59
|
+
gives live no-deploy toggles without undermining git-as-truth, because the whitelist bounds what can
|
|
60
|
+
be changed outside git.
|
|
61
|
+
|
|
62
|
+
## Interim guardrails (if staying on SQL/EAV)
|
|
63
|
+
|
|
64
|
+
- Unique index on `SurfaceOverrides(surfaceElementId, attribute, roleId, personaId, languageId)`.
|
|
65
|
+
- Strict SQL mode (so unknown ENUM values error instead of coercing to `''`).
|
|
66
|
+
- Autocommit on.
|
|
67
|
+
- Idempotent, re-runnable migrations.
|
|
@@ -6,7 +6,7 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-13
|
|
10
10
|
owners: [jcardinal, apeterson]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Core/Surface.php
|
|
@@ -137,6 +137,70 @@ breaks logic.
|
|
|
137
137
|
`_Model_<Client>_*` overrides + `Client_ApiPayloadInterceptor` hooks — the Surface layer does
|
|
138
138
|
**not** absorb business logic.
|
|
139
139
|
|
|
140
|
+
## Per-client override of the Tier-1 gating RULES (not just static flags)
|
|
141
|
+
|
|
142
|
+
Originally only static flags (`isVisible`/`isEnabled`) and `config` were per-client overridable
|
|
143
|
+
via `SurfaceOverrides`; the **Tier-1 gating rules** (`visibilityRule`/`enabledRule`) were frozen on
|
|
144
|
+
the Core element. That meant two-stage-approval clients could not be expressed as data — they needed
|
|
145
|
+
per-client PHP. Two new override attributes make the declarative rules per-client overridable so the
|
|
146
|
+
two-stage behavior is **pure data**:
|
|
147
|
+
|
|
148
|
+
- `_Model_Client_SurfaceOverride` now declares `ATTRIBUTE__VISIBILITY_RULE` and
|
|
149
|
+
`ATTRIBUTE__ENABLED_RULE` constants.
|
|
150
|
+
- In `Surface.php` `_resolveElement`, the resolver decodes and applies both attributes: the locals
|
|
151
|
+
are seeded from the Core element's own `visibilityRule`/`enabledRule`, then overridden by any client
|
|
152
|
+
override row. `_castOverride` decodes them as JSON alongside `CONFIG`.
|
|
153
|
+
|
|
154
|
+
> **⚠ LOCAL only (as of 2026-07-13).** The `_underscore` code change **and** the ENUM migration below
|
|
155
|
+
> must ship together to beta/prod on promotion. php-reviewer pass + a PHPUnit regression test for the
|
|
156
|
+
> rule-override path are still outstanding.
|
|
157
|
+
|
|
158
|
+
**Mandatory ENUM migration (ships with the code).** Each client schema's
|
|
159
|
+
`SurfaceOverrides.attribute` ENUM must be widened *before* any rule-override row is inserted:
|
|
160
|
+
`ALTER TABLE Client_*.SurfaceOverrides MODIFY attribute ENUM(...,'VISIBILITY_RULE','ENABLED_RULE') NOT NULL`
|
|
161
|
+
across all 32 `Client_*` schemas. In **non-strict SQL mode** an insert of an unknown ENUM value
|
|
162
|
+
**silently coerces to `''`** — which makes the resolver ignore the rows and `NOT EXISTS` guards never
|
|
163
|
+
match (duplicate rows pile up). Widen the ENUM first, always.
|
|
164
|
+
|
|
165
|
+
### Override semantics: REPLACE, not deep-merge
|
|
166
|
+
A `CONFIG` (and now `VISIBILITY_RULE`/`ENABLED_RULE`) override **replaces the whole decoded object**
|
|
167
|
+
— it does **not** deep-merge (`Surface.php` ~line 818). So a `CONFIG` override value must carry the
|
|
168
|
+
**full** config (e.g. `role` + `filterKey` + `additionalData`), not just the changed key.
|
|
169
|
+
|
|
170
|
+
### Two separate gating mechanisms on the sales-order listing (do not conflate)
|
|
171
|
+
- The **Approvals FILTER** on the sales-order listing is driven by `config.additionalData._status`.
|
|
172
|
+
- The **per-button record actions** are gated by `visibilityRule`/`enabledRule`.
|
|
173
|
+
These are separate mechanisms on separate surfaces; a filter change is a `CONFIG` override, a
|
|
174
|
+
button-gating change is a rule override.
|
|
175
|
+
|
|
176
|
+
## Core re-baselined to a neutral default + per-client diffs (decision, 2026-07-13)
|
|
177
|
+
|
|
178
|
+
Core was effectively "Client Zero = Compass", not a neutral default, so every client inherited
|
|
179
|
+
Compass's shape. Core was re-baselined as a true shared default so clients store **only diffs**:
|
|
180
|
+
|
|
181
|
+
1. The approvals gate is governed by the **`navigation-approvals` ACL grant**, not per-tenant
|
|
182
|
+
`IS_VISIBLE` overrides.
|
|
183
|
+
2. Default approvals filter = `pendingApproval` only (single-stage).
|
|
184
|
+
3. No Tier-2 `config.tier2` caveat baked into Core.
|
|
185
|
+
4. Edit-in-Commerce only for Compass / CompassCanada.
|
|
186
|
+
5. Columns button hidden by default.
|
|
187
|
+
|
|
188
|
+
Per-client diffs layered on top:
|
|
189
|
+
- **Quad** — Approve + Deny only; filter narrowed to `pendingApproval`; filter button visible.
|
|
190
|
+
- **Compass USA / Compass Canada** — two-stage approvals: record-action `visibilityRule`/`enabledRule`
|
|
191
|
+
= `in [pendingApproval, pendingInitialApproval]`. **Compass Canada** additionally gets a `CONFIG`
|
|
192
|
+
override on the approvals filter button (surface `sales-order-listing-actions`) to add
|
|
193
|
+
`pendingInitialApproval`.
|
|
194
|
+
|
|
195
|
+
> ⚠ **Regression left open:** the Compass / CompassCanada **non-rule** overrides (Edit-in-Commerce
|
|
196
|
+
> flags, role-8 Manager hides) were **wiped during the rebuilds and not re-added**. The two-stage rule
|
|
197
|
+
> overrides are also LOCAL only (see above).
|
|
198
|
+
|
|
199
|
+
### Operational gotcha — raw SQL bypasses the cache-bust
|
|
200
|
+
Raw SQL against the surface tables bypasses the model-layer cache-bust, so the on-disk bundle cache
|
|
201
|
+
at `~/toga/cache/surface_meta/*.json` **must be cleared manually** after any raw-SQL surface edit.
|
|
202
|
+
(Cache confusion earlier was a local-setup artifact, not an inherent design issue.)
|
|
203
|
+
|
|
140
204
|
## How a surfaces scripted-API call is authorized (route-level gate)
|
|
141
205
|
|
|
142
206
|
Authorizing a surfaces script (`meta`, `meta-group`, `debug`) is a **two-layer** check. Get the
|
|
@@ -234,6 +298,17 @@ Core record grants + their logic-group expressions all evaluate `all`/`"1"`. The
|
|
|
234
298
|
match Compass, a follow-up migration aligning both `meta` and `meta-group` to roles 1,3,4 is needed.
|
|
235
299
|
|
|
236
300
|
## Change history
|
|
301
|
+
- 2026-07-13 — Made the Tier-1 gating RULES per-client overridable: added
|
|
302
|
+
`ATTRIBUTE__VISIBILITY_RULE`/`ATTRIBUTE__ENABLED_RULE` to `_Model_Client_SurfaceOverride`;
|
|
303
|
+
`_resolveElement` seeds locals from the Core element's rules then applies overrides;
|
|
304
|
+
`_castOverride` decodes them as JSON — so two-stage approvals are pure data, not per-client PHP.
|
|
305
|
+
Requires widening `Client_*.SurfaceOverrides.attribute` ENUM (32 schemas) first (non-strict mode
|
|
306
|
+
silently coerces unknown ENUM → `''`). Documented override REPLACE-not-merge semantics, the
|
|
307
|
+
filter (`config.additionalData._status`) vs button-rule split, the raw-SQL cache-bust bypass
|
|
308
|
+
(`~/toga/cache/surface_meta/*.json`), and the Core neutral-default re-baseline (ACL-gated
|
|
309
|
+
approvals, single-stage default, per-client diffs: Quad = Approve+Deny, Compass/CC = two-stage).
|
|
310
|
+
⚠ Code + ENUM migration are LOCAL only; non-rule Compass/CC overrides wiped in rebuild not yet
|
|
311
|
+
re-added; php-reviewer + regression test outstanding. (apeterson)
|
|
237
312
|
- 2026-07-01 — Clarified vocabulary/label per-client relabeling (new section): Vocabulary `slug`
|
|
238
313
|
and VocabularyTerm `value` are stable internal ids never shown to users; the displayed label
|
|
239
314
|
always resolves from `Core.Messages`; clients relabel via a `SurfaceOverrides` `LABEL_MESSAGE`
|
|
@@ -8,5 +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/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/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/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/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/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 |
|
|
12
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 |
|
|
@@ -6,7 +6,7 @@ project: TOGa 2.5 Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-07-13
|
|
10
10
|
owners: [jcardinal, apeterson]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/src/surface/useFetchSurfaceMeta.ts
|
|
@@ -27,6 +27,7 @@ files:
|
|
|
27
27
|
- toga25-supply/src/surface/index.ts
|
|
28
28
|
- toga25-supply/src/pages/Login/LoginPage.tsx
|
|
29
29
|
- toga25-supply/src/pages/SalesOrders/SalesOrders.tsx
|
|
30
|
+
- toga25-supply/src/pages/SalesOrders/view/SurfaceRowActions.tsx
|
|
30
31
|
- toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx
|
|
31
32
|
- toga25-supply/src/pages/Items/ItemsPage.tsx
|
|
32
33
|
- toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx
|
|
@@ -245,10 +246,24 @@ seeding NYCHH/Prudential/SPGlobal is deferred; the Client-DB prod cross-cluster
|
|
|
245
246
|
(the path form 404s EV-6).
|
|
246
247
|
- **Tier-1 only on the client.** Business-logic gates (Tier-2) arrive as resolved booleans from the
|
|
247
248
|
backend (`meta.surface`); never re-encode business rules in the FE evaluator.
|
|
249
|
+
- **A React portal overlay's click bubbles through the COMPONENT tree, not the DOM tree.** The
|
|
250
|
+
SalesOrders row-actions menu (`SurfaceRowActions.tsx`) renders its dismiss overlay via a portal;
|
|
251
|
+
an outside-click on that fixed overlay propagated up the React component tree to the row's
|
|
252
|
+
`handleRowClick` and **opened the record modal**. Fix: call `e.stopPropagation()` (plus close) in
|
|
253
|
+
the overlay's `onClick`. Portal DOM position does not stop React synthetic-event bubbling.
|
|
254
|
+
- **The sticky row-actions column must be gated on the active filter.** In `SalesOrders.tsx` the
|
|
255
|
+
`actionColumns` array is only included when `activeFilterKey === APPROVALS_FILTER_KEY`
|
|
256
|
+
(`"approvals"`); otherwise the row-actions column showed unconditionally.
|
|
248
257
|
- **`tsc` not yet run** this session (the private `@agilant/toga-blox` registry needs npm creds);
|
|
249
258
|
treat type-checking as pending. Runtime `GET /v2/surfaces/{slug}/meta` also not yet exercised.
|
|
250
259
|
|
|
251
260
|
## Change history
|
|
261
|
+
- 2026-07-13 — Fixed two SalesOrders row-actions bugs: (1) the portal dismiss overlay in
|
|
262
|
+
`SurfaceRowActions.tsx` propagated an outside-click up the React component tree to the row handler
|
|
263
|
+
and opened the record modal — added `e.stopPropagation()` (+ close) on the overlay `onClick`;
|
|
264
|
+
(2) gated the sticky `actionColumns` array in `SalesOrders.tsx` on
|
|
265
|
+
`activeFilterKey === APPROVALS_FILTER_KEY` so the row-actions column only shows under the Approvals
|
|
266
|
+
filter. (apeterson)
|
|
252
267
|
- 2026-06-30 — Follow-up: fixed the GROUPED meta fetch returning `{}` (every migrated-client
|
|
253
268
|
SalesOrders section had vanished). The meta-group envelope double-nests the slug-keyed `surfaces`
|
|
254
269
|
map under an outer ROUTE-keyed `surfaces` slot; the old `extractGroup` grabbed the first (outer,
|
package/knowledge/INDEX.md
CHANGED
|
@@ -17,7 +17,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
17
17
|
|
|
18
18
|
## 2.0 framework
|
|
19
19
|
|
|
20
|
-
- **_underscore** (_Underscore) _(framework core)_ —
|
|
20
|
+
- **_underscore** (_Underscore) _(framework core)_ — 29 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
21
21
|
- **worker2** (Worker) — 27 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
22
22
|
- **api2** (API) — 10 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
23
23
|
- **dbchanges2** (Database Changes) _(framework core)_ — 3 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
package/package.json
CHANGED