toga-ai 1.0.113 → 1.0.115

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,3 +4,4 @@
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
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/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/api/UpdateShipmentApi.ts, toga2-supply/src/pages/Shipments/view/components/ShipmentsCardTableForm/ShipmentsCardTableForm.tsx, toga2-supply/src/pages/Shipments/api/ShipmentsApi.ts, _underscore/Model/Client/ItemFulfillment.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Component/Library/Carriers/Ups/Ups.php |
7
+ | [AWS Amplify Build & Deploy (non-prod environments)](workflows/amplify-build-and-deploy.md) | How `toga2-supply` (React + Vite) builds and deploys to **AWS Amplify** for non-prod environments (qc-security, qc-performance, alpha, beta, gamma, …). | toga2-supply/amplify.yml, toga2-supply/package.json, toga2-supply/.env.qc-security |
@@ -0,0 +1,77 @@
1
+ ---
2
+ title: AWS Amplify Build & Deploy (non-prod environments)
3
+ framework: "2.0"
4
+ repo: toga2-supply
5
+ project: TOGa Supply
6
+ client: shared
7
+ type: workflow
8
+ status: active
9
+ updated: 2026-06-17
10
+ owners: ["jcardinal"]
11
+ files:
12
+ - toga2-supply/amplify.yml
13
+ - toga2-supply/package.json
14
+ - toga2-supply/.env.qc-security
15
+ related:
16
+ - ../architecture.md
17
+ ---
18
+
19
+ ## Summary
20
+
21
+ How `toga2-supply` (React + Vite) builds and deploys to **AWS Amplify** for non-prod
22
+ environments (qc-security, qc-performance, alpha, beta, gamma, …). Note that the
23
+ **alpha/beta/gamma/production** environments historically deploy via **GitHub Actions**
24
+ (`.github/workflows/*.yml`) into the `agilantsolutions/toga2-supply-build` repo — Amplify
25
+ is the newer path used when standing up additional non-prod environments. The first Amplify
26
+ build of this app failed with a JavaScript heap OOM; this doc records the fix and the
27
+ scalable per-environment build setup.
28
+
29
+ ## Steps
30
+
31
+ 1. **`amplify.yml` at the repo root drives the build.** When present, it **overrides the
32
+ Amplify console build settings entirely, for every branch of the app** — you cannot split
33
+ "memory in the file, build command in the console." Whichever owns the build phase owns all
34
+ of it. Our `amplify.yml`:
35
+ - Sets `NODE_OPTIONS=--max-old-space-size=4096` in both `preBuild` and `build` (the OOM fix).
36
+ - Runs `npx tsc && npx vite build --mode "$VITE_BUILD_MODE"` — the build mode is **not**
37
+ hardcoded.
38
+ - Publishes `dist/` as the artifact baseDirectory.
39
+ 2. **Per environment, set one Amplify env var on that branch:** `VITE_BUILD_MODE` =
40
+ `qc-security` | `qc-performance` | `alpha` | `beta` | `gamma`. No new npm script per env.
41
+ 3. **Each mode needs a matching `.env.<mode>` file** committed in the repo (Vite loads
42
+ `.env.<mode>` for `--mode <mode>`). e.g. `.env.qc-security` provides `VITE_API`. A new
43
+ environment requires creating its `.env.<mode>` file.
44
+ 4. Commit `amplify.yml` + the env file, push the branch; Amplify rebuilds.
45
+
46
+ ## Systems involved
47
+
48
+ - AWS Amplify (build + hosting), per-branch environment variables.
49
+ - Vite 4 build (`vite build --mode <mode>`), TypeScript `tsc` precompile.
50
+ - Sentry source-map upload via `@sentry/vite-plugin` (source maps are **on** —
51
+ `build.sourcemap: true` in `vite.config.ts`).
52
+
53
+ ## Edge cases & escalation
54
+
55
+ - **OOM root cause:** Amplify gives Node the default ~2 GB old-space heap. This app
56
+ (1793 modules + source maps on) exceeds it during Rollup's `rendering chunks` phase →
57
+ `FATAL ERROR: Ineffective mark-compacts near heap limit`. Fix = raise `NODE_OPTIONS`.
58
+ - **Heap vs. instance RAM:** `--max-old-space-size=4096` only helps if the build container
59
+ has ≥4 GB. Amplify **Standard** compute is 4 GB (tight with source maps on). If it OOMs
60
+ again, switch to the **Large build instance** (Amplify → App settings → Build settings)
61
+ and raise to `6144`/`7168`. Setting the heap above physical RAM makes it worse, not better.
62
+ - **Mode/filename mismatch:** `--mode qc-security` loads `.env.qc-security` exactly — a file
63
+ named `.env.qc-security.beta` is silently ignored (build succeeds but ships with no
64
+ `VITE_API`). Name the env file to match the mode.
65
+ - **Secret hygiene:** `.env.<mode>` files have historically carried a live
66
+ `SENTRY_AUTH_TOKEN`. Prefer rotating it and moving it to an Amplify env var rather than
67
+ committing it.
68
+
69
+ ## Change history
70
+
71
+ - 2026-06-17 — Created on first Amplify setup of a new QC environment: added root `amplify.yml`
72
+ with raised Node heap + parameterized `VITE_BUILD_MODE`; renamed `.env.qc-security.beta` →
73
+ `.env.qc-security` (jcardinal)
74
+
75
+ ## Related docs
76
+
77
+ - [TOGa Supply architecture](../architecture.md)
@@ -7,6 +7,6 @@
7
7
  | [Creating Worker Actions](features/creating-worker-actions.md) | How to add a new callable Worker action — a PHP class whose `public static` methods are invoked as background jobs (via webhook, cron, or `_Worker::runTask()`). | worker2/Worker/, worker2/Controller/Index.php, _underscore/Worker.php |
8
8
  | [Elite Freshservice Sync (worker2)](features/elite-freshservice-sync.md) | `_Worker_Elite` processes Freshservice webhook events and syncs them into TOGA 2. | worker2/Worker/Elite.php, worker2/Config/dev-kmaramreddy-laptop.ini |
9
9
  | [Monitoring Framework (Orchestrator + Child Monitors)](features/monitoring-framework.md) | A unified, DB-driven monitoring framework for business-critical data flows (Compass POs, Prudential asset imports, AIG closed claims, …). | worker2/Worker/Monitor.php, worker2/Worker/Monitors/, worker2/Worker/Notification/Email.php, dbchanges2/Core/2026-05-21 - Monitors.sql |
10
- | [NetSuite → TOGA Opportunity Sync (API Message Queue + worker2 webhook)](features/netsuite-opportunity-sync.md) | Outbound sync from NetSuite to TOGA for the record types the Forecast2 importer pulls (opportunities first; sales/items/etc. | worker2/Worker/Netsuite.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Controller/Index.php, _underscore/Worker.php, test/@dave/NetSuite/api-message-queue/lib_amq_queue.js, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/NetSuite/api-message-queue/ue_amq_drain.js, test/@dave/NetSuite/api-message-queue/ss_amq_drain.js, test/@dave/NetSuite/api-message-queue/DEPLOY_RUNBOOK.md, test/@dave/clickup/backfill_opportunity_numbers.php, test/@dave/clickup/probe_opportunity_fields.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
10
+ | [NetSuite → TOGA Opportunity Sync (API Message Queue + worker2 webhook)](features/netsuite-opportunity-sync.md) | Outbound sync from NetSuite to TOGA for the record types the Forecast2 importer pulls (opportunities first; sales/items/etc. | worker2/Worker/Netsuite.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Controller/Index.php, _underscore/Worker.php, test/@dave/NetSuite/api-message-queue/lib_amq_queue.js, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/NetSuite/api-message-queue/ue_amq_drain.js, test/@dave/NetSuite/api-message-queue/ss_amq_drain.js, test/@dave/NetSuite/api-message-queue/DEPLOY_RUNBOOK.md, test/@dave/clickup/backfill_opportunity_numbers.php, test/@dave/clickup/probe_opportunity_fields.php, test/@dave/probe_clickup_desc_match.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
11
11
  | [NetSuite → Forecast Open-Orders Sync (salesOrder webhook → OpenOrderItems)](features/netsuite-salesorder-open-orders-sync.md) | Webhook-driven, single-record port of the legacy open-orders importer (TRUE-79142). | worker2/Worker/Netsuite/SalesOrder.php, worker2/Worker/Netsuite.php, test/@dave/probe_salesorder_rest_shape.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
12
12
  | [Teams Meeting Transcript Export](features/teams-transcript-export.md) | `_Worker_Team_Transcripts` (action `Team/Transcripts/Export`) polls Microsoft Graph for Teams meeting transcripts produced by a set of organizers, classifies ea | worker2/Worker/Team/Transcripts.php, worker2/Config/production.ini |
@@ -6,7 +6,7 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-16
9
+ updated: 2026-06-17
10
10
  owners: ["dfranks"]
11
11
  files:
12
12
  - worker2/Worker/Netsuite.php
@@ -20,6 +20,7 @@ files:
20
20
  - test/@dave/NetSuite/api-message-queue/DEPLOY_RUNBOOK.md
21
21
  - test/@dave/clickup/backfill_opportunity_numbers.php
22
22
  - test/@dave/clickup/probe_opportunity_fields.php
23
+ - test/@dave/probe_clickup_desc_match.php
23
24
  - worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php
24
25
  related:
25
26
  - ./netsuite-salesorder-open-orders-sync.md
@@ -89,8 +90,16 @@ second, independently-gated concern in the same handler.
89
90
 
90
91
  1. `findClickupTaskByOpportunityNumber($tranId)` — GET the list filtered by the `Opportunity #`
91
92
  custom field (`include_closed=true&include_archived=true&custom_fields=[{field_id,operator:'=',value}]`).
92
- 2. **Match → `updateTask()`**: PUT name/description, then POST each custom field individually to
93
- `/task/{id}/field/{fieldId}` (ClickUp has no bulk custom-field set on an existing task).
93
+ 2. **Match → `updateTask()`**: **diffs first, writes only what changed** (change-detection backstop,
94
+ since 2026-06-17). PUT name/description **only if** one differs; POST **only** the custom fields
95
+ that differ (each to `/task/{id}/field/{fieldId}` — ClickUp has no bulk custom-field set). When
96
+ nothing differs it writes nothing and returns `task unchanged`. This kills the no-op-write echo: a
97
+ blind rewrite fires a `taskUpdated` webhook on every sync. Comparison is **block-to-block** —
98
+ `buildDescription()` is regenerated from current NS data and compared to the stored block (no field
99
+ extraction needed), and `findClickupTaskByOpportunityNumber()` returns the **full task object** so
100
+ the diff has the existing name/description/custom-field values. Helpers: `clickupFieldMatches()`
101
+ (text = string compare; Presales Lead users field = add-only, matches when already assigned) and
102
+ `normalizeDescription()`.
94
103
  3. **No match → `createTask()`**: POST to list `901111987449`, `custom_item_id` = `1009`.
95
104
 
96
105
  Shared helpers: `buildCustomFields()` (the field array, used by both create and update),
@@ -118,7 +127,10 @@ Shared helpers: `buildCustomFields()` (the field array, used by both create and
118
127
  JS `Date.toString()` style in **America/Los_Angeles**, e.g. `GMT-0800 (PST)`, auto PST/PDT by date);
119
128
  Stage = `entityStatus.refName` (the percent string like "10%", NOT `probability`); Details = `memo`
120
129
  (which carries NetSuite's "Missing Required Details…" fallback verbatim for pre-mandatory records).
121
- The blank separator lines are a single space. Rendered verbatim no trailing-period normalization.
130
+ The blank separator lines are emitted as a single space, but **ClickUp stores them as truly empty
131
+ lines** (our `\n \n` comes back `\n\n`). Change-detection's `normalizeDescription()` rtrims each line
132
+ so that whitespace delta doesn't read as a change (otherwise every sync would re-write the
133
+ description). Confirmed against task 868jh6x08 / opp 73142 via `test/@dave/probe_clickup_desc_match.php`.
122
134
  - **Custom-field value mapping** (the non-obvious part — these were swapped before 2026-06-16):
123
135
  `Opportunity #` (`a5529cdc-…`) ← `tranId`; `Customer #` (`170dc118-…`) ← `entity->refName`
124
136
  (the customer **name**, deliberately — not the NetSuite customer number). Plus `Sales Rep`,
@@ -227,7 +239,37 @@ None — platform-wide Forecast sync.
227
239
  window to the load→claim gap. `findSendableIds` must list every sendable status
228
240
  (`Created`,`Pending`,`Retry`,`Sending`) — omitting `Created` silently matches nothing (`candidates:0`).
229
241
 
242
+ ## CU→NS direction (future — not yet built): reverse mapping
243
+
244
+ The current sync is **NS→CU only**. When the ClickUp→NetSuite direction is built, it cannot reuse the
245
+ block-to-block compare, because the source of the edit is the ClickUp **composite** but the destination
246
+ is **discrete NetSuite fields**. It must **reverse-map** — extract the real NS values out of the wrapper
247
+ before comparing-to / writing-to NetSuite:
248
+
249
+ - **Title → NS `title`:** the task name is `{opp#} — {customer} — {title}`. Do **not** naively split on
250
+ ` — ` (a title can contain a dash). Strip the **known** `{Opportunity#} — {Customer#} — ` prefix using
251
+ the values already on the `Opportunity #` / `Customer #` custom fields as anchors; the remainder is
252
+ the NS title.
253
+ - **Description → NS `memo`:** take only the lines between the `Details:` marker and the trailing
254
+ `NetSuite Internal ID:` line. Company / Amount / Expected Close / Stage are NS-derived display, not
255
+ ClickUp-authored — parse them out and ignore.
256
+ - **Custom fields** are already discrete — no extraction.
257
+
258
+ Pair this with: a **field-ownership gate** (only write fields ClickUp is authoritative for — Amount/
259
+ Stage/Expected Close are NS-owned, and HubSpot also writes these records, so don't push them back), the
260
+ same **skip-if-unchanged** compare on the extracted values, and **actor-identity suppression** (drop
261
+ `taskUpdated` events authored solely by the ClickUp bot/integration user) so our own NS→CU writes don't
262
+ trigger a CU→NS write. The NS→CU change-detection above is the complementary backstop, not a substitute.
263
+
230
264
  ## Change history
265
+ - 2026-06-17 — ClickUp **change-detection backstop** added to `updateTask()`: diff before write, skip
266
+ no-op syncs (`task unchanged`), PUT name/description only when changed, POST only changed custom
267
+ fields. `findClickupTaskByOpportunityNumber()` now returns the full task object; added
268
+ `clickupFieldMatches()` (add-only users field) + `normalizeDescription()` (per-line rtrim — ClickUp
269
+ trims our single-space separator lines to empty, which previously read as a permanent change).
270
+ Verified opp 73142 (changed in NS → correctly flagged) and same-data (→ `unchanged`). Also recorded
271
+ the future **CU→NS reverse-mapping** design (extract title/memo, field-ownership gate, actor-identity).
272
+ (dfranks)
231
273
  - 2026-06-16 — ClickUp task description switched to a structured block (`buildDescription()` +
232
274
  `formatAmount()`/`formatExpectedClose()`): Company/Opportunity/Amount/Expected Close/Stage/Details/
233
275
  Internal ID. Field sources confirmed via `probe_opportunity_fields.php`; Expected Close rendered
@@ -18,7 +18,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
18
18
  - **worker2** (Worker) — 8 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
19
19
  - **api2** (API) — 3 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
20
20
  - **dbchanges2** (Database Changes) _(framework core)_ — 1 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
21
- - **toga2-supply** (TOGa Supply) — 2 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
21
+ - **toga2-supply** (TOGa Supply) — 3 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
22
22
  - **saml** (SAML SSO Gateway) — 2 doc(s) → [2.0/apps/saml/INDEX.md](2.0/apps/saml/INDEX.md)
23
23
  - **toga2-view** (TOGa View Frontend) — 0 doc(s) → [2.0/apps/toga2-view/INDEX.md](2.0/apps/toga2-view/INDEX.md)
24
24
  - **toga2-hub** (TOGa Hub) — 2 doc(s) → [2.0/apps/toga2-hub/INDEX.md](2.0/apps/toga2-hub/INDEX.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.113",
3
+ "version": "1.0.115",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",