toga-ai 1.0.413 → 1.0.415
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/knowledge/2.0/apps/_underscore/INDEX.md +1 -0
- package/knowledge/2.0/apps/_underscore/features/persona-name-translation.md +146 -0
- package/knowledge/2.0/apps/dbchanges2/INDEX.md +1 -1
- package/knowledge/2.0/apps/dbchanges2/features/surface-layer-schema.md +39 -2
- package/knowledge/2.0/apps/toga2-commerce/INDEX.md +1 -1
- package/knowledge/2.0/apps/toga2-commerce/features/client-fields.md +82 -8
- package/knowledge/2.0/apps/toga25-supply/features/surface-frontend.md +19 -0
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/compass-canada/profile.md +10 -1
- package/package.json +1 -1
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
| [_Model::save() vs raw _Query — no atomic conditional update](features/model-save-vs-query-atomic-update.md) | `_Model::save()` is a plain load-then-write ORM primitive and **cannot express an atomic conditional update** (an optimistic-concurrency / row-claim guard such | _underscore/Model.php, _underscore/Query.php |
|
|
23
23
|
| [NetSuite REST Client (_Component_Api_Netsuite) — record writes & SuiteQL](features/netsuite-rest-client.md) | `_Component_Api_Netsuite` is the **2.0 `_underscore` NetSuite REST client** — the shared primitive every worker2/api2 NetSuite caller uses for record GETs, Suit | _underscore/Component/Api/Netsuite/Netsuite.php |
|
|
24
24
|
| [Per-Client Database Connections & the Local Logs Trap](features/per-client-database-connections.md) | When `_underscore` serves a request for a client it opens **three distinct per-client database connections**, not one. | _underscore/Database.php, _underscore/ApiRequest.php, _underscore/Model/Client/Logs/Api.php |
|
|
25
|
+
| [Persona Name Translation (PersonaTranslations sidecar)](features/persona-name-translation.md) | Serves Persona **names** in multiple languages by adding a per-language **sidecar** table `PersonaTranslations`, reusing the platform's existing metadata-driven | _underscore/Model/Client/PersonaTranslation.php, dbchanges2/Client/2026-07-22a - PersonaTranslations.sql, dbchanges2/Core/2026-07-22a - PersonaTranslationsRecord.sql, dbchanges2/Client/2026-07-22b - PersonaTranslationsAcl.sql, dbchanges2/Client_CompassCanada/2026-07-22a - PersonaTranslationsFrench.sql |
|
|
25
26
|
| [Recursive Item Fulfillments (upstream mirroring)](features/recursive-item-fulfillments.md) | In a multi-tier supply chain a sales order (SO) spawns a purchase order (PO) that becomes another SO downstream, and so on. | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemFulfillmentItem.php, _underscore/Model/Client/ItemFulfillmentItemUnit.php, _underscore/Model/Client/ItemFulfillmentPackage.php, _underscore/Model/Compass/AdvanceShippingNotice.php, dbchanges2/Core/2026-02-13 - 75601 - RecursiveItemFulfillmentCreation.sql, dbchanges2/Core/2026-06-04 - RecursiveItemFulfillmentPut.sql, dbchanges2/Client_Compass/2026-07-02a - FixSA133377TrackingSerialAndDuplicateIF.sql |
|
|
26
27
|
| [Surface Resolver (_Model_Core_Surface::resolve — replaces Page::meta)](features/surface-resolver.md) | The runtime for the platform-wide **Surface** UI presentation layer: 9 `_underscore` models plus a cached resolver, `_Model_Core_Surface::resolve(&$api, string | _underscore/Model/Core/Surface.php, _underscore/Model/Client/AclRecordScript.php, _underscore/Model/Core/RecordScript.php, dbchanges2/Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql, dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql, dbchanges2/Client_Quad/2026-07-01a - GrantSurfacesMetaGroupScriptAcl.sql, dbchanges2/Client_CompassCanada/2026-07-01a - GrantSurfacesMetaGroupScriptAcl.sql, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql, dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql, dbchanges2/Client_Compass/2026-07-15f - SalesOrderRecordActionsRemoveDeadConfigRuleOverrides.sql, dbchanges2/Core/2026-07-17h - Update - ClearApprovalsFilterButtonConfig.sql, dbchanges2/Client_Compass/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Compass/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_Quad/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Quad/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, _underscore/Model/Core/SurfaceElement.php, _underscore/Model/Core/Action.php, _underscore/Model/Core/Vocabulary.php, _underscore/Model/Core/VocabularyTerm.php, _underscore/Model/Core/Message.php, _underscore/Model/Client/SurfaceOverride.php, _underscore/Model/Client/MessageTranslation.php, _underscore/Model/Client/ThemeToken.php, _underscore/Model/Core/Page.php, api2/Component/Api/V2/V2.php |
|
|
27
28
|
| [Table-View Hyperlink Columns (meta → ACL → computed URL → render)](features/tableview-hyperlink-columns.md) | Any 2.0 table-view column can render its value as a clickable link instead of plain text. | _underscore/Model/Client/TableView.php, _underscore/Model/Client/TrackingNumber.php, api2/Component/Api/V2/V2.php, toga2-supply/src/api/toga.ts, toga2-supply/src/components/ui/Tables/PrimaryTable/helperFunctions/formatTableData.tsx, toga2-supply/src/components/ui/Tables/PrimaryTable/helperFunctions/convertData.tsx, toga2-supply/src/components/ui/Tables/hooks/useDataTableState.tsx, dbchanges2/Client/2026-07-20 - TrackingNumberHyperlinkAndFieldPermission.sql |
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Persona Name Translation (PersonaTranslations sidecar)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: _underscore
|
|
5
|
+
project: _Underscore
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: draft
|
|
9
|
+
updated: 2026-07-22
|
|
10
|
+
owners: ["tcox"]
|
|
11
|
+
files:
|
|
12
|
+
- _underscore/Model/Client/PersonaTranslation.php
|
|
13
|
+
- dbchanges2/Client/2026-07-22a - PersonaTranslations.sql
|
|
14
|
+
- dbchanges2/Core/2026-07-22a - PersonaTranslationsRecord.sql
|
|
15
|
+
- dbchanges2/Client/2026-07-22b - PersonaTranslationsAcl.sql
|
|
16
|
+
- dbchanges2/Client_CompassCanada/2026-07-22a - PersonaTranslationsFrench.sql
|
|
17
|
+
related:
|
|
18
|
+
- ./assortment-name-translation.md
|
|
19
|
+
- ../../api2/features/language-translation-layer.md
|
|
20
|
+
- ./acl-permission-chain.md
|
|
21
|
+
- ../../toga2-commerce/features/client-fields.md
|
|
22
|
+
- ../../../clients/compass-canada/profile.md
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Summary
|
|
26
|
+
|
|
27
|
+
Serves Persona **names** in multiple languages by adding a per-language **sidecar** table
|
|
28
|
+
`PersonaTranslations`, reusing the platform's existing metadata-driven translation mechanism —
|
|
29
|
+
built as an exact copy of the [Assortment Name Translation](./assortment-name-translation.md)
|
|
30
|
+
pattern. English stays in `Personas.name`; the sidecar holds only non-English overlays (null →
|
|
31
|
+
English fallback). Because the translation layer is fully metadata-driven (see
|
|
32
|
+
[Language Translation Layer](../../api2/features/language-translation-layer.md)), wiring this up
|
|
33
|
+
required **no api2 PHP logic change**: `GET /personas` names flow through `getTranslatedFieldValue`
|
|
34
|
+
at the top-level serialization sites. Compass Canada's French (fr-CA) persona names are the first
|
|
35
|
+
(and so far only) intended consumer.
|
|
36
|
+
|
|
37
|
+
> **Status (2026-07-22): DRAFTED / DEFERRED — not committed, not run.** All five artifacts live in
|
|
38
|
+
> local working trees only; the developer explicitly deferred the backend/DB changes this session.
|
|
39
|
+
> `sql-reviewer` verdict was **SAFE TO MERGE (0 critical)**. See **Rollout / pre-run verification**
|
|
40
|
+
> before executing — several ids and row-matches are *assumed* and must be confirmed against the live
|
|
41
|
+
> DB first. DB verification could not be completed this session: all RDS endpoints (prod readers,
|
|
42
|
+
> global, sandbox) are VPC-restricted from the dev machine — needs VPN or the claude.ai TOGa DB
|
|
43
|
+
> connector.
|
|
44
|
+
|
|
45
|
+
## Key files / entry points
|
|
46
|
+
|
|
47
|
+
- `_underscore/Model/Client/PersonaTranslation.php` — the sidecar model
|
|
48
|
+
(`_Model_Client_PersonaTranslation`), a declarative copy of `AssortmentTranslation.php`.
|
|
49
|
+
- `dbchanges2/Client/2026-07-22a - PersonaTranslations.sql` — the sidecar table (every client DB).
|
|
50
|
+
- `dbchanges2/Core/2026-07-22a - PersonaTranslationsRecord.sql` — Core record + fields + the field link.
|
|
51
|
+
- `dbchanges2/Client/2026-07-22b - PersonaTranslationsAcl.sql` — Base-role ACL grant (every client DB).
|
|
52
|
+
- `dbchanges2/Client_CompassCanada/2026-07-22a - PersonaTranslationsFrench.sql` — fr-CA seed.
|
|
53
|
+
|
|
54
|
+
## How it works
|
|
55
|
+
|
|
56
|
+
The mechanism itself is the data-driven translation layer documented in
|
|
57
|
+
[Language Translation Layer](../../api2/features/language-translation-layer.md) — this feature only
|
|
58
|
+
**registers a new translatable field** into it (identical shape to the assortment sidecar):
|
|
59
|
+
|
|
60
|
+
1. **Sidecar table.** `Client.PersonaTranslations` holds non-English overlays for persona names.
|
|
61
|
+
English remains in `Personas.name`; a null/absent sidecar row falls back to English.
|
|
62
|
+
`UNIQUE(personaId, languageId)`; collation `utf8mb4_unicode_ci` to match the translation-sidecar
|
|
63
|
+
family (`AssortmentTranslations`, `ItemTranslations`, the feature sidecars).
|
|
64
|
+
2. **Model.** `_Model_Client_PersonaTranslation` is the ORM model for the sidecar (declarative copy
|
|
65
|
+
of `AssortmentTranslation.php`).
|
|
66
|
+
3. **Core record registration.** `Core.Records` registers route `persona-translations` →
|
|
67
|
+
model `\_Model_Client_PersonaTranslation` at record **id 346** (assumed next-free), with
|
|
68
|
+
`Core.RecordFields` rows **2458–2462** for `id`, `uuid`, `personaId`, `languageId`, `name`, and an
|
|
69
|
+
`UPDATE ... JOIN` that sets `translationRecordFieldId` on the `Personas.name` RecordField.
|
|
70
|
+
4. **The field link (what makes it translate automatically).** The translatable `Personas.name`
|
|
71
|
+
RecordField gets its `Core.RecordFields.translationRecordFieldId` set to the sidecar's `name`
|
|
72
|
+
RecordField. The api2 V2 layer reads `translationRecordFieldId` for every field and automatically
|
|
73
|
+
returns the sidecar value for the caller's resolved language (falling back to English when absent).
|
|
74
|
+
For persona names this works with **no api2 code change** because `GET /personas` name is emitted
|
|
75
|
+
only at top-level serialization sites, all of which run through `getTranslatedFieldValue`.
|
|
76
|
+
(Contrast the assortment sidecar, which additionally needed a `join=`/joined-select fix — persona
|
|
77
|
+
names are not surfaced via a joined-select read path, so that 5th-serialization-site concern does
|
|
78
|
+
not apply here.)
|
|
79
|
+
5. **ACL.** A full ACL chain grants the **Base** role read/write on the new record. Because the
|
|
80
|
+
record's `aclDatabase = 'CLIENT'`, the grant lives in each client DB — see
|
|
81
|
+
[ACL Permission Chain](./acl-permission-chain.md).
|
|
82
|
+
|
|
83
|
+
## Data model
|
|
84
|
+
|
|
85
|
+
- `Client.PersonaTranslations` — `id, uuid, dtCreated, dtUpdated, personaId (FK Personas),
|
|
86
|
+
languageId (FK Languages), name`; `UNIQUE(personaId, languageId)`; `utf8mb4_unicode_ci`. English
|
|
87
|
+
stays in `Personas.name`; sidecar holds only non-English overlays (null → English fallback).
|
|
88
|
+
- `Core.Records` **346** (assumed) = `persona-translations` (`aclDatabase = 'CLIENT'`);
|
|
89
|
+
`Core.RecordFields` **2458–2462** (assumed) for id/uuid/personaId/languageId/name.
|
|
90
|
+
- `Personas.name` RecordField → `translationRecordFieldId` points at the sidecar `name` RecordField.
|
|
91
|
+
|
|
92
|
+
## Rollout / pre-run verification (do this BEFORE executing)
|
|
93
|
+
|
|
94
|
+
The ids and several row-matches in these migrations are **assumptions from a repo scan**, not
|
|
95
|
+
confirmed against the live DB. Before running:
|
|
96
|
+
|
|
97
|
+
1. **Verify next-free Core ids.** Confirm live `MAX(Core.Records.id) = 345` and
|
|
98
|
+
`MAX(Core.RecordFields.id) = 2457` so that 346 and 2458–2462 are actually free. (Consistent with
|
|
99
|
+
the feature-translation work, which used Records 343/344/345 + RecordFields 2442–2457.) If the live
|
|
100
|
+
maxima differ, renumber the record/field ids in `PersonaTranslationsRecord.sql` and the ACL SQL.
|
|
101
|
+
2. **Verify the 3 out-of-band personas.** The fr-CA seed matches 30 personas by known `Personas.uuid`
|
|
102
|
+
(from the 2025-12-19 Personas seed), plus **3 personas matched by exact English name** — *Admin
|
|
103
|
+
Canada*, *MacBooks*, *Managers/VIPs* — that exist only in the live DB (no repo migration). Confirm
|
|
104
|
+
those three exist with those exact names and that `Personas` has **no duplicate names** (the
|
|
105
|
+
name-match would double-seed otherwise).
|
|
106
|
+
3. **Confirm the glossary is complete.** The business-glossary screenshot the fr-CA text came from was
|
|
107
|
+
cut off after "Managers/VIPs → Gestionnaires/VIP" — confirm there are no additional personas below
|
|
108
|
+
that line before treating the seed as complete.
|
|
109
|
+
|
|
110
|
+
fr-CA is `languageId 2` (same as the item-feature translations — see the Compass Canada profile and
|
|
111
|
+
`UserGlobalSettings.settingId = 2`).
|
|
112
|
+
|
|
113
|
+
## Frontend consumption (toga2-commerce)
|
|
114
|
+
|
|
115
|
+
For the translated names to appear on the storefront **without a full reload** after a language
|
|
116
|
+
switch, the persona React Query caches must be language-sensitive. The persona query keys
|
|
117
|
+
`userPersonaDataOnLogin` and `userPersonaDataOnViewAs` were added to `LANGUAGE_SENSITIVE_QUERY_KEYS`
|
|
118
|
+
in `src/queries/queries.ts` (toga2-commerce) so they refetch on language change — see
|
|
119
|
+
[Client Fields](../../toga2-commerce/features/client-fields.md). This frontend change shipped this
|
|
120
|
+
session; the backend above did not.
|
|
121
|
+
|
|
122
|
+
## Client variations
|
|
123
|
+
|
|
124
|
+
- **Compass Canada** is the intended first consumer: `2026-07-22a - PersonaTranslationsFrench.sql`
|
|
125
|
+
seeds fr-CA names for 33 personas (30 by uuid + 3 by exact English name). Deferred / not yet run —
|
|
126
|
+
see the Compass Canada profile.
|
|
127
|
+
|
|
128
|
+
## Change history
|
|
129
|
+
|
|
130
|
+
- 2026-07-22 — Drafted the `PersonaTranslations` sidecar end-to-end following the
|
|
131
|
+
`AssortmentTranslations` pattern exactly: sidecar table + `_Model_Client_PersonaTranslation` model +
|
|
132
|
+
Core record 346 / RecordFields 2458–2462 registration + `translationRecordFieldId` link on
|
|
133
|
+
`Personas.name` + Base-role ACL chain + Compass Canada fr-CA seed (33 personas). `sql-reviewer` SAFE
|
|
134
|
+
TO MERGE (0 critical). **Not committed, not run** — ids and the 3 name-matched personas need live-DB
|
|
135
|
+
verification first (RDS was VPC-unreachable this session). No api2 code change required (persona
|
|
136
|
+
names serialize only at top-level `getTranslatedFieldValue` sites). (tcox)
|
|
137
|
+
|
|
138
|
+
## Related docs
|
|
139
|
+
|
|
140
|
+
- [Assortment Name Translation](./assortment-name-translation.md) — the pattern this copies.
|
|
141
|
+
- [Language Translation Layer](../../api2/features/language-translation-layer.md)
|
|
142
|
+
- [ACL Permission Chain](./acl-permission-chain.md)
|
|
143
|
+
- [Client Fields](../../toga2-commerce/features/client-fields.md) — storefront language switching.
|
|
144
|
+
- [Compass Canada profile](../../../clients/compass-canada/profile.md)
|
|
145
|
+
</content>
|
|
146
|
+
</invoke>
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [Database Changes (dbchanges2) Repository Architecture](architecture.md) | `dbchanges2` is the **schema-migration / SQL change-set repository** for the entire 2.0 platform. | Core/, Client/, Client_<Tenant>/, Logs/, Logs_Client/, _modules/ |
|
|
6
|
-
| [Surface Layer Schema (UI presentation/config tables)](features/surface-layer-schema.md) | The persistent schema for the platform-wide **Surface** UI presentation/configuration layer (see the `_underscore` [surface-resolver](../../_underscore/features | dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client_Compass/2026-06-25d - SalesOrderSurfaceClientSeed.sql, _underscore/Model/Client/ThemeToken.php, toga25-supply/src/themeConfig.json, dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql, dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql, dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql, dbchanges2/Core/2026-06-29a - ItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql, dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql, dbchanges2/Core/2026-06-29d - VendorItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29e - InventorySurfaceSeed.sql, dbchanges2/Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql, dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql, dbchanges2/Client_Compass/2026-06-30a - SalesOrderDisplaySectionManagerOverrides.sql, dbchanges2/Client_CompassCanada/2026-06-30a - SalesOrderSurfaceManagerOverrides.sql, dbchanges2/Client_Quad/2026-06-30a - SalesOrderSurfaceClientOverrides.sql, dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql, dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Core/2026-07-17h - Update - ClearApprovalsFilterButtonConfig.sql, dbchanges2/Client_Compass/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Compass/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_Quad/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Quad/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Core/2026-07-20a - Update - HideAdminNotesSectionByDefault.sql, dbchanges2/Core/2026-07-20b - Update - NotesSectionFieldElements.sql, dbchanges2/Client_Compass/2026-07-20a - AdminNotesSectionVisibilityOverride.sql, dbchanges2/Client_Compass/2026-07-20b - NotesSectionFieldsOverride.sql, dbchanges2/Client_CompassCanada/2026-07-20a - AdminNotesSectionVisibilityOverride.sql, dbchanges2/Client_CompassCanada/2026-07-20b - NotesSectionFieldsOverride.sql, dbchanges2/Client_Quad/2026-07-20a - NotesSectionFieldsOverride.sql, dbchanges2/Core/2026-07-20c - Update - VendorItemsToggleSurfaceSeed.sql, dbchanges2/Client_Compass/2026-07-20c - ItemRecordEditButtonEnable.sql, dbchanges2/Client_Compass/2026-07-20d - ItemRecordVendorItemsEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20c - ItemRecordEditButtonEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20d - ItemRecordVendorItemsEnable.sql, dbchanges2/Core/2026-07-20e - RestoreApproveDenyRowActions.sql, dbchanges2/Client_Compass/2026-07-20e - RowActionsApprovalWorkflowAdminEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20e - RowActionsApprovalWorkflowAdminEnable.sql, dbchanges2/Core/2026-07-17 - README - RUN ORDER.md, dbchanges2/Client_Compass/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_Compass/2026-07-21b - SalesOrderApprovalsGateEnable.sql, dbchanges2/Client_CompassCanada/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_CompassCanada/2026-07-21b - SalesOrderApprovalsGateEnable.sql, dbchanges2/Client_Quad/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_Quad/2026-07-21b - SalesOrderApproveDisabledTooltipTranslation.sql, dbchanges2/Core/2026-07-21a - SalesOrderDecisionSummarySurfaceSeed.sql, dbchanges2/Core/2026-07-21b - SalesOrderDecisionActionSurfaceSeed.sql |
|
|
6
|
+
| [Surface Layer Schema (UI presentation/config tables)](features/surface-layer-schema.md) | The persistent schema for the platform-wide **Surface** UI presentation/configuration layer (see the `_underscore` [surface-resolver](../../_underscore/features | dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client_Compass/2026-06-25d - SalesOrderSurfaceClientSeed.sql, _underscore/Model/Client/ThemeToken.php, toga25-supply/src/themeConfig.json, dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql, dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql, dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql, dbchanges2/Core/2026-06-29a - ItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql, dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql, dbchanges2/Core/2026-06-29d - VendorItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29e - InventorySurfaceSeed.sql, dbchanges2/Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql, dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql, dbchanges2/Client_Compass/2026-06-30a - SalesOrderDisplaySectionManagerOverrides.sql, dbchanges2/Client_CompassCanada/2026-06-30a - SalesOrderSurfaceManagerOverrides.sql, dbchanges2/Client_Quad/2026-06-30a - SalesOrderSurfaceClientOverrides.sql, dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql, dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Core/2026-07-17h - Update - ClearApprovalsFilterButtonConfig.sql, dbchanges2/Client_Compass/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Compass/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_Quad/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Quad/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Core/2026-07-20a - Update - HideAdminNotesSectionByDefault.sql, dbchanges2/Core/2026-07-20b - Update - NotesSectionFieldElements.sql, dbchanges2/Client_Compass/2026-07-20a - AdminNotesSectionVisibilityOverride.sql, dbchanges2/Client_Compass/2026-07-20b - NotesSectionFieldsOverride.sql, dbchanges2/Client_CompassCanada/2026-07-20a - AdminNotesSectionVisibilityOverride.sql, dbchanges2/Client_CompassCanada/2026-07-20b - NotesSectionFieldsOverride.sql, dbchanges2/Client_Quad/2026-07-20a - NotesSectionFieldsOverride.sql, dbchanges2/Core/2026-07-20c - Update - VendorItemsToggleSurfaceSeed.sql, dbchanges2/Client_Compass/2026-07-20c - ItemRecordEditButtonEnable.sql, dbchanges2/Client_Compass/2026-07-20d - ItemRecordVendorItemsEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20c - ItemRecordEditButtonEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20d - ItemRecordVendorItemsEnable.sql, dbchanges2/Core/2026-07-20e - RestoreApproveDenyRowActions.sql, dbchanges2/Client_Compass/2026-07-20e - RowActionsApprovalWorkflowAdminEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20e - RowActionsApprovalWorkflowAdminEnable.sql, dbchanges2/Core/2026-07-17 - README - RUN ORDER.md, dbchanges2/Client_Compass/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_Compass/2026-07-21b - SalesOrderApprovalsGateEnable.sql, dbchanges2/Client_CompassCanada/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_CompassCanada/2026-07-21b - SalesOrderApprovalsGateEnable.sql, dbchanges2/Client_Quad/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_Quad/2026-07-21b - SalesOrderApproveDisabledTooltipTranslation.sql, dbchanges2/Core/2026-07-21a - SalesOrderDecisionSummarySurfaceSeed.sql, dbchanges2/Core/2026-07-21b - SalesOrderDecisionActionSurfaceSeed.sql, dbchanges2/Client_Quad/2026-07-21c - SalesOrderDecisionSummaryOverride.sql, dbchanges2/Client_Compass/2026-07-21c - SalesOrderDecisionSummaryTotalConcat.sql, dbchanges2/Client_CompassCanada/2026-07-21c - SalesOrderDecisionSummaryTotalConcat.sql |
|
|
7
7
|
| [2.0 New-Client Onboarding (manual process)](workflows/client-onboarding.md) | > **A local browser wizard now automates this.** Steps 2–9 below (create DBs, generate Core/API > inserts, append to `Clients_Db.txt`) — plus the dbchanges2 bla | Client/, Client_<Tenant>/, Core/, Logs_Client/ |
|
|
@@ -62,6 +62,9 @@ files:
|
|
|
62
62
|
- dbchanges2/Client_Quad/2026-07-21b - SalesOrderApproveDisabledTooltipTranslation.sql
|
|
63
63
|
- dbchanges2/Core/2026-07-21a - SalesOrderDecisionSummarySurfaceSeed.sql
|
|
64
64
|
- dbchanges2/Core/2026-07-21b - SalesOrderDecisionActionSurfaceSeed.sql
|
|
65
|
+
- dbchanges2/Client_Quad/2026-07-21c - SalesOrderDecisionSummaryOverride.sql
|
|
66
|
+
- dbchanges2/Client_Compass/2026-07-21c - SalesOrderDecisionSummaryTotalConcat.sql
|
|
67
|
+
- dbchanges2/Client_CompassCanada/2026-07-21c - SalesOrderDecisionSummaryTotalConcat.sql
|
|
65
68
|
related:
|
|
66
69
|
- ../../_underscore/features/surface-resolver.md
|
|
67
70
|
---
|
|
@@ -306,8 +309,31 @@ Both seeds are **ADDITIVE, id-agnostic, and re-runnable**: `NOT EXISTS`-guarded
|
|
|
306
309
|
`config.role`/`valueKey`; Messages guarded by `messageKey`; the surfaces inherit `appId`/`recordId`
|
|
307
310
|
from the existing `order-details` sibling surface **by slug** (never a hardcoded id). Same authoring
|
|
308
311
|
pattern as the `2026-07-17g` seed. These are **Core→Core only** (prod-safe — no cross-DB Client
|
|
309
|
-
subselect).
|
|
310
|
-
|
|
312
|
+
subselect).
|
|
313
|
+
|
|
314
|
+
### Per-client decision-summary overrides (Quad hides + Compass/CC concat Total, 2026-07-21c)
|
|
315
|
+
The per-client divergence off the shared `sales-order-decision-summary` surface was seeded (all three
|
|
316
|
+
files use suffix `c`; `b` was already taken in each client folder, and each `run after Core/2026-07-21a`
|
|
317
|
+
per the RUN ORDER README). All are **client-level** (`roleId`/`personaId`/`languageId` NULL — see the
|
|
318
|
+
decision below), id-agnostic (resolved by `Surfaces.slug` + `config.valueKey`, never a hardcoded id),
|
|
319
|
+
and `NOT EXISTS`-guarded.
|
|
320
|
+
|
|
321
|
+
- **Quad field hides** (`Client_Quad/2026-07-21c`) — three `IS_VISIBLE='0'` overrides hiding **Assigned
|
|
322
|
+
Manager** (`assignedTo._name`), **Cost Center** (`c_erpEntityId`), and **Reason for Request**
|
|
323
|
+
(`reasonForRequest`, the read-only summary row — **NOT** the editable reason input). `IS_VISIBLE` is
|
|
324
|
+
in the base attribute ENUM (no widener needed). Renders end-to-end with **no FE change** (the resolver
|
|
325
|
+
applies visibility; the summary adapter already honors `el.isVisible`). Applied to local Client_Quad
|
|
326
|
+
and verified.
|
|
327
|
+
- **Compass + Compass Canada concatenated Total** (`Client_Compass/2026-07-21c`,
|
|
328
|
+
`Client_CompassCanada/2026-07-21c`) — a `CONFIG` override on the Total element setting the **full**
|
|
329
|
+
config `{"valueKey":"_totalPurchase","isConcatenated":{"valueKey":"_totalLease","suffix":"/mo",
|
|
330
|
+
"layout":"stacked","valueType":"currency"}}` — stacks the monthly lease figure under the purchase
|
|
331
|
+
Total for the lease clients. Because a `CONFIG` override **replaces the whole object** (see the resolver
|
|
332
|
+
doc), the override **must** carry the base `valueKey` (`_totalPurchase`), not just the added
|
|
333
|
+
`isConcatenated` key — omitting it silently drops the base binding. Resolved by slug +
|
|
334
|
+
`config.valueKey='_totalPurchase'`. Requires the small FE adapter extension to emit `isConcatenated`
|
|
335
|
+
(see [surface-frontend](../../toga25-supply/features/surface-frontend.md)). Authored this session;
|
|
336
|
+
only Quad confirmed applied locally.
|
|
311
337
|
|
|
312
338
|
## ThemeTokens — per-tenant, physically isolated in each client DB
|
|
313
339
|
|
|
@@ -473,6 +499,17 @@ rule resumes.
|
|
|
473
499
|
override is added). **Open follow-up.**
|
|
474
500
|
|
|
475
501
|
## Change history
|
|
502
|
+
- 2026-07-21 — Seeded the **per-client decision-summary overrides** off the shared
|
|
503
|
+
`sales-order-decision-summary` surface (all `2026-07-21c`, client-level — `roleId`/`personaId`/
|
|
504
|
+
`languageId` NULL): **Quad** hides three summary rows (`assignedTo._name`, `c_erpEntityId`,
|
|
505
|
+
`reasonForRequest` — the read-only row, not the reason input) via `IS_VISIBLE='0'` (base ENUM, no FE
|
|
506
|
+
change); **Compass + Compass Canada** stack the monthly lease figure under Total via a `CONFIG`
|
|
507
|
+
override carrying the **full** config (`_totalPurchase` + `isConcatenated{_totalLease, suffix "/mo",
|
|
508
|
+
layout stacked, currency}`). All id-agnostic (slug + `config.valueKey`), NOT EXISTS-guarded, registered
|
|
509
|
+
in the RUN ORDER README (`run after Core/2026-07-21a`). Reinforces the CONFIG-replaces-wholesale rule:
|
|
510
|
+
a partial CONFIG override silently drops the base `valueKey`. Scoped client-level not role-scoped (a
|
|
511
|
+
display flourish for every tenant user; open follow-up to re-scope Compass concat to a role if wanted).
|
|
512
|
+
Only Quad confirmed applied locally. (apeterson)
|
|
476
513
|
- 2026-07-21 — Seeded the **SalesOrder decision-modal (approve + deny) surfaces**, migrating the
|
|
477
514
|
per-client `approvalActionFields.json` display config to metadata. `Core/2026-07-21a` = one SHARED
|
|
478
515
|
`sales-order-decision-summary` SECTION surface (cardType `decisionSummary`, 8 read-only FIELD summary
|
|
@@ -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/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 |
|
|
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/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 |
|
|
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
|
| [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 |
|
|
12
12
|
| [AWS Amplify Build & Deploy (non-prod environments)](workflows/amplify-build-and-deploy.md) | How `toga2-commerce` (React + Vite, "commerce2-react") builds and deploys on **AWS Amplify**. | toga2-commerce/amplify.yml, toga2-commerce/.gitattributes, toga2-commerce/package.json, toga2-commerce/.github/workflows/sync-stage-environments.yml |
|
|
@@ -6,7 +6,7 @@ project: TOGa Commerce
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-22
|
|
10
10
|
owners: ["apeterson", "tcox"]
|
|
11
11
|
files:
|
|
12
12
|
- src/fieldsConfig/index.ts
|
|
@@ -22,11 +22,20 @@ files:
|
|
|
22
22
|
- src/hooks/useDynamicConditionalFieldOptions.ts
|
|
23
23
|
- src/stores/useFieldsStore.ts
|
|
24
24
|
- src/components/BaseDetailField/BaseDetailField.tsx
|
|
25
|
+
- src/components/NavIcons/NavIconItem.tsx
|
|
26
|
+
- src/components/Submenus/AlertSubmenu.tsx
|
|
27
|
+
- src/components/Submenus/types.ts
|
|
28
|
+
- src/pages/Account/AccountPage.tsx
|
|
29
|
+
- src/pages/Account/view/MyOrdersView.tsx
|
|
30
|
+
- src/pages/GetSupport/GetSupportPage.tsx
|
|
31
|
+
- src/pages/GetSupport/viewModel/useGetSupportViewModel.ts
|
|
32
|
+
- src/queries/queries.ts
|
|
25
33
|
related:
|
|
26
34
|
- 2.0/apps/toga2-commerce/architecture.md
|
|
27
35
|
- 2.0/apps/toga2-commerce/features/multi-tenant-theming.md
|
|
28
36
|
- 2.0/apps/toga2-commerce/features/cart-notification-emails.md
|
|
29
37
|
- 2.0/apps/toga2-commerce/features/cart-page-config-architecture.md
|
|
38
|
+
- 2.0/apps/_underscore/features/persona-name-translation.md
|
|
30
39
|
---
|
|
31
40
|
|
|
32
41
|
## Summary
|
|
@@ -141,7 +150,7 @@ export const FIELDS: Record<string, any> = {
|
|
|
141
150
|
USER: { ... },
|
|
142
151
|
},
|
|
143
152
|
},
|
|
144
|
-
COMPASSCANADA: { en: { ADMIN, SUPERUSER, MANAGER, USER }, fr: { ... } },
|
|
153
|
+
COMPASSCANADA: { en: { ADMIN, SUPERUSER, MANAGER, USER }, "fr-CA": { ... } },
|
|
145
154
|
QUAD: { en: { GLOBALADMIN, BUYER, ITSHOPPER /*, USER */ } },
|
|
146
155
|
};
|
|
147
156
|
```
|
|
@@ -156,8 +165,11 @@ export const FIELDS: Record<string, any> = {
|
|
|
156
165
|
`ERRORFIELDS`, `LOGINSETTINGSFIELDS`, …).
|
|
157
166
|
|
|
158
167
|
- **Tenant** keys: `COMPASS`, `COMPASSCANADA`, `QUAD`.
|
|
159
|
-
- **Language** keys are
|
|
160
|
-
|
|
168
|
+
- **Language** keys are **`en` / `fr-CA`** in the registry (verified `src/fieldsConfig/index.ts`).
|
|
169
|
+
⚠️ Two mismatches to keep straight: (1) the registry French key is **`fr-CA`**, *not* `fr`; and
|
|
170
|
+
(2) both differ from the on-disk **folder** names, which are uppercase `ENGLISH` / `FRENCH`. Only
|
|
171
|
+
COMPASSCANADA has `fr-CA`. See the "Language value & persistence" note and gotchas below — code that
|
|
172
|
+
compares the language must match `fr-CA` (use `startsWith("fr")`), never `=== "fr"`.
|
|
161
173
|
- **Role** keys: COMPASS/COMPASSCANADA → `ADMIN`, `SUPERUSER`, `MANAGER`, `USER`;
|
|
162
174
|
QUAD → `GLOBALADMIN`, `BUYER`, `ITSHOPPER` (+ `USER`).
|
|
163
175
|
|
|
@@ -195,7 +207,7 @@ const data = FIELDS?.[clientName]?.[language]?.[role] || null;
|
|
|
195
207
|
|
|
196
208
|
Inputs:
|
|
197
209
|
- **`clientName`** = `useFieldsStore.fieldKey` (the tenant `host`, set at login — see architecture doc).
|
|
198
|
-
- **`language`** = `useFieldsStore.language` (default `"en"`; set to `"fr"`
|
|
210
|
+
- **`language`** = `useFieldsStore.language` (default `"en"`; set to `"fr-CA"` via `setLanguage`
|
|
199
211
|
from the login settings switcher or `MySettingsView`).
|
|
200
212
|
- **`user`** = the logged-in user (role flags).
|
|
201
213
|
|
|
@@ -203,6 +215,16 @@ It returns `{ fields, loading, error, resetFieldsMapCache }`; `resetFieldsMapCac
|
|
|
203
215
|
`["clientFields"]` query (used when role/language/tenant changes mid-session). Page ViewModels then
|
|
204
216
|
pluck the section they need, e.g. `fieldsCall["MYACCOUNTFIELDS"]`.
|
|
205
217
|
|
|
218
|
+
### Language value & persistence
|
|
219
|
+
|
|
220
|
+
`useFieldsStore.language` is the resolved registry language key — **`"en"` or `"fr-CA"`** for
|
|
221
|
+
COMPASSCANADA. It is persisted to `localStorage` via the zustand `persist` key **`"fields-key"`**
|
|
222
|
+
(`src/stores/useFieldsStore.ts`), so a chosen language survives reload. The Account settings save
|
|
223
|
+
(`MySettingsView`) stores the selected option's **uuid** (`"en"` / `"fr-CA"`) through `setLanguage`.
|
|
224
|
+
Any code that branches on the current language must compare against **`"fr-CA"`** — the safe test is
|
|
225
|
+
`language.startsWith("fr")`, **never `language === "fr"`** (that comparison silently fails and was the
|
|
226
|
+
root cause of the Get Support image bug — see gotchas).
|
|
227
|
+
|
|
206
228
|
## What's inside a FIELDS JSON
|
|
207
229
|
|
|
208
230
|
Shape varies by page, but common forms:
|
|
@@ -235,14 +257,56 @@ Common field attributes: `uuid`, `label`, `valueKey` (path into the data object)
|
|
|
235
257
|
- **New role:** extend the role-derivation in `useAssignClientFields.ts` **and** add the role key
|
|
236
258
|
under each `FIELDS[tenant][lang]` branch + per-page role folders.
|
|
237
259
|
- **New language:** set `languageSwitcher.isEnabled` in the tenant's `clientFields` JSON, add the
|
|
238
|
-
`fr`/`xx` branch to `FIELDS[tenant]
|
|
260
|
+
`fr-CA`/`xx` branch to `FIELDS[tenant]` (use the exact registry key the switcher option's uuid
|
|
261
|
+
yields), and the `<LANGUAGE>` on-disk folders.
|
|
239
262
|
- **Editing copy:** change the page's `FIELDS/<CLIENT>/[<LANG>/][<ROLE>/]<NAME>FIELDS.json`. Remember
|
|
240
263
|
to update **all** affected role/language variants — there is no inheritance/fallback between roles.
|
|
241
264
|
|
|
265
|
+
## Keying previously-hardcoded JSX through FIELDS (with an in-code English fallback)
|
|
266
|
+
|
|
267
|
+
When a piece of UI text was hard-coded in JSX and now needs to render French for COMPASSCANADA, the
|
|
268
|
+
pattern is: **add the key to the COMPASSCANADA FIELDS JSON only (EN + FR × every role), and read it in
|
|
269
|
+
the component with an in-code English literal fallback** — e.g. `fields?.alertsSubmenu?.markAllAsRead
|
|
270
|
+
?? "Mark all as read"`. Because COMPASS/QUAD never get the key, they fall through to the English
|
|
271
|
+
literal and are **unaffected** — this is the deliberate way to add a COMPASSCANADA-only translation
|
|
272
|
+
without touching other tenants' JSON (distinct from the "no fallback" rule for whole role/section
|
|
273
|
+
lookups, which still holds). Examples shipped 2026-07-22 (all wired to fall back to English):
|
|
274
|
+
|
|
275
|
+
- **`HEADERFIELDS`** — new `adminSubmenu.manageRequests` ("Gérer les demandes") and an `alertsSubmenu`
|
|
276
|
+
group (`markAllAsRead` "Tout marquer comme lu", `openExternalLink` "Ouvrir TOGa Supply",
|
|
277
|
+
`alertSingular`/`alertPlural` "alerte"/"alertes", `allUpToDate` "Tout est à jour !"). Added to all
|
|
278
|
+
**8** COMPASSCANADA HEADERFIELDS files (EN+FR × 4 roles); consumed in
|
|
279
|
+
`src/components/NavIcons/NavIconItem.tsx` and `src/components/Submenus/AlertSubmenu.tsx` (+ `types.ts`).
|
|
280
|
+
- **`ORDERHISTORYFIELDS`** — Orders-for-Others page `ordersForOthersHeading` /
|
|
281
|
+
`ordersForOthersSubheading`, and the order-search box `pageSearchInput` ("Rechercher un numéro de
|
|
282
|
+
commande" / placeholder "Rechercher un nº de commande..."). Added to all 8 files; consumed in
|
|
283
|
+
`src/pages/Account/AccountPage.tsx` and `src/pages/Account/view/MyOrdersView.tsx`.
|
|
284
|
+
- **Get Support illustration is language-switched** (not a FIELDS key — an asset swap): the viewModel
|
|
285
|
+
(`src/pages/GetSupport/viewModel/useGetSupportViewModel.ts`) exposes `language`, and
|
|
286
|
+
`GetSupportPage.tsx` picks `assets/get-support-img-french.svg` when `language.startsWith("fr")`.
|
|
287
|
+
|
|
288
|
+
## Language-sensitive React Query caches (`LANGUAGE_SENSITIVE_QUERY_KEYS`)
|
|
289
|
+
|
|
290
|
+
`FIELDS` covers static copy, but **server data** that itself comes back translated (e.g. persona names
|
|
291
|
+
via the [Persona Name Translation](../_underscore/features/persona-name-translation.md) sidecar) is
|
|
292
|
+
cached by React Query and does **not** refetch on a language switch by default. `src/queries/queries.ts`
|
|
293
|
+
keeps a `LANGUAGE_SENSITIVE_QUERY_KEYS` allow-list of query keys to invalidate/refetch when the
|
|
294
|
+
language changes. The persona query keys `userPersonaDataOnLogin` and `userPersonaDataOnViewAs` were
|
|
295
|
+
added to it 2026-07-22 so translated persona names appear after a language switch without a full page
|
|
296
|
+
reload. When you add a new API-driven string that must respond to the language switcher, add its query
|
|
297
|
+
key here.
|
|
298
|
+
|
|
242
299
|
## Gotchas
|
|
243
300
|
|
|
244
|
-
-
|
|
245
|
-
|
|
301
|
+
- **Registry French key is `fr-CA`, not `fr` — and never compare with `=== "fr"`.** The COMPASSCANADA
|
|
302
|
+
French registry key is `"fr-CA"`; any language branch must use `startsWith("fr")`. Comparing
|
|
303
|
+
`language === "fr"` silently never matches and shipped a real bug this session (Get Support kept the
|
|
304
|
+
English illustration in French mode). The doc previously mis-stated this key as `fr`.
|
|
305
|
+
- **`en`/`fr-CA` (registry) vs `ENGLISH`/`FRENCH` (folders)** — mismatching these is the classic bug;
|
|
306
|
+
the runtime lookup uses the registry keys, not the folder names.
|
|
307
|
+
- **The header "Admin" nav button stays "Admin" in French — do not "fix" it.** Decided 2026-07-22:
|
|
308
|
+
"Admin" is the natural French abbreviation of "administration", so the top-level button is left
|
|
309
|
+
untranslated on purpose (the dropdown *contents* are translated). It is not a localization gap.
|
|
246
310
|
- **No fallback** in `FIELDS[client][language][role]` — a missing tenant/role/language combo returns
|
|
247
311
|
`null` and the page renders empty. Keep all role variants in sync.
|
|
248
312
|
- Role is derived from string `"1"` flags on the user (`user?._isAdmin === "1"`), not booleans.
|
|
@@ -262,6 +326,16 @@ Common field attributes: `uuid`, `label`, `valueKey` (path into the data object)
|
|
|
262
326
|
slip. Do not "reconcile" the two lists.
|
|
263
327
|
|
|
264
328
|
## Change history
|
|
329
|
+
- 2026-07-22 — **Correction:** the registry French key for COMPASSCANADA is **`fr-CA`, not `fr`**
|
|
330
|
+
(verified `src/fieldsConfig/index.ts`); documented language persistence (zustand `persist` key
|
|
331
|
+
`"fields-key"`, `setLanguage` stores the option uuid `en`/`fr-CA`) and the `startsWith("fr")` rule —
|
|
332
|
+
a `=== "fr"` comparison caused a real Get Support illustration bug. Documented the "key hard-coded
|
|
333
|
+
JSX through COMPASSCANADA FIELDS with an in-code English fallback so COMPASS/QUAD are unaffected"
|
|
334
|
+
pattern and the 2026-07-22 French-completion keys (`HEADERFIELDS` `adminSubmenu.manageRequests` +
|
|
335
|
+
`alertsSubmenu` group; `ORDERHISTORYFIELDS` orders-for-others headings + `pageSearchInput`; Get
|
|
336
|
+
Support language-switched illustration). Added the `LANGUAGE_SENSITIVE_QUERY_KEYS` mechanism (persona
|
|
337
|
+
query keys now refetch on language switch). Recorded the decision that the "Admin" nav button stays
|
|
338
|
+
"Admin" in French. (tcox)
|
|
265
339
|
- 2026-06-23 — Initial: documented the two-layer field system (global `clientFields/<TENANT>.json`
|
|
266
340
|
+ the per-page `FIELDS` registry), the `tenant→language→role` resolution in `useAssignClientFields`,
|
|
267
341
|
the `en`/`fr` vs `ENGLISH`/`FRENCH` distinction, role derivation per tenant, and field JSON shapes
|
|
@@ -292,6 +292,17 @@ values, so JSON-only functional fields survive: **Surface owns all display attri
|
|
|
292
292
|
visibility, icons, `isRequired`, placeholder, `textAreaHeight`); JSON fills gaps and functional
|
|
293
293
|
wiring. The per-client `approvalActionFields.json` files are kept on disk as reference (not deleted).
|
|
294
294
|
|
|
295
|
+
**Concatenated summary values (`isConcatenated`).** `surfaceBundlesToDecisionFields.ts` now passes an
|
|
296
|
+
element's `config.isConcatenated` through **verbatim** onto the decision-summary row: it exports a
|
|
297
|
+
`DecisionSummaryConcat` type (string shorthand | descriptor `{valueKey, prefix?, suffix?, valueType?,
|
|
298
|
+
layout?: 'inline'|'stacked', label?}`, mirroring blox `BaseDetailField`'s `isConcatenated`) and added an
|
|
299
|
+
optional `isConcatenated?` to `DecisionSummaryField`; `buildSummaryFields` copies `config.isConcatenated`
|
|
300
|
+
straight through. This is purely additive — before it, the adapter dropped the concat config, so the
|
|
301
|
+
Compass / Compass Canada Total `CONFIG` override (stacking `_totalLease` under `_totalPurchase`, seed
|
|
302
|
+
side in [surface-layer-schema](../../dbchanges2/features/surface-layer-schema.md)) would have seeded data
|
|
303
|
+
the FE ignored. blox `BaseDetailField` (`renderBaseDetailFieldValue.tsx`) already supports
|
|
304
|
+
`valueKey`+`suffix`+`layout:'stacked'`+`valueType:'currency'`. tsc + eslint clean.
|
|
305
|
+
|
|
295
306
|
**Shared summary, separate per-action chrome (decision).** The 8 order-summary rows are identical for
|
|
296
307
|
approve and deny, so they come from **one** shared surface (`sales-order-decision-summary`); the action
|
|
297
308
|
chrome (title, reason-input required-ness, confirm button, icons, toasts) diverges, so it is **two**
|
|
@@ -460,6 +471,14 @@ now carry it (2026-07-21):
|
|
|
460
471
|
treat type-checking as pending. Runtime `GET /v2/surfaces/{slug}/meta` also not yet exercised.
|
|
461
472
|
|
|
462
473
|
## Change history
|
|
474
|
+
- 2026-07-21 — Extended `surfaceBundlesToDecisionFields.ts` to emit **`isConcatenated`** on
|
|
475
|
+
decision-summary rows: new exported `DecisionSummaryConcat` type (string shorthand | descriptor
|
|
476
|
+
`{valueKey, prefix?, suffix?, valueType?, layout?, label?}`, mirroring blox `BaseDetailField`); optional
|
|
477
|
+
`isConcatenated?` added to `DecisionSummaryField`; `buildSummaryFields` passes `config.isConcatenated`
|
|
478
|
+
through verbatim. Purely additive (tsc + eslint clean). The adapter previously dropped concat config, so
|
|
479
|
+
the Compass / Compass Canada Total `CONFIG` override (stacks `_totalLease` under `_totalPurchase`)
|
|
480
|
+
would have been ignored FE-side; blox `renderBaseDetailFieldValue.tsx` already renders
|
|
481
|
+
valueKey+suffix+stacked+currency. (apeterson)
|
|
463
482
|
- 2026-07-21 — Migrated the **SalesOrders approve/deny decision modal** display config onto Surface via
|
|
464
483
|
an **overlay** adapter seam. New `surfaceBundlesToDecisionFields.ts` adapts the 3 resolved bundles
|
|
465
484
|
(`DECISION_SURFACE_SLUGS`) into `{orderContentDetails, approve, deny}`, finding elements by
|
package/knowledge/INDEX.md
CHANGED
|
@@ -17,7 +17,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
17
17
|
|
|
18
18
|
## 2.0 framework
|
|
19
19
|
|
|
20
|
-
- **_underscore** (_Underscore) _(framework core)_ —
|
|
20
|
+
- **_underscore** (_Underscore) _(framework core)_ — 37 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
21
21
|
- **worker2** (Worker) — 31 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
22
22
|
- **api2** (API) — 13 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
23
23
|
- **dbchanges2** (Database Changes) _(framework core)_ — 3 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
@@ -14,7 +14,7 @@ project: _Underscore
|
|
|
14
14
|
client: compass-canada
|
|
15
15
|
type: profile
|
|
16
16
|
status: active
|
|
17
|
-
updated: 2026-07-
|
|
17
|
+
updated: 2026-07-22
|
|
18
18
|
owners: [jcardinal, bala, tcox, apeterson]
|
|
19
19
|
files: []
|
|
20
20
|
related:
|
|
@@ -70,6 +70,15 @@ to but distinct from Compass USA. Like Compass USA it spans the **2.0** commerce
|
|
|
70
70
|
(16 rows seeded). French was extracted from the old bilingual `"English/French"` `Assortments.name`
|
|
71
71
|
values, which were then cleaned to English-only. See
|
|
72
72
|
[Assortment Name Translation](../../2.0/apps/_underscore/features/assortment-name-translation.md).
|
|
73
|
+
- **Persona names (fr-CA) — DRAFTED / DEFERRED, not yet run.** A `PersonaTranslations` sidecar copying
|
|
74
|
+
the assortment pattern (Core record 346, fr-CA seed for 33 personas) is fully drafted in working
|
|
75
|
+
trees and `sql-reviewer`-approved but **not committed or run** — ids and 3 name-matched personas need
|
|
76
|
+
live-DB verification first (RDS was VPC-unreachable). The storefront side (persona query keys made
|
|
77
|
+
language-sensitive) already shipped. See
|
|
78
|
+
[Persona Name Translation](../../2.0/apps/_underscore/features/persona-name-translation.md).
|
|
79
|
+
- **Storefront French localization** (labels, submenus, orders-for-others, Get Support illustration) is
|
|
80
|
+
driven by the COMPASSCANADA FIELDS JSON; the registry language key is **`fr-CA`** (not `fr`). See
|
|
81
|
+
[Client Fields](../../2.0/apps/toga2-commerce/features/client-fields.md).
|
|
73
82
|
- The 2026-06-08 ASN → ItemFulfillment work was for **Compass USA**, not Compass Canada.
|
|
74
83
|
- **Order status is shipped-only**, same as Compass USA (shared `_Model_Compass_SalesOrder`).
|
|
75
84
|
Compass Canada's own IF lifecycle stages (picked/packed/shipped) + shipped backfill are seeded by
|
package/package.json
CHANGED