toga-ai 1.0.764 → 1.0.766

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.
@@ -10,5 +10,5 @@
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
11
  | [Local vs prod MySQL config parity — why “it passed locally” is not evidence](workflows/local-vs-prod-mysql-config-parity.md) | Several migration failures that look like "prod-only bugs" are actually **per-machine MySQL server-configuration differences**. | |
12
12
  | [Repairing non-prod metadata drift (works in prod, broken in beta/dev-sandbox)](workflows/nonprod-metadata-drift-repair.md) | Almost all 2.0 platform behavior is **metadata** — `Core.Records`/`RecordFields`, `Core.RecordScripts`, `Core.ApiPayloadInterceptors`, and per-client `Acl*` row | dbchanges2/Core/2026-06-30a - ItemFulfillmentStageDefaultInterceptor.sql, dbchanges2/Client_Compass/2026-08-06 - RemoveBrokenSalesOrderItemPostPostInterceptor.sql, dbchanges2/Core/2026-07-16a - TrackingNumberSignatureTypeRecordField.sql, dbchanges2/_modules/netsuite/2026-07-10a - UnitInventoryFields.sql, dbchanges2/Client_Aig/_modules.txt, dbchanges2/Client/2026-07-22c - TrackingNumberMeasureIdsFieldPermission.sql, api2/Config/beta.ini, api2/Config/sandbox-dev.ini, dbchanges2/Client/2026-07-22a - EntitlementServiceAddressId.sql, dbchanges2/Client/2026-07-23a - EntitlementServiceAddressIdFieldPermission.sql |
13
- | [Targeting the right database (tenant restores, and running a change file against the wrong DB)](workflows/targeting-the-right-database.md) | Two ways to point at the wrong database — a dump taken from the wrong host, and a change file piped into the wrong schema. | api2/Config/production.ini, dbchanges2/Core/2026-08-28a - TransferOrderSurfaceSeed.sql |
13
+ | [Targeting the right database (tenant restores, and running a change file against the wrong DB)](workflows/targeting-the-right-database.md) | Two ways to point at the wrong database — a dump taken from the wrong host, and a change file piped into the wrong schema. | api2/Config/production.ini, api2/Config/sandbox-client.ini, dbchanges2/Core/2026-08-28a - TransferOrderSurfaceSeed.sql |
14
14
  | [Verifying whether a dbchanges2 migration actually ran in an environment](workflows/verifying-a-migration-ran.md) | dbchanges2 has no execution ledger you can query — "did this file run here?" has to be answered from the **rows the file would have produced**. | Client/2026-08-11b - SalesOrderPurchaseOrdersField.sql, Client/2026-08-13 - UnitsForItemsPO_SalesOrderItemsJoin.sql |
@@ -6,7 +6,7 @@ project: Database Changes
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-02
9
+ updated: 2026-09-03
10
10
  owners: [jcardinal, apeterson, tcox]
11
11
  files:
12
12
  - dbchanges2/Core/2026-09-02a - TalosAssistantSurfaceSeed.sql
@@ -399,6 +399,11 @@ and `NOT EXISTS`-guarded.
399
399
  `config.valueKey='_totalPurchase'`. Requires the small FE adapter extension to emit `isConcatenated`
400
400
  (see [surface-frontend](../../toga25-supply/features/surface-frontend.md)). Authored this session;
401
401
  only Quad confirmed applied locally.
402
+ **Do NOT add `"suffix":"+"` to this element's config to get the "$4,737.00 +" joiner (2026-09-03).**
403
+ blox renders a field-level `suffix` **unconditionally**, but the stacked lease line auto-hides when
404
+ `_totalLease` is null/0 — a seeded `+` would dangle on every non-lease order. The joiner is FE-side
405
+ and conditional (`withStackedCurrencyJoiner`, see
406
+ [surface-frontend](../../toga25-supply/features/surface-frontend.md)).
402
407
 
403
408
  ## ThemeTokens — per-tenant, physically isolated in each client DB
404
409
 
@@ -1288,6 +1293,11 @@ uncommitted at the time. See
1288
1293
  [Transfer Orders page](../../toga25-supply/features/transfer-orders-page.md).
1289
1294
 
1290
1295
  ## Change history
1296
+ - 2026-09-03 — Flagged that the Compass / Compass Canada stacked-Total `CONFIG` override must **not**
1297
+ gain a `"suffix":"+"`: blox renders a field `suffix` unconditionally while the stacked lease line
1298
+ auto-hides on null/0, so a seeded `+` dangles on non-lease orders. The joiner is conditional FE code
1299
+ (`withStackedCurrencyJoiner`) — see
1300
+ [surface-frontend](../../toga25-supply/features/surface-frontend.md). (tcox)
1291
1301
  - 2026-09-02 — Wrote down the **`Core.Records` / `Core.RecordFields` id protocol**, the counterpart to
1292
1302
  this doc's reserved-block discipline: they are the only two hand-maintained-id tables platform-wide,
1293
1303
  so **ask the developer for the next free id**, never `AUTO_INCREMENT` them, and **never infer the
@@ -6,10 +6,11 @@ project: Database Changes
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-09-01
9
+ updated: 2026-09-03
10
10
  owners: [apeterson]
11
11
  files:
12
12
  - api2/Config/production.ini
13
+ - api2/Config/sandbox-client.ini
13
14
  - dbchanges2/Core/2026-08-28a - TransferOrderSurfaceSeed.sql
14
15
  related:
15
16
  - ../architecture.md
@@ -39,6 +40,30 @@ Connection **locations** only — passwords live in the config file, never here.
39
40
 
40
41
  Username is `admin`. **The password is in the matching section of
41
42
  `api2/Config/production.ini` — read it from there; never paste it anywhere, including here.**
43
+ The sandbox-client host's password lives in `api2/Config/sandbox-client.ini` `[database]`; DNS
44
+ resolves `client.sandbox.database.togahub.com` to the **writer**, so `DROP`/`CREATE` work against
45
+ that same hostname.
46
+
47
+ 🚨 **The password values in these ini files are WRAPPED IN DOUBLE QUOTES.** An extractor that only
48
+ strips `password[ \t]*=[ \t]*` keeps the quotes and you get
49
+ `ERROR 1045 (28000): Access denied for user 'admin'@'<ip>' (using password: YES)`. The password *is*
50
+ being sent — just with literal `"` around it — so it reads like a permissions or VPN problem and is
51
+ not one. **Strip the quotes.** Also compare the whole section header (`$0 == "[" S "]"`), or
52
+ `[database]` matches `[databaseClient]` / `[databaseLogs]` and you extract the wrong password:
53
+
54
+ ```bash
55
+ # zsh/bash: export the password for one section of one ini, quotes stripped
56
+ setpw() {
57
+ local pw
58
+ pw=$(awk -v S="$2" '$0=="["S"]"{f=1;next} /^\[/{f=0} f && /^password/{
59
+ sub(/^password[ \t]*=[ \t]*/,""); gsub(/[ \t\r]+$/,""); print; exit}' "$1")
60
+ pw=${pw%\"}; pw=${pw#\"}
61
+ export MYSQL_PWD="$pw"
62
+ }
63
+ ```
64
+
65
+ `MYSQL_PWD` keeps the password out of `ps` and history the same way a defaults file does — use it
66
+ when the client rejects `--defaults-extra-file` (some builds do).
42
67
 
43
68
  **Keep the password out of your shell history and out of `ps`** by building a throwaway
44
69
  credentials file from the ini instead of passing `-p` on the command line:
@@ -66,6 +91,40 @@ Delete the file when you are done.
66
91
  4. **Verify the restored copy against the SOURCE you intended**, repeating step 1 against the
67
92
  restored schema.
68
93
 
94
+ ## Resetting a shared environment from production (dump → drop → reload)
95
+
96
+ Used 2026-09-03 to bring sandbox-client's stale `Client_Nychh` TableViews back in line with prod.
97
+ The developer runs every one of these; do not execute them for them.
98
+
99
+ 1. **Dump each source database**, one file each — prod `Core` from
100
+ `reader1.core.database.togahub.com`, prod tenant from `reader1.client.database.togahub.com`:
101
+ ```
102
+ mysqldump --single-transaction --quick --routines --triggers --events \
103
+ --set-gtid-purged=OFF --no-tablespaces <DB> > <DB>.sql
104
+ ```
105
+ `--set-gtid-purged=OFF` because RDS has GTID on; `--no-tablespaces` because the `admin` user
106
+ lacks `PROCESS` (otherwise **error 1227**).
107
+ 2. 🚨 **`tail -1` every dump file and confirm it ends with `-- Dump completed` BEFORE you drop
108
+ anything.** A truncated dump loads without complaining. DNS dropped mid-session on a host that
109
+ had resolved seconds earlier (`ERROR 2005 Unknown MySQL server host`) — the dump just stops.
110
+ 3. **Prove which environment each connection actually reached.** Better than `@@hostname`: check the
111
+ resolved IP — production resolves into **`10.201.x`**, sandbox into **`10.200.x`**.
112
+ 4. **Drop + create the target schema, then pipe the dump in.**
113
+ 5. **Verify with a row count known to differ** (step 1 of the procedure above), against the schema
114
+ you intended to copy.
115
+
116
+ ### ⚠ Which `mysql` / `mysqldump` binary you are running is load-bearing
117
+
118
+ - **A MariaDB-branded `mysqldump` rejects `--set-gtid-purged=OFF`.** Both TOGA clusters are **MySQL
119
+ 8.0.42**, so use a **MySQL 8.0** client. On an XAMPP machine both ship and neither is on `PATH`:
120
+ `xamppfiles/bin/mysqldump` is **MariaDB 10.4** (rejects the flag);
121
+ `xamppfiles/mysql/bin/mysqldump` is **MySQL 8.0.44** (correct). Run `mysqldump --version` before
122
+ you trust it.
123
+ - **A shell alias can hijack a remote connection.** XAMPP's `mysql` alias hardcodes
124
+ `-uroot -pmysql --socket=…`, so a command you wrote for a remote host silently talks to localhost.
125
+ Call the client by **full path** for anything remote — and remember an exact match with your local
126
+ DB is evidence of a bad restore, not a good one (see the gotcha below).
127
+
69
128
  ## Gotchas
70
129
 
71
130
  - 🚨 **A wrong-source restore is silent and total.** A dump taken from `127.0.0.1`, believed to be
@@ -98,6 +157,17 @@ Delete the file when you are done.
98
157
  [Verifying whether a migration actually ran](./verifying-a-migration-ran.md).
99
158
 
100
159
  ## Change history
160
+ - 2026-09-03 — Added the **reset-a-shared-environment-from-production** procedure (the `mysqldump`
161
+ flag set, `--set-gtid-purged=OFF` for RDS GTID, `--no-tablespaces` for the missing `PROCESS`
162
+ grant, the mandatory `tail -1` "`-- Dump completed`" check because a truncated dump loads
163
+ silently, and the resolved-IP environment check: prod `10.201.x` vs sandbox `10.200.x`). Recorded
164
+ two traps that both masquerade as something else: **the ini password values are wrapped in double
165
+ quotes**, so a naive extractor produces `ERROR 1045 Access denied` that looks like a permissions
166
+ or VPN problem (fixed extractor + `MYSQL_PWD` for clients that reject `--defaults-extra-file`);
167
+ and **which client binary you run matters** — a MariaDB `mysqldump` rejects
168
+ `--set-gtid-purged=OFF`, and a shell alias hardcoding `-uroot --socket=…` hijacks a remote
169
+ connection to localhost. Captured while resetting sandbox-client (`Core` +
170
+ `Client_Nychh`) from prod after its `purchase-orders` TableView was found stale. (apeterson)
101
171
  - 2026-09-01 — Added the **backup-database hazard for `Client/` fan-outs**: the production client
102
172
  cluster hosts dated snapshots alongside live tenants (`Client_Compass_20260831_120000`, found
103
173
  during a 36-database ACL survey), and a naive "every `Client_*` schema" loop will run against
@@ -9,12 +9,12 @@
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/surface/useColumnVisibilityModalConfig.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableData.tsx, dbchanges2/Core/2026-08-28b - TransferOrderListActionsSurfaceSeed.sql |
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
- | [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/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 |
12
+ | [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
13
  | [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 |
14
14
  | [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 |
15
- | [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/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/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 |
15
+ | [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/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 |
16
16
  | [Talos Integration (AppLayout host, live Aegra streaming, LangGraph→blox mapper)](features/talos-integration.md) | toga25-supply is the first host of the shared blox [Talos assistant](../../toga-blox/features/talos-assistant.md). | toga25-supply/src/api/talos.ts, toga25-supply/src/hooks/useTalosSession.ts, toga25-supply/src/hooks/useTalosThreads.ts, toga25-supply/src/hooks/useTalosThreads.test.ts, toga25-supply/src/hooks/useTalosSurface.ts, toga25-supply/src/hooks/useTalosSurface.test.ts, toga25-supply/src/App.tsx, toga25-supply/db-migrations/PLAYBOOK.md, dbchanges2/Core/2026-09-02a - TalosAssistantSurfaceSeed.sql, dbchanges2/Client_Nychh/2026-09-02a - TalosAssistantEnable.sql, toga25-supply/src/providers/TalosStreamProvider.tsx, toga25-supply/src/utils/talosMessages.ts, toga25-supply/src/utils/talosMessages.test.ts, toga25-supply/src/layout/AppLayout/AppLayout.tsx, toga25-supply/src/components/Header/Header.tsx, toga25-supply/src/components/Header/Header.module.css, toga25-supply/src/index.css, toga25-supply/src/assets/talos-owl.png |
17
- | [Transfer Orders page (TableView → Core surfaces → React page + record modal)](features/transfer-orders-page.md) | The Transfer Orders screen — list + read-only record modal — built end to end on 2026-08-28 from a Claude Design prototype. | toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useCreateTransferOrder.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/PanelSelect.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/ContactSelect.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/SelectTargetLocationModal.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/TransferItemPickerTable.tsx, _underscore/Model/Client/TransferOrder.php, _underscore/Model/Client/TransferOrderNote.php, _underscore/Model/Client/TransferOrderNoteType.php, dbchanges2/Client/2026-09-01b - TransferOrderNotesTables.sql, dbchanges2/Client/2026-09-01c - TransferOrderContactCreatedByAndDescription.sql, dbchanges2/Core/2026-09-01c - TransferOrderNotesRecords.sql, dbchanges2/Core/2026-09-01d - TransferOrderContactCreatedByAndDescriptionRecordFields.sql, toga25-supply/src/pages/TransferOrders/TransferOrders.tsx, toga25-supply/src/pages/TransferOrders/hooks/useTransferOrdersTableState.ts, toga25-supply/src/pages/TransferOrders/viewModel/useTransferOrdersPageViewModel.tsx, toga25-supply/src/pages/TransferOrders/view/TransferOrdersTableLayout/TransferOrdersTableLayout.tsx, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/TransferOrderRecordModalLayout.tsx, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/hooks/useTransferOrderRecord.ts, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/viewModel/useTransferOrderRecordModalLayoutModel.ts, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/view/TransferOrderRecordView.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/CreateTransferOrderModal.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/index.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/usePurchaseOrderItemRows.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useTargetLocationOptions.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useTransferSourceLocation.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useLocationContacts.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useCreateLocationContact.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/AddContactModal.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/TransferNotesField.tsx, dbchanges2/Client/2026-09-01a - PurchaseOrderItemQtyFieldsApiRoleRead.sql, toga25-supply/src/surface/useColumnVisibilityModalConfig.ts, toga25-supply/src/routes.tsx, dbchanges2/Core/2026-08-28a - TransferOrderSurfaceSeed.sql, dbchanges2/Core/2026-08-28b - TransferOrderListActionsSurfaceSeed.sql, dbchanges2/Client/2026-08-28a - TransferOrderStageThemeTokens.sql, dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestamps.sql, dbchanges2/Client_Nychh/2026-08-28a - TransferOrdersTableView.sql, dbchanges2/Client_Nychh/2026-08-28b - TransferOrdersNavigationEnable.sql, dbchanges2/Client_Nychh/2026-08-28c - TransferOrderRecordAcl.sql |
17
+ | [Transfer Orders page (TableView → Core surfaces → React page + record modal)](features/transfer-orders-page.md) | The Transfer Orders screen — list + read-only record modal — built end to end on 2026-08-28 from a Claude Design prototype. | toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useCreateTransferOrder.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/PanelSelect.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/ContactSelect.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/SelectTargetLocationModal.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/TransferItemPickerTable.tsx, _underscore/Model/Client/TransferOrder.php, _underscore/Model/Client/TransferOrderNote.php, _underscore/Model/Client/TransferOrderNoteType.php, dbchanges2/Client/2026-09-01b - TransferOrderNotesTables.sql, dbchanges2/Client/2026-09-01c - TransferOrderContactCreatedByAndDescription.sql, dbchanges2/Core/2026-09-01c - TransferOrderNotesRecords.sql, dbchanges2/Core/2026-09-01d - TransferOrderContactCreatedByAndDescriptionRecordFields.sql, toga25-supply/src/pages/TransferOrders/TransferOrders.tsx, toga25-supply/src/pages/TransferOrders/hooks/useTransferOrdersTableState.ts, toga25-supply/src/pages/TransferOrders/viewModel/useTransferOrdersPageViewModel.tsx, toga25-supply/src/pages/TransferOrders/view/TransferOrdersTableLayout/TransferOrdersTableLayout.tsx, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/TransferOrderRecordModalLayout.tsx, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/hooks/useTransferOrderRecord.ts, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/viewModel/useTransferOrderRecordModalLayoutModel.ts, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/view/TransferOrderRecordView.tsx, toga25-supply/src/pages/TransferOrders/helpers/buildInventoryPurchaseOrderUrl.ts, toga25-supply/src/pages/TransferOrders/TransferOrder.module.css, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/CreateTransferOrderModal.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/index.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/usePurchaseOrderItemRows.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useTargetLocationOptions.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useTransferSourceLocation.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useLocationContacts.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useCreateLocationContact.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/AddContactModal.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/TransferNotesField.tsx, dbchanges2/Client/2026-09-01a - PurchaseOrderItemQtyFieldsApiRoleRead.sql, toga25-supply/src/surface/useColumnVisibilityModalConfig.ts, toga25-supply/src/routes.tsx, dbchanges2/Core/2026-08-28a - TransferOrderSurfaceSeed.sql, dbchanges2/Core/2026-08-28b - TransferOrderListActionsSurfaceSeed.sql, dbchanges2/Client/2026-08-28a - TransferOrderStageThemeTokens.sql, dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestamps.sql, dbchanges2/Client_Nychh/2026-08-28a - TransferOrdersTableView.sql, dbchanges2/Client_Nychh/2026-08-28b - TransferOrdersNavigationEnable.sql, dbchanges2/Client_Nychh/2026-08-28c - TransferOrderRecordAcl.sql |
18
18
  | [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, toga25-supply/.env.development |
19
19
  | [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
20
  | [Porting a page (or query) from toga2-supply to toga25-supply](workflows/porting-a-page-from-toga2-supply.md) | `toga25-supply` re-implements pages that already exist in `toga2-supply`. | toga25-supply/src/pages/SalesOrders/viewModel/FIELDS/PRUDENTIAL/exportApiFields.json, toga25-supply/src/pages/SalesOrders/api/prudentialExportApi.ts, toga2-supply/src/pages/Orders/api/OrdersApi.ts, toga2-supply/src/components/ui/Tables/hooks/useExportableData.tsx |
@@ -24,6 +24,7 @@ files:
24
24
  - toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx
25
25
  - toga25-supply/src/pages/Inventory/viewModel/FIELDS/DEFAULT/inventoryGroupings.json
26
26
  - toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/TransferOrderRecordModalLayout.tsx
27
+ - toga25-supply/src/pages/TransferOrders/helpers/buildInventoryPurchaseOrderUrl.ts
27
28
  - toga25-supply/src/hooks/useTableCellInteractions.ts
28
29
  - toga25-supply/src/hooks/useServerTableUrlState.ts
29
30
  - toga25-supply/src/layout/RecordApprovalModal/helpers/handleFormatApprovalWorkflowPayload.ts
@@ -166,6 +167,37 @@ interactions so both are reachable on one nested row (Inventory `items-for-purch
166
167
  projection: a **hidden (`isVisible=0`) `Items.uuid` column** is added to the table view — see
167
168
  [tableview-field-metadata](../../api2/features/tableview-field-metadata.md).
168
169
 
170
+ ### Deep-linking from a modal INTO a nested table (close, then navigate — never both)
171
+
172
+ A value inside one record modal can link to a filtered, pre-expanded row on another page's
173
+ `GenericNestedTables` grid. Reference: the Transfer Order modal's Purchase Order value →
174
+ Inventory (`src/pages/TransferOrders/helpers/buildInventoryPurchaseOrderUrl.ts`).
175
+
176
+ The URL is built from three `useServerTableUrlState` params, and each one has a rule:
177
+
178
+ | Param | Shape | Rule |
179
+ |---|---|---|
180
+ | grouping | `grouping=<elementKey>` | **Pin it.** The default is whichever grouping the client's surface returns first, so a reseed can silently aim the link at a view without the level you want. An unknown key falls back to the default anyway. |
181
+ | column filter | `<slug>_<columnId>_filter=<mode>:<value>` | Set it through `URLSearchParams.set` with the value `mode + ":" + encodeURIComponent(value)` — the same serializer `useServerTableUrlState` uses. Hand-writing the query string double-encodes, or breaks a value containing `:` / `,`. `<columnId>` is the **table-meta column id**, not the record field name. |
182
+ | active row | `<slug>=<uuid>` | Named **exactly** the table slug (`activeRowUuid = searchParams.get(slug)`). On an `"expand"` level this **opens the child sub-table inline on load** — the same effect as a row click. Omit it when the uuid is unknown; the filter alone still lands on the row, collapsed. |
183
+
184
+ **Sequencing: hold the target in a `useRef`, fire it from `onClosed`, and RETURN instead of calling
185
+ `onClose()`.** Never `setTimeout` to match the slide-down: a timer either cuts the animation short or
186
+ fires after the user opened something else.
187
+
188
+ ```tsx
189
+ const pendingNavigation = useRef<string | null>(null);
190
+
191
+ <TableRecordModal
192
+ onClosed={() => {
193
+ const target = pendingNavigation.current;
194
+ pendingNavigation.current = null;
195
+ if (target) { navigate(target); return; } // NOT onClose() first
196
+ onClose();
197
+ }}
198
+ />
199
+ ```
200
+
169
201
  ## Pattern 6 — Create/edit record modal (form-driven)
170
202
 
171
203
  Scaffold per the `ItemRecordModalLayout` pattern (`src/layout/ItemRecordModalLayout/`). Form-driven
@@ -205,6 +237,19 @@ Scaffold per the `ItemRecordModalLayout` pattern (`src/layout/ItemRecordModalLay
205
237
  **suffix** can't be used because `useServerTableUrlState`'s columnFilters parser treats any
206
238
  `${slug}_*` key as a column filter — hence a prefix.
207
239
  - **`additionalData` in the query key is mandatory** for WHERE-filtered modal tables (Pattern 4).
240
+ - 🚨 **Never call `onClose()` and then `navigate()` — it races two navigations and strips your
241
+ params.** `onClose` is `handleModalClose` in `src/hooks/useServerTableUrlState.ts`, which clears
242
+ the URL with `setSearchParams`. That queues a **second** navigation in the same tick as yours, both
243
+ racing to own the URL; the loser silently drops the grouping / filter / active-row params, so a
244
+ deep link lands on the page with its sub-table collapsed (or unfiltered). Navigating away replaces
245
+ the whole query string anyway, so clearing it first is pointless as well as risky. **Navigate and
246
+ return.**
247
+ - **A bordered scroll box inside a modal must be `flex: 0 1 auto`, not `flex: 1`.** The element that
248
+ carries the border is the table the user sees: `flex: 1` stretches it to the bottom of the modal,
249
+ so a single row renders inside a box of empty white. Put `flex: 1` on the wrapper that draws
250
+ nothing, and keep `min-height: 0` + `overflow: auto` on the bordered box so it still shrinks and
251
+ scrolls when the rows outgrow the space. Hit on the Transfer Order modal's items table; the design
252
+ agrees (its items table passes no `fillerHeight`, so it reserves no rows it does not have).
208
253
  - **Per-consumer cache `scope`** — two consumers fetching the same record with different field
209
254
  configs must use a `scope` discriminator in the query key or they overwrite each other's cache
210
255
  (record modal vs. approval modal: the approval modal omitting item-image fields blanked the
@@ -286,6 +331,14 @@ Scaffold per the `ItemRecordModalLayout` pattern (`src/layout/ItemRecordModalLay
286
331
  [Transfer Orders page](transfer-orders-page.md).
287
332
 
288
333
  ## Change history
334
+ - 2026-09-03 — Added the **deep-link-from-a-modal-into-a-nested-table** pattern (pin `grouping`;
335
+ build the column-filter chip with the `useServerTableUrlState` serializer, keyed by the table-meta
336
+ **column id**; set `<slug>=<uuid>` to pre-expand the row) and the rule that makes it work:
337
+ **hold the target in a `useRef`, fire it from `onClosed`, and `return` — never `onClose()` then
338
+ `navigate()`**, because `onClose` clears the URL with `setSearchParams` and the two navigations
339
+ race, stripping the link's params. Also recorded the modal scroll-box flex rule (`flex: 0 1 auto`
340
+ on the bordered box, `flex: 1` on the wrapper). Found wiring the Transfer Order modal's Purchase
341
+ Order value to Inventory. (apeterson)
289
342
  - 2026-09-03 — Documented that the FE **appends `.uuid`** to a `GenericNestedTables` swap level's
290
343
  `additionalDataSlug`, so the stored `Core.SurfaceElements.config` value must be the **bare table
291
344
  name** (`PurchaseOrders`), not `<Table>.uuid` — a `.uuid` in the stored value produces a doubled
@@ -6,9 +6,11 @@ project: TOGa 2.5 Supply
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-02
9
+ updated: 2026-09-03
10
10
  owners: [jcardinal, apeterson, tcox]
11
11
  files:
12
+ - toga25-supply/src/layout/RecordApprovalModal/helpers/stackedCurrencyJoiner.ts
13
+ - toga25-supply/src/layout/RecordApprovalModal/helpers/stackedCurrencyJoiner.test.ts
12
14
  - toga25-supply/src/App.tsx
13
15
  - toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/DenialBanner.tsx
14
16
  - toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/SalesOrderRecordModal.module.css
@@ -354,13 +356,23 @@ full replacement**, because the surfaces cover only part of `recordActionFields`
354
356
 
355
357
  **Overlay, not replacement (decision).** The 3 surfaces cover only 3 of the 7 `recordActionFields`
356
358
  keys; keys NOT yet migrated stay JSON-sourced (`guardrailCopy`, `actionOptions` [the approve
357
- step-two enable rule], `approvalWorkflow`, `orderContentDetailsWorkflow`). The JSON approve/deny
359
+ step-two enable rule], `approvalWorkflow`). The JSON approve/deny
358
360
  blocks also carry **functional** attributes the surface does not emit (`characterLimit 255`,
359
361
  `inputContainerWidth`, submit `valueKeys`). An `overlayDefined()` helper copies only *defined* surface
360
362
  values, so JSON-only functional fields survive: **Surface owns all display attributes** (labels,
361
363
  visibility, icons, `isRequired`, placeholder, `textAreaHeight`); JSON fills gaps and functional
362
364
  wiring. The per-client `approvalActionFields.json` files are kept on disk as reference (not deleted).
363
365
 
366
+ **`orderContentDetailsWorkflow` IS surface-driven — it is the SAME array as approve/deny (verified
367
+ 2026-09-03).** `overlayDecisionSurfaceFields` points **both** `orderContentDetails` and
368
+ `orderContentDetailsWorkflow` at `decision.orderContentDetails`
369
+ (`surfaceBundlesToDecisionFields.ts` ~L458), so the Approval Workflow modal's summary rows come from
370
+ `sales-order-decision-summary` exactly like the approve/deny modals. The header comment in
371
+ `useApprovalModalViewModel.tsx` (~L155) still lists `orderContentDetailsWorkflow` among the
372
+ "unmigrated keys" — that comment is **stale**. Practical effect: any seed or adapter change to the
373
+ decision summary lands in **all three** modal modes at once, and the two grids share one `colSpan`
374
+ value (see the colSpan caveat below).
375
+
364
376
  **Concatenated summary values (`isConcatenated`).** `surfaceBundlesToDecisionFields.ts` now passes an
365
377
  element's `config.isConcatenated` through **verbatim** onto the decision-summary row: it exports a
366
378
  `DecisionSummaryConcat` type (string shorthand | descriptor `{valueKey, prefix?, suffix?, valueType?,
@@ -372,6 +384,46 @@ side in [surface-layer-schema](../../dbchanges2/features/surface-layer-schema.md
372
384
  the FE ignored. blox `BaseDetailField` (`renderBaseDetailFieldValue.tsx`) already supports
373
385
  `valueKey`+`suffix`+`layout:'stacked'`+`valueType:'currency'`. tsc + eslint clean.
374
386
 
387
+ ### The stacked-Total `+` joiner is FE code, NOT a seeded `suffix` (2026-09-03)
388
+
389
+ Design wants the two stacked Total lines to read as one sum, so the primary line gets a trailing
390
+ `+` — but **only when the lease line is actually there**:
391
+
392
+ ```
393
+ $4,737.00 +
394
+ $107.00/mo
395
+ ```
396
+
397
+ `withStackedCurrencyJoiner(field, data)` (`src/layout/RecordApprovalModal/helpers/stackedCurrencyJoiner.ts`)
398
+ returns the field with `suffix: "+"` (appended after any existing suffix) **only** when all of this
399
+ holds: `field.valueType === 'currency'`, `field.isConcatenated` is a **descriptor** (not the string
400
+ shorthand) with `layout: 'stacked'` and `valueType: 'currency'`, **and** the add-on value resolves to
401
+ something non-null / non-empty / non-zero. Otherwise it returns the **same field reference**. The
402
+ visibility check deliberately mirrors blox `renderBaseDetailFieldValue.resolveSecondValue` — dotted
403
+ `valueKey` walk plus the optional `getValue` resolver — so the `+` and the second line appear and
404
+ disappear together.
405
+
406
+ Wired in `RecordApprovalModalLayout.tsx` `renderField` as
407
+ `field={isWorkflow ? withStackedCurrencyJoiner(field, data) : field}`, covering the workflow modal's
408
+ editable **and** read-only views. **Approve/deny modals are deliberately unchanged** — extending to
409
+ them is a one-line change (drop the `isWorkflow` check).
410
+
411
+ **Why this is app code and not blox or a seed (decision).** blox `BaseDetailField` renders a
412
+ field-level `suffix` **unconditionally** after a currency primary (`base = "$ X" + " " + suffix`),
413
+ while the stacked add-on line **auto-hides** on null/empty/0. There is no surface or blox option for
414
+ "suffix only when the second line shows", so a seed-side `suffix:"+"` on the Total element would
415
+ leave a **dangling `+`** on every order with no monthly lease. Doing it in blox would need a new
416
+ concat-descriptor option plus a publish, a pin bump in toga25-supply, and SQL for Compass and Compass
417
+ Canada — a lot of machinery for a presentation flourish. This is the same line as the grid-geometry
418
+ boundary below: **config describes, code decides.** (blox pin at the time: `1.1.1-production.139`.)
419
+
420
+ Covered by `stackedCurrencyJoiner.test.ts` (13 vitest cases) — the joiner is pure, so it is testable
421
+ without rendering the modal.
422
+
423
+ **Not implemented:** the mockup also showed `(36 months)` after the lease line. Nothing in the
424
+ current surface config, seeds, or FE code resolves a lease **term**, so it was left out — it needs a
425
+ data source before it can be rendered.
426
+
375
427
  **Shared summary, separate per-action chrome (decision).** The 8 order-summary rows are identical for
376
428
  approve and deny, so they come from **one** shared surface (`sales-order-decision-summary`); the action
377
429
  chrome (title, reason-input required-ness, confirm button, icons, toasts) diverges, so it is **two**
@@ -914,6 +966,22 @@ button except **View Log** renders and does nothing on click. Whether the SR mod
914
966
  actions surface is **still an open decision**, not an oversight.
915
967
 
916
968
  ## Change history
969
+ - 2026-09-03 — **Built the stacked-Total `+` joiner in the Approval Workflow modal**
970
+ (`withStackedCurrencyJoiner`, new `RecordApprovalModal/helpers/stackedCurrencyJoiner.ts` + 13
971
+ vitest cases; one-line wiring in `RecordApprovalModalLayout` `renderField`, workflow modal only —
972
+ approve/deny untouched). It adds `suffix:"+"` to a currency primary **only** when its
973
+ `isConcatenated` descriptor is `layout:'stacked'` + `valueType:'currency'` **and** the add-on value
974
+ will actually render (mirrors blox `resolveSecondValue`, incl. `getValue` + dotted `valueKey`), so
975
+ the `+` never dangles on an order with no monthly lease. **DECISION: this stays app code** — blox
976
+ renders a field `suffix` unconditionally and there is no "suffix only if the second line shows"
977
+ option, so a seeded `suffix:"+"` would be wrong for non-lease orders, and a blox option would cost
978
+ a publish + pin bump + SQL for Compass and Compass Canada. Same boundary as grid geometry: config
979
+ describes, code decides. Also **corrected** the doc's claim that `orderContentDetailsWorkflow` is
980
+ unmigrated/JSON-sourced — `overlayDecisionSurfaceFields` points it at the **same**
981
+ `sales-order-decision-summary` array as approve/deny (the stale claim also survives as a comment in
982
+ `useApprovalModalViewModel.tsx` ~L155). Mockup's `(36 months)` lease term is **not** implemented —
983
+ no data source for it. tsc + eslint clean, vitest 18/18 in `src/layout/RecordApprovalModal`; blox
984
+ pin `1.1.1-production.139`; uncommitted on `TRUE-80850`. (tcox)
917
985
  - 2026-09-02 — **FIXED the surface bundle being cached in `localStorage` for up to 24h, so a
918
986
  permission or feature change could not take effect.** `App.tsx`'s `PersistQueryClientProvider` had
919
987
  **no `dehydrateOptions`**, so every query — including the `staleTime: Infinity` surface bundle —
@@ -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-02
9
+ updated: 2026-09-03
10
10
  owners: [apeterson, bala]
11
11
  files:
12
12
  - toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useCreateTransferOrder.ts
@@ -29,6 +29,8 @@ files:
29
29
  - toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/hooks/useTransferOrderRecord.ts
30
30
  - toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/viewModel/useTransferOrderRecordModalLayoutModel.ts
31
31
  - toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/view/TransferOrderRecordView.tsx
32
+ - toga25-supply/src/pages/TransferOrders/helpers/buildInventoryPurchaseOrderUrl.ts
33
+ - toga25-supply/src/pages/TransferOrders/TransferOrder.module.css
32
34
  - toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/CreateTransferOrderModal.tsx
33
35
  - toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/index.ts
34
36
  - toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/usePurchaseOrderItemRows.ts
@@ -261,6 +263,79 @@ fields. One FK hop out from the order they need `calcDepth 2`; at the default of
261
263
  the loaded state swaps element types at the same tree position and replays the slide-up animation —
262
264
  see the gotcha in [record-modals-and-nested-tables](./record-modals-and-nested-tables.md).
263
265
 
266
+ ### The items table HUGS its rows — `flex: 0 1 auto`, not `flex: 1`
267
+
268
+ `TransferOrder.module.css`. **`.tableScroll` is the box that carries the border, so it is the table
269
+ the user sees — it must size to its rows.** Under `flex: 1` it stretched to the bottom of the modal,
270
+ so a one-line transfer drew a bordered box with ~400px of empty white under its single row.
271
+
272
+ - `.tableScroll` → **`flex: 0 1 auto`**: sizes to content, and still **shrinks** (paired with the
273
+ existing `min-height: 0`) when the rows outgrow the space, at which point `overflow: auto` scrolls
274
+ under the sticky header.
275
+ - `.tableRegion` keeps **`flex: 1`** and absorbs the slack. It draws nothing, so stretching it is
276
+ invisible.
277
+ - Everything above the table (`.reviewCards`, `.sectionHead`, `.itemsHeadFlush`) already carries
278
+ `flex-shrink: 0`, so the table shrinking never squashes them.
279
+
280
+ **Ground truth:** the design's `TransferItemsTable` (`demo/transfer-modal.jsx:598`) passes **no
281
+ `fillerHeight`** to `SupplyTable` — it reserves no rows it does not have. Only `CmFulTable` reserves,
282
+ and it asks for it explicitly. The Create modal's picker (`TransferItemPickerTable.tsx`) hugs the
283
+ same way.
284
+
285
+ ### The Purchase Order value deep-links into Inventory, scoped to that PO
286
+
287
+ `helpers/buildInventoryPurchaseOrderUrl.ts` (new) + `TransferOrderRecordModalLayout.tsx`.
288
+ `onOpenPurchaseOrder` had been **declared** on `TransferOrderRecordView` and threaded to
289
+ `TransferReviewBody`'s `purchaseOrder.onOpen` since the modal was built, but nothing ever passed it —
290
+ so the PO always rendered as plain text. It now navigates:
291
+
292
+ ```
293
+ /inventory?grouping=posByItems&purchase-orders_customerPurchaseOrder_filter=includes%3A<number>&purchase-orders=<uuid>
294
+ ```
295
+
296
+ Three params, all addressing Inventory grouping `posByItems` level 0 (`fetchSlug: purchase-orders`):
297
+
298
+ - **`grouping=posByItems` is PINNED, not left to the default.** It *is* the default today
299
+ (`groupingOptions[0]` in `useInventoryPageViewModel`), but that default is whichever grouping the
300
+ client's surface returns first — a reseed that reorders them would silently aim the link at a view
301
+ with no `purchase-orders` level. An unknown key falls back to the default, so pinning can only help.
302
+ - **The filter chip is set through `URLSearchParams.set`, with the value
303
+ `"includes:" + encodeURIComponent(number)`** — mirroring the serializer in `hooks/useServerTableUrlState.ts` exactly. Hand-writing the query string
304
+ double-encodes, or breaks a value containing `:` or `,`. The key's middle segment is the **column
305
+ id from the table meta** (`customerPurchaseOrder`), not the record field name — renaming that
306
+ `TableViewFields` column breaks the link with nothing to warn you.
307
+ - **`purchase-orders=<uuid>` is the active-row param**, named **exactly** the table slug
308
+ (`activeRowUuid = searchParams.get(slug)`). That level's `rowInteraction` is the default `"expand"`,
309
+ so setting it **opens the child sub-table inline on page load** — the same thing a row click does.
310
+ Omitted when the uuid is unknown; the filter alone still lands on the row, just collapsed.
311
+
312
+ The uuid is read off the record (`transferOrder.purchaseOrder.uuid`, already in the record fetch — no
313
+ API change), **not** off the surface: the surface says *which number to show*; the route it navigates
314
+ to is FE wiring (the config/logic boundary, [frontend standard](../../../standards/frontend.md) §20).
315
+
316
+ > ⚠ **Never `onClose()` before `navigate()`.** See the gotcha below — it races two navigations and
317
+ > silently strips the params the link is made of.
318
+
319
+ ### Blank Item column in the RECORD modal too — `COLUMN_FALLBACK_KEYS`
320
+
321
+ Same NYCHH data gap as the picker, different table. Confirmed on client-sandbox: `transfer-order-items`
322
+ line 1 returns `"title": null` with a real `partNumber`. `item.description` was added to
323
+ `TRANSFER_ORDER_ITEM_FIELDS` (`hooks/useTransferOrderRecord.ts`), and `TransferOrderRecordView` gained:
324
+
325
+ ```ts
326
+ const COLUMN_FALLBACK_KEYS: Record<string, string[]> = { item: ["item.description"] };
327
+ ```
328
+
329
+ tried in order when a column's own surface `valueKey` resolves empty.
330
+
331
+ - **Deliberately a FLAT MAP keyed by `config.columnKey`** — *not* string surgery on the valueKey.
332
+ Deriving `item.description` by swapping the tail of `item.title` breaks silently the day a tenant
333
+ repoints that column.
334
+ - Same chain the Create modal's picker already uses, except the record modal keeps its **em-dash**
335
+ for both-empty rather than `"(untitled item)"`.
336
+ - **No part-number fallback** — Part Number has its own column, so that would duplicate a cell
337
+ instead of naming the item. The real fix is still populating `Items.title`.
338
+
264
339
  ## The prod-only state this vertical sits on top of — four UNTRACKED layers
265
340
 
266
341
  Standing the page up locally failed **one layer at a time**, each with a different-looking error,
@@ -570,11 +645,24 @@ column binds the parent order's `purchaseOrder.number` and repeats on every row.
570
645
  resolves NULL because their PO links are **upstream** — see
571
646
  [NYCHH PO direction](../../../../clients/nychh/features/po-number-upstream-direction.md). It is left
572
647
  visible in Core (neutral default; other tenants populate `TransferOrders.purchaseOrderId` normally).
648
+ **The header's PO value is now a link into Inventory** (above); for NYCHH it is the same null, so
649
+ nothing renders there for this tenant yet.
573
650
 
574
651
  ## Rollout status & open items
575
652
 
576
- - **Run state after sandbox-client was reset from production (2026-08-31).** Nothing has been run on
577
- **production**. On **sandbox-client** the three `Client_Nychh` layers are already satisfied — the
653
+ - 🚨 **CORRECTED 2026-09-03 — the Core seeds ARE on production.** The line below said "nothing has
654
+ been run on production"; that was true on 2026-08-31 and is **stale**. Verified live on
655
+ `reader1.core.database.togahub.com`: `Core.Surfaces` = 53 rows and
656
+ `slug LIKE 'transfer-order%' OR 'talos%'` returns **`transfer-orders-list`,
657
+ `transfer-orders-list-actions`, `transfer-order-record-header`, `transfer-order-details`,
658
+ `transfer-order-summary`, `transfer-order-items`, `transfer-order-create-items`** and
659
+ `talos-assistant`. Someone ran them on prod between 2026-08-31 and 2026-09-03. Note
660
+ **`transfer-order-create-items` is a surface this doc's table never listed.** Prod `Client_Nychh`
661
+ also carries **12 `SurfaceOverrides`** rows and **1,930 `TransferOrders`** (1,927 on 2026-08-31 —
662
+ the tenant is live). *A migration's run status in this doc or in `dbchanges2` is never evidence
663
+ about an environment — read the environment.*
664
+ - **Run state as of 2026-08-31 (superseded for production by the line above).** On **sandbox-client**
665
+ the three `Client_Nychh` layers are already satisfied — the
578
666
  `transfer-orders` TableView (**22**, 4 fields) plus **4 `AclRecordPermissions` and 34
579
667
  `AclFieldPermissions`** for records **312/313** — and `TransferOrderItems` already carries
580
668
  `dtCreated` / `dtUpdated` / `qtyCommitted`, all pulled back in by the reset from prod's hand-applied
@@ -665,6 +753,14 @@ deliberate, separate exception — see [surface-frontend](./surface-frontend.md)
665
753
 
666
754
  - **⚠ An empty dropdown with no request in the network tab is a numeric `where` value or a
667
755
  non-zero `staleTime`** — see the section above. Neither logs anything.
756
+ - **⚠ A bordered scroll box must be `flex: 0 1 auto`, never `flex: 1`.** `flex: 1` on the element
757
+ that carries the border stretches the *visible* table to the bottom of the modal, so one row draws
758
+ a box full of white. Put `flex: 1` on the wrapper that draws nothing.
759
+ - **⚠ Closing a modal and navigating in the same tick races two navigations.** `onClose` clears the
760
+ URL with `setSearchParams`; if you also `navigate()`, the loser strips the params your link is made
761
+ of. Navigate **and return** — skip `onClose()` entirely.
762
+ - **A deep link's filter key uses the table-meta COLUMN id, not the record field name.** Renaming
763
+ the `TableViewFields` column breaks the link silently.
668
764
  - **A missing `AclRecordPermissions` grant on a joined record deletes a column, not a request.** The
669
765
  Status column would have silently vanished; record 351 had no grants at all.
670
766
  - **A missing `AclFieldPermissions` grant on the DEFAULT-SORT column is a 500, not a 403.** The
@@ -706,6 +802,22 @@ deliberate, separate exception — see [surface-frontend](./surface-frontend.md)
706
802
  earlier migration without names, so a name-based lookup on them finds nothing.
707
803
 
708
804
  ## Change history
805
+ - 2026-09-03 — **CORRECTED the production run state:** the Core transfer-order surfaces (all six
806
+ record/list surfaces **plus `transfer-order-create-items`**) and `talos-assistant` **are on
807
+ production** — verified live on `reader1.core.database.togahub.com` (`Surfaces` = 53). The
808
+ 2026-08-31 "nothing has been run on production" line is stale and is now marked so. Record-modal
809
+ work, all **uncommitted on branch `TRUE-80852`**: (1) the items table now **hugs its rows** —
810
+ `.tableScroll` moved from `flex: 1` to `flex: 0 1 auto` so a one-line transfer stops drawing ~400px
811
+ of empty bordered white (ground truth: the design's `TransferItemsTable` passes no `fillerHeight`);
812
+ (2) the header's **Purchase Order value now deep-links into Inventory**, scoped to that PO —
813
+ `grouping=posByItems` pinned, a `slug_column_filter=includes:<number>` chip built through the same
814
+ serializer as `useServerTableUrlState`, and `purchase-orders=<uuid>` (the active-row param, named
815
+ exactly the slug) to expand the child table on load; (3) **fixed a race in that first pass** —
816
+ `onClose()` before `navigate()` queues two navigations in one tick and the loser strips the link's
817
+ params, so the navigation now returns early instead of closing; (4) the record modal's **Item
818
+ column no longer blanks when `Items.title` is NULL** (NYCHH) — added `item.description` to the
819
+ fetch and a flat `COLUMN_FALLBACK_KEYS` map keyed by `columnKey`, deliberately not string surgery
820
+ on the surface `valueKey`. (apeterson)
709
821
  - 2026-09-02 — **Finished the Create Transfer Order modal end to end** (front end + framework model +
710
822
  Core metadata + client schema) and **closed both required POST changes**: the transfer now writes
711
823
  header and every line in **ONE `POST /transfer-orders`** with the lines nested under
@@ -75,13 +75,30 @@ table views. Client-specific DB change-sets live in `dbchanges2/Client_Nychh/`.
75
75
  seeds a **Developer-role (role 2)** record ACL grant chain (`AclRecordPermissions` →
76
76
  `AclLogicGroups` → `AclLogicGroupExpressions`, reusing record 17's `all` expression) and reads
77
77
  `AclFieldPermissions` for the displayed fields, resolving `roleId` by role **name**. Written
78
- idempotent / uuid-keyed; applied to **prod and client-sandbox**.
78
+ idempotent / uuid-keyed; applied to **prod**.
79
+ 🚨 **client-sandbox was NOT rebased on 2026-09-03** (an earlier note here claiming it was is
80
+ **stale**). Verified live: prod `Client_Nychh.TableViews` id **15** `purchase-orders` sits at
81
+ `recordId` **17** with `apiWhereClause (vendorId:eq:172)`, while sandbox-client still rendered the
82
+ pre-rebase column set *Purchase Order · Order Date · Order · Est. Total · Ship To* — the rebased
83
+ view is **number · dateOrder · total** (the rebase drops the sales-order "Order" column and omits
84
+ Ship To on purpose). **The symptom was a uuid mismatch, not a visibly wrong grid:** the PO row uuid
85
+ on the sandbox Inventory grid (`9056ea7b-…`) did not match the transfer order's
86
+ `purchaseOrder.uuid` (`ea8419cd-…`) — same PO number displayed, two different records — so a deep
87
+ link by uuid expanded the wrong row or none. Fixed by **resetting sandbox-client (Core +
88
+ `Client_Nychh`) from production** on 2026-09-03; see
89
+ [targeting the right database](../../2.0/apps/dbchanges2/workflows/targeting-the-right-database.md).
90
+ 🚩 **OPEN, not investigated:** that prod view's `apiWhereClause (vendorId:eq:172)` means a PO from
91
+ any other vendor **does not appear in the Inventory grid at all**, so a deep link to it lands on an
92
+ empty list no matter how correct the uuid is.
79
93
  - **NYCHH is the ONLY tenant granted the Talos AI assistant (2026-09-02).** Talos is seeded **off
80
94
  in Core for every client** and enabled per tenant with 5 `SurfaceOverrides` rows on the
81
95
  `talos-assistant` surface; `Client_Nychh/2026-09-02a - TalosAssistantEnable.sql` is the only grant
82
96
  file. The grants are **client-wide** (`roleId` NULL), so every NYCHH user including devteam gets
83
- it — matching how this tenant already grants navigation. **Applied on LOCAL only; beta and prod
84
- are not run**, and the element ids are per-environment. Their Aegra `assistant_id` is
97
+ it — matching how this tenant already grants navigation. ~~Applied on LOCAL only; beta and prod are
98
+ not run~~ — **STALE. Corrected 2026-09-03: the `talos-assistant` surface IS on production**
99
+ (verified live on `reader1.core.database.togahub.com`; `Core.Surfaces` = 53 rows and
100
+ `talos-assistant` is among them), and prod `Client_Nychh` carries **12 `SurfaceOverrides`** rows.
101
+ Someone ran it between 2026-08-31 and 2026-09-03. Element ids are still per-environment. Their Aegra `assistant_id` is
85
102
  `c7981164-1fe9-5878-b115-85c38dadad43` (`TOGa Supply`), and thread auto-titles are broken
86
103
  **server-side** on that assistant. See
87
104
  [NYCHH Talos enablement](./features/talos-assistant-enablement.md).
@@ -97,10 +114,12 @@ table views. Client-specific DB change-sets live in `dbchanges2/Client_Nychh/`.
97
114
  items 1129/1129, VendorItems 2414/2414) so both stay INNER. Seeds Developer-role record grants for
98
115
  records 18 and 19 (both had only Base+API) plus Developer read on `PurchaseOrderItems.quantity`
99
116
  (field 94); roles resolved by name, uuid-keyed, idempotent. Mechanism:
100
- [tableview-joins](../../2.0/apps/_underscore/features/tableview-joins.md). **Applied to prod AND
101
- client-sandbox** — both verified rebased 2026-09-03 (view 16 at base recordId 18, no PurchaseOrders
102
- join). An earlier note here said this rebase was prod-only / not on client-sandbox; that is **stale**
103
- — client-sandbox is also rebased. **OPEN:** the parent `purchase-orders` view filters to vendor 172
117
+ [tableview-joins](../../2.0/apps/_underscore/features/tableview-joins.md). **Applied to prod** — prod re-verified
118
+ 2026-09-03 on `reader1.client.database.togahub.com` (view 16 at base `recordId` **18**, no
119
+ `apiWhereClause`). Its client-sandbox state has flipped in the notes more than once; the parent
120
+ `purchase-orders` view was found **stale** on sandbox-client the same day, which is why
121
+ sandbox-client was reset from production on 2026-09-03. **Read the environment before trusting
122
+ either claim.** **OPEN:** the parent `purchase-orders` view filters to vendor 172
104
123
  (`apiWhereClause
105
124
  (vendorId:eq:172)`); this item view has no `apiWhereClause`, so it lists items for all POs — left
106
125
  unfiltered (mirrors prior state), flagged as a decision for the dev. A "3L-2250" error reported
@@ -214,6 +233,11 @@ table views. Client-specific DB change-sets live in `dbchanges2/Client_Nychh/`.
214
233
  [NYCHH PO Number direction](./features/po-number-upstream-direction.md).
215
234
 
216
235
  ## Notes
236
+ - **`Items.title` is NULL on most NYCHH rows — `description` is the populated label field.** Confirmed
237
+ 2026-09-03 on client-sandbox (a `transfer-order-items` line returned `"title": null` with a real
238
+ `partNumber`). Every screen that labels an item for this tenant needs the
239
+ `title || description` chain — the Transfer Order record modal and the Create modal's item picker
240
+ both carry it. The real fix is populating `Items.title`.
217
241
  - **Role ids in `Client_Nychh` (verified 2026-09-01):** `1 = Base`, `2 = Developer`, `3 = API`.
218
242
  Role ids differ per client DB — resolve by **name** in migrations, never hard-code these.
219
243
  - **PO Number is upstream-sourced** (`PurchaseOrders_SalesOrders`), which is why the downstream
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.764",
3
+ "version": "1.0.766",
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",