toga-ai 1.0.389 → 1.0.391
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 -1
- package/knowledge/2.0/apps/_underscore/features/acl-permission-chain.md +36 -6
- package/knowledge/2.0/apps/api2/INDEX.md +1 -1
- package/knowledge/2.0/apps/api2/features/v2-api-error-codes.md +16 -3
- package/knowledge/2.0/apps/toga-blox/INDEX.md +1 -0
- package/knowledge/2.0/apps/toga-blox/workflows/dynamic-publish-pipeline.md +88 -0
- package/knowledge/2.0/apps/toga2-commerce/workflows/amplify-build-and-deploy.md +48 -36
- package/knowledge/2.0/apps/toga2-supply/INDEX.md +1 -1
- package/knowledge/2.0/apps/toga2-supply/features/fulfill-and-ship.md +65 -1
- package/knowledge/2.0/standards/frontend-deploy.md +24 -1
- package/knowledge/INDEX.md +1 -1
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [Proposed — git-sourced base+overlay JSON authoring for the Surface layer](architecture/surface-authoring-proposal.md) | A **proposal / handoff recommendation** (not implemented) that the Surface layer's *authoring* model move off hand-authored SQL against the `SurfaceOverrides` E | _underscore/Model/Core/Surface.php, _underscore/Model/Client/SurfaceOverride.php |
|
|
6
6
|
| [_underscore Framework Architecture](architecture.md) | `_underscore` is the shared PHP backend framework for **all 2.0 applications**. | _underscore/_underscore.php, _underscore/Loader.php, _underscore/Framework.php, _underscore/Model.php, _underscore/Database.php, _underscore/Query.php, _underscore/Route.php, _underscore/Component.php |
|
|
7
|
-
| [ACL Permission Chain (Record & Field Authorization)](features/acl-permission-chain.md) | Authorization in the 2.0 API is **metadata-driven**: whether a role may Create/Read/Update/Delete a record is decided by rows across **four linked tables**, not | api2/Component/Api/V2/V2.php, _underscore/Model/Core/Page.php, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql |
|
|
7
|
+
| [ACL Permission Chain (Record & Field Authorization)](features/acl-permission-chain.md) | Authorization in the 2.0 API is **metadata-driven**: whether a role may Create/Read/Update/Delete a record is decided by rows across **four linked tables**, not | api2/Component/Api/V2/V2.php, _underscore/Model/Core/Page.php, _underscore/Model/Client/TrackingNumber.php, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql |
|
|
8
8
|
| [Address Validation (carrier waterfall + validateAddress scripted endpoint)](features/address-validation.md) | `_Model_Client_Address::validateAddress` verifies a US address against a **carrier waterfall (USPS → FedEx → UPS)** and returns a single canonical, carrier-norm | _underscore/Model/Client/Address.php |
|
|
9
9
|
| [_ApiRequest ENCODE__JSON now sends Content-Type: application/json](features/apirequest-json-content-type.md) | `_ApiRequest::execute()`'s `ENCODE__JSON` branch json-encoded the request body but never set a `Content-Type` header. | _underscore/ApiRequest.php |
|
|
10
10
|
| [Assortment Name Translation (AssortmentTranslations sidecar)](features/assortment-name-translation.md) | Serves Assortment (product-grouping) **names** in multiple languages by adding a per-language **sidecar** table `AssortmentTranslations`, reusing the platform's | _underscore/Model/Client/AssortmentTranslation.php, dbchanges2/Client/2026-06-26a - AssortmentTranslations.sql, dbchanges2/Core/2026-06-26a - AssortmentTranslationsRecord.sql, dbchanges2/Client/2026-06-26b - AssortmentTranslationsAcl.sql |
|
|
@@ -6,11 +6,12 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-21
|
|
10
10
|
owners: ["jcardinal", "mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
13
13
|
- _underscore/Model/Core/Page.php
|
|
14
|
+
- _underscore/Model/Client/TrackingNumber.php
|
|
14
15
|
- dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql
|
|
15
16
|
- dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql
|
|
16
17
|
---
|
|
@@ -112,10 +113,11 @@ Worked example: the reprint API `GET /v2/tracking-numbers/reprint`
|
|
|
112
113
|
Client-DB `AclRecordScripts` grant (migrations in `dbchanges2`) before it stopped 403ing — see the
|
|
113
114
|
carrier-shipping-labels feature doc.
|
|
114
115
|
|
|
115
|
-
## Add a writable field to a V2 record (
|
|
116
|
+
## Add a writable field to a V2 record (FOUR requirements)
|
|
116
117
|
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
Making a field persisted **and** API-writable on an existing V2 record takes **four** things —
|
|
119
|
+
three migration files plus a code change in the generated model. Miss any one and the API
|
|
120
|
+
rejects the write with a **specific code** (see the
|
|
119
121
|
[V2 API error codes](../../api2/features/v2-api-error-codes.md) map):
|
|
120
122
|
|
|
121
123
|
1. **Core `RecordFields` registration** (`dbchanges2/Core/`) — register the field name + type
|
|
@@ -130,11 +132,30 @@ Adding a persisted, API-writable field to an existing V2 record is a **three-fil
|
|
|
130
132
|
writable sibling field** on the same record — pick a sibling that actually has explicit
|
|
131
133
|
`AclFieldPermissions` rows (e.g. on `TrackingNumbers`, `containsBattery` has them, whereas
|
|
132
134
|
`requiresSignature` had none), and resolve `roleId` by subselect.
|
|
135
|
+
4. **Generated model-class declaration** (`_underscore/Model/Client/<Name>.php`) — the field must
|
|
136
|
+
be declared on the **generated** PHP model class, e.g.
|
|
137
|
+
`public $needsReturnLabel = self::FIELD_BOOLEAN;`. **Without it the API returns `EO-1`**
|
|
138
|
+
("There is no field called 'X' in the '_Model_Client_Y' model"). This is a **code**
|
|
139
|
+
requirement, not a migration, so it is the one most easily missed when the DB side is already
|
|
140
|
+
complete — see the branch-mismatch gotcha below.
|
|
133
141
|
|
|
134
142
|
Worked example (TRUE-79191, `TrackingNumbers.signatureType`): `Core/... - TrackingNumberSignatureTypeRecordField.sql`
|
|
135
143
|
(RecordField, `STRING`), `Client/... - TrackingNumberSignatureType.sql` (guarded `ALTER TABLE`),
|
|
136
|
-
`Client/... - TrackingNumberSignatureTypeFieldPermission.sql` (cloned `containsBattery`'s grants)
|
|
137
|
-
|
|
144
|
+
`Client/... - TrackingNumberSignatureTypeFieldPermission.sql` (cloned `containsBattery`'s grants),
|
|
145
|
+
plus the `signatureType` declaration on `_underscore/Model/Client/TrackingNumber.php`. This
|
|
146
|
+
mirrors the earlier `needsReturnLabel` / `returnAddressId` sets.
|
|
147
|
+
|
|
148
|
+
**⚠ EO-1 is usually a cross-repo git BRANCH MISMATCH, not a DB gap.** An `EO-1` "no field called
|
|
149
|
+
'X' in the model" with the DB column + `Core.RecordFields` both present means the field isn't in
|
|
150
|
+
the generated model class the running `_underscore` is loading — most often because the repos are
|
|
151
|
+
on **different branches** (`_underscore` on a branch whose generated `Model/Client/<Name>.php`
|
|
152
|
+
predates the field the DB already has). **Fix:** put all the feature's repos (`_underscore`,
|
|
153
|
+
`api2`, `dbchanges2`, `toga2-supply`) on the **same** feature branch before chasing migrations.
|
|
154
|
+
On a local dev box with PHP opcache disabled, a branch switch takes effect immediately (files are
|
|
155
|
+
read fresh per request) — no Apache restart needed. Concrete case: `TrackingNumbers.needsReturnLabel`
|
|
156
|
+
(migrations `2026-07-02b` Client column + Core RecordField, `2026-07-02c` client AclFieldPermission)
|
|
157
|
+
threw EO-1 purely because `_underscore` was on a branch whose generated `TrackingNumber.php` lacked
|
|
158
|
+
the field.
|
|
138
159
|
|
|
139
160
|
## Checklist (so the chain is never half-built)
|
|
140
161
|
|
|
@@ -144,9 +165,18 @@ This mirrors the earlier `needsReturnLabel` / `returnAddressId` 3-file sets.
|
|
|
144
165
|
- [ ] `AclLogicGroupExpressions` row binding the group to the expression.
|
|
145
166
|
- [ ] `AclFieldPermissions` rows for every field that must be readable/writable.
|
|
146
167
|
- [ ] For `aclDatabase = 'CLIENT'` records: rows go in client DB(s); resolve `roleId` by subselect.
|
|
168
|
+
- [ ] The field is declared on the **generated** `_underscore/Model/Client/<Name>.php` (else `EO-1`),
|
|
169
|
+
and every repo is on the **same branch** so the generated model matches the DB.
|
|
147
170
|
|
|
148
171
|
## Change history
|
|
149
172
|
|
|
173
|
+
- **2026-07-21** — TRUE-79191: promoted the writable-field recipe from **3-file** to **FOUR
|
|
174
|
+
requirements** — added the **generated model-class declaration** (`_underscore/Model/Client/<Name>.php`,
|
|
175
|
+
e.g. `public $needsReturnLabel = self::FIELD_BOOLEAN;`) as the 4th, whose absence returns **`EO-1`**
|
|
176
|
+
("no field called 'X' in the '_Model_Client_Y' model"). Documented that EO-1 with a fully migrated DB
|
|
177
|
+
is usually a **cross-repo git branch mismatch** (`_underscore` on a branch whose generated model
|
|
178
|
+
predates the field) — fix is to align all fulfill/ship repos on one branch (opcache-disabled dev
|
|
179
|
+
picks up the switch immediately). Concrete: `TrackingNumbers.needsReturnLabel`. (mhammontree)
|
|
150
180
|
- **2026-07-17** — TRUE-79191: added the **"add a writable field to a V2 record" 3-file migration**
|
|
151
181
|
recipe (Core `RecordFields` → `EV-8` if missing; guarded Client column; Client `AclFieldPermissions`
|
|
152
182
|
→ `EV-9` if missing, cloned from a writable sibling like `containsBattery`), worked example
|
|
@@ -12,6 +12,6 @@
|
|
|
12
12
|
| [TableView row-filtering via apiWhereClause (options.where grammar, end to end)](features/tableview-apiwhereclause-row-filtering.md) | `TableViews.apiWhereClause` (TEXT, nullable) is the sanctioned, code-free way to restrict or exclude rows from a 2.0 table view. | api2/Component/Api/V2/V2.php, _underscore/Model/Client/TableView.php, toga2-supply/src/api/toga.ts |
|
|
13
13
|
| [TableView field/column metadata (TableViewFields, hidden projected columns)](features/tableview-field-metadata.md) | The columns of a 2.0 table view are defined by DB metadata, not code. | _underscore/Model/Client/TableView.php, api2/Component/Api/V2/V2.php, dbchanges2/Client/2026-07-20 - ItemsUuidForPurchaseOrderItemsTableView.sql |
|
|
14
14
|
| [Tickets API (/v2/tickets)](features/tickets-api.md) | The generic ticket endpoint of the 2.0 REST API. | Component/Api/V2/V2.php |
|
|
15
|
-
| [V2 API error/message codes (EV/EZ troubleshooting map)](features/v2-api-error-codes.md) | The V2 JSON engine (`Component/Api/V2/V2.php`) returns short **message codes** in the response `error` field, grouped by family: `EN-*` authentication, `EZ-*` a | api2/Component/Api/V2/V2.php |
|
|
15
|
+
| [V2 API error/message codes (EV/EZ troubleshooting map)](features/v2-api-error-codes.md) | The V2 JSON engine (`Component/Api/V2/V2.php`) returns short **message codes** in the response `error` field, grouped by family: `EN-*` authentication, `EZ-*` a | api2/Component/Api/V2/V2.php, _underscore/Model/Client/TrackingNumber.php |
|
|
16
16
|
| [AWS CodePipeline Deployment via CodeConnections (GitHub → Elastic Beanstalk)](workflows/codepipeline-codeconnections-deploy.md) | 2.0 apps (`api2`, `_underscore`) are deployed through **AWS CodePipeline**. | |
|
|
17
17
|
| [New Environment Configuration & Provisioning (api2)](workflows/environment-configuration-and-provisioning.md) | What it takes for a 2.0 API environment (e.g. | api2/Config/<environment>.ini, api2/Controller/Index.php, dbchanges2/Core/2026-06-16a - DatabaseHosts for new QA QC stage demo environments.sql, dbchanges2/Logs/, _underscore/Route.php |
|
|
@@ -6,10 +6,11 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-21
|
|
10
10
|
owners: [mhammontree]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
13
|
+
- _underscore/Model/Client/TrackingNumber.php
|
|
13
14
|
related:
|
|
14
15
|
- ../../_underscore/features/acl-permission-chain.md
|
|
15
16
|
- scripted-api-post-body-args.md
|
|
@@ -33,6 +34,7 @@ migration instead of re-deriving it. All field/script ACL rows live in the **CLI
|
|
|
33
34
|
| **EV-6** | Resource / route not found | A **scripted-API** route segment is being treated as a record uuid because no Record Script matches — i.e. the `Core.RecordScripts` route row is missing | Register the `Core.RecordScripts` row (`recordId`, `method`, `route`, `phpMethod`) |
|
|
34
35
|
| **EV-8** | Request field does not exist | A field sent in the payload isn't registered as a **`Core.RecordFields`** row for that record | Register the field in `Core.RecordFields` (see the "add a field to a V2 record" recipe in the ACL doc) |
|
|
35
36
|
| **EV-9** | No permission to write field | The field exists but the caller's role has no **`AclFieldPermissions`** grant (`isWritable=1`) in the CLIENT DB | Add the `AclFieldPermissions` row for the role (clone a writable sibling field's grant) |
|
|
37
|
+
| **EO-1** | Operation failed — identifier "There is no field called 'X' in the '_Model_Client_Y' model" | The DB column **and** `Core.RecordFields` exist, but the **generated model class** `_underscore/Model/Client/<Name>.php` doesn't declare the field. This is the **4th** requirement beyond the 3-file migration — and most often it's a **cross-repo git branch mismatch** (`_underscore` on a branch whose generated model lacks a field the DB/RecordFields already carry) | Declare the field in the generated model class (`public $field = self::FIELD_*`) and put all related repos (`_underscore`, `api2`, `dbchanges2`, `toga2-supply`) on the **same** feature branch — see the ACL doc's writable-field recipe |
|
|
36
38
|
| **EZ-1** | Unauthorized record/script dispatch | Missing **`AclRecordScripts`** (scripted APIs) or the **`AclRecordPermissions`** four-table chain (records) for the caller's role | Grant `AclRecordScripts` (scripts) or complete the record-CRUD chain |
|
|
37
39
|
| **EV-5** | Duplicate `transactionId` | The globally-unique `transactionId` was reused | Send a fresh unique `transactionId` per request |
|
|
38
40
|
| **EV-12** | Record's parent not synced | (Fulfill & Ship) POST `/item-fulfillments` when the Sales Order isn't synced into Toga yet | `GET /sales-orders/syncNetsuiteSalesOrder?netsuiteInternalSalesOrderId=<id>` first |
|
|
@@ -48,6 +50,13 @@ migration instead of re-deriving it. All field/script ACL rows live in the **CLI
|
|
|
48
50
|
3. **EV-6 vs EZ-1** are the two halves of exposing a scripted API: EV-6 = no `Core.RecordScripts`
|
|
49
51
|
route (the segment falls through to record lookup); EZ-1 = the route exists but there's no
|
|
50
52
|
`AclRecordScripts` dispatch grant for the caller's role.
|
|
53
|
+
4. **EO-1 ("no field called 'X' in the model") is NOT a DB problem.** EV-8 means the Core
|
|
54
|
+
`RecordFields` registration is missing; EO-1 means the DB column and RecordFields are both
|
|
55
|
+
present but the **generated PHP model class** lacks the field. Before touching migrations,
|
|
56
|
+
confirm every fulfill/ship repo is on the **same branch** — a mismatched `_underscore`
|
|
57
|
+
branch (whose generated `Model/Client/<Name>.php` predates the field) reproduces EO-1 with the
|
|
58
|
+
DB fully migrated. On a local dev box with PHP opcache disabled a branch switch takes effect
|
|
59
|
+
immediately (files are read fresh per request) — no Apache restart needed.
|
|
51
60
|
|
|
52
61
|
## Related recipes
|
|
53
62
|
|
|
@@ -62,5 +71,9 @@ migration instead of re-deriving it. All field/script ACL rows live in the **CLI
|
|
|
62
71
|
discovered wiring the Signature Type field and the `addresses/validate` scripted route on Fulfill
|
|
63
72
|
& Ship — each code mapped to the missing migration (RecordField / AclFieldPermissions /
|
|
64
73
|
RecordScripts / AclRecordScripts) and the resolve-role-by-sibling technique. (mhammontree)
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
- 2026-07-21 — TRUE-79191: added **EO-1** ("There is no field called 'X' in the '_Model_Client_Y'
|
|
75
|
+
model") to the map — the DB column + `Core.RecordFields` are present but the **generated model
|
|
76
|
+
class** `_underscore/Model/Client/<Name>.php` doesn't declare the field (the 4th requirement
|
|
77
|
+
beyond the 3-file migration), most often a **cross-repo git branch mismatch**; fix is to align
|
|
78
|
+
all fulfill/ship repos on one branch. Concrete: `TrackingNumbers.needsReturnLabel`. Also fixed
|
|
79
|
+
stray tag text at end of file. (mhammontree)
|
|
@@ -9,3 +9,4 @@
|
|
|
9
9
|
| [Primary Table templates (server/client, sizing, virtualization)](features/primary-table-templates.md) | `src/templates/PrimaryTable/` is the **production, wired-up table** built on the [Table component](table.md). | toga-blox/src/templates/PrimaryTable/PrimaryTable.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableServerTemplate.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableClientTemplate.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableHeaderCell.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableBodyCell.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableRow.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableExpandableRow.tsx, toga-blox/src/templates/PrimaryTable/types.ts |
|
|
10
10
|
| [TableRecordModal (record-detail modal shell)](features/table-record-modal.md) | `TableRecordModal` is a **generic, presentational modal shell** for showing a single table record (row) in detail — typically opened from a table row click. | toga-blox/src/components/TableRecordModal/TableRecordModal.tsx, toga-blox/src/components/TableRecordModal/index.ts, toga-blox/src/components/TableRecordModal/tableRecordModal.module.css |
|
|
11
11
|
| [Table component (cells, action cells, filters & sorts, hooks, theming)](features/table.md) | The `Table` component (`src/components/Table/`) is the **TanStack Table v8** building block behind the [Primary Table templates](primary-table-templates.md). | toga-blox/src/components/Table/index.ts, toga-blox/src/components/Table/types.ts, toga-blox/src/components/Table/utils/buildTanstackColumns.tsx, toga-blox/src/components/Table/utils/resolveCellType.tsx, toga-blox/src/components/Table/components/cellTypes, toga-blox/src/components/Table/components/actionCells, toga-blox/src/components/Table/components/columnFiltersAndSorts, toga-blox/src/components/Table/hooks, toga-blox/src/components/Table/themeConfig |
|
|
12
|
+
| [Dynamic npm Publish Pipeline (branch → channel)](workflows/dynamic-publish-pipeline.md) | How `@agilant/toga-blox` (checkout folder `toga-blox-npm`, registry repo key `toga-blox`) publishes a per-environment npm **channel** (dist-tag) from a `_<mode> | toga-blox/.github/workflows/publish.yml, toga-blox/src/utils/getFontAwesomeIcon.tsx |
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Dynamic npm Publish Pipeline (branch → channel)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga-blox
|
|
5
|
+
project: TOGa Blox
|
|
6
|
+
client: shared
|
|
7
|
+
type: workflow
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-21
|
|
10
|
+
owners: [jcardinal]
|
|
11
|
+
files:
|
|
12
|
+
- toga-blox/.github/workflows/publish.yml
|
|
13
|
+
- toga-blox/src/utils/getFontAwesomeIcon.tsx
|
|
14
|
+
related:
|
|
15
|
+
- ../architecture.md
|
|
16
|
+
- ../../toga2-commerce/workflows/amplify-build-and-deploy.md
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## What it is
|
|
20
|
+
|
|
21
|
+
How `@agilant/toga-blox` (checkout folder `toga-blox-npm`, registry repo key `toga-blox`)
|
|
22
|
+
publishes a per-environment npm **channel** (dist-tag) from a `_<mode>` release branch. As of
|
|
23
|
+
2026-07-21 the `publish.yml` workflow is **fully dynamic** — there is no per-branch hardcoded
|
|
24
|
+
logic. Use this doc when adding a new environment channel, debugging a channel that won't
|
|
25
|
+
resolve in a consuming app's build, or touching `publish.yml`.
|
|
26
|
+
|
|
27
|
+
## How it works
|
|
28
|
+
|
|
29
|
+
### Branch → channel (1:1, load-bearing)
|
|
30
|
+
|
|
31
|
+
- Trigger is `on.push.branches: ['_*']` — **any** underscore-prefixed branch publishes.
|
|
32
|
+
- The npm dist-tag (channel) is derived by stripping the one leading underscore from the
|
|
33
|
+
branch name: pushing `_sandbox-client` publishes `@agilant/toga-blox@sandbox-client`.
|
|
34
|
+
Branch mode == channel, strictly one-to-one.
|
|
35
|
+
- **Every** branch (including `_production`) publishes a **prerelease** version
|
|
36
|
+
`X.Y.Z-<mode>.<run>` under dist-tag `<mode>`. There is no longer a production special case.
|
|
37
|
+
Verified live: `production:1.0.315-production.96`, `sandbox-client:1.0.315-sandbox-client.97`.
|
|
38
|
+
|
|
39
|
+
### What was removed (and why it's safe)
|
|
40
|
+
|
|
41
|
+
- The old workflow hardcoded a branch list (`_production/_gamma/_beta/_qc-security`) with a
|
|
42
|
+
production special case: `_production` published dist-tag `latest` with a plain (non-prerelease)
|
|
43
|
+
version, committed the version bump back, and git-tagged. **All of that is gone.**
|
|
44
|
+
- **Consequence:** the workflow no longer maintains the `@latest` npm tag. This is fine because
|
|
45
|
+
consumers never install bare `npm install @agilant/toga-blox`; they install by explicit
|
|
46
|
+
`@<mode>` channel (commerce's `amplify.yml`) or by a pinned/ranged version in `package.json`.
|
|
47
|
+
|
|
48
|
+
### The cross-repo publish-then-build model (key mental model)
|
|
49
|
+
|
|
50
|
+
A GitHub branch and an npm channel are **unrelated**. Creating a `_<mode>` branch in a *consuming
|
|
51
|
+
app* repo (e.g. `toga2-commerce`) only tells Amplify what to build — it does **not** create a
|
|
52
|
+
blox channel. A channel exists **only** when this repo (`toga-blox-npm`) publishes with
|
|
53
|
+
`npm publish --tag <mode>`, which `publish.yml` now does automatically for any `_<mode>` branch.
|
|
54
|
+
|
|
55
|
+
**Correct order to stand up a new environment:**
|
|
56
|
+
1. Create + push `_<mode>` in `toga-blox-npm` → channel publishes. Verify with
|
|
57
|
+
`npm view @agilant/toga-blox dist-tags`.
|
|
58
|
+
2. Then the app's `_<mode>` Amplify build succeeds, because `npm install @agilant/toga-blox@<mode>`
|
|
59
|
+
resolves.
|
|
60
|
+
|
|
61
|
+
This publish-before-build ordering is the ongoing cost of the strict 1:1 model: every
|
|
62
|
+
environment needs its own maintained `_<mode>` branch in `toga-blox-npm`.
|
|
63
|
+
|
|
64
|
+
## Gotchas
|
|
65
|
+
|
|
66
|
+
- **Publish before build.** `npm install @agilant/toga-blox@<mode>` fails `ETARGET No matching
|
|
67
|
+
version` in the app build until this repo has published that channel. GitHub Actions runs the
|
|
68
|
+
workflow version *on the pushed branch*, so the branch must carry the current `publish.yml`.
|
|
69
|
+
- **FontAwesome v7 `style`-prop TS2322** (fixed 2026-07-21 in `getFontAwesomeIcon.tsx`, and a
|
|
70
|
+
reusable pattern for any consumer). FA v7 types the `FontAwesomeIcon` `style` prop as
|
|
71
|
+
`CSSProperties & CSSVariables`, where `CSSVariables` requires a `--fa-*` custom-property index
|
|
72
|
+
signature that plain `React.CSSProperties` lacks. Cast the passed style to
|
|
73
|
+
`React.CSSProperties & Record<\`--${string}\`, string>` — the value type must be `string`
|
|
74
|
+
(**not** `string | number`; `CSSVariables` requires `string`). Runtime and function signature
|
|
75
|
+
are unchanged, so it is safe for the ~40 consumers.
|
|
76
|
+
- **Secret hygiene done right.** `publish.yml` writes a transient `.npmrc` at build time from
|
|
77
|
+
GitHub secrets (`${NODE_AUTH_TOKEN}` / FontAwesome token) — it never commits tokens. This is
|
|
78
|
+
the pattern the consuming apps should follow (see the commerce workflow's secret-hygiene note).
|
|
79
|
+
|
|
80
|
+
## Change history
|
|
81
|
+
- 2026-07-21 — Made `publish.yml` fully dynamic: trigger `_*`, branch→dist-tag by stripping the
|
|
82
|
+
leading underscore (mode == channel 1:1), every branch (incl. `_production`) publishes a
|
|
83
|
+
`X.Y.Z-<mode>.<run>` prerelease; removed the `_production`→`latest` special case (workflow no
|
|
84
|
+
longer maintains `@latest`); resolved a stash/merge conflict by keeping the dynamic path and
|
|
85
|
+
dropping the redundant hardcoded `_qc-security` case. Fixed `getFontAwesomeIcon.tsx` FA v7
|
|
86
|
+
TS2322. Documented the cross-repo publish-then-build model. (jcardinal)
|
|
87
|
+
</content>
|
|
88
|
+
</invoke>
|
|
@@ -6,7 +6,7 @@ project: TOGa Commerce
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-07-21
|
|
10
10
|
owners: ["jcardinal"]
|
|
11
11
|
files:
|
|
12
12
|
- toga2-commerce/amplify.yml
|
|
@@ -15,50 +15,52 @@ files:
|
|
|
15
15
|
- toga2-commerce/.github/workflows/sync-stage-environments.yml
|
|
16
16
|
related:
|
|
17
17
|
- ../../toga2-supply/workflows/amplify-build-and-deploy.md
|
|
18
|
+
- ../../toga-blox/workflows/dynamic-publish-pipeline.md
|
|
18
19
|
---
|
|
19
20
|
|
|
20
21
|
## Summary
|
|
21
22
|
|
|
22
23
|
How `toga2-commerce` (React + Vite, "commerce2-react") builds and deploys on **AWS Amplify**.
|
|
23
|
-
Same branch-per-environment model as `
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
24
|
+
Same single-shared-`amplify.yml`, branch-per-environment model as `toga25-supply` (mode derived
|
|
25
|
+
from `AWS_BRANCH`), but with commerce-specific build constraints preserved: it **dynamically
|
|
26
|
+
installs a per-environment `@agilant/toga-blox` channel** (channel == mode, strict 1:1), needs a
|
|
27
|
+
larger heap, and pulls FontAwesome Pro. As of 2026-07-21 the build is **inline and fully dynamic**
|
|
28
|
+
(no per-branch `case`, no `npm run <script>`), so one DRY spec serves all ~21 environments.
|
|
29
|
+
|
|
30
|
+
## Deployment model — one dynamic `amplify.yml` for every branch
|
|
31
|
+
|
|
32
|
+
- Branches are **underscore-prefixed** `_<mode>`; the Vite mode is the same name **without** the
|
|
33
|
+
underscore, derived at build time from `$AWS_BRANCH`. **Fails loudly** if the branch lacks the
|
|
34
|
+
leading underscore or `.env.<mode>` is missing — a misconfigured branch can never silently ship
|
|
35
|
+
as production. Single-line commands, LF only (`.gitattributes` pins `*.yml` to `eol=lf`).
|
|
36
|
+
- The build is **inline in `amplify.yml`**, NOT `npm run <script>`, so the per-mode `package.json`
|
|
37
|
+
build scripts are untouched. Steps: dynamic `npm install "@agilant/toga-blox@$MODE"
|
|
38
|
+
--legacy-peer-deps`, FontAwesome Pro install, `npm install` (not `npm ci`) `--legacy-peer-deps`,
|
|
39
|
+
`npx tsc -b` (tsconfig is composite / project references), then `npx vite build --mode "$MODE"`.
|
|
40
|
+
`node_modules` is cached.
|
|
41
|
+
- **`NODE_OPTIONS="--max-old-space-size=8192"` must be re-exported in the `build` phase**, not
|
|
42
|
+
just `preBuild` — the memory-heavy Rollup step runs in `build`, and Amplify may use a separate
|
|
43
|
+
shell per phase, so the default heap OOMs otherwise.
|
|
38
44
|
- `sync-stage-environments.yml` fans `_stage` → `_qc-security` on push.
|
|
39
45
|
|
|
40
46
|
## blox per-environment channel + the publish chain
|
|
41
47
|
|
|
42
|
-
Unlike supply (which pins blox via a normal dependency),
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
Unlike supply (which pins blox via a normal dependency), commerce installs a **blox dist-tag
|
|
49
|
+
matching the mode**: `npm install "@agilant/toga-blox@$MODE" --legacy-peer-deps` — no hardcoded
|
|
50
|
+
channel list, resolved from `$MODE` at build time.
|
|
45
51
|
|
|
46
|
-
Those
|
|
47
|
-
`.github/workflows/publish.yml`, which
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
branch, and let it publish; (2) reference `@agilant/toga-blox@<channel>` in the app's build script.
|
|
52
|
+
Those channels are produced by the **`toga-blox-npm`** repo's now-fully-dynamic
|
|
53
|
+
`.github/workflows/publish.yml`, which publishes `@agilant/toga-blox@<mode>` for any pushed
|
|
54
|
+
`_<mode>` branch (branch → dist-tag, underscore stripped, 1:1). See the
|
|
55
|
+
[toga-blox dynamic publish pipeline](../../toga-blox/workflows/dynamic-publish-pipeline.md) for the
|
|
56
|
+
full cross-repo publish-then-build model. **The channel must exist before the app build runs.**
|
|
52
57
|
|
|
53
|
-
## Steps (to stand up a new environment
|
|
58
|
+
## Steps (to stand up a new environment)
|
|
54
59
|
|
|
55
|
-
1. In `toga-blox-npm`:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
2. In `toga2-commerce`: add a `build<Env>` script (blox `@<channel>` install + `--legacy-peer-deps`
|
|
60
|
-
+ `tsc` + `vite build --mode <mode>`); add the `case` arm in `amplify.yml`; commit `.env.<mode>`.
|
|
61
|
-
3. Connect the Amplify branch; push; watch for the `Branch '...' -> Vite mode '...'` echo.
|
|
60
|
+
1. In `toga-blox-npm`: create + push `_<mode>` → `publish.yml` publishes `@agilant/toga-blox@<mode>`
|
|
61
|
+
automatically. Verify: `npm view @agilant/toga-blox dist-tags`.
|
|
62
|
+
2. In `toga2-commerce`: commit `.env.<mode>` (public base URLs only, no secrets).
|
|
63
|
+
3. Connect the Amplify branch `_<mode>`; push; the inline spec derives the mode and builds.
|
|
62
64
|
|
|
63
65
|
## Systems involved
|
|
64
66
|
|
|
@@ -83,12 +85,22 @@ branch, and let it publish; (2) reference `@agilant/toga-blox@<channel>` in the
|
|
|
83
85
|
peers: `@tanstack/react-table@^8`, `react-table@^7.8.0`, `react-table-sticky@^1.1.3`. Check
|
|
84
86
|
`npm view @agilant/toga-blox@<tag> peerDependencies` against commerce deps when a resolve error
|
|
85
87
|
appears.
|
|
86
|
-
- **Secret hygiene:** commerce
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
- **Secret hygiene (open remediation):** `toga2-commerce/.npmrc` is **git-tracked** — it is listed
|
|
89
|
+
in `.gitignore` but was committed before that, so git still tracks it, and it holds a live
|
|
90
|
+
FontAwesome registry token and a live npm auth token in plaintext. Remediation: (1) **rotate both
|
|
91
|
+
tokens** (treat as compromised); (2) `git rm --cached .npmrc`; (3) move the tokens to Amplify
|
|
92
|
+
environment variables and write a **transient `.npmrc` at build time** from
|
|
93
|
+
`$FONTAWESOME_NPM_AUTH_TOKEN` — exactly what blox's `publish.yml` already does. Never document the
|
|
94
|
+
token values.
|
|
89
95
|
|
|
90
96
|
## Change history
|
|
91
97
|
|
|
98
|
+
- 2026-07-21 — Reworked `amplify.yml` to a **fully dynamic inline build** (no per-branch `case`, no
|
|
99
|
+
`npm run <script>`): derive mode from `$AWS_BRANCH`, dynamic `npm install
|
|
100
|
+
"@agilant/toga-blox@$MODE"`, `npx tsc -b` (composite), `npx vite build --mode "$MODE"`. Re-export
|
|
101
|
+
`NODE_OPTIONS=--max-old-space-size=8192` in the **build** phase (Rollup OOM). Publish chain now
|
|
102
|
+
dynamic (any `_<mode>` branch in toga-blox-npm). Flagged the git-tracked `.npmrc` live-token issue
|
|
103
|
+
with rotate/untrack/transient-`.npmrc` remediation. (jcardinal)
|
|
92
104
|
- 2026-06-18 — Initial Amplify setup: added `amplify.yml` (8192 heap, underscore-strip, fail-loud,
|
|
93
105
|
case→npm script), `.gitattributes` (LF), `sync-stage-environments.yml`; added `buildQcSecurity`
|
|
94
106
|
with `@qc-security` blox channel + `--legacy-peer-deps` (and same flag on alpha/beta/gamma);
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
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
|
-
| [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 | 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 |
|
|
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 | toga2-supply/src/components/ui/Tables/BasicTable/BasicTable.tsx, toga2-supply/src/pages/ShipmentItems/view/forms/ShipmentItemsTable.tsx, 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 |
|
|
@@ -6,9 +6,12 @@ project: TOGa Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-21
|
|
10
10
|
owners: [mhammontree]
|
|
11
11
|
files:
|
|
12
|
+
- toga2-supply/src/components/ui/Tables/BasicTable/BasicTable.tsx
|
|
13
|
+
- toga2-supply/src/pages/ShipmentItems/view/forms/ShipmentItemsTable.tsx
|
|
14
|
+
- toga2-supply/src/pages/EditShipment/view/components/forms/EditShipmentForm.tsx
|
|
12
15
|
- toga2-supply/src/components/ui/BaseInput/UnitSelect.tsx
|
|
13
16
|
- toga2-supply/src/pages/EditShipment/view/modals/SerialNumbersModal.tsx
|
|
14
17
|
- toga2-supply/src/pages/ShipmentItems/view/ShipmentItemsPage.tsx
|
|
@@ -123,6 +126,10 @@ success/failure.
|
|
|
123
126
|
`shipToAddressUuid` / `returnAddressUuid`.
|
|
124
127
|
- **Decision:** a confirmation dialog was added even though the Pending-card trash deletes
|
|
125
128
|
**without** one, because this is a large, explicit, destructive button.
|
|
129
|
+
- **Destructive-red confirm button (2026-07-21).** `SupplyFormGuardrail` defaults
|
|
130
|
+
`rightButtonClasses` to the primary blue (`#3F5DCA`). The delete-shipment confirm passes a
|
|
131
|
+
**crimson-700 (`#D60027`)** destructive style to signal the permanent-delete impact. Crimson-700 is
|
|
132
|
+
the app's destructive red; there is **no** crimson-800 token, so the hover uses a darker hex directly.
|
|
126
133
|
|
|
127
134
|
## Update Info / Edit Shipment form (config-driven; Figma-alignment pass 2026-07)
|
|
128
135
|
|
|
@@ -216,6 +223,22 @@ bare string** — see the gotcha below before wiring a new select field.
|
|
|
216
223
|
12px check glyph. This is the **shared** checkbox, so the change applies **app-wide**, not just to
|
|
217
224
|
the shipment forms — audit other consumers when touching it.
|
|
218
225
|
|
|
226
|
+
### Mint-green "dirty field" border on the fulfill/ship forms (2026-07-21, Figma node 3554-5206)
|
|
227
|
+
|
|
228
|
+
A field changed from its saved value shows a **mint-600 (`#2CB224`) border**; it clears on save
|
|
229
|
+
(Update Info navigates away, the return modal closes). Implemented in `BaseInput.tsx` +
|
|
230
|
+
`renderEditShipmentFormInput.tsx`:
|
|
231
|
+
- **Opt-in only.** The mint styling is gated behind the existing **`hasDirtyValidation`** prop, so it
|
|
232
|
+
activates only on forms that pass it (enabled here via `renderEditShipmentFormInput`) — no app-wide
|
|
233
|
+
spread onto every `BaseInput`.
|
|
234
|
+
- **Dirty detection keys off `input.valueKey`.** `mint-600` = `#2CB224` in `tailwind.config.cjs`,
|
|
235
|
+
matching the Figma token.
|
|
236
|
+
- **⚠ `dirtyFields` lookup must use the field's real key.** `BaseInput`'s dirty check was looking up
|
|
237
|
+
`dirtyFields["undefined.message"]` (always `undefined`) for uuid-less fields, so **no** field ever
|
|
238
|
+
showed the mint border. Fix: key `isFieldDirty` off `input.valueKey`. Also — programmatic `setValue`
|
|
239
|
+
for a user-meaningful prefill (e.g. the warehouse-address prefill) must pass `{ shouldDirty: true }`
|
|
240
|
+
or RHF reports the field clean and the border never appears (see the RHF `isDirty` gotcha below).
|
|
241
|
+
|
|
219
242
|
## Signature Type — 4-level enum (2026-07)
|
|
220
243
|
|
|
221
244
|
Signature moved from a **boolean `requiresSignature`** to a **4-level `signatureType` enum**:
|
|
@@ -472,6 +495,37 @@ the call 403s and returns no label.
|
|
|
472
495
|
remove/relabel the nav link; (B) add a global "All Pending Shipments" view — drop the SO filter
|
|
473
496
|
(client scoping is automatic from the JWT), but each row must then carry its own SO `internalId`
|
|
474
497
|
for the edit/fulfill links.
|
|
498
|
+
- **⚠ `BasicTable` headers misalign from their column values unless you pass a HORIZONTAL
|
|
499
|
+
`headerCellPadding` (recurring).** `BasicTable` renders through daisyUI's `table` class, whose base
|
|
500
|
+
`:where(th,td)` applies ~16px horizontal cell padding. Body cells override it with
|
|
501
|
+
`cellPadding="px-2"` (8px), but headers by default override only the **vertical** padding — so header
|
|
502
|
+
text sits 8px off from the column values (and the select-all checkbox misaligns with the row
|
|
503
|
+
checkboxes). **Always pass a horizontal `headerCellPadding` (e.g. `"px-2 py-3"`) that matches
|
|
504
|
+
`cellPadding`.** Two related items fixed alongside: (1) a **sticky** header showed a doubled seam
|
|
505
|
+
before scroll (a real CSS `border` **plus** a box-shadow) — use a **box-shadow-only** seam for sticky
|
|
506
|
+
headers and add a left inset seam for the first column; column dividers `2px`→`1px` to match Figma;
|
|
507
|
+
(2) a cell that **double-pads** (e.g. a serial cell carrying its own `px-2`) must drop the extra
|
|
508
|
+
padding so it aligns at the same 8px gutter. Files: `components/ui/Tables/BasicTable/BasicTable.tsx`,
|
|
509
|
+
`ShipmentItems/view/forms/ShipmentItemsTable.tsx`, `EditShipment/view/components/SelectedShipmentItemsTable.tsx`.
|
|
510
|
+
- **⚠ React Hook Form `formState.isDirty` read ONLY inside an event handler is stale (`false`).**
|
|
511
|
+
`formState` is a **Proxy** that subscribes to a key only if you **read it during render**. Reading
|
|
512
|
+
`formState.isDirty` exclusively inside a handler (e.g. a modal close/backdrop handler) returns a
|
|
513
|
+
stale `false`, so a backdrop click closed the return modal without the save/discard guardrail firing.
|
|
514
|
+
**Fix: destructure/read `isDirty` during render** and use that value in the handler. Corollary: a
|
|
515
|
+
programmatic `setValue` for a user-meaningful prefill must pass `{ shouldDirty: true }` or the form
|
|
516
|
+
still reads "clean" (also affects the mint dirty-border above). File:
|
|
517
|
+
`EditShipment/view/modals/ReturnShippingModal.tsx`.
|
|
518
|
+
- **⚠ api2 V2 field SELECTORS are dot-paths of REAL DB column names — a wrong name silently returns
|
|
519
|
+
nothing.** V2 responses select fields by dot-path (e.g.
|
|
520
|
+
`"itemFulfillmentItems.salesOrderItem.item.skuNumber"`). The real `Items` columns are **`skuNumber`
|
|
521
|
+
/ `upcCode`**; requesting `item.sku` / `item.upc` (which don't exist) returns **no value, no error**
|
|
522
|
+
— the field just comes back empty. (Manufacturer worked only because `manufacturer.name` is the
|
|
523
|
+
correct relation name.) When a field renders blank, verify the selector matches the actual DB column,
|
|
524
|
+
not a guessed alias, and keep the name consistent across the API request, the response mapping, the
|
|
525
|
+
component read, and the TS types. Concrete fix (SKU/UPC not displaying): standardized on
|
|
526
|
+
`skuNumber`/`upcCode` across `EditShipment/api/UpdateShipmentApi.ts`,
|
|
527
|
+
`view/components/forms/EditShipmentForm.tsx`, `SelectedShipmentItemsTable.tsx`, `types.ts`, and
|
|
528
|
+
`ShipmentItems/view/forms/ShipmentItemsTable.tsx` + `types.ts`.
|
|
475
529
|
|
|
476
530
|
## Remaining work
|
|
477
531
|
|
|
@@ -534,6 +588,16 @@ not the base `_Model_Client_ItemFulfillment`. Tested with GroWrk; UPS support wa
|
|
|
534
588
|
for Compass and is not yet in prod.
|
|
535
589
|
|
|
536
590
|
## Change history
|
|
591
|
+
- 2026-07-21 — TRUE-79191 (toga2-supply UI fixes + Figma polish): documented three durable, reusable
|
|
592
|
+
gotchas — (1) **`BasicTable` header/value misalignment** (daisyUI base `:where(th,td)` ~16px padding;
|
|
593
|
+
always pass a horizontal `headerCellPadding` matching `cellPadding`, box-shadow-only seam for sticky
|
|
594
|
+
headers, drop double-padding on self-padded cells); (2) **RHF `formState.isDirty` is stale when read
|
|
595
|
+
only inside an event handler** (Proxy subscribes on render-time reads only) — read it during render;
|
|
596
|
+
`setValue` prefills need `{ shouldDirty: true }`; (3) **api2 V2 field selectors are dot-paths of real
|
|
597
|
+
DB column names** — `item.sku`/`item.upc` silently returned nothing; the real columns are
|
|
598
|
+
`skuNumber`/`upcCode`. Built the **mint-600 (#2CB224) dirty-field border** (opt-in via
|
|
599
|
+
`hasDirtyValidation`; fixed the `dirtyFields["undefined.message"]` bug by keying off `input.valueKey`)
|
|
600
|
+
and gave the **Delete Shipment** confirm a crimson-700 (#D60027) destructive button. (mhammontree)
|
|
537
601
|
- 2026-07-17 — TRUE-79191 (David design-review pass, toga2-supply UI): documented four durable,
|
|
538
602
|
app-wide component gotchas — (1) **`BaseInput` react-select menus now portal to `<body>`**
|
|
539
603
|
(`menuPortalTarget` + `menuPortal zIndex 9999`) so a sibling scroll box can't clip them, and the
|
|
@@ -5,10 +5,11 @@ project: _Underscore
|
|
|
5
5
|
client: shared
|
|
6
6
|
type: standard
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-07-
|
|
8
|
+
updated: 2026-07-21
|
|
9
9
|
owners: [jcardinal]
|
|
10
10
|
related:
|
|
11
11
|
- ../apps/toga25-supply/workflows/amplify-deployment.md
|
|
12
|
+
- ../apps/toga-blox/workflows/dynamic-publish-pipeline.md
|
|
12
13
|
---
|
|
13
14
|
|
|
14
15
|
## Scope
|
|
@@ -34,3 +35,25 @@ Applies to all 2.0 Vite + React/TS frontends deployed on AWS Amplify.
|
|
|
34
35
|
|
|
35
36
|
## Node
|
|
36
37
|
- Vite 7 requires Node 20.19+ or 22.12+ (Node 22 recommended). Pin via `.nvmrc` if needed.
|
|
38
|
+
|
|
39
|
+
## Per-environment library channel (optional; when an app installs a versioned shared lib)
|
|
40
|
+
|
|
41
|
+
Some 2.0 frontends install a **per-environment channel** of a shared npm library instead of a
|
|
42
|
+
single pinned version (e.g. `toga2-commerce` installs `@agilant/toga-blox@$MODE`, channel == mode,
|
|
43
|
+
strict 1:1). When an app does this:
|
|
44
|
+
|
|
45
|
+
- The library's own release repo must publish the channel **before** the app build runs — a
|
|
46
|
+
`_<mode>` branch in the *app* repo only tells Amplify what to build; it does not create a
|
|
47
|
+
channel. Install-by-channel fails `ETARGET No matching version` until the library has published.
|
|
48
|
+
See `../apps/toga-blox/workflows/dynamic-publish-pipeline.md`.
|
|
49
|
+
- Install the channel dynamically from the derived mode (`npm install "@lib@$MODE"`), not from a
|
|
50
|
+
hardcoded per-branch list, so one `amplify.yml` serves every environment.
|
|
51
|
+
- `--legacy-peer-deps` may be required when the app's and library's peer ranges diverge; when it
|
|
52
|
+
is used, npm will not auto-install the library's peers, so any peer not already in the app's
|
|
53
|
+
dependencies must be added explicitly.
|
|
54
|
+
|
|
55
|
+
## Build memory / heap
|
|
56
|
+
|
|
57
|
+
For memory-heavy Rollup builds, set `NODE_OPTIONS=--max-old-space-size=<MB>` and **re-export it in
|
|
58
|
+
the `build` phase**, not only `preBuild` — Amplify may use a separate shell per phase, so a
|
|
59
|
+
preBuild-only export leaves the Rollup step on the default heap and it OOMs.
|
package/knowledge/INDEX.md
CHANGED
|
@@ -30,7 +30,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
30
30
|
- **ai-bdr** (AI-BDR) — 8 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
|
|
31
31
|
- **toga2-commerce** (TOGa Commerce) — 9 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
|
|
32
32
|
- **toga25-supply** (TOGa 2.5 Supply) — 10 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
|
|
33
|
-
- **toga-blox** (TOGa Blox) —
|
|
33
|
+
- **toga-blox** (TOGa Blox) — 8 doc(s) → [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
|
|
34
34
|
- **bdr** (BDR) — 0 doc(s) → [2.0/apps/bdr/INDEX.md](2.0/apps/bdr/INDEX.md)
|
|
35
35
|
|
|
36
36
|
## standalone framework
|
package/package.json
CHANGED