toga-ai 1.0.536 → 1.0.538

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.
Files changed (36) hide show
  1. package/knowledge/1.0/apps/library/features/startech-pcmaticb2b-sync.md +37 -15
  2. package/knowledge/1.0/apps/library/features/toga2-api-client-and-bridge.md +9 -1
  3. package/knowledge/1.0/standards/backend-php.md +40 -29
  4. package/knowledge/1.0/standards/framework-rules.md +9 -5
  5. package/knowledge/2.0/apps/_underscore/features/error-reporting-issue-event.md +26 -1
  6. package/knowledge/2.0/apps/_underscore/features/tracking-number-bridges.md +22 -2
  7. package/knowledge/2.0/apps/_underscore/features/units-for-items-for-purchase-orders.md +17 -3
  8. package/knowledge/2.0/apps/_underscore/workflows/local-db-refresh-from-beta.md +15 -2
  9. package/knowledge/2.0/apps/api2/features/v2-api-error-codes.md +57 -2
  10. package/knowledge/2.0/apps/toga2-commerce/INDEX.md +1 -1
  11. package/knowledge/2.0/apps/toga2-commerce/features/client-fields.md +42 -2
  12. package/knowledge/2.0/apps/toga2-supply/INDEX.md +1 -1
  13. package/knowledge/2.0/apps/toga2-supply/workflows/client-host-scoping.md +82 -5
  14. package/knowledge/2.0/apps/worker2/features/creating-worker-actions.md +11 -0
  15. package/knowledge/INDEX.md +3 -3
  16. package/knowledge/clients/compass-canada/profile.md +15 -1
  17. package/knowledge/clients/compass-usa/INDEX.md +3 -0
  18. package/knowledge/clients/compass-usa/features/persona-model-and-levy-gating.md +132 -0
  19. package/knowledge/clients/compass-usa/profile.md +17 -1
  20. package/knowledge/clients/compass-usa/workflows/persona-population-env-comparison.md +82 -0
  21. package/knowledge/clients/compass-usa/workflows/persona-refactor-migration.md +138 -0
  22. package/knowledge/clients/elite/INDEX.md +2 -1
  23. package/knowledge/clients/elite/features/supply2-scope.md +65 -23
  24. package/knowledge/clients/elite/features/supply2-tableview-config-drift.md +107 -0
  25. package/knowledge/clients/elite/profile.md +19 -2
  26. package/knowledge/clients/nycdoe/features/servicenow-integration.md +75 -1
  27. package/knowledge/clients/quad/INDEX.md +1 -0
  28. package/knowledge/clients/quad/features/po-asn-email-import.md +177 -0
  29. package/knowledge/clients/quad/profile.md +16 -2
  30. package/knowledge/clients/rate/INDEX.md +1 -1
  31. package/knowledge/clients/rate/features/aig-contract-creation.md +53 -1
  32. package/knowledge/clients/rate/features/paypal-subscription-purchase-webhook.md +129 -17
  33. package/knowledge/sessions/2026-08-06-elite-inventory-fe-tcox.md +80 -0
  34. package/knowledge/sessions/2026-08-06-quad-asn-import-jam-ajean.md +256 -0
  35. package/knowledge/standalone/apps/claude/workflows/mcp-tool-usage.md +12 -2
  36. package/package.json +1 -1
@@ -6,8 +6,8 @@ project: Library
6
6
  client: pcmaticb2b
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-07-29
10
- owners: [snaredla]
9
+ updated: 2026-08-10
10
+ owners: [snaredla, mhammontree]
11
11
  files:
12
12
  - library/app/api/toga2.php
13
13
  - library/app/api/startechticket.php
@@ -59,6 +59,32 @@ $payload['ticketType'] = ['code' => $toga2TicketTypeCode];
59
59
 
60
60
  Both `PCS Ticket` and `TOGaDesk-PCS` map to `PCS` in TOGA 2.0.
61
61
 
62
+ ## Onboarding a new StarTech client — `STARTECH_TOGADESK_CLIENTS`
63
+
64
+ `App_Api_Toga2::STARTECH_TOGADESK_CLIENTS` (`library/app/api/toga2.php`, top of the class) is the
65
+ registry that identifies StarTech-integrated clients. It maps **TOGaDesk client id => the client's
66
+ TOGA 2.0 API credentials**:
67
+
68
+ ```php
69
+ const STARTECH_TOGADESK_CLIENTS = [
70
+ App_Model_TogaDesk_Ticket::TOGADESK_CLIENT_ID__PCMATICB2B => [
71
+ 'clientUuid' => self::CLIENT_UUID_PCMATICB2B,
72
+ 'apiUuid' => self::API_UUID_PCMATICB2B,
73
+ 'apiSecret' => self::API_SECRET_PCMATICB2B,
74
+ ],
75
+ ];
76
+ ```
77
+
78
+ - **A new row must be added per client onboard.** Consumers (e.g.
79
+ `getStartechSelectableTicketStageNames`) look the client up here and **short-circuit to an empty
80
+ result with no DB hit** when absent — so a missing row is a silent no-op (an empty status
81
+ dropdown), not an error. This is the operational step to remember.
82
+ - The row's values are references to the per-client `CLIENT_UUID_*` / `API_UUID_*` /
83
+ `API_SECRET_*` class constants declared at the top of the same file — that is **where** the
84
+ credentials live. Do not reproduce their values in the KB, tickets, or logs (see the hardcoded-credentials
85
+ gotcha in [toga2-api-client-and-bridge](./toga2-api-client-and-bridge.md)).
86
+ - Currently **one** row: TOGaDesk client **177** (PC Matic B2B).
87
+
62
88
  ## Gotchas
63
89
 
64
90
  - **Do not add a NULL early-return guard in this function.** NULL `c_escalateToStartech` means the ticket was created in TOGaDesk — it must still sync to TOGA 2.0. The Startech gate lives in `_Trait_Startech_Ticket::postPost`, not here.
@@ -93,16 +119,12 @@ Imports Startech (Easeedesk V3) config into TOGA 2.0 — users, groups, devices,
93
119
  the 1.0 → 2.0 payload. Both reference the client's 2.0 schema **by name** (from
94
120
  `App_Model_Client::getClientDatabaseNames()`) so the JOIN stays inside one client schema — do not use
95
121
  `qqJoinClientsTable(…, true)` here, its UNION-across-all-clients form would cross-match ids between
96
- clients. StarTech clients are identified by `App_Api_Toga2::STARTECH_TOGADESK_CLIENTS`
97
- (TOGaDesk client id => TOGA 2.0 client id); non-StarTech clients short-circuit with no DB hit.
98
- - 2026-07-29: Added two per-ticket-type stage helpers to `App_Api_Toga2` (see
99
- `clients/pcmaticb2b/features/startech-per-ticket-type-stages.md`):
100
- `getStartechSelectableTicketStageNames($togadeskClientId, $togadeskDepartmentId)` resolves the
101
- agent-selectable stage names for TOGaDesk's status dropdown (department → ticket type via
102
- `TicketTypes.c_togadeskTicketDepartmentId`, filtered by `c_isSelectable = 1`), and
103
- `getStartechTicketStageUuid($ticketTypeName, $ticketStageName)` resolves a stage within a type for
104
- the 1.0 → 2.0 payload. Both reference the client's 2.0 schema **by name** (from
105
- `App_Model_Client::getClientDatabaseNames()`) so the JOIN stays inside one client schema — do not use
106
- `qqJoinClientsTable(…, true)` here, its UNION-across-all-clients form would cross-match ids between
107
- clients. StarTech clients are identified by `App_Api_Toga2::STARTECH_TOGADESK_CLIENTS`
108
- (TOGaDesk client id => TOGA 2.0 client id); non-StarTech clients short-circuit with no DB hit.
122
+ clients. StarTech clients are identified by `App_Api_Toga2::STARTECH_TOGADESK_CLIENTS`;
123
+ non-StarTech clients short-circuit with no DB hit.
124
+ - 2026-08-10: **Corrected** the description of `STARTECH_TOGADESK_CLIENTS` — it maps TOGaDesk client
125
+ id to the client's **TOGA 2.0 API credentials** (`clientUuid`/`apiUuid`/`apiSecret`, referencing the
126
+ `CLIENT_UUID_*`/`API_UUID_*`/`API_SECRET_*` class constants), **not** to a TOGA 2.0 client id as the
127
+ two 2026-07-29 entries stated. Added the onboarding section: a row must be added per client onboard
128
+ or the client silently short-circuits (empty stage list, no error). Also deduplicated the
129
+ accidentally-doubled 2026-07-29 entry. Observed while resolving a `_production` merge on TRUE-79401;
130
+ the const itself was authored by a teammate. (mhammontree)
@@ -6,7 +6,7 @@ project: Library
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-08-05
9
+ updated: 2026-08-10
10
10
  owners: [jcardinal, mhammontree, bala]
11
11
  files:
12
12
  - library/app/api/toga2.php
@@ -213,6 +213,10 @@ tickets) was **explicitly rejected** — see the gotcha about the single-recipie
213
213
  intact under a large merge. **Still open: whether it is deployed to the prod worker.**
214
214
  Review technique worth reusing: `git diff _production...HEAD -- <file>` isolates a ticket's real
215
215
  contribution from merge noise when an old branch is finally synced.
216
+ **Re-synced 2026-08-10** (`c64164fe`, pushed): `_production` merged into `TRUE-79401` again; the
217
+ only conflict was a positional collision in the class const block of `app/api/toga2.php` (this
218
+ ticket's `CUSTOMER_EMAIL_DELIMITER` landing next to incoming consts) — resolved by keeping both,
219
+ no logic change. Patch still intact; **deploy to the prod worker remains unverified.**
216
220
  2. **Beta verification of the nested write is outstanding.** Assert real
217
221
  `Client_Aig.ContactEmailAddresses` **row counts** (never the HTTP status), covering **both** a
218
222
  brand-new contact (CREATE) **and** a second entitlement for an **existing** contact (UPDATE).
@@ -361,6 +365,10 @@ enable flags** and an optional `$monitorTogadeskDepartmentIds[]`:
361
365
 
362
366
  ## Change history
363
367
 
368
+ - 2026-08-10 — TRUE-79401 re-synced with `_production` (`c64164fe`, pushed); the sole conflict was a
369
+ positional const-block collision, resolved by keeping both sides. Patch intact; deploy still
370
+ unverified. `STARTECH_TOGADESK_CLIENTS` (credential registry, add a row per client onboard) is
371
+ documented on [startech-pcmaticb2b-sync](./startech-pcmaticb2b-sync.md). (mhammontree)
364
372
  - 2026-08-05 — Recorded that a 2.0 GET **silently truncates a nested child collection** (observed
365
373
  `itemFulfillmentItemUnits` 45 of 70) with no error — so verification/counts must read the child
366
374
  resource **flat + fully paginated** and use `meta.totalRecordCount`, never a single nested
@@ -5,8 +5,8 @@ project: Library
5
5
  client: shared
6
6
  type: standard
7
7
  status: active
8
- updated: 2026-08-05
9
- owners: [jcardinal, rgirish, mhammontree]
8
+ updated: 2026-08-06
9
+ owners: [jcardinal, rgirish, mhammontree, ajean]
10
10
  files: []
11
11
  related:
12
12
  - ../apps/library/architecture.md
@@ -442,33 +442,44 @@ class Browser_Datagrid_Accounts extends Browser_Datagrid {
442
442
  * The `@` error-suppression operator is acceptable only for defensive file I/O (e.g. reading an optional cache file); do not use it to hide real errors.
443
443
  * Use structured logging to capture application behavior and errors.
444
444
 
445
- ### PHP warnings throw — guard fragile I/O, do not rely on `!== false`
446
-
447
- The bootstrap sets `error_reporting(E_ALL)` and `App_Error::handleError` (`library/app/error.php`)
448
- converts **every** PHP warning into an `ErrorException`. Consequences you must design around:
449
-
450
- - Warning-emitting calls **throw at the call site** rather than returning their documented
451
- failure value. `file_get_contents()` on an unreachable/403/timed-out URL raises an
452
- `ErrorException` **inside** the call — so a following `if ($x !== false)` guard never runs.
453
- - In a loop (e.g. a cron), one such throw aborts the **entire** remaining batch, not just the
454
- current item. This is especially dangerous in two-phase "stamp then act" crons where earlier
455
- phases have already committed state.
456
-
457
- Wrap fragile I/O and return the failure value yourself:
458
-
459
- ```php
460
- function safeFetchPdf(string $url) {
461
- try {
462
- return file_get_contents($url);
463
- } catch (\ErrorException $e) {
464
- error_log('PDF fetch failed: ' . $e->getMessage());
465
- return false;
466
- }
467
- }
468
- ```
469
-
470
- Then guard each dependent step **independently** (not a joint `&&`) so one failure degrades
471
- gracefully instead of dropping unrelated work or crashing the loop.
445
+ ### A notice/warning cannot be contained by `try/catch` — and it jams queues
446
+
447
+ **Canonical mechanics are above:** see
448
+ [*Any PHP warning terminates the request — how to opt out locally*](#any-php-warning-terminates-the-request--how-to-opt-out-locally).
449
+ `App_Error::handleError` does **not** throw; it calls `handleException`, which `exit`s. Two
450
+ sanctioned opt-outs (a local `set_error_handler`, or
451
+ `App_Error::setThrowExceptionsEnabled(false)` with a `finally` restore) are documented there.
452
+ This section records what that means for **job/queue code** specifically.
453
+
454
+ Additional verified detail:
455
+
456
+ - **Both exit paths.** `handleException` ends in `exit` whether Sentry is absent
457
+ (`library/app/error.php` ~L407) or present (~L671). Apps that call `\Sentry\init` in
458
+ `_/app/framework.php` — `worker` included — take the Sentry path, so "Sentry is configured"
459
+ does **not** mean execution continues.
460
+ - **The `return false;` after `exit;` in `handleError` is unreachable dead code.** Do not read
461
+ it as a signal that the handler returns control to the call site.
462
+ - **A notice jams a queue exactly like an uncaught exception would.** In an inbox- or
463
+ queue-driven loop, any statement *after* the failure point — an ack, an
464
+ `$imap->deleteMessage()`, a status stamp — never executes, so the same item is re-read and
465
+ re-fails on every run, forever. Confirmed against production evidence: after the notice
466
+ fired, the importer made **zero** further api2 calls in that run.
467
+ - **Diagnostic fingerprint:** an occurrence count that is an exact multiple of the job's
468
+ scheduled frequency (e.g. 20/hour for `*/3 * * * *`) means **one** poisoned item retried,
469
+ not many distinct failures.
470
+ - **In job code, prevention beats containment.** Guard optional data with `isset()` before
471
+ reading it, and consume/ack the item in a `finally` (or under a `catch (Throwable)`) so a
472
+ genuine exception cannot strand it. You cannot wrap your way out of a notice.
473
+
474
+ > **Existing guidance under review.** An earlier version of this section recommended
475
+ > wrapping fragile I/O in `try { ... } catch (\ErrorException $e)` relying on the GLOBAL
476
+ > handler to convert a warning. Given the `exit` above, that cannot work for a PHP
477
+ > warning or notice — the process ends before the `catch`. Uses of that pattern need
478
+ > **re-verifying** rather than assuming they are safe or assuming they are broken: the
479
+ > call may be throwing a genuine exception, or installing a local handler. Known
480
+ > occurrences to check: `clients/prudential/features/transmit-ordershipped-email.md`
481
+ > (~L112-123), `clients/prudential/profile.md` (~L75). Prefer the local-handler pattern
482
+ > from the canonical section above for anything new.
472
483
 
473
484
  ## Security Best Practices
474
485
 
@@ -5,8 +5,8 @@ project: Library
5
5
  client: shared
6
6
  type: standard
7
7
  status: active
8
- updated: 2026-06-16
9
- owners: [jcardinal]
8
+ updated: 2026-08-06
9
+ owners: [jcardinal, ajean]
10
10
  files: []
11
11
  related:
12
12
  - ../apps/library/architecture.md
@@ -114,9 +114,13 @@ repos is only query strings embedded in PHP code — not standalone migration fi
114
114
 
115
115
  ## PHP 8.x deprecations abort the request (audit before running on 8.5)
116
116
 
117
- The 1.0 framework's error handler promotes PHP **deprecation notices to `ErrorException`**,
118
- which aborts the request. On PHP 8.5, a single deprecated call in a live code path is therefore
119
- a hard failure, not a silent notice — it surfaces to users as a generic error.
117
+ The 1.0 framework's error handler **terminates** on a PHP deprecation notice
118
+ (`App_Error::handleError` → `handleException` → `exit`; it does not throw a catchable
119
+ `ErrorException`), so the request/job dies where the deprecation fires. On PHP 8.5 a single
120
+ deprecated call in a live code path is therefore a hard, uncontainable failure — a
121
+ surrounding `try/catch` will not save it. See the back-end standard's
122
+ *"Any PHP warning terminates the request — how to opt out locally"* section for the two
123
+ sanctioned local opt-outs.
120
124
 
121
125
  - When porting or running 1.0 code on PHP 8.x, **audit for deprecated-in-8.x calls** in every
122
126
  reachable code path before deploy.
@@ -7,7 +7,7 @@ client: shared
7
7
  type: feature
8
8
  status: active
9
9
  updated: 2026-08-06
10
- owners: ["dfranks", "jcardinal", "mhammontree"]
10
+ owners: ["dfranks", "jcardinal", "mhammontree", "ajean"]
11
11
  files:
12
12
  - _underscore/Error.php
13
13
  - _underscore/Database.php
@@ -466,6 +466,23 @@ gracefully — it silently deletes all observability for the whole tier. Any cha
466
466
  `varchar(255)` (`errorMessage`, `subject`) **throws** on `->save()`. Inside the handler's
467
467
  `catch(Throwable)` the throw is swallowed and the row is silently dropped — precisely when a
468
468
  long message matters most. Truncate to column width before `save()`.
469
+ - **A truncated `errorMessage` reads as "the API returned nothing" — it did not.** When a
470
+ captured message is a `print_r`-dumped api2 response envelope, the `varchar(255)` cut lands
471
+ **exactly where `status`, `error.code` and `messages[]` begin**, so the console shows an
472
+ envelope with no verdict in it and the field looks empty rather than severed. (Observed: a
473
+ pasted error cut at precisely 255 chars sent an investigation after a phantom empty field.)
474
+ Escalation path when the console message stops mid-envelope:
475
+ **(1)** the **full** request/response is in the **client-scoped** `Logs_<Client>.Api`
476
+ (e.g. `Logs_Quad.Api`) — **not** core `Logs.Api`; **(2)** the original inbound payload is in
477
+ `Logs.Event.context` (for an email importer, the attachment content verbatim — this is how a
478
+ poison file was recovered). Pull `context` before the **7-day** `Event` retention purges it.
479
+ - **`_Error::buildContext` redacts the DECODED token but not the RAW response string (OPEN
480
+ security note).** `access_token` is stripped from the decoded response object while the raw
481
+ `rawResponse` / `lastResponse` strings are stored intact, so live bearer JWTs reach
482
+ `Logs.Event.context` in **cleartext**. Two consequences: the raw strings need adding to the
483
+ redactor (in `_underscore/Error.php` **and** `library/app/error/capture.php` together, per the
484
+ gotcha above), and until then **never paste a `Logs.Event.context` or `Logs_<Client>.Api` row
485
+ into a ticket, chat, or knowledge doc** — those rows carry live tokens and customer PII.
469
486
  - **Hand-written SQL is what a table rename actually breaks — and it fails *after* the model
470
487
  succeeds.** The singular rename left plural literals in hand-written statements across four
471
488
  files, so `$issue->save()` succeeded and then `UPDATE Issues …` threw **1146** before the
@@ -563,6 +580,14 @@ clientUserId). **Neither was built.** As built instead:
563
580
 
564
581
  ## Change history
565
582
 
583
+ - 2026-08-06 — Added two debugging/security notes found while unjamming the Quad PO/ASN importer:
584
+ (1) a `varchar(255)`-truncated `errorMessage` severs a dumped api2 envelope exactly at
585
+ `status`/`error.code`/`messages[]`, so escalate to the **client-scoped** `Logs_<Client>.Api`
586
+ for the full envelope and to `Logs.Event.context` (7-day purge) for the original payload;
587
+ (2) OPEN — `_Error::buildContext` redacts `access_token` in the decoded object but not in the
588
+ raw `rawResponse`/`lastResponse` strings, so `Event.context` holds cleartext JWTs; do not paste
589
+ those rows anywhere. (ajean)
590
+
566
591
  - 2026-08-06 — Recorded an **open defect**: error capture is entirely dead on sandbox-dev/beta
567
592
  because `Logs.Issue` there lacks the **`clickupPriority`** column, so the capture INSERT fails with
568
593
  MySQL 1054 and **no** Issue row, Event row, or ClickUp task is written — the only trace is
@@ -6,8 +6,8 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-08-05
10
- owners: ["jcardinal", "mhammontree", "dfranks", "apeterson", "bala"]
9
+ updated: 2026-08-06
10
+ owners: ["jcardinal", "mhammontree", "dfranks", "apeterson", "bala", "tcox"]
11
11
  files:
12
12
  - api2/Component/Api/V2/V2.php
13
13
  - _underscore/Model/Client/AdvanceShippingNoticeItemUnit.php
@@ -177,9 +177,29 @@ returnTrackingNumber: {...} }]`, and an IFIU's as `itemFulfillmentItemUnitTracki
177
177
  `toga25-supply/sync_compasscanada_schema.sql` against `Client_CompassCanada` (local-only — prod
178
178
  already has the tables). See the
179
179
  [local-DB-refresh-from-beta workflow](../workflows/local-db-refresh-from-beta.md).
180
+ - **⚠ Clients that were not hand-migrated still carry `TableViewJoins` pointing at the DELETED
181
+ RecordFields — and nothing validates them until a user opens the view.** The per-client TableView
182
+ repointing was done one client at a time (Compass, NYCHH, Quad); a client onboarded before it, or
183
+ skipped, keeps joins on dropped ids and its views **500** — first
184
+ `table-views/meta` with *"Model build for primary key id '1431' for `_Model_Core_RecordField` … 0
185
+ rows"*, then the data query with *"Unknown column … in 'on clause'"* once the missing bridge join
186
+ record leaves the alias undefined. **`Client_Elite` has 11 such references across 9 views, in
187
+ production as well as sandbox** — see
188
+ [Elite supply2 TableView config drift](../../../clients/elite/features/supply2-tableview-config-drift.md)
189
+ for the full map and the NYCHH-mirroring fix. Before switching on a supply surface for a client
190
+ onboarded before 2026-06, diff its `TableViewJoins` against a migrated client. Note the same
191
+ audit found `sales-order-shipments` (join 10) referencing dead RecordField **211 in
192
+ `Client_Nychh` too** — that view is broken platform-wide and needs a backend decision, not a
193
+ copy.
180
194
  - **Porting the item-fulfillment TableView fix to another client is two independent decisions, not a copy-paste.** The **re-root** (Units 31 → ItemFulfillmentItems 29 + OUTER unit chain) is universally portable and fixes the "0 records" bug everywhere. The **tracking source is client-specific**: Compass writes one tracking number per item fulfillment at the **item level (318)**, so its views read 318. Other clients populate different bridge levels — verify with `SELECT COUNT(*)` per bridge before choosing. As of 2026-06-19, **record 318 is empty in `Client_Nychh` and `Client_Quad`**; their tracking lives at the **IF/shipment level (317)** (covers IFIs: NYCHH 7955/10049 ≈ 79%, Quad 4878/4967 ≈ 98%) and the **unit level (319)** (≈ 7–9% — serialized only). Copying Compass's 318 join into a client that doesn't write 318 yields a structurally-correct view with permanently blank tracking until 318 is backfilled. (NYCHH's pre-fix view pointed at the now-deleted record 41 = old IF-level/package bridge, i.e. it originally intended IF-level 317.)
181
195
 
182
196
  ## Change history
197
+ - 2026-08-06 — Recorded that the per-client TableView repointing left **unmigrated clients broken**:
198
+ `Client_Elite` still has 11 references to deleted RecordFields (211/321/932/358/1431) across 9
199
+ views in **prod and sandbox**, so its views 500 on `table-views/meta` and then on the data query
200
+ once the bridge join record (319 / 289) is missing. Added the pre-go-live check (diff a new
201
+ client's `TableViewJoins` against a migrated one) and noted `sales-order-shipments` join 10
202
+ references dead 211 in `Client_Nychh` as well. (tcox)
183
203
  - 2026-08-05 — Recorded that the 1.0 `App_Api_Toga2::syncItemFulfillmentFromNetsuite` full reconcile
184
204
  now maintains **all three** IF bridges (317/318/319) and, when pruning units/items NetSuite dropped,
185
205
  **deletes their bridge links first** because the bridge parent FKs are `RESTRICT` (no cascade) —
@@ -6,10 +6,12 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-22
10
- owners: ["bala"]
9
+ updated: 2026-08-06
10
+ owners: ["bala", "tcox"]
11
11
  files: []
12
- related: []
12
+ related:
13
+ - ./tracking-number-bridges.md
14
+ - ../../../clients/elite/features/supply2-tableview-config-drift.md
13
15
  ---
14
16
 
15
17
  ## Summary
@@ -82,7 +84,19 @@ None — uniform. The linkage structure is framework-level and identical across
82
84
  sales order (over/under-attribution), because attribution is by PO line, not by the
83
85
  fulfillment that shipped the unit.
84
86
 
87
+ - **A 500 here is usually stale per-client view config, not missing data.** This view's joins were
88
+ repointed onto the bridge records (319 tracking, 289 `SalesOrderItems_PurchaseOrderItems`) per
89
+ client. A client that never got that migration 500s on `table-views/meta` (*"Model build for
90
+ primary key id '1431' for `_Model_Core_RecordField` … 0 rows"*) and then on the data query
91
+ (*"Unknown column `SalesOrderItems_PurchaseOrderItems.salesOrderItemId` in 'on clause'"*).
92
+ Confirmed on `Client_Elite` (view 17) — see
93
+ [Elite TableView config drift](../../../clients/elite/features/supply2-tableview-config-drift.md).
94
+ Check the view config before investigating the linkage paths above.
95
+
85
96
  ## Change history
97
+ - 2026-08-06 — Added the gotcha that a 500 on this view is usually **stale per-client
98
+ `TableViewJoins`** left behind by the bridge migrations (records 319 / 289), not a data-linkage
99
+ problem; confirmed on `Client_Elite` view 17. (tcox)
86
100
  - 2026-06-22 — Documented the two-path units linkage model behind the
87
101
  units-for-items-for-purchase-orders view and why the receipt-only traversal hides
88
102
  fulfillment-only units. (bala)
@@ -6,8 +6,8 @@ project: _Underscore
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-07-27
10
- owners: ["apeterson", "mhammontree"]
9
+ updated: 2026-08-06
10
+ owners: ["apeterson", "mhammontree", "tcox"]
11
11
  files:
12
12
  - api2/Config/
13
13
  - _underscore/Loader.php
@@ -59,6 +59,15 @@ Order matters — dump first so a failed dump never wipes a local schema it can'
59
59
 
60
60
  ## Gotchas / known issues
61
61
 
62
+ - **"Beta" and "dev-sandbox" are the same cluster — confirm it in `Core.DatabaseHosts`, not in old
63
+ migrations.** `Core.DatabaseHosts` maps environment **`beta`** for `Client_*` schemas to the
64
+ **sandbox-dev** RDS cluster (`sandbox-dev.cluster-…us-east-1.rds.amazonaws.com`,
65
+ a.k.a. `dev.sandbox.database.togahub.com`). Practical consequences: a frontend running locally
66
+ against `VITE_API=https://api.beta.togahub.com/v2` authenticates and reads **dev-sandbox** data,
67
+ so seeding a test user there means inserting into the dev-sandbox `Client_<Id>`; and in the DB MCP
68
+ the environment to query is **`dev-sandbox`** (`client-beta` exposes no schemas). 2024-era
69
+ onboarding SQL that shows beta on a us-west-2 `client-cluster` is stale — that has been
70
+ consolidated.
62
71
  - **Beta `admin` lacks `RELOAD` → `--single-transaction` fails.** The managed sandbox account
63
72
  cannot `FLUSH TABLES WITH READ LOCK`, so the default `mysqldump --single-transaction` dies with
64
73
  `Couldn't execute 'FLUSH TABLES WITH READ LOCK': Access denied ... (1045)`. **Fix:** drop
@@ -105,6 +114,10 @@ Order matters — dump first so a failed dump never wipes a local schema it can'
105
114
 
106
115
  ## Change history
107
116
 
117
+ - 2026-08-06 — Recorded that environment **`beta` resolves to the dev-sandbox cluster** in
118
+ `Core.DatabaseHosts` for `Client_*` schemas, so an app pointed at `api.beta.togahub.com` is
119
+ reading dev-sandbox data and the DB MCP environment to query is `dev-sandbox` (not
120
+ `client-beta`, which exposes no schemas). (tcox)
108
121
  - 2026-07-27 — TRUE-79191: added the reverse-drift gotcha — pulling updated `_underscore` models from
109
122
  prod **without** applying the matching `dbchanges2` migrations locally throws `1054 Unknown column`
110
123
  (e.g. `Items.isFulfillable` from the 2026-07-17 migration); apply pending migrations or reset the
@@ -6,8 +6,8 @@ project: API
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-07-30
10
- owners: [mhammontree, tcox, jcardinal]
9
+ updated: 2026-08-10
10
+ owners: [mhammontree, tcox, jcardinal, ajean]
11
11
  files:
12
12
  - api2/Component/Api/V2/V2.php
13
13
  - api2/Component/Api/V2/Response/Response.php
@@ -97,6 +97,46 @@ migration instead of re-deriving it. All field/script ACL rows live in the **CLI
97
97
  DB fully migrated. On a local dev box with PHP opcache disabled a branch switch takes effect
98
98
  immediately (files are read fresh per request) — no Apache restart needed.
99
99
 
100
+ 7. **`EV-12` can just be a mis-prefixed field name — check native vs. custom before anything
101
+ else.** The `c_` prefix is **not** cosmetic: `c_foo` and `foo` are different field names, and
102
+ a `c_`-prefixed name that has no custom-field registration simply does not exist, so api2
103
+ rejects **the entire write** (not just that key). This bites hardest on records where *some*
104
+ sibling keys genuinely are custom — e.g. `Client_Quad.Units` carries custom
105
+ `c_hostIdentifier` / `c_pkIdentifier` next to **native** `serialNumber`, `assetTag` and
106
+ `macAddress`, and sending `c_macAddress` failed the whole item fulfillment with `EV-12`.
107
+ Diagnose by running `SHOW COLUMNS` on the tenant table: a real column name → send it
108
+ **unprefixed**; no column → it must be a registered custom field to be sendable at all.
109
+ Verify **every** key in a nested payload, not just the one you changed.
110
+
111
+ 8. **`EV-12` on a *lookup-by-name* means ambiguous, not missing.** A nested payload that
112
+ identifies a parent by a non-unique field (e.g. `shippingMethod: {name: 'Courier'}` where
113
+ the tenant's `ShippingMethods` holds two `Courier` rows under different carriers) cannot
114
+ resolve, and reads as a validation failure on the child (`trackingNumber`). Before hunting
115
+ for a missing record, check for **duplicates** — especially where the caller has its own
116
+ create-if-missing path, which is how the duplicate usually got there. Fix by narrowing the
117
+ identifier (nest the carrier), not by re-creating the parent.
118
+
119
+ 9. **Turn an `EO-1` into a stack trace via `error.id`.** The 500 envelope carries
120
+ `error.id` like `"8K-6"`. The part **before the dash** is `Logs.Issue.reference` —
121
+ `SELECT * FROM Logs.Issue WHERE reference = '8K'` gives the full `errorMessage` and stack
122
+ trace. The envelope itself tells you nothing useful; always go to `Logs.Issue`.
123
+ Corollary: if `transactionId`, `authority` and `audience` are **all NULL** in the error
124
+ envelope, the request failed **before client context was established**.
125
+
126
+ 10. **A "wrong client" bearer token looks like a payload bug.** POSTing a **Rate**-shaped
127
+ entitlement payload while holding an **AIG** token routes into
128
+ `_Model_Aig_Entitlement::prePost()` and dies with
129
+ `Undefined property: stdClass::$partNumber` — which reads as a malformed payload, not an
130
+ auth problem. **Switching Postman folders does NOT change the token**: it lives in an
131
+ environment variable and must be re-minted by re-running the auth request. Cheap
132
+ verification before debugging anything else — send any `GET` and check
133
+ **`audience.client`** in the response envelope.
134
+
135
+ 11. **api2 matches nested objects by `uuid` ONLY.** Browser payloads often identify a related
136
+ record by a client-custom key (e.g. Rate's `c_borrowerId` for the customer). Replaying such
137
+ a payload server-side will **create a duplicate** rather than match the existing record.
138
+ When replaying or backfilling, rewrite every nested reference to use `uuid`.
139
+
100
140
  ## BREAKING (2026-07-30): `error` is now an object, not a bare string
101
141
 
102
142
  The V2 envelope's `error` changed from a bare string (`"EO-1"`) to an **object**:
@@ -182,6 +222,21 @@ Full mechanics:
182
222
 
183
223
  ## Change history
184
224
 
225
+ - 2026-08-10 — TRUE-80575: added diagnosis notes 9-11 from debugging a Rate entitlement
226
+ replay — (9) `error.id`'s prefix before the dash **is** `Logs.Issue.reference`, so an `EO-1`
227
+ 500 becomes a real stack trace via `SELECT * FROM Logs.Issue WHERE reference = '…'`, and
228
+ all-NULL `transactionId`/`authority`/`audience` means the failure preceded client context;
229
+ (10) a **wrong-client bearer token** masquerades as a payload bug (Rate payload + AIG token →
230
+ `Undefined property: stdClass::$partNumber` from `_Model_Aig_Entitlement::prePost()`), and
231
+ Postman folders do not switch tokens — verify `audience.client` on any GET first; (11) api2
232
+ matches nested objects by **`uuid` only**, so replaying a browser payload that references a
233
+ record by a custom key (`c_borrowerId`) creates a duplicate. (mhammontree)
234
+ - 2026-08-06 — Added two `EV-12` diagnosis notes from the Quad PO/ASN importer: (6) a
235
+ mis-prefixed field name (`c_macAddress` for the **native** `Units.macAddress` column) makes
236
+ api2 reject the whole write — verify every nested key against `SHOW COLUMNS`, since custom
237
+ and native keys sit side by side on the same record; (7) `EV-12` on a lookup-by-name means
238
+ the parent is **ambiguous** (duplicate `ShippingMethods` rows named `Courier`), not missing —
239
+ narrow the identifier rather than re-creating the parent. (ajean)
185
240
  - 2026-08-04 — Recorded the catch-order trap from `_Exception_Business` now extending
186
241
  `_Exception_Validation` (business conditions answer 4xx and are still captured, but any
187
242
  `catch (_Exception_Validation …)` must test business **first** or they go unrecorded), and the
@@ -6,7 +6,7 @@
6
6
  | [Cart Bundle Submission & the bundleUuid Identity Contract](features/cart-bundle-submission-and-identity.md) | How cart **bundles** (kits) are turned into `SalesOrderItems` when a cart is submitted or an existing order is edited, and the **identity-field contract** every | src/api/syncSalesOrderItemsFromLocalStorageCartToApi.ts, src/utils/formatSalesOrderBundlesFromApi.ts, src/stores/useCartStoreZu.ts, src/pages/OrderDetails/helpers/formatSalesOrderDataFromLocalStorage.ts, src/pages/OrderDetails/view/components/OrderItems.tsx |
7
7
  | [Cart Notification Emails — duplicate prevention](features/cart-notification-emails.md) | On the cart "Notifications" section a user can add CC email addresses to an order. | src/pages/Cart/CartPage.tsx, src/pages/Cart/view/cartForm/CartForm.tsx, src/stores/useEmailOptionsStore.ts, src/stores/useCartSalesQuoteZu.ts, src/pages/Cart/viewModel/FIELDS/*/*/*/CARTPAGE.ts |
8
8
  | [Cart Page — config-driven form architecture (current state + planned refactor)](features/cart-page-config-architecture.md) | The Cart page (`src/pages/Cart/`) is the most config-heavy page in `toga2-commerce`. | src/pages/Cart/CartPage.tsx, src/pages/Cart/view/cartForm/CartForm.tsx, src/pages/Cart/view/cartForm/CartFormSection.tsx, src/pages/Cart/view/cartForm/CartFormRenderer.tsx, src/pages/Cart/view/EditCart.tsx, src/pages/Cart/view/EditOrder.tsx, src/pages/Cart/viewModel/useEditOrderOrEditCartViewModel.ts, src/pages/Cart/viewModel/FIELDS/*/*/*/CARTPAGE.ts, src/hooks/useAssignClientFields.ts |
9
- | [Client Fields — per-tenant / language / role content & config](features/client-fields.md) | Almost no user-facing text, field layout, or page config is hard-coded in `toga2-commerce`. | src/pages/Account/view/MySettingsView.tsx, src/fieldsConfig/index.ts, src/fieldsConfig/getClientLoginFields.ts, src/fieldsConfig/clientFields/COMPASS.json, src/fieldsConfig/clientFields/COMPASSCANADA.json, src/fieldsConfig/clientFields/QUAD.json, src/pages/Cart/api/CartApi.ts, src/hooks/useAuthenticationFlow.ts, src/contexts/AuthContext.tsx, src/pages/Login/viewModel/useLoginPageViewModel.ts, src/hooks/useAssignClientFields.ts, src/hooks/useDynamicConditionalFieldOptions.ts, src/stores/useFieldsStore.ts, src/components/BaseDetailField/BaseDetailField.tsx, src/components/NavIcons/NavIconItem.tsx, src/components/Submenus/AlertSubmenu.tsx, src/components/Submenus/types.ts, src/pages/Account/AccountPage.tsx, src/pages/Account/view/MyOrdersView.tsx, src/pages/GetSupport/GetSupportPage.tsx, src/pages/GetSupport/viewModel/useGetSupportViewModel.ts, src/queries/queries.ts, src/App.tsx, src/pages/Filter/FilterPage.tsx, src/pages/Filter/viewModel/FIELDS/COMPASS/ENGLISH/USER/FILTERPAGEFIELDS.json |
9
+ | [Client Fields — per-tenant / language / role content & config](features/client-fields.md) | Almost no user-facing text, field layout, or page config is hard-coded in `toga2-commerce`. | src/pages/Account/view/MySettingsView.tsx, src/contexts/helpers/getLoginSettings.ts, src/pages/Account/viewModel/useAccountViewModel.ts, src/fieldsConfig/index.ts, src/fieldsConfig/getClientLoginFields.ts, src/fieldsConfig/clientFields/COMPASS.json, src/fieldsConfig/clientFields/COMPASSCANADA.json, src/fieldsConfig/clientFields/QUAD.json, src/pages/Cart/api/CartApi.ts, src/hooks/useAuthenticationFlow.ts, src/contexts/AuthContext.tsx, src/pages/Login/viewModel/useLoginPageViewModel.ts, src/hooks/useAssignClientFields.ts, src/hooks/useDynamicConditionalFieldOptions.ts, src/stores/useFieldsStore.ts, src/components/BaseDetailField/BaseDetailField.tsx, src/components/NavIcons/NavIconItem.tsx, src/components/Submenus/AlertSubmenu.tsx, src/components/Submenus/types.ts, src/pages/Account/AccountPage.tsx, src/pages/Account/view/MyOrdersView.tsx, src/pages/GetSupport/GetSupportPage.tsx, src/pages/GetSupport/viewModel/useGetSupportViewModel.ts, src/queries/queries.ts, src/App.tsx, src/pages/Filter/FilterPage.tsx, src/pages/Filter/viewModel/FIELDS/COMPASS/ENGLISH/USER/FILTERPAGEFIELDS.json |
10
10
  | [Config-Driven Expedited Shipping Gating (Cart)](features/expedited-shipping-gating.md) | On the toga2-commerce **Cart** page, expedited shipping options (**"2nd Day EOB"** and **"Next Day Air"**) are only offered in the *Shipping Method* dropdown wh | toga2-commerce/src/pages/Cart/helpers/shippingOptionGates.ts, toga2-commerce/src/pages/Cart/viewModel/FIELDS/shared/shippingOptionGates.ts, toga2-commerce/src/pages/Cart/view/cartForm/CartForm.tsx, toga2-commerce/src/pages/Cart/CartPage.tsx |
11
11
  | [Filter / Search-Results Page & the Two Search Entry Points](features/filter-search-results-page.md) | The storefront has **two distinct search entry points that render the same card component through completely different code paths and different FIELDS files**. | src/pages/Filter/FilterPage.tsx, src/pages/Filter/viewModel/useFilterViewModel.ts, src/pages/Filter/viewModel/FIELDS/COMPASS/ENGLISH/USER/FILTERPAGEFIELDS.json, src/pages/Filter/viewModel/FIELDS/COMPASSCANADA/FRENCH/USER/FILTERPAGEFIELDS.json, src/pages/Filter/viewModel/FIELDS/QUAD/ENGLISH/BUYER/FILTERPAGEFIELDS.json, src/components/Header/Header.tsx, src/pages/Home/view/components/BundlesSection.tsx, src/pages/Home/viewModel/FIELDS/COMPASS/ENGLISH/USER/HOMEPAGEFIELDS.json, src/components/Cards/BundleViewCard.tsx, src/utils/renderBadge.tsx, src/hooks/useAssignClientFields.ts |
12
12
  | [Multi-Tenant Resolution & Theming](features/multi-tenant-theming.md) | `toga2-commerce` serves multiple clients from one codebase. | src/themeConfig/themes.json, src/themeConfig/ThemeContext.tsx, src/themeConfig/types.ts, src/components/ThemeSwitcher/ThemeSwitcher.tsx, src/components/AuthLayout/AuthLayout.tsx, src/api/axiosInstance.ts, src/contexts/AuthContext.tsx, tailwind.config.js |
@@ -6,10 +6,12 @@ project: TOGa Commerce
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-07-28
9
+ updated: 2026-08-06
10
10
  owners: ["apeterson", "tcox", "bala"]
11
11
  files:
12
12
  - src/pages/Account/view/MySettingsView.tsx
13
+ - src/contexts/helpers/getLoginSettings.ts
14
+ - src/pages/Account/viewModel/useAccountViewModel.ts
13
15
  - src/fieldsConfig/index.ts
14
16
  - src/fieldsConfig/getClientLoginFields.ts
15
17
  - src/fieldsConfig/clientFields/COMPASS.json
@@ -68,7 +70,10 @@ resolved per tenant, per language, and per role**. There are **two layers**:
68
70
  ```
69
71
 
70
72
  - `languageSwitcher.isEnabled` — **only COMPASSCANADA is `true`** (English/French). COMPASS and
71
- QUAD are `false`.
73
+ QUAD are `false`. This is a **hard branch-reachability fact**, not a preference: it gates the
74
+ *entire first branch* of `getLoginSettings.ts`, so that branch is **unreachable for Compass USA**
75
+ and QUAD. See "Login-settings branches" below before judging whether a `getLoginSettings` change
76
+ affects US or CA.
72
77
  - `userApiFields` — the allow-list of user columns fetched at login; importantly it includes the
73
78
  tenant's **role flags**: COMPASS/COMPASSCANADA carry `_isAdmin`, `_isSupervisor`, `_isSuperAdmin`;
74
79
  QUAD carries `_isGlobalAdmin`, `_isItShopper`, `_isBuyer`. All include `_personaIds`. This key is
@@ -91,6 +96,33 @@ Resolved by **`src/fieldsConfig/getClientLoginFields.ts`** — a `switch(client)
91
96
  **default is `COMPASS`** (not DEFAULT). Called from `AuthContext` as `getClientLoginFields(host)`.
92
97
  The `languageSwitcher.isEnabled` value feeds the login-settings branch in `getLoginSettings.ts`.
93
98
 
99
+ ### Login-settings branches (`src/contexts/helpers/getLoginSettings.ts`)
100
+
101
+ The top-level shape is `if (languageSwitcher.isEnabled) … else if (isPersonaSwitcherEnabled) … else …`,
102
+ and each leaf calls `setUser({ ...userData, contactUuid, _personaOptions })`. Two facts decide who is
103
+ affected by an edit here:
104
+
105
+ - **Branch 1 (language) is COMPASSCANADA-only** — COMPASS/QUAD `languageSwitcher.isEnabled` is
106
+ `false`, so a change confined to branch 1 **cannot** affect Compass USA.
107
+ - **Within Canada, the "no saved language" sub-branch is the common path.** Only **69 of 4,655**
108
+ active Canada users have a stored language preference (`UserGlobalSettings` page `language`), so
109
+ **~98% of Canada logins** take the `else` sub-branch that opens the settings modal.
110
+
111
+ > **Known defect (production) — Canada My Account shows a blank Persona field.** That
112
+ > "no saved language" sub-branch was the **only one of the seven `setUser` calls** in
113
+ > `getLoginSettings` not passing `_personaOptions`. All **8** Canada `MYACCOUNTFIELDS` configs
114
+ > (EN + FR × 4 roles) display a Persona field, and
115
+ > `src/pages/Account/viewModel/useAccountViewModel.ts` (~line 311) guards on `user._personaOptions`
116
+ > before populating it. **Nothing else sets it for those users** — the persona switcher is QUAD-only
117
+ > via `settingsKey`, and `NavIconList` only runs under View-As — so the field renders blank. This is
118
+ > a real defect, not cosmetic: Canada has 34 personas, 7,017 users with personas, 1,461 with more
119
+ > than one. A one-line fix exists on `TRUE-80672` / `_beta` (commit `5313a9b1`, "populate
120
+ > `_personaOptions` on the no-language login path") but is **not in `_production`** — verify the
121
+ > branch before assuming it is fixed. Related: the persisted-store caveat in
122
+ > [Language-sensitive React Query caches](#language-sensitive-react-query-caches-language_sensitive_query_keys)
123
+ > — `_personaOptions` lives in the persisted Zustand user store, so if it is never written at login,
124
+ > no cache invalidation will heal it.
125
+
94
126
  ### `fetchUsersApiFields` / `fetchUserApiFields` and the config-driven Cart fetches (C6)
95
127
 
96
128
  `fetchUsers()` in `src/pages/Cart/api/CartApi.ts` no longer hardcodes its field list. It resolves it
@@ -378,6 +410,14 @@ on switch.
378
410
  slip. Do not "reconcile" the two lists.
379
411
 
380
412
  ## Change history
413
+ - 2026-08-06 — Added a **Login-settings branches** section for `getLoginSettings.ts`: branch 1 is
414
+ gated by `languageSwitcher.isEnabled` and is therefore **unreachable for COMPASS/QUAD**, and within
415
+ Canada only **69 of 4,655** active users have a stored language, so ~98% take the "no saved
416
+ language" sub-branch. Recorded the resulting **production defect** — that sub-branch was the only
417
+ one of seven `setUser` calls omitting `_personaOptions`, so Canada My Account renders a blank
418
+ Persona field (all 8 COMPASSCANADA `MYACCOUNTFIELDS` show it; `useAccountViewModel` guards on
419
+ `user._personaOptions`; nothing else populates it). Fix `5313a9b1` is on `TRUE-80672`/`_beta` only,
420
+ **not `_production`**. (bala)
381
421
  - 2026-07-28 — Documented two failure modes found while fixing the header-search kit card: (1) there is
382
422
  **no fallback for an individual key** inside a present page JSON — a missing key silently yields
383
423
  `undefined` (empty uncolored badge, `"3 undefineds"`), and key names differ per page
@@ -5,4 +5,4 @@
5
5
  | [TOGa Supply (toga2-supply) Architecture](architecture.md) | `toga2-supply` is the **React + Vite frontend** for TOGa Supply — warehouse fulfillment tooling (shipment selection, fulfill & ship against carrier APIs, NetSui | toga2-supply/src/api/toga.ts, toga2-supply/src/pages/ShipmentItems/view/ShipmentItemsPage.tsx, toga2-supply/src/pages/EditShipment/view/EditShipmentPage.tsx, toga2-supply/src/pages/EditShipment/api/UpdateShipmentApi.ts, toga2-supply/src/pages/Shipments/view/components/ShipmentsCardTableForm/ShipmentsCardTableForm.tsx |
6
6
  | [Fulfill & Ship](features/fulfill-and-ship.md) | Fulfill & Ship lets a warehouse user select sales-order line items, enter serials, pick a carrier/method, and in one action: create the Item Fulfillment records | _underscore/Model/Client/Measure.php, _underscore/Model/Client/TrackingNumber.php, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/getEditShipmentFormOptions.ts, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/validateFormOnSubmit.ts, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/checkDimensions.ts, toga2-supply/src/components/ui/Tables/BasicTable/BasicTable.tsx, toga2-supply/src/components/ui/Tables/types.ts, toga2-supply/src/components/ui/GoogleMapsLink.tsx, toga2-supply/src/pages/ShipmentItems/view/forms/ShipmentItemsTable.tsx, toga2-supply/src/pages/ShipmentItems/api/ShipmentItemsApi.ts, toga2-supply/src/pages/ShipmentItems/types.ts, toga2-supply/src/pages/EditShipment/viewModel/FIELDS/RETURNLABELFIELDS.json, toga2-supply/src/pages/EditShipment/view/components/forms/EditShipmentForm.tsx, toga2-supply/src/components/ui/BaseInput/UnitSelect.tsx, toga2-supply/src/pages/EditShipment/view/modals/SerialNumbersModal.tsx, toga2-supply/src/pages/ShipmentItems/view/ShipmentItemsPage.tsx, toga2-supply/src/pages/EditShipment/view/EditShipmentPage.tsx, toga2-supply/src/pages/EditShipment/view/components/forms/EditShipmentForm.tsx, toga2-supply/src/pages/EditShipment/view/components/SelectedShipmentItemsTable.tsx, toga2-supply/src/pages/EditShipment/view/helpers/ShipmentDetailsForm/renderEditShipmentFormInput.tsx, toga2-supply/src/pages/EditShipment/viewModel/FIELDS/DUMMYUPDATESHIPMENTFIELDS.json, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/renderEditShipmentFormInput.tsx, toga2-supply/tailwind.config.cjs, toga2-supply/src/pages/EditShipment/view/modals/ReturnShippingModal.tsx, toga2-supply/src/styles/index.scss, toga2-supply/src/components/ui/BaseInput/BaseInput.tsx, toga2-supply/src/pages/EditShipment/api/UpdateShipmentApi.ts, toga2-supply/src/pages/EditShipment/viewModel/signatureTypes.ts, toga2-supply/src/pages/EditShipment/view/modals/SelectReturnAddressModal.tsx, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/formatShipmentData.ts, toga2-supply/src/pages/EditShipment/types.ts, toga2-supply/src/pages/Shipments/view/ShipmentsPage.tsx, toga2-supply/src/pages/Shipments/view/components/ShipmentsCardTableForm/ShipmentsCardTableForm.tsx, toga2-supply/src/pages/Shipments/api/ShipmentsApi.ts, toga2-supply/src/pages/Shipments/types.ts, toga2-supply/src/pages/FulfilledShipments/view/FulfilledShipmentsPage.tsx, toga2-supply/src/components/ui/CardTable/CardTable.tsx, toga2-supply/src/components/ui/CardTable/types.ts, toga2-supply/src/assets/pen-line.svg, _underscore/Model/Client/ItemFulfillment.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Component/Library/Carriers/Ups/Ups.php |
7
7
  | [AWS Amplify Build & Deploy (non-prod environments)](workflows/amplify-build-and-deploy.md) | How `toga2-supply` (React + Vite) builds and deploys on **AWS Amplify**. | toga2-supply/amplify.yml, toga2-supply/.gitattributes, toga2-supply/.github/workflows/sync-stage-environments.yml, toga2-supply/.env.qc-security |
8
- | [Onboarding a client to the supply2 frontend (host-scoping)](workflows/client-host-scoping.md) | How a new tenant becomes a "client" in the `toga2-supply` frontend. | toga2-supply/src/stores/useHostNameStore.ts, toga2-supply/src/hooks/useHostName.tsx, toga2-supply/src/hooks/usePageDetails.tsx, toga2-supply/src/hooks/usePageListDetails.tsx, toga2-supply/src/components/layout/SlideMenu/SlideMenu.tsx, toga2-supply/src/utils/handleClientAuthentication.ts, toga2-supply/src/App.tsx, toga2-supply/src/pages/Inventory/viewModel/FIELDS/DUMMYGROUPOPTIONS.ts, toga2-supply/src/pages/Inventory/listing/InventoryPage.tsx, toga2-supply/src/api/toga.ts |
8
+ | [Onboarding a client to the supply2 frontend (host-scoping)](workflows/client-host-scoping.md) | How a new tenant becomes a "client" in the `toga2-supply` frontend. | toga2-supply/src/stores/useHostNameStore.ts, toga2-supply/src/utils/resolveClientHostName.ts, toga2-supply/src/hooks/useHostName.tsx, toga2-supply/src/hooks/usePageDetails.tsx, toga2-supply/src/hooks/usePageListDetails.tsx, toga2-supply/src/components/layout/SlideMenu/SlideMenu.tsx, toga2-supply/src/utils/handleClientAuthentication.ts, toga2-supply/src/App.tsx, toga2-supply/src/pages/Inventory/viewModel/FIELDS/DUMMYGROUPOPTIONS.ts, toga2-supply/src/pages/Inventory/viewModel/FIELDS/INVENTORYPAGEFIELDS.ts, toga2-supply/src/pages/Inventory/listing/InventoryPage.tsx, toga2-supply/src/pages/Inventory/listing/InventorySubTablePage.tsx, toga2-supply/src/api/toga.ts, toga2-supply/package.json |