toga-ai 1.0.202 → 1.0.203

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.
@@ -3,7 +3,8 @@
3
3
  | Doc | Summary | Files |
4
4
  |-----|---------|-------|
5
5
  | [TOGa 2.5 Supply — Architecture](architecture.md) | `toga25-supply` ("TOGa 2.5 Supply") is the **React/TypeScript frontend** for the 2.0 Supply application — an iteration and improvement of `toga2-supply`. | toga25-supply/src/main.tsx, toga25-supply/src/App.tsx, toga25-supply/src/routes.tsx, toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/fieldsConfig/useClientFields.ts, toga25-supply/src/layout/, toga25-supply/src/pages/, toga25-supply/src/hooks/useTableCellInteractions.ts |
6
- | [Client-Configurable Fields (useClientFields / fieldsConfig)](features/client-configurable-fields.md) | The mechanism for config that **varies by client** (or client × role) — field overrides, filter buttons, group-by options, column pickers, layout toggles — with | toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/fieldsConfig/useClientFields.ts, toga25-supply/src/pages/SalesOrders/viewModel/FIELDS/, toga25-supply/src/pages/Inventory/viewModel/FIELDS/, toga25-supply/src/pages/Inventory/README.md |
6
+ | [Action-Button Rule Engine (Flag / Rule grammar)](features/action-button-rule-engine.md) | A declarative, fully config-driven rule engine that resolves the boolean-ish flags (`isEnabled`, `isVisible`, `isComplete`) on SalesOrder action-button options. | toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts, toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/useApprovalModalViewModel.tsx |
7
+ | [Client-Configurable Fields (useClientFields / fieldsConfig)](features/client-configurable-fields.md) | The mechanism for config that **varies by client** (or client × role) — field overrides, filter buttons, group-by options, column pickers, layout toggles — with | toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/fieldsConfig/useClientFields.ts, toga25-supply/src/pages/SalesOrders/viewModel/FIELDS/, toga25-supply/src/pages/Inventory/viewModel/FIELDS/, toga25-supply/src/pages/Inventory/README.md, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/FIELDS/, toga25-supply/src/layout/VendorItemRecordModalLayout/viewModel/FIELDS/ |
7
8
  | [Column Visibility (URL-driven show/hide columns)](features/column-visibility.md) | A "Columns" header button that opens a modal listing every column from the table meta, lets the user show/hide columns, adjusts the table live, and persists the | toga25-supply/src/components/ColumnVisibilityModal/, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableData.tsx |
8
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 |
9
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 |
@@ -0,0 +1,119 @@
1
+ ---
2
+ title: Action-Button Rule Engine (Flag / Rule grammar)
3
+ framework: "2.0"
4
+ repo: toga25-supply
5
+ project: TOGa 2.5 Supply
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-25
10
+ owners: [apeterson]
11
+ files:
12
+ - toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts
13
+ - toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts
14
+ - toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx
15
+ - toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/useApprovalModalViewModel.tsx
16
+ related:
17
+ - client-configurable-fields.md
18
+ - ../architecture.md
19
+ ---
20
+
21
+ ## What it is
22
+
23
+ A declarative, fully config-driven rule engine that resolves the boolean-ish flags
24
+ (`isEnabled`, `isVisible`, `isComplete`) on SalesOrder action-button options. Every such
25
+ flag is a **`Flag`** — either a static `true`/`false` or a **`Rule`** tree evaluated against
26
+ the order's runtime state. Because the entire grammar is data, the backend can ship a rule
27
+ tree verbatim and adding or re-gating a button becomes a pure config change with no frontend
28
+ code edit. This is the realization of the long-stated goal that SalesOrders action-button
29
+ config be completely field-driven and backend-suppliable.
30
+
31
+ ## How it works
32
+
33
+ The engine lives in `src/pages/SalesOrders/helpers/evaluateEnableRule.ts` and is applied by
34
+ `buildPatchedTenantFields.ts`.
35
+
36
+ ### The grammar
37
+
38
+ ```ts
39
+ type Flag = boolean | Rule | undefined;
40
+
41
+ type Rule =
42
+ | { all: Rule[] } // AND — every child true
43
+ | { any: Rule[] } // OR — at least one child true
44
+ | { not: Rule } // NOT
45
+ | { field: string; op: FieldOp; value? } // dot-path predicate into RuleContext
46
+ | { type: string }; // named domain primitive (rare)
47
+
48
+ type FieldOp = "eq" | "ne" | "gt" | "gte" | "lt" | "lte"
49
+ | "in" | "nin" | "truthy" | "falsy";
50
+ ```
51
+
52
+ - **Field predicates** address into the `RuleContext` by dot-path (e.g. `order._status`,
53
+ `currentStage.ApprovalTemplateStages.step`). `getByPath` walks the path with optional
54
+ chaining; a missing path yields `undefined`.
55
+ - **`RuleContext`** is `{ order, currentStage?, stages? }`. `currentStage`/`stages` carry
56
+ approval-workflow state when available.
57
+ - **Named rules** (`{ type }`) are bespoke leaf checks that a field predicate cannot express
58
+ — they live in the `NAMED_RULES` registry. Currently only `stepTwoAssigned` (walks the
59
+ approval stages to confirm a step-2 assignee exists). An unknown `type` resolves to `true`.
60
+
61
+ ### Resolution primitives
62
+
63
+ - `evaluateRule(rule, ctx): boolean` — recursive evaluator over the combinators.
64
+ - `resolveFlag(flag, ctx, fallback = false): boolean` — the single primitive behind every
65
+ flag. Static booleans pass through; an object is treated as a `Rule` and evaluated;
66
+ `undefined` returns `fallback`. The same prop can therefore be static for one client and
67
+ rule-driven for another with **no code change**.
68
+
69
+ ### Applying flags to tenant config
70
+
71
+ `buildPatchedTenantFields({ tenantFields, context })` maps over
72
+ `tenantFields.recordActionFields.actionOptions` and patches each option's resolvable flags:
73
+
74
+ - `isEnabled` — via `resolveActionEnabled` (see back-compat below).
75
+ - `isVisible` and `isComplete` — only patched when the key is present on the option, each via
76
+ `resolveFlag(..., false)`.
77
+
78
+ It returns a new `tenantFields` with a patched `recordActionFields.actionOptions`. Callers:
79
+ `useSalesOrderRecordModalLayoutModel.tsx` and `useApprovalModalViewModel.tsx`.
80
+
81
+ ### Back-compat shim
82
+
83
+ The previous single-purpose `EnableRule` grammar (`alwaysEnabled`, `requireStepTwoAssigned`,
84
+ `requireOrderField`, `statusIn`) is mapped onto the generic engine via `evaluateEnableRule`,
85
+ so existing configs keep working while they migrate to `Rule`. `resolveActionEnabled` picks
86
+ the path:
87
+
88
+ 1. `isEnabled` is an object → treat as a `Rule`, `resolveFlag`.
89
+ 2. else a separate `enableRule` exists → legacy `evaluateEnableRule`.
90
+ 3. else → return the static `isEnabled`.
91
+
92
+ Retire the shim once all configs express flags as `Rule`.
93
+
94
+ ## Adding or re-gating a button
95
+
96
+ 1. **Pure config change (preferred):** edit the action option's `isEnabled` / `isVisible` /
97
+ `isComplete` in the per-client `recordActionFields` config to a static boolean or a `Rule`
98
+ tree. No code edit. The backend can ship the same tree.
99
+ 2. **New primitive check only:** if a gate genuinely can't be expressed as field predicates
100
+ + combinators (e.g. it must walk approval stages), add one entry to `NAMED_RULES` and
101
+ reference it as `{ type: "yourCheck" }`. This is the only expected reason to edit the
102
+ engine file.
103
+
104
+ ## Gotchas
105
+
106
+ - **`recordActionFields` is the current key** (renamed from `approvalActionFields` /
107
+ `orderViewFields`-era naming). `buildPatchedTenantFields` reads
108
+ `recordActionFields.actionOptions`; missing config falls back to `[]`.
109
+ - **`isVisible` / `isComplete` default to `false`** when present-but-undefined, while
110
+ `isEnabled` falls through to its static value. Don't assume a missing flag means "shown".
111
+ - **The table row menu (`RowActionButtons.tsx`) reads raw config directly** and is
112
+ intentionally out of scope for rule-driven enablement — it does not go through
113
+ `buildPatchedTenantFields`.
114
+ - **Prefer the `Rule` grammar over magic named keys.** Anything expressible as a field
115
+ comparison must stay config-only; adding to `NAMED_RULES` is the rare exception, not the
116
+ default.
117
+
118
+ ## Change history
119
+ - 2026-06-25 — Documented the generic Flag/Rule engine: combinators, field predicates, named-rule registry, `resolveFlag`/`buildPatchedTenantFields`, and the legacy `EnableRule` back-compat shim. (apeterson)
@@ -6,7 +6,7 @@ project: TOGa 2.5 Supply
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-23
9
+ updated: 2026-06-25
10
10
  owners: [apeterson]
11
11
  files:
12
12
  - toga25-supply/src/fieldsConfig/index.ts
@@ -14,9 +14,12 @@ files:
14
14
  - toga25-supply/src/pages/SalesOrders/viewModel/FIELDS/
15
15
  - toga25-supply/src/pages/Inventory/viewModel/FIELDS/
16
16
  - toga25-supply/src/pages/Inventory/README.md
17
+ - toga25-supply/src/layout/ItemRecordModalLayout/viewModel/FIELDS/
18
+ - toga25-supply/src/layout/VendorItemRecordModalLayout/viewModel/FIELDS/
17
19
  related:
18
20
  - ../architecture.md
19
21
  - column-visibility.md
22
+ - action-button-rule-engine.md
20
23
  ---
21
24
 
22
25
  ## What it is
@@ -28,10 +31,13 @@ and the `useClientFields()` hook.
28
31
 
29
32
  ## How it works
30
33
 
31
- 1. **Per-client JSON lives next to the consuming page**, under `FIELDS/<CLIENT>/`
32
- (e.g. `src/pages/SalesOrders/viewModel/FIELDS/COMPASS/salesOrdersPageFields.json`). There is
33
- **always a `DEFAULT/`** folder every client falls back to. `<CLIENT>` must match the hostname
34
- `clientSlug` (uppercase — `COMPASS`, `NYCHH`, …).
34
+ 1. **Per-client JSON lives next to the consuming page _or layout_**, under `FIELDS/<CLIENT>/`
35
+ (e.g. `src/pages/SalesOrders/viewModel/FIELDS/COMPASS/salesOrdersPageFields.json`, or for a
36
+ record-modal layout `src/layout/ItemRecordModalLayout/viewModel/FIELDS/COMPASS/itemRecordViewFields.json`).
37
+ There is **always a `DEFAULT/`** folder every client falls back to. `<CLIENT>` must match the
38
+ hostname `clientSlug` (uppercase — `COMPASS`, `COMPASSCANADA`, `NYCHH`, …). Record-modal layouts
39
+ typically split their config per modal mode — `*ViewFields.json`, `*EditFields.json`,
40
+ `*CreateFields.json` — one file per mode, per client.
35
41
  2. **`src/fieldsConfig/index.ts`** imports those JSON files and wires them into the
36
42
  `ClientFields` bundle — a normalized `FIELDS[clientSlug][role]` map merged over `DEFAULT`.
37
43
  3. **`useClientFields()`** resolves the active `clientSlug` (from `useHostnameStore`) + `role`
@@ -64,6 +70,22 @@ registry that maps enum → real value (e.g. `MODAL_RENDERERS: Record<ModalKey,
64
70
  This is the one place app-owned JSX is grafted back onto client-authored config. Clients never
65
71
  author JSX.
66
72
 
73
+ ### Record-modal header & action config
74
+
75
+ Record-modal layouts (`ItemRecordModalLayout`, `VendorItemRecordModalLayout`) now drive the
76
+ modal's chrome from the same per-client JSON, not from the component:
77
+
78
+ - **`header`** — `{ modalTag: { icon, iconStyle, title }, labelTemplate, statusBadge }`.
79
+ `labelTemplate` is a `"{dotted.path} literal"` string resolved against the record (same
80
+ `fillTemplate` convention as `optionLabelTemplate`). `statusBadge` is
81
+ `{ field, activeLabel, inactiveLabel }` — the component reads `record[field]` (e.g.
82
+ `isActive`) and renders the matching label. The component bakes in no titles or labels.
83
+ - **`editItem`** — a `{ valueKey, kind: "button", label, icon, isVisible, isEnabled }` action
84
+ block. `isVisible` / `isEnabled` are `Flag`s, so the Edit button is gated declaratively per
85
+ client (e.g. visible+enabled for vendor items, hidden for the DEFAULT item view). These flags
86
+ resolve through the same engine as SalesOrder action buttons — see
87
+ [action-button-rule-engine.md](action-button-rule-engine.md).
88
+
67
89
  ## Adding a new client-configurable field
68
90
 
69
91
  1. Author the serializable JSON under `FIELDS/DEFAULT/<thing>.json` + a folder per overriding client.
@@ -81,8 +103,18 @@ author JSX.
81
103
  - **New client *file* = no code change** (just import/wiring). **New render style/modal type
82
104
  (new enum value) = code change** in the hydration registry — that's the intended boundary
83
105
  between client config and app rendering.
106
+ - **Flat (un-wrapped) bundles need a fallback that tolerates the missing `DEFAULT` key.** Some
107
+ bundles wrap their config under a top-level `DEFAULT` key, others are authored flat. In
108
+ `fieldsConfig/index.ts`, derive the default defensively rather than assuming the wrapper exists:
109
+ `const DEFAULT_ORDER_VIEW_FIELDS = (defaultBundle as any).DEFAULT ?? (defaultBundle as any) ?? {};`
110
+ — fall through to the bundle itself, then `{}`, so a flat JSON file does not resolve to `undefined`.
84
111
  - Worked examples: `salesOrdersPageFields` (client × role) and `inventoryGroupings` (client-only +
85
- hydration; full write-up in `src/pages/Inventory/README.md`).
112
+ hydration; full write-up in `src/pages/Inventory/README.md`); `itemRecordViewFields` /
113
+ `vendorItemRecordViewFields` are record-modal-layout examples living under `src/layout/.../viewModel/FIELDS/`.
86
114
 
87
115
  ## Change history
116
+ - 2026-06-25 — Extended to record-modal layouts: per-mode `*ViewFields`/`*EditFields` JSON under `src/layout/.../viewModel/FIELDS/`, JSON-driven modal `header` (modalTag/labelTemplate/statusBadge) + `editItem` button gated via the Flag/Rule engine, and the flat-bundle defensive-default gotcha. (apeterson)
88
117
  - 2026-06-23 — Documented from the `add-client-fields` skill during initial knowledge seed. (apeterson)
118
+ - 2026-06-25 — Client field JSON also lives under `src/layout/<Modal>/viewModel/FIELDS/<CLIENT>/`
119
+ (record-modal layouts; split per mode: View/Edit/Create). Documented the defensive
120
+ `?? (defaultBundle) ?? {}` fallback for flat (un-`DEFAULT`-wrapped) bundles. (apeterson)
@@ -27,7 +27,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
27
27
  - **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
28
28
  - **ai-bdr** (AI-BDR) — 4 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
29
29
  - **toga2-commerce** (TOGa Commerce) — 6 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
30
- - **toga25-supply** (TOGa 2.5 Supply) — 5 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
30
+ - **toga25-supply** (TOGa 2.5 Supply) — 6 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
31
31
  - **toga-blox** (TOGa Blox) — 7 doc(s) → [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
32
32
 
33
33
  ## standalone framework
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.202",
3
+ "version": "1.0.203",
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",