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
|
-
| [
|
|
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-
|
|
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
|
|
33
|
-
|
|
34
|
-
|
|
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)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -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) —
|
|
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