toga-ai 1.0.240 → 1.0.241

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.
@@ -9,3 +9,4 @@
9
9
  | [Meta-Driven Page & Table Setup](features/meta-driven-table-data.md) | A page in this app is **meta-driven end to end**: the page view model fetches *page meta* (labels, sections, ACL) and *table meta* (the columns/fields + table s | toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableState.ts, toga25-supply/src/hooks/useTablePageMeta.ts, toga-blox-npm/dist/hooks/useFetchPageMeta.d.ts, toga-blox-npm/dist/hooks/useFetchTablePageMeta.d.ts, toga-blox-npm/dist/hooks/useAssignTableFieldLabels.d.ts, toga-blox-npm/dist/components/Table/hooks/useTableData.d.ts |
10
10
  | [Record Modals & Nested Tables](features/record-modals-and-nested-tables.md) | The repo's family of modal + nested-table patterns layered over toga-blox `TableRecordModal` and `PrimaryTable*Layout`. | toga25-supply/src/layout/ItemRecordModalLayout/, toga25-supply/src/layout/SalesOrderRecordModalLayout/, toga25-supply/src/layout/SalesOrderItemsTableLayout/, toga25-supply/src/layout/ItemFulfillmentModal/, toga25-supply/src/layout/GenericNestedTables/, toga25-supply/src/hooks/useTableCellInteractions.ts |
11
11
  | [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from `GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported J | toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/surface/actionRegistry.ts, toga25-supply/src/surface/SurfaceActionBar.tsx, toga25-supply/src/surface/SurfaceSection.tsx, toga25-supply/src/surface/resolve.ts, toga25-supply/src/surface/types.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/Login/LoginPage.tsx, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/Items/ItemsPage.tsx, toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx, toga25-supply/src/pages/VendorItems/VendorItemsPage.tsx, toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx, toga25-supply/src/pages/Inventory/Inventory.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/index.ts, toga25-supply/src/fieldsConfig/index.ts |
12
+ | [Cypress Testing Harness (component + e2e)](workflows/cypress-testing.md) | The Cypress test harness for the `toga25-supply` frontend, bootstrapped from scratch (`cypress` was already a dependency but there was no config, no `cypress/` | toga25-supply/cypress.config.ts, toga25-supply/cypress/tsconfig.json, toga25-supply/cypress/support/component.tsx, toga25-supply/cypress/support/component-index.html, toga25-supply/cypress/support/e2e.ts, toga25-supply/cypress/support/commands.ts, toga25-supply/cypress/support/fixtures.ts, toga25-supply/cypress/support/mocks/useApprovalModalViewModel.ts, toga25-supply/cypress/component/SalesOrderApprovalModalsLayout.cy.tsx, toga25-supply/cypress/component/RecordApprovalModalLayout.cy.tsx, toga25-supply/cypress/component/EnterPoNumberModal.cy.tsx, toga25-supply/cypress/e2e/salesOrderApproval.cy.ts |
@@ -141,7 +141,22 @@ Scaffold per the `ItemRecordModalLayout` pattern (`src/layout/ItemRecordModalLay
141
141
  configs must use a `scope` discriminator in the query key or they overwrite each other's cache
142
142
  (record modal vs. approval modal: the approval modal omitting item-image fields blanked the
143
143
  record modal's images until scoping isolated the entries).
144
+ - **`poNumber` does NOT go through the approval modal.** `SalesOrderView` routes
145
+ `activeModal === "poNumber"` to a **separate** `EnterPoNumberModal` and explicitly excludes it
146
+ from `SalesOrderApprovalModalsLayout` (`activeModal !== "poNumber"`). The approval-modal
147
+ `modalSelected` union still lists `"poNumber"`, but that path is handled by the PO modal, not
148
+ the approval flow — don't add PO-number handling to the approval layout.
149
+ - **Approval submit payload: workflow vs single decision differ.** A single approve/deny posts a
150
+ **boolean** `isApproved` (`true`/`false`). The workflow path
151
+ (`handleFormatApprovalWorkflowPayload`) posts a **string** `"1"`/`"0"`, emits a request **only**
152
+ for stages whose `note` / `assignedTo` / `decision` actually changed, and chooses verb by
153
+ `stage.ApprovalDecisions.uuid` — `null` → POST `/approval-decisions`, otherwise PUT
154
+ `/approval-decisions/{uuid}`.
144
155
 
145
156
  ## Change history
157
+ - 2026-06-30 — Recorded two behaviors confirmed while building approval-modal tests: `poNumber`
158
+ is handled by a separate `EnterPoNumberModal` (excluded from the approval layout), and the
159
+ workflow vs single-decision submit payloads differ (string `"1"`/`"0"` + changed-stages-only +
160
+ POST/PUT by decision uuid, vs boolean `isApproved`). (apeterson)
146
161
  - 2026-06-23 — Documented from CLAUDE.md Modal Patterns 1–5 + the `create-record-modal` skill during
147
162
  initial knowledge seed. (apeterson)
@@ -0,0 +1,143 @@
1
+ ---
2
+ title: Cypress Testing Harness (component + e2e)
3
+ framework: "2.0"
4
+ repo: toga25-supply
5
+ project: TOGa 2.5 Supply
6
+ client: shared
7
+ type: workflow
8
+ status: active
9
+ updated: 2026-06-30
10
+ owners: [apeterson]
11
+ files:
12
+ - toga25-supply/cypress.config.ts
13
+ - toga25-supply/cypress/tsconfig.json
14
+ - toga25-supply/cypress/support/component.tsx
15
+ - toga25-supply/cypress/support/component-index.html
16
+ - toga25-supply/cypress/support/e2e.ts
17
+ - toga25-supply/cypress/support/commands.ts
18
+ - toga25-supply/cypress/support/fixtures.ts
19
+ - toga25-supply/cypress/support/mocks/useApprovalModalViewModel.ts
20
+ - toga25-supply/cypress/component/SalesOrderApprovalModalsLayout.cy.tsx
21
+ - toga25-supply/cypress/component/RecordApprovalModalLayout.cy.tsx
22
+ - toga25-supply/cypress/component/EnterPoNumberModal.cy.tsx
23
+ - toga25-supply/cypress/e2e/salesOrderApproval.cy.ts
24
+ related:
25
+ - ../features/record-modals-and-nested-tables.md
26
+ - ../features/action-button-rule-engine.md
27
+ - ../architecture.md
28
+ ---
29
+
30
+ ## What it is
31
+
32
+ The Cypress test harness for the `toga25-supply` frontend, bootstrapped from scratch
33
+ (`cypress` was already a dependency but there was no config, no `cypress/` dir, and zero
34
+ tests anywhere in the repo). It defines **both** a `component` project (Vite devServer,
35
+ mounts a single component) and an `e2e` project, plus reusable support code for mounting
36
+ components with the app's providers, mocking a component's data hook without a network or
37
+ tenant config, and sharing fixtures. The first specs cover the Sales Order **approval
38
+ modal flow**. Use this doc when adding any new test to the repo.
39
+
40
+ ## How the harness is wired
41
+
42
+ ### `cypress.config.ts` — two projects
43
+
44
+ - **`component`** — `devServer: { framework: "react", bundler: "vite" }` with a `viteConfig`
45
+ that **mirrors the app's `vite.config.ts`**: the `@` → `src` alias, the regex aliases that
46
+ force a single React copy, `optimizeDeps.exclude: ["@agilant/toga-blox"]` +
47
+ `optimizeDeps.include: ["dayjs"]`. This is the load-bearing part — toga-blox must resolve
48
+ the same way it does under `npm run dev`, or mounted components fail to bundle or get a
49
+ second React copy.
50
+ - **`e2e`** — standard `baseUrl` + `supportFile` setup for full-journey specs.
51
+
52
+ ### `cypress/support/component.tsx` + `commands.ts` — mounting with providers
53
+
54
+ `cy.mountWithProviders` is a custom command that wraps the mounted component in
55
+ `QueryClientProvider` + `ToasterProvider`. It accepts an **injectable `queryClient`** so a
56
+ spec can `cy.spy()` on `removeQueries` / `invalidateQueries` and assert the cache routine a
57
+ component runs on success. The default QueryClient is configured with `retry: false` for
58
+ determinism. Support also imports `@/index.css` (styling) and `@/api/api` — the latter so
59
+ toga-blox's `apiPost` / `apiPut` have a configured axios instance available during component
60
+ tests.
61
+
62
+ ### Window-backed hook-mock pattern (key technique)
63
+
64
+ `cypress/support/mocks/useApprovalModalViewModel.ts` lets you unit-test a data-binding
65
+ component **without** a network or tenant config. The component devServer aliases the
66
+ component's **relative** import of its view model to the mock:
67
+
68
+ ```ts
69
+ // in the component viteConfig resolve.alias
70
+ { find: /^\.\/viewModel\/useApprovalModalViewModel$/, replacement: "<abs>/cypress/support/mocks/useApprovalModalViewModel.ts" }
71
+ ```
72
+
73
+ The mock returns whatever the spec parked on `window.__approvalModalViewModel`. In Cypress
74
+ component testing the spec and the component share the **same `window`**, so a plain
75
+ assignment before `cy.mount` is visible to the hook. Helpers `setApprovalModalViewModel` /
76
+ `resetApprovalModalViewModel` set and clear it. To mock a different component's hook, add a
77
+ parallel regex alias + a parallel `window.__<name>` mock.
78
+
79
+ ### `cypress/support/fixtures.ts` — shared fixtures
80
+
81
+ Imports the **real shipped** tenant config
82
+ `src/pages/SalesOrders/.../viewModel/FIELDS/QUAD/approvalActionFields.json` as
83
+ `recordActionFields`, so tests track the actual field / label / guardrail config the app
84
+ ships (not a hand-rolled copy that drifts). Adds synthetic order / approval-stages / PO
85
+ fixtures alongside it. (The QUAD file is reused only as a representative real config — this
86
+ harness is generic test infra, not Quad-specific behavior.)
87
+
88
+ ## What the specs cover
89
+
90
+ - **`SalesOrderApprovalModalsLayout.cy.tsx`** (component) — the sales-order binding:
91
+ null-guards, `isApproveButtonEnabled` derivation, and the documented `onSuccess` cache
92
+ routine asserted via the spied QueryClient.
93
+ - **`RecordApprovalModalLayout.cy.tsx`** (component) — the generic approval modal flow: deny
94
+ requires a reason (approve optional), submit fires the decision API + `onSuccess`/`onClose`,
95
+ discard-reason guardrail on Cancel/backdrop, workflow approve-enablement, deep workflow
96
+ stage editing (deny one stage with a required note → submits only that stage; an empty
97
+ required note blocks submit), and the **decision-failure path** (500 → modal stays open,
98
+ `onSuccess`/`onClose` not called).
99
+ - **`EnterPoNumberModal.cy.tsx`** (component) — the PO-number action: create requires a PO#,
100
+ POST `/purchase-orders/`, credit-card flag `0`/`1`, edit → PUT.
101
+ - **`salesOrderApproval.cy.ts`** (e2e) — full journey: seed auth via
102
+ `localStorage.accessToken`, row-action menu → Deny → submit, stubbing only the write.
103
+
104
+ ## Conventions for writing tests here
105
+
106
+ - **Component test of a data-binding component** → use the window-backed hook mock so you
107
+ never need a real network/tenant config; park state on `window.__<hook>` before `cy.mount`.
108
+ - **Asserting a cache side-effect** (invalidate/remove queries on success) → inject a spied
109
+ `queryClient` into `cy.mountWithProviders`.
110
+ - **Testing an async error path** → use `cy.on("uncaught:exception", ...)` to swallow the
111
+ rejected `mutateAsync` so the rejection doesn't fail the spec, then assert the modal stayed
112
+ open and `onSuccess`/`onClose` were not called.
113
+ - **e2e auth** → seed `localStorage.accessToken`; stub only the write request, let reads hit
114
+ the configured backend (document any backend prerequisites in the spec).
115
+ - **Keep the component devServer config in sync with `vite.config.ts`** — when the app's
116
+ Vite aliases or `optimizeDeps` change, mirror the change into `cypress.config.ts` or
117
+ component mounts break.
118
+
119
+ ## package.json scripts
120
+
121
+ `cy:open`, `cy:run:ct` (component), `cy:run:e2e`, `test:ct`.
122
+ `.gitignore` excludes `cypress/videos|screenshots|downloads`.
123
+
124
+ ## Gotchas
125
+
126
+ - **Cypress's bundled Electron cannot launch in the sandboxed macOS dev environment** — it
127
+ fails the pre-run smoke test (`bad option: --no-sandbox / --smoke-test / --ping`), which
128
+ blocks **all** specs before any run. The specs in this harness were validated by
129
+ `tsc -p cypress/tsconfig.json` (0 errors) and `eslint cypress` (clean) but **could not be
130
+ executed in that sandbox** — run them locally with `npm run cy:run:ct`.
131
+ - **`eslint.config.js` is protected by a config-protection hook.** When a spec/support file
132
+ trips a lint rule (e.g. `no-explicit-any`), **fix the source to satisfy the linter** (use
133
+ `unknown` / typed shapes) — do **not** weaken the shared eslint config to make tests pass.
134
+ The override edit to `eslint.config.js` is blocked by the hook by design.
135
+ - **`@agilant/toga-blox` consumes `dist/`** — same as the app, the harness sees built
136
+ toga-blox, so a toga-blox source change needs a rebuild before tests observe it.
137
+
138
+ ## Change history
139
+ - 2026-06-30 — Bootstrapped the Cypress harness from scratch (dual component + e2e config,
140
+ `cy.mountWithProviders`, window-backed hook-mock pattern, shared fixtures off the real
141
+ QUAD `approvalActionFields.json`) and added the first specs for the Sales Order approval
142
+ modal flow. Documented the sandbox-Electron blocker and the protected-eslint decision.
143
+ (apeterson)
@@ -28,7 +28,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
28
28
  - **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
29
29
  - **ai-bdr** (AI-BDR) — 4 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
30
30
  - **toga2-commerce** (TOGa Commerce) — 7 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
31
- - **toga25-supply** (TOGa 2.5 Supply) — 7 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
31
+ - **toga25-supply** (TOGa 2.5 Supply) — 8 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
32
32
  - **toga-blox** (TOGa Blox) — 7 doc(s) → [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
33
33
 
34
34
  ## standalone framework
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.240",
3
+ "version": "1.0.241",
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",