toga-ai 1.0.781 → 1.0.782

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.
@@ -4,7 +4,7 @@
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
6
  | [Re-runnable additive migrations (uuid4 in SQL, INSERT guards, the DISTINCT trap, conditional ALTERs)](features/rerunnable-additive-inserts.md) | Most `dbchanges2` files are **additive data grants** run by hand against production, often more than once (once per environment, or twice because someone was no | dbchanges2/Client_Compass/2026-08-26a - CompassCreativeStudioPersona.sql, dbchanges2/Core/2026-08-27a - Insert - Netsuite Location SyncAll CronJob.sql, dbchanges2/Client/2026-09-01a - PurchaseOrderItemQtyFieldsApiRoleRead.sql |
7
- | [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_Compass/2026-09-04a - ItemRecordRestrictToPersonaVisible.sql, dbchanges2/Client_CompassCanada/2026-09-04a - ItemRecordRestrictToPersonaVisible.sql, dbchanges2/Core/2026-09-02a - TalosAssistantSurfaceSeed.sql, dbchanges2/Client_Nychh/2026-09-02a - TalosAssistantEnable.sql, dbchanges2/Core/2026-08-31a - SalesOrderDenialDetailsSurfaceSeed.sql, dbchanges2/Client_Compass/2026-08-31a - SalesOrderDenialDetailsReadOnlyRules.sql, toga25-supply/db-migrations/PLAYBOOK.md, toga25-supply/db-migrations/SURFACE-FEATURE-RUNBOOK.md, dbchanges2/Client_Compass/, dbchanges2/Client_CompassCanada/, dbchanges2/Client_Quad/, dbchanges2/Core/2026-08-28b - TransferOrderListActionsSurfaceSeed.sql, dbchanges2/Core/2026-08-24b - SalesOrderStatusFilterSurfaceSeed.sql, dbchanges2/Client_Compass/2026-08-24b - SalesOrderStatusFilterHides.sql, dbchanges2/Client_CompassCanada/2026-08-24b - SalesOrderStatusFilterHides.sql, dbchanges2/Client_Quad/2026-08-24b - SalesOrderStatusFilterHides.sql, dbchanges2/Client_Nychh/2026-08-24b - SalesOrderStatusFilterHides.sql, dbchanges2/Core/2026-08-21 - SalesOrderDecisionSurfacesReseed.sql, dbchanges2/Core/2026-08-24 - RestoreApproveDenyRowActionsVisibility.sql, dbchanges2/Client_Quad/2026-08-24 - ProdPortApprovePoNumberEnabledRule.sql, dbchanges2/Client_Quad/2026-07-21b - ApproveDisabledTooltipPoNumber.sql, dbchanges2/Client_Quad/2026-08-21 - SalesOrderDecisionSummaryOverride.sql, dbchanges2/Client_Compass/2026-08-24 - ProdPortApprovalsGateAndApproveStepTwoRule.sql, dbchanges2/Client_CompassCanada/2026-08-24 - ProdPortApprovalsGateAndApproveStepTwoRule.sql, dbchanges2/Client_Nychh/2026-08-24 - FixInventoryGroupingsUnitsTopologyOverrideIds.sql, 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, dbchanges2/Client_Compass/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_Compass/2026-07-21b - SalesOrderApprovalsGateEnable.sql, dbchanges2/Client_CompassCanada/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_CompassCanada/2026-07-21b - SalesOrderApprovalsGateEnable.sql, dbchanges2/Client_Quad/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_Quad/2026-07-21b - SalesOrderApproveDisabledTooltipTranslation.sql, dbchanges2/Core/2026-07-21a - SalesOrderDecisionSummarySurfaceSeed.sql, dbchanges2/Core/2026-07-21b - SalesOrderDecisionActionSurfaceSeed.sql, dbchanges2/Client_Quad/2026-07-21c - SalesOrderDecisionSummaryOverride.sql, dbchanges2/Client_Compass/2026-07-21c - SalesOrderDecisionSummaryTotalConcat.sql, dbchanges2/Client_CompassCanada/2026-07-21c - SalesOrderDecisionSummaryTotalConcat.sql, dbchanges2/Core/2026-07-23a - PoNumberDetailFieldValueKey.sql, dbchanges2/Core/2026-07-29a - SalesOrderApprovalDetailsSurfaceSeed.sql, dbchanges2/Core/2026-08-05b - ApprovalDetailsShippingMethodConcatCharge.sql, dbchanges2/Core/2026-08-14a - NoteBadgesVocabularySurface.sql, dbchanges2/Client/2026-08-14a - NoteBadgeThemeTokens.sql, dbchanges2/Client/2026-08-14b - NoteBadgeUserColorFix.sql, dbchanges2/Client_Compass/2026-08-14a - NoteBadgeDelegateThemeTokens.sql, dbchanges2/Client_CompassCanada/2026-08-14a - NoteBadgeDelegateThemeTokens.sql, dbchanges2/Core/2026-08-26a - ServiceRequestRecordHeaderSurfaceSeed.sql, dbchanges2/Core/2026-08-26b - ServiceRequestDetailsSurfaceSeed.sql, dbchanges2/Core/2026-08-28a - TransferOrderSurfaceSeed.sql, dbchanges2/Client/2026-08-28a - TransferOrderStageThemeTokens.sql, dbchanges2/Client_Nychh/2026-08-28b - TransferOrdersNavigationEnable.sql, dbchanges2/Core/2026-09-03c - ColumnsModalSurfaceSeed.sql, dbchanges2/Core/2026-09-03d - ColumnsModalRetireConfigModal.sql |
7
+ | [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_Compass/2026-09-04a - ItemRecordRestrictToPersonaVisible.sql, dbchanges2/Client_CompassCanada/2026-09-04a - ItemRecordRestrictToPersonaVisible.sql, dbchanges2/Core/2026-09-03a - TransferOrderStatusBadgeRecordFields.sql, dbchanges2/Core/2026-09-03b - InventoryCreateTransferOrderAction.sql, dbchanges2/Client_Nychh/2026-09-03a - TransferOrdersStatusBadge.sql, dbchanges2/Client_Nychh/2026-09-03b - InventoryCreateTransferOrderShow.sql, dbchanges2/Core/2026-09-02a - TalosAssistantSurfaceSeed.sql, dbchanges2/Client_Nychh/2026-09-02a - TalosAssistantEnable.sql, dbchanges2/Core/2026-08-31a - SalesOrderDenialDetailsSurfaceSeed.sql, dbchanges2/Client_Compass/2026-08-31a - SalesOrderDenialDetailsReadOnlyRules.sql, toga25-supply/db-migrations/PLAYBOOK.md, toga25-supply/db-migrations/SURFACE-FEATURE-RUNBOOK.md, dbchanges2/Client_Compass/, dbchanges2/Client_CompassCanada/, dbchanges2/Client_Quad/, dbchanges2/Core/2026-08-28b - TransferOrderListActionsSurfaceSeed.sql, dbchanges2/Core/2026-08-24b - SalesOrderStatusFilterSurfaceSeed.sql, dbchanges2/Client_Compass/2026-08-24b - SalesOrderStatusFilterHides.sql, dbchanges2/Client_CompassCanada/2026-08-24b - SalesOrderStatusFilterHides.sql, dbchanges2/Client_Quad/2026-08-24b - SalesOrderStatusFilterHides.sql, dbchanges2/Client_Nychh/2026-08-24b - SalesOrderStatusFilterHides.sql, dbchanges2/Core/2026-08-21 - SalesOrderDecisionSurfacesReseed.sql, dbchanges2/Core/2026-08-24 - RestoreApproveDenyRowActionsVisibility.sql, dbchanges2/Client_Quad/2026-08-24 - ProdPortApprovePoNumberEnabledRule.sql, dbchanges2/Client_Quad/2026-07-21b - ApproveDisabledTooltipPoNumber.sql, dbchanges2/Client_Quad/2026-08-21 - SalesOrderDecisionSummaryOverride.sql, dbchanges2/Client_Compass/2026-08-24 - ProdPortApprovalsGateAndApproveStepTwoRule.sql, dbchanges2/Client_CompassCanada/2026-08-24 - ProdPortApprovalsGateAndApproveStepTwoRule.sql, dbchanges2/Client_Nychh/2026-08-24 - FixInventoryGroupingsUnitsTopologyOverrideIds.sql, 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, dbchanges2/Client_Compass/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_Compass/2026-07-21b - SalesOrderApprovalsGateEnable.sql, dbchanges2/Client_CompassCanada/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_CompassCanada/2026-07-21b - SalesOrderApprovalsGateEnable.sql, dbchanges2/Client_Quad/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_Quad/2026-07-21b - SalesOrderApproveDisabledTooltipTranslation.sql, dbchanges2/Core/2026-07-21a - SalesOrderDecisionSummarySurfaceSeed.sql, dbchanges2/Core/2026-07-21b - SalesOrderDecisionActionSurfaceSeed.sql, dbchanges2/Client_Quad/2026-07-21c - SalesOrderDecisionSummaryOverride.sql, dbchanges2/Client_Compass/2026-07-21c - SalesOrderDecisionSummaryTotalConcat.sql, dbchanges2/Client_CompassCanada/2026-07-21c - SalesOrderDecisionSummaryTotalConcat.sql, dbchanges2/Core/2026-07-23a - PoNumberDetailFieldValueKey.sql, dbchanges2/Core/2026-07-29a - SalesOrderApprovalDetailsSurfaceSeed.sql, dbchanges2/Core/2026-08-05b - ApprovalDetailsShippingMethodConcatCharge.sql, dbchanges2/Core/2026-08-14a - NoteBadgesVocabularySurface.sql, dbchanges2/Client/2026-08-14a - NoteBadgeThemeTokens.sql, dbchanges2/Client/2026-08-14b - NoteBadgeUserColorFix.sql, dbchanges2/Client_Compass/2026-08-14a - NoteBadgeDelegateThemeTokens.sql, dbchanges2/Client_CompassCanada/2026-08-14a - NoteBadgeDelegateThemeTokens.sql, dbchanges2/Core/2026-08-26a - ServiceRequestRecordHeaderSurfaceSeed.sql, dbchanges2/Core/2026-08-26b - ServiceRequestDetailsSurfaceSeed.sql, dbchanges2/Core/2026-08-28a - TransferOrderSurfaceSeed.sql, dbchanges2/Client/2026-08-28a - TransferOrderStageThemeTokens.sql, dbchanges2/Client_Nychh/2026-08-28b - TransferOrdersNavigationEnable.sql, dbchanges2/Core/2026-09-03c - ColumnsModalSurfaceSeed.sql, dbchanges2/Core/2026-09-03d - ColumnsModalRetireConfigModal.sql |
8
8
  | [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/ |
9
9
  | [Auditing a client DB that drifted from its models (partially applied module migration)](workflows/client-schema-drift-audit.md) | A recurring 2.0 failure mode: **one client's database drifts from what the PHP models declare**, usually because a `_modules/<module>/` migration was applied to | dbchanges2/Client_Growrk/2026-05-28.sql, dbchanges2/Client_Growrk/2026-08-10c - GrowrkServiceRequestCustomFieldsCatchUp.sql, dbchanges2/Client_Growrk/2026-08-10d - GrowrkServiceRequestTypeAndDispositionSeeds.sql, dbchanges2/_modules/netsuite/2026-07-10a - UnitInventoryFields.sql, dbchanges2/Client_Growrk/2026-08-10 - GrowrkUnitInventoryFieldsCatchUp.sql, dbchanges2/Client_Growrk/2026-08-10b - GrowrkUnitItemDescriptionAcl.sql, dbchanges2/Client_Growrk/_modules.txt |
10
10
  | [Framework branch running AHEAD of schema — environment-wide 1054/EO-1 after a deploy](workflows/framework-branch-ahead-of-schema.md) | A **third** kind of 2.0 schema drift, distinct from the two already documented: nobody's database went backwards — **the code went forwards**. | _underscore/Model/Core/RecordField.php, _underscore/Model/Client/TableViewField.php, _underscore/Model/Client/TableView.php, _underscore/Model/Core/Surface.php, _underscore/Model/Client/Urgency.php, _underscore/Model/Client/RecordFieldSetting.php, _underscore/Model/Client/ClientRecordFieldSetting.php, api2/Component/Api/V2/V2.php, dbchanges2/Core/2026-08-14c - RecordFieldsValidationMetadata.sql, dbchanges2/Client/2026-08-14a - RecordFieldSettingsClientTable.sql, dbchanges2/Client/2026-08-13a - UserNavPreferencesClientTable.sql, dbchanges2/Client/2026-08-13c - PersonaNavVisibilityClientTable.sql |
@@ -11,6 +11,10 @@ owners: [jcardinal, apeterson, tcox]
11
11
  files:
12
12
  - dbchanges2/Client_Compass/2026-09-04a - ItemRecordRestrictToPersonaVisible.sql
13
13
  - dbchanges2/Client_CompassCanada/2026-09-04a - ItemRecordRestrictToPersonaVisible.sql
14
+ - dbchanges2/Core/2026-09-03a - TransferOrderStatusBadgeRecordFields.sql
15
+ - dbchanges2/Core/2026-09-03b - InventoryCreateTransferOrderAction.sql
16
+ - dbchanges2/Client_Nychh/2026-09-03a - TransferOrdersStatusBadge.sql
17
+ - dbchanges2/Client_Nychh/2026-09-03b - InventoryCreateTransferOrderShow.sql
14
18
  - dbchanges2/Core/2026-09-02a - TalosAssistantSurfaceSeed.sql
15
19
  - dbchanges2/Client_Nychh/2026-09-02a - TalosAssistantEnable.sql
16
20
  - dbchanges2/Core/2026-08-31a - SalesOrderDenialDetailsSurfaceSeed.sql
@@ -145,6 +149,25 @@ per-tenant deltas live in **Client** (mirrors `TableViews`/ACL/`ItemTranslations
145
149
  `value varchar(255)` + `c_longValue mediumtext`. Precedence **base < client < persona < role**
146
150
  with language as an orthogonal overlay; the resolver applies it most-specific-last in one pass.
147
151
 
152
+ ### Which family is this table in? — the cheat sheet a UI-layer migration actually needs
153
+
154
+ Every `.sql` file for a UI-layer change has to be checked statement-by-statement against the
155
+ **cluster-isolation hard rule** (see the [dbchanges2 architecture](../architecture.md)), and the
156
+ tables that come up over and over split cleanly. **Verified placement:**
157
+
158
+ | Family | Tables |
159
+ |---|---|
160
+ | **CORE** | `Records`, `RecordFields`, `Messages`, `Surfaces`, `SurfaceElements`, `Actions`, `Vocabularies`, `VocabularyTerms` |
161
+ | **CLIENT** (per tenant) | `TableViews`, `TableViewJoins`, `TableViewFields`, `AclRecordPermissions` / `AclFieldPermissions` / the rest of `Acl*`, `SurfaceOverrides`, `MessageTranslations`, `ThemeTokens` |
162
+
163
+ Proof for the TableView half, rather than assumption: `_underscore/Model/Client/TableView.php:74`
164
+ declares `database: _underscore::DB_CLIENT`. The Core half is the `Model/Core/*` counterpart.
165
+
166
+ **The practical consequence:** a badge, a button or a column almost always needs **two files** — a
167
+ `Core/` one and a `Client_<Tenant>/` one — and **every Core id the client file needs must be inlined
168
+ as a literal, never sub-selected**. A `SELECT … FROM Core.…` inside a `Client_*` file cannot resolve
169
+ in production and the hook refuses the write.
170
+
148
171
  ## SurfaceElements — two orthogonal axes + edit-mode columns (one tree serves view + edit)
149
172
 
150
173
  A record modal is modeled as **ONE** set of `<record>-record-*` SECTION surfaces (a header +
@@ -872,6 +895,14 @@ rule resumes.
872
895
 
873
896
  ## Gotchas
874
897
 
898
+ - **🚨 A reserved-id comment block is NOT evidence.** Repo comments claimed `SurfaceElements`
899
+ reserved through 218; prod's `MAX(id)` was 220 and sandbox-client's was 232 (2026-09-03). Seeds
900
+ using AUTO_INCREMENT + `NOT EXISTS` consume ids invisibly and land differently per environment.
901
+ **Measure `MAX(id)` in every environment before picking a literal.**
902
+ - **`TableViews` / `TableViewJoins` / `TableViewFields` / `Acl*` are CLIENT tables**
903
+ (`_underscore/Model/Client/TableView.php:74` → `DB_CLIENT`); `Records` / `RecordFields` /
904
+ `Messages` / `Surfaces` / `SurfaceElements` are **CORE**. A UI-layer change therefore needs two
905
+ files, and the client file must inline every Core id as a literal.
875
906
  - **🚨 SHIP-TOGETHER: `Client_Nychh/2026-08-24 - FixInventoryGroupingsUnitsTopologyOverrideIds.sql`
876
907
  MUST be in the SAME release as `Core/2026-08-21 - SalesOrderDecisionSurfacesReseed.sql`.**
877
908
  `Client_Nychh/2026-08-13a` resolved its Core ids via a cross-DB join and **stored the authoring
@@ -1058,6 +1089,22 @@ exactly (`MAX(Surfaces.id)` 44, `MAX(SurfaceElements.id)` 160) — that was **ve
1058
1089
  Using literals is still correct: a collision then fails loudly on the primary key instead of silently
1059
1090
  mis-wiring (which is what `INSERT IGNORE` did in the 2026-08-24 incident above).
1060
1091
 
1092
+ #### 🚨 The reserved-id COMMENTS in this repo do not match reality — and they drift PER ENVIRONMENT
1093
+
1094
+ The comment blocks at the top of the seed files are a record of what was **claimed**, not of what is
1095
+ **taken**. Verified 2026-09-03: the repo comments said `SurfaceElements` were reserved through
1096
+ **218**, while `SELECT MAX(id)` returned **220 on production** and **232 on sandbox-client**.
1097
+
1098
+ **Cause:** several seeds insert `SurfaceElements` with **AUTO_INCREMENT behind a `NOT EXISTS`
1099
+ guard** (the purely-additive pattern documented below) rather than with a literal id. Those rows are
1100
+ real and consume ids, but they leave no literal anywhere for a later author to read — and because
1101
+ each environment runs them at a different time against a different ceiling, the ids they land on
1102
+ **differ per environment**.
1103
+
1104
+ > **Rule: measure `MAX(id)` in EVERY environment before picking a literal id.** Never take the
1105
+ > number from a comment block, a change-history entry, or this doc. The two authoring patterns are
1106
+ > both legitimate, but mixing them means the repo can never be an id ledger.
1107
+
1061
1108
  #### Ceiling snapshot, verified read-only 2026-08-28 (before reserving the transfer-order block)
1062
1109
 
1063
1110
  | Environment | `MAX(Surfaces.id)` | `MAX(SurfaceElements.id)` | `MAX(Messages.id)` |
@@ -1383,6 +1430,21 @@ still unrun at the time, so this is a reservation, not a deployed state.
1383
1430
  rendered empty. Also recorded the **FE-first ship order**: the override alone exposes an
1384
1431
  unformatted value and crashes the modal (React error #31). Run on **client-sandbox `Client_Compass`
1385
1432
  only**; prod and Canada pending. (tcox)
1433
+ - 2026-09-04 — 🚨 **The reserved-id comment blocks in this repo do NOT match reality, and drift per
1434
+ environment.** Comments said `SurfaceElements` were reserved through **218**; `SELECT MAX(id)`
1435
+ returned **220 on production** and **232 on sandbox-client**. Cause: several seeds insert
1436
+ `SurfaceElements` with **AUTO_INCREMENT behind a `NOT EXISTS` guard** instead of a literal id, so
1437
+ the rows consume ids invisibly and land on different numbers in each environment. Rule recorded:
1438
+ **measure `MAX(id)` in every environment before picking a literal** — a comment records a claim,
1439
+ not a reservation. Also added a **verified Core-vs-Client placement cheat sheet** for the tables a
1440
+ UI-layer migration touches (`TableViews`/`TableViewJoins`/`TableViewFields`/`Acl*`/
1441
+ `SurfaceOverrides`/`MessageTranslations`/`ThemeTokens` = **CLIENT**, proven at
1442
+ `_underscore/Model/Client/TableView.php:74` → `DB_CLIENT`; `Records`/`RecordFields`/`Messages`/
1443
+ `Surfaces`/`SurfaceElements`/`Actions`/`Vocabularies`/`VocabularyTerms` = **CORE**), because a
1444
+ badge or button change is almost always a two-file change and every Core id in the client file
1445
+ must be an inlined literal. Confirmed on the transfer-order status badge + Inventory
1446
+ create-transfer-order button seeds (`Core/2026-09-03a`+`b`, `Client_Nychh/2026-09-03a`+`b`), which
1447
+ again followed the Core-seeds-OFF / tenant-opts-in default. (apeterson)
1386
1448
  - 2026-09-03 — Recorded the rule that **a modal needing surface-driven copy becomes its own Surface**:
1387
1449
  the resolver reads messages only from `*MessageId` columns and never from `config`, so
1388
1450
  `Core/2026-08-28b`'s `config.modal` keys could never resolve and the Columns modal rendered raw
@@ -10,7 +10,7 @@
10
10
  | [Force Logout on Deployment (useDeploymentGuard)](features/force-logout-on-deployment.md) | On large deployments the backend bumps the Core parameter `META_LAST_REFRESH_DATETIME`. | toga25-supply/src/hooks/useDeploymentGuard.tsx, toga25-supply/src/App.tsx |
11
11
  | [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/TransferOrders/viewModel/useTransferOrdersPageViewModel.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 |
12
12
  | [Persisted React Query cache (localStorage `supply-chain-query-cache`)](features/persisted-query-cache.md) | `localStorage["supply-chain-query-cache"]` is **not a hand-written cache**. | toga25-supply/src/App.tsx, toga25-supply/src/contexts/AuthContext.tsx, toga25-supply/src/hooks/useDeploymentGuard.tsx, toga25-supply/src/fieldsConfig/useClientFields.ts, toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/hooks/useCurrentUser.ts, toga25-supply/src/hooks/useStatusTypeValues.ts, toga25-supply/src/surface/useStatusColors.ts, toga25-supply/vite.config.ts |
13
- | [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/pages/SalesOrders/view/SalesOrderRecordModalLayout/hooks/usePurchaseOrderDetails.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/FIELDS/apiFields.json, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/layout/PrimaryTableServerLayout/PrimaryTableServerLayout.tsx, toga25-supply/src/layout/PrimaryTableServerLayout/types.ts, 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/pages/TransferOrders/view/TransferOrderRecordModalLayout/TransferOrderRecordModalLayout.tsx, toga25-supply/src/pages/TransferOrders/helpers/buildInventoryPurchaseOrderUrl.ts, toga25-supply/src/hooks/useTableCellInteractions.ts, toga25-supply/src/hooks/useServerTableUrlState.ts, toga25-supply/src/layout/RecordApprovalModal/helpers/handleFormatApprovalWorkflowPayload.ts, toga25-supply/src/layout/RecordApprovalModal/api/approvalDecisionsApi.ts, toga25-supply/src/layout/RecordApprovalModal/ApprovalModal.module.css |
13
+ | [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/pages/SalesOrders/view/SalesOrderRecordModalLayout/hooks/usePurchaseOrderDetails.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/FIELDS/apiFields.json, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/layout/PrimaryTableServerLayout/PrimaryTableServerLayout.tsx, toga25-supply/src/layout/PrimaryTableServerLayout/types.ts, toga25-supply/src/layout/ItemRecordModalLayout/, toga25-supply/src/layout/SalesOrderRecordModalLayout/, toga25-supply/src/layout/SalesOrderItemsTableLayout/, toga25-supply/src/layout/ItemFulfillmentModal/, toga25-supply/src/layout/ItemFulfillmentModal/useItemFulfillmentModalViewModel.tsx, 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/pages/TransferOrders/view/TransferOrderRecordModalLayout/TransferOrderRecordModalLayout.tsx, toga25-supply/src/pages/TransferOrders/helpers/buildInventoryPurchaseOrderUrl.ts, toga25-supply/src/hooks/useTableCellInteractions.ts, toga25-supply/src/hooks/useServerTableUrlState.ts, toga25-supply/src/layout/RecordApprovalModal/helpers/handleFormatApprovalWorkflowPayload.ts, toga25-supply/src/layout/RecordApprovalModal/api/approvalDecisionsApi.ts, toga25-supply/src/layout/RecordApprovalModal/ApprovalModal.module.css |
14
14
  | [Side navigation & default route — an empty nav renders a BLANK PAGE and gets reported as "cannot log in"](features/side-navigation-and-default-route.md) | The 2.5 side nav is **100% backend-driven** by the `navigation` surface bundle, and the same list also decides **which routes exist** and **where `/` lands**. | toga25-supply/src/routes.tsx, toga25-supply/src/layout/AppLayout/viewModel/useAppLayoutViewModel.ts, api2/Component/Api/V2/V2.php, _underscore/Model/Core/Surface.php |
15
15
  | [SSO redirect & public-vs-user session gating (useAuthenticationFlow)](features/sso-redirect-and-session-gating.md) | How 2.5 Supply decides, on every navigation, whether an anonymous visitor should be bounced to their client's SSO IdP instead of the local `/login` form. | toga25-supply/src/hooks/useAuthenticationFlow.ts, toga25-supply/src/routes.tsx, toga25-supply/src/contexts/AuthContext.tsx, toga25-supply/src/api/api.ts |
16
16
  | [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/layout/RecordApprovalModal/helpers/stackedCurrencyJoiner.ts, toga25-supply/src/layout/RecordApprovalModal/helpers/stackedCurrencyJoiner.test.ts, toga25-supply/src/App.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/DenialBanner.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/SalesOrderRecordModal.module.css, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/SalesOrderApprovalModalsLayout.tsx, toga25-supply/src/layout/RecordApprovalModal/RecordApprovalModalLayout.tsx, toga25-supply/src/layout/RecordApprovalModal/view/ApprovalTimelineView.tsx, toga25-supply/src/layout/RecordApprovalModal/ApprovalModal.module.css, toga25-supply/src/utils/formatDateTime.ts, toga25-supply/src/utils/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/layout/RecordApprovalModal/, toga25-supply/src/contexts/AuthContext.tsx, toga25-supply/src/surface/applyColSpan.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/sectionRenderers.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getVisibleSections.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderApprovalSummaryGrid.tsx, toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Bundles/viewModel/useBundlesPageViewModel.tsx, toga25-supply/src/layout/ItemFulfillmentModal/useItemFulfillmentModalViewModel.tsx, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/ServiceRequestRecordModalLayout.tsx, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/view/ServiceRequestsView.tsx, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/viewModel/useServiceRequestRecordModalLayoutModel.tsx, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/viewModel/FIELDS/apiFields.json, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, 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/view/sections/AdminNotesSection.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getAdminNotes.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/hooks/useSalesOrderVip.ts, 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, toga25-supply/src/surface/useStatusColors.ts, toga25-supply/src/surface/SurfaceHeader.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/helpers/surfaceBundlesToDecisionFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/useApprovalModalViewModel.tsx |
@@ -6,7 +6,7 @@ project: TOGa 2.5 Supply
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-03
9
+ updated: 2026-09-04
10
10
  owners: [apeterson]
11
11
  files:
12
12
  - toga25-supply/src/components/ColumnVisibilityModal/
@@ -187,6 +187,11 @@ leaves a clean URL, and a column added to the meta later still shows up for that
187
187
 
188
188
  ## Gotchas
189
189
 
190
+ - **⚠ The six-page `useColumnState` migration cannot be reverted file-by-file.** The shared
191
+ `ColumnVisibilityModalLabels` type gained **required** fields (`shown`, `hidden`, `lockedTip`,
192
+ `pinnedTip`), so restoring an old call site no longer type-checks — the build breaks. Roll the
193
+ whole change back together or not at all. (The dead `columnsButton` return in the SalesOrders view
194
+ model was deleted in the same pass.)
190
195
  - **No hardcoded copy in the component** — every user-facing string (incl. Close `aria-label`) is a
191
196
  required `labels` prop. On the **legacy** path the fallbacks live in the view model's resolution
192
197
  (never in the component or default props); on the **surface** path there are **no fallbacks at
@@ -211,6 +216,11 @@ leaves a clean URL, and a column added to the meta later still shows up for that
211
216
  meta later won't appear for an old link until Reset (intended).
212
217
 
213
218
  ## Change history
219
+ - 2026-09-04 — ⚠ Recorded that the six-page `useColumnState` migration is **not reversible
220
+ file-by-file**: the shared `ColumnVisibilityModalLabels` type gained required fields (`shown`,
221
+ `hidden`, `lockedTip`, `pinnedTip`), so an old call site no longer type-checks and a partial revert
222
+ breaks the build. Roll it back whole or not at all. The dead `columnsButton` return in the
223
+ SalesOrders view model was removed in the same pass. (apeterson)
214
224
  - 2026-09-03 — The modal became **its own MODAL Surface** (`columns-modal`, **Core 60**; Messages
215
225
  293–298, SurfaceElements 222–232, `Core/2026-09-03c`). The 08-28 approach was unworkable: the
216
226
  resolver only collects messages from `*MessageId` columns and **never scans `config`**, so the
@@ -19,6 +19,7 @@ files:
19
19
  - toga25-supply/src/layout/SalesOrderRecordModalLayout/
20
20
  - toga25-supply/src/layout/SalesOrderItemsTableLayout/
21
21
  - toga25-supply/src/layout/ItemFulfillmentModal/
22
+ - toga25-supply/src/layout/ItemFulfillmentModal/useItemFulfillmentModalViewModel.tsx
22
23
  - toga25-supply/src/layout/GenericNestedTables/GenericNestedTables.tsx
23
24
  - toga25-supply/src/layout/GenericNestedTables/GenericTableLayout.tsx
24
25
  - toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx
@@ -110,6 +111,29 @@ navigation). `ItemFulfillmentModal` sets it because the fulfillment table is rea
110
111
  scoped so all other `PrimaryTableServerLayout` consumers keep normal row-click behavior — pass it
111
112
  only for genuinely read-only tables.
112
113
 
114
+ ### ⚠ `ItemFulfillmentModal`'s `slug` prop IS a TableView slug — and it is still hardcoded to sales orders
115
+
116
+ The shared "Qty Fulfilled" drill-down modal (`src/layout/ItemFulfillmentModal/`) reads as if `slug`
117
+ were a component variant. It is not: **`slug` is a TableView slug**, and the whole modal is built
118
+ from it — `useTablePageMeta({ slug })` supplies the columns and
119
+ `useTableData({ slug, tableViewMeta })` supplies the rows
120
+ (`useItemFulfillmentModalViewModel.tsx`).
121
+
122
+ **No TableView row for that slug → the modal opens EMPTY.** No error, no failed request. Reusing this
123
+ modal for a new record type is therefore a **migration** first, a front-end change second.
124
+
125
+ **Two spots are still hardcoded to sales orders** (blocking reuse as of 2026-09-04):
126
+
127
+ 1. **line ~27** — the label fetch always asks for page meta `"item-fulfillments-for-sales-orders"`,
128
+ whatever slug was passed in.
129
+ 2. **lines ~55–60** — the record filter falls back to **`"SalesOrders.uuid"`** for any unrecognised
130
+ slug, so a non-sales-order slug filters on the wrong column and returns nothing.
131
+
132
+ **Measured in `Client_Nychh`: only TableViews 12 (`item-fulfillments-for-sales-orders`) and 13
133
+ (`item-fulfillments-for-sales-order-items`) exist, both on `recordId` 29.** There is no
134
+ transfer-order equivalent in any environment — see
135
+ [transfer-orders-page](./transfer-orders-page.md).
136
+
113
137
  ## Pattern 5 — `GenericNestedTables` (config-driven multi-level / swap tables)
114
138
 
115
139
  A config-driven renderer: declare an array of `TableLevel` objects instead of hand-wiring each
@@ -343,6 +367,14 @@ Scaffold per the `ItemRecordModalLayout` pattern (`src/layout/ItemRecordModalLay
343
367
  EDIT is still JSON**: a field visible only in edit mode means a missing client `SurfaceOverride`,
344
368
  not a data bug (found on Compass's "Restrict to Persona" field). Detail in
345
369
  [surface-frontend](surface-frontend.md). (tcox)
370
+ - 2026-09-04 — ⚠ Recorded that **`ItemFulfillmentModal`'s `slug` prop is a TableView slug**, not a
371
+ component variant: `useTablePageMeta`/`useTableData` build the entire modal from it, so a missing
372
+ TableView row opens the modal **empty and silently**. Reusing it for a new record type is a
373
+ migration before it is a front-end change. Also flagged the **two spots still hardcoded to sales
374
+ orders** — the label fetch always requests `item-fulfillments-for-sales-orders` (line ~27) and the
375
+ record filter falls back to `SalesOrders.uuid` for any unrecognised slug (lines ~55–60), so a
376
+ transfer-order slug today filters the wrong column and returns nothing. `Client_Nychh` has only
377
+ TableViews 12/13, both on `recordId` 29; no transfer-order equivalent exists anywhere. (apeterson)
346
378
  - 2026-09-03 — Added the **deep-link-from-a-modal-into-a-nested-table** pattern (pin `grouping`;
347
379
  build the column-filter chip with the `useServerTableUrlState` serializer, keyed by the table-meta
348
380
  **column id**; set `<slug>=<uuid>` to pre-expand the row) and the rule that makes it work:
@@ -783,8 +783,45 @@ in an edit to the shared blox stylesheet.
783
783
  ⚠ This is **shared infra** — `SurfaceActionBar` renders every migrated screen's chrome, so a change
784
784
  here is a cross-screen change. Re-check Sales Orders, Items and Vendor Items after touching it.
785
785
 
786
+ ## Hidden elements ARRIVE in the bundle — every hand-rolled builder must filter on `isVisible`
787
+
788
+ The resolver ships the **whole** element tree and marks state on it; it does **not** prune. So a
789
+ component that maps `bundle.elements` to its own controls **renders elements the Core seed
790
+ deliberately turned off** unless it checks `el.isVisible` itself. `SurfaceActionBar` does this for
791
+ you — a page that builds its own header buttons does not.
792
+
793
+ Found 2026-09-03 on the **Inventory** page: `useInventoryPageViewModel` built its header buttons
794
+ without the filter. It had no symptom only because no hidden element existed on that bar yet; the
795
+ moment `Core/2026-09-03b` seeded the Create Transfer Order button `isVisible = 0`, it would have
796
+ appeared for **every tenant** — defeating the whole Core-neutral-default / tenant-opt-in model. See
797
+ [transfer-orders-page](./transfer-orders-page.md).
798
+
799
+ > **Check for this wherever a view model reads `elements` directly.** `isVisible = 0` is the platform's
800
+ > off switch; ignoring it turns every neutral default into an accidental release.
801
+
802
+ ## `useStatusColors` is keyed by `{ route, keyField }` — vocabularies do NOT all key on slug
803
+
804
+ The original hook mapped a vocabulary slug to an API **route** and assumed the term `value` was
805
+ always the record's **slug**. That held for sales orders and breaks for transfer orders:
806
+ `TransferOrderStages` has no slug column, so its `VocabularyTerms.value` holds the stage **NAME**.
807
+
808
+ `src/surface/useStatusColors.ts` therefore keys each vocabulary to a **`{ route, keyField }` pair**,
809
+ and matches the term value against that field of the fetched rows
810
+ (`?fields=slug,name,colorHex`). Adding a third STATUS vocabulary means answering *"what does its
811
+ `VocabularyTerms.value` actually hold?"* before wiring it — a wrong `keyField` produces a badge with
812
+ the right label and no colour, and logs nothing.
813
+
814
+ Full badge chain (`RecordFields.type='STATUS'` → `Records.statusRecordId` → route → typeValues →
815
+ blox `StatusCell`, which is a plain `typeValues[value]` lookup):
816
+ [transfer-orders-page](./transfer-orders-page.md).
817
+
786
818
  ## Gotchas
787
819
 
820
+ - **⚠ Hidden elements are IN the bundle** — a hand-rolled control builder that ignores
821
+ `el.isVisible` ships every element the Core seed turned off (see the section above).
822
+ - **⚠ A STATUS badge renders label-but-no-colour when the vocabulary's `keyField` is wrong** —
823
+ `StatusCell` is a plain map lookup on the cell value, so a name against a slug-keyed vocabulary
824
+ just misses, silently.
788
825
  - **A vitest file that reaches `@agilant/toga-blox` must stub the whole package:
789
826
  `vi.mock("@agilant/toga-blox", () => ({}))`.** blox's `dist` entry imports `global.css`, which
790
827
  node's ESM loader rejects, so **any** import chain that transitively pulls blox in fails to load
@@ -1055,6 +1092,15 @@ unused).
1055
1092
  "Unexpected Application Error!" because the app has no top-level `ErrorBoundary`.
1056
1093
  `formatPersonaItems` now accepts both value shapes and is registered under both keys. **Deploy the
1057
1094
  FE first or together — the SQL alone crashes the modal.** (tcox)
1095
+ - 2026-09-04 — Two shared-infra findings from the transfer-order status badge. (1) ⚠ **Hidden
1096
+ elements arrive in the resolved bundle** — the resolver marks state, it never prunes — so any view
1097
+ model that builds its own controls from `bundle.elements` must filter on `el.isVisible`. The
1098
+ Inventory header-button builder did not; it was symptomless only until `Core/2026-09-03b` seeded a
1099
+ deliberately-hidden button, which would then have shown for every tenant. (2) **`useStatusColors`
1100
+ is now keyed by `{ route, keyField }`, not by route alone**: sales-order terms carry the status
1101
+ **slug** but transfer-order terms carry the stage **NAME** (`TransferOrderStages` has no slug
1102
+ column), and a wrong key field yields a correctly-labelled badge with no colour and no log line.
1103
+ (apeterson)
1058
1104
  - 2026-09-03 — Recorded that the 2.5 app **still calls `GET /v2/pages/meta`** from five view
1059
1105
  models (SalesOrders — twice per load — VendorItems, Inventory, Bundles, ItemFulfillmentModal)
1060
1106
  even though nothing in `src/` reads a field off the result. Found by the new Cypress
@@ -6,7 +6,7 @@ project: TOGa 2.5 Supply
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-03
9
+ updated: 2026-09-04
10
10
  owners: [apeterson]
11
11
  files:
12
12
  - toga25-supply/src/components/ToolbarButton/ToolbarButton.tsx
@@ -64,6 +64,24 @@ than by luck with whatever font metrics resolve.
64
64
  **The "applied" signal never fills the button.** Instead the glyph moves into a small filled rounded
65
65
  square — Group by: 18×18, radius 4, `#3F5DCA`, white glyph.
66
66
 
67
+ ### The API — `shape` + `activeAppearance`, generalised from `ColumnsToolbarButton`
68
+
69
+ `ToolbarButton` was **generalised out of the one-off `ColumnsToolbarButton`**, not written fresh, so
70
+ the Columns button is the reference call site.
71
+
72
+ - **`shape`** picks between the two design shapes above: **`pill`** for actions (Refresh, Reset),
73
+ **`chip`** for management controls (Columns, Group by). One component, two shapes — not two
74
+ components.
75
+ - **`activeAppearance`** separates the **transient** active state (pressed / in-flight) from the
76
+ **persistent** one (a filter or grouping is currently applied). They look different by design and
77
+ had previously been conflated, so a button could look permanently "on" after a single click.
78
+
79
+ ### Toolbar spacing — `gap-x-2` (8px), not `gap-x-5` (20px)
80
+
81
+ Three labeled-mode pages (Transfer Orders, Sales Orders, Inventory) shipped with `gap-x-5` = **20px**
82
+ between toolbar buttons; the mockup is **8px**. Corrected to `gap-x-2` on all three. Worth stating
83
+ explicitly because 20px looks deliberate rather than wrong until it is next to the design.
84
+
67
85
  **Buttons the design does not specify keep `BaseButton`:** PRIMARY create actions, and the Approvals
68
86
  filter button (SurfaceElement 22, `config.role = filterButton`), whose navy fill is a clearer "on"
69
87
  signal than a chip.
@@ -84,6 +102,13 @@ signal than a chip.
84
102
  `Columns (N)` label when the columns differ from default.
85
103
 
86
104
  ## Change history
105
+ - 2026-09-04 — Recorded the component's actual API and the spacing fix: `ToolbarButton` was
106
+ **generalised out of `ColumnsToolbarButton`** and takes a **`shape`** prop (`pill` for actions,
107
+ `chip` for management controls) plus an **`activeAppearance`** prop that separates a **transient**
108
+ active state (pressed/in-flight) from a **persistent** one (a filter or grouping is applied) —
109
+ previously conflated, which left a button looking permanently on. Also corrected the toolbar gap
110
+ from `gap-x-5` (20px) to **`gap-x-2` (8px)** on the three labeled-mode pages (Transfer Orders,
111
+ Sales Orders, Inventory) to match the mockup. (apeterson)
87
112
  - 2026-09-03 — Created. A page toolbar rendered three buttons at three different heights; the cause is
88
113
  that blox `.secondaryBtnAction` hard-codes `height: 26px` with **no theme token**, while
89
114
  `.primaryBtn` sets no height at all. Rethemeing would hit every button in the app, so the toolbar
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.781",
3
+ "version": "1.0.782",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",