# SCRATCHPAD — Invoice Workflow Expression Binding Unification - Plan slug: `invoice-workflow-expression-unification` - Created: `2026-03-04` ## What This Is Working notes for unifying invoice designer bindings and Workflow v2 expression authoring on a shared expression foundation while preserving each runtime/storage contract. ## Decisions - (2026-03-04) Use a shared abstraction centered on typed context paths + expression modes (`path-only`, `template`, `expression`) instead of forcing one persisted expression format across products. - (2026-03-04) Shared primitives module location: `shared/workflow/expression-authoring/*` so both invoice (`packages/billing`) and workflow designer (`ee/server`) can import through `@shared/workflow/...`. - (2026-03-04) Shared context contract includes explicit `SharedExpressionContextRoot` and `SharedExpressionPathOption` interfaces plus deterministic root serialization to stabilize cross-editor behavior/tests. - (2026-03-04) Shared path discovery contract uses deterministic lexical traversal with explicit array item marker segments (`[]`) and additional-property wildcard (`*`) to produce stable picker options. - (2026-03-04) Shared insertion helper is split into pure value insertion (`insertTextIntoValue`) plus environment-specific adapters (`insertTextIntoDomControl`, `insertTextIntoMonacoEditor`) with explicit no-op reasons. - (2026-03-04) Shared validation contract (`SharedExpressionValidationResult`) normalizes severity ordering and preserves path/root/range attribution for downstream UI rendering. - (2026-03-04) Invoice adapter roots stay domain-native (`invoice`, `customer`, `tenant`, `item`) for this migration to preserve existing author mental model and template bindings. - (2026-03-04) Workflow adapter includes dynamic loop roots (`itemVar`, `indexVar`) so forEach-scoped expressions can use the same shared path options as global roots. - (2026-03-04) Invoice palette fields now derive from `buildInvoiceExpressionPathOptions(...)`; static catalog import path is no longer used in rendering/search. - (2026-03-04) Invoice FIELDS panel grouping now maps directly to shared root keys (`invoice/customer/tenant/item`) and only renders leaf path options from the shared model. - (2026-03-04) `DesignerShell` now uses `insertTextIntoDomControl` and target-path mode inference: inspector fields ending in `bindingKey` receive raw path insertion (`path-only`). - (2026-03-04) Invoice text fallback insertion path also uses shared insertion primitives (`insertTextIntoValue`) and enforces template token formatting for text/label nodes. - (2026-03-04) Workspace AST export now parses moustache text edits into dynamic expressions: full-token moustache becomes `path`, mixed literal+moustache becomes `template` with deterministic arg keys. - (2026-03-04) Existing roundtrip suites (`workspaceAst.roundtrip.*`) remain green after adapter/insertion changes, confirming deterministic export/import behavior. - (2026-03-04) Shared path validator (`validateSourcePaths`) now powers invoice insertion feedback; unknown paths emit informational diagnostics without breaking insertion flow. - (2026-03-04) Workflow step config validation now derives shared path options from `DataContext` via `buildWorkflowExpressionPathOptions(...)` and uses `validateSourcePaths(...)` in place of legacy `${...}` scanners. - (2026-03-04) Workflow step validation UI now consumes shared diagnostic objects directly (`severity`, `code`, `message`, `path`) and renders grouped Error/Warning/Info cards from the shared contract. - (2026-03-04) Runtime function allowlist is now centralized in `shared/workflow/runtime/expressionFunctions.ts` and consumed by both runtime validation and Monaco completion generation. - (2026-03-04) Workflow function completion/snippet suggestions are now constrained to runtime-allowlisted helpers (`$nowIso`, `$coalesce`, `$len`, `$toString`, `$append`). - (2026-03-04) Workflow diagnostics now validate `$function(...)` tokens against runtime-allowlisted helpers only; non-allowlisted JSONata built-ins are flagged as unknown in-editor. - (2026-03-04) Workflow persisted expression shape remains `{ $expr: string }`; migration changes were limited to authoring/validation paths and retained `ensureExpr` + runtime schema guards. - (2026-03-04) Added focused runtime guardrail tests (`expressionEngine.test.ts`) to lock function allowlist rejection, timeout enforcement, JSON-serializable result checks, and max output size checks. - (2026-03-04) Shared schema/context-aware path checks (`validateSourcePaths`) are now used in both designers where author-time validation exists: - invoice insertion feedback (`DesignerShell`) - workflow step config validation (`WorkflowDesigner`) - (2026-03-04) Workflow Monaco insertion paths now route through shared `insertTextIntoMonacoEditor` for both programmatic insert and drag/drop insert, with existing `$0` placeholder stripping preserved. - (2026-03-04) Invoice input/textarea insertion remains shared-helper based (`insertTextIntoDomControl` + `insertTextIntoValue`) for selection-aware replacement and repeated insert behavior. - (2026-03-04) Invoice preview interpolation semantics remain intact post-migration (resolved known tokens + visibly marked unresolved tokens), validated by preview-mode suite. - (2026-03-04) Workflow runtime expression suites (`expressionEngine`, `mappingResolver`, `mappingValidator`) remain green after migration; contracts for `{ $expr }` evaluation/validation are preserved. - (2026-03-04) Deprecated invoice static variable catalog path is removed; field discovery now only flows through shared adapter-driven options. - (2026-03-04) Deprecated workflow `${...}` scanner helpers are removed from step validation flow; only shared-path validation remains. - (2026-03-04) Test implementation standards are codified in `PRD.md` and operationalized as explicit meta-test backlog items (`T041`, `T043`, `T044`) for checklist enforcement. - (2026-03-04) Added shared expression-authoring foundational unit suites: - `shared/workflow/expression-authoring/__tests__/coreContracts.test.ts` - `shared/workflow/expression-authoring/__tests__/insertionDom.test.ts` These cover T001-T012 contract behaviors. - (2026-03-04) T002 verified by `coreContracts.test.ts` roundtrip serialization/deserialization assertions for shared context roots. - (2026-03-04) T003 verified by deterministic path ordering assertions in `coreContracts.test.ts` (`buildPathOptionsFromContextRoots` nested schema flattening). - (2026-03-04) T004 verified by array marker segment assertions in `coreContracts.test.ts` (`payload.items[]` and `payload.items[].id`). - (2026-03-04) T005 verified in `insertionDom.test.ts` with start/middle/end insertion coverage for `insertTextIntoValue`. - (2026-03-04) T006 verified in `insertionDom.test.ts` by replacing an active range (`{{invoice.total}}` -> `{{customer.name}}`). - (2026-03-04) T007 verified in `insertionDom.test.ts` with explicit readonly/disabled/unfocused no-op assertions for `insertTextIntoDomControl`. - (2026-03-04) T008 verified via `coreContracts.test.ts` validation-result normalization assertions (severity fallback + deterministic diagnostic ordering). - (2026-03-04) T009 verified via `coreContracts.test.ts` invoice adapter assertions (roots + canonical paths for invoice/customer/tenant/item). - (2026-03-04) T010 verified via `coreContracts.test.ts` stable label assertions for known invoice adapter roots/paths. - (2026-03-04) T011 verified via `coreContracts.test.ts` workflow adapter root emission assertions (payload/vars/meta/error + forEach vars). - (2026-03-04) T012 verified via `coreContracts.test.ts` recursive vars-path assertions (`vars.previous.account.id`). - (2026-03-04) T013 validated with `ComponentPalette.fields.integration.test.tsx` by exercising FIELDS tab insertion path callback using adapter-derived field options. - (2026-03-04) T014 validated with `ComponentPalette.fields.integration.test.tsx` search assertions (query filters adapter-derived labels/paths). - (2026-03-04) Preserve persisted contracts: - Invoice keeps AST value expressions (`literal|binding|path|template`). - Workflow keeps `{ $expr: string }`. - (2026-03-04) Migrate incrementally via adapters; no big-bang rewrite. - (2026-03-04) Treat runtime allowlist as source of truth for workflow function capability; editor metadata must conform to it. - (2026-03-04) Keep plan scope focused on logic/functionality and correctness; exclude metrics/observability/production-readiness additions. - (2026-03-04) Replace doc-only feature/test entries with explicit test implementation standards and enforceable meta-tests (framework, selector strategy, anti-tautology, feature-test traceability). ## Discoveries / Constraints - (2026-03-04) This branch already had uncommitted invoice-designer WIP focused on static `templateVariableCatalog` and local insertion logic; foundational shared abstractions were still missing. - (2026-03-04) Invoice currently uses multiple authoring paths: - `metadata.bindingKey` (path string) - moustache text in `metadata.text` - AST conversion in `workspaceAst.ts`. - (2026-03-04) Invoice preview now resolves moustache interpolation in canvas preview path but this is distinct from export compilation. - (2026-03-04) Workflow currently has duplicated expression validation paths: - Monaco diagnostics provider - Legacy `${...}` extraction/validation in `WorkflowDesigner.tsx`. - (2026-03-04) `WorkflowDesigner.tsx` no longer defines `extractExpressionPaths` / `validateExpressionPath`; step validation now emits issues from shared validation diagnostics. - (2026-03-04) Editor metadata previously exposed broad JSONata helpers; completion hints were not runtime-aligned until runtime allowlist filtering was introduced. - (2026-03-04) Workflow editor function metadata includes many JSONata functions, while runtime currently allowlists only a small helper set in `shared/workflow/runtime/expressionEngine.ts`. - (2026-03-04) Removing drift between workflow editor and runtime is required to avoid misleading UX. ## Commands / Runbooks - Implement F001 shared mode contract: - `shared/workflow/expression-authoring/modes.ts` (`EXPRESSION_MODES`, `ExpressionMode`, `isExpressionMode`) - Implement F002 context and path option contracts: - `shared/workflow/expression-authoring/context.ts` - Implement F003 path discovery helper: - `shared/workflow/expression-authoring/pathDiscovery.ts` - Implement F004 insertion helper API: - `shared/workflow/expression-authoring/insertion.ts` - Implement F005 validation result contract: - `shared/workflow/expression-authoring/validation.ts` - Implement F006 invoice adapter: - `shared/workflow/expression-authoring/adapters/invoiceContextAdapter.ts` - Implement F007 workflow adapter: - `shared/workflow/expression-authoring/adapters/workflowContextAdapter.ts` - Validate invoice field palette integration after adapter wiring: - `cd server && npx vitest run ../packages/billing/src/components/invoice-designer/palette/ComponentPalette.fields.integration.test.tsx` - Validate invoice shell + fields insertion wiring: - `cd server && npx vitest run ../packages/billing/src/components/invoice-designer/palette/ComponentPalette.fields.integration.test.tsx ../packages/billing/src/components/invoice-designer/DesignerShell.selectedContext.integration.test.tsx` - Validate moustache-to-AST behavior: - `cd server && npx vitest run ../packages/billing/src/components/invoice-designer/ast/workspaceAst.test.ts` - Validate roundtrip determinism: - `cd server && npx vitest run ../packages/billing/src/components/invoice-designer/ast/workspaceAst.roundtrip.templates.test.ts ../packages/billing/src/components/invoice-designer/ast/workspaceAst.roundtrip.nodes.test.ts ../packages/billing/src/components/invoice-designer/ast/workspaceAst.roundtrip.styles.test.ts` - Validate invoice insertion + validation integration: - `cd server && npx vitest run ../packages/billing/src/components/invoice-designer/ast/workspaceAst.test.ts ../packages/billing/src/components/invoice-designer/palette/ComponentPalette.fields.integration.test.tsx ../packages/billing/src/components/invoice-designer/DesignerShell.selectedContext.integration.test.tsx` - Validate workflow editor test baseline after validation migration: - `cd ee/server && npx vitest run src/components/workflow-designer/expression-editor/__tests__/completionProvider.test.ts src/components/workflow-designer/expression-editor/__tests__/diagnosticsProvider.test.ts` - Validate workflow designer type safety after shared validation UI refactor: - `cd ee/server && npm run -s typecheck` - Validate allowlisted function completions and diagnostics baseline: - `cd ee/server && npx vitest run src/components/workflow-designer/expression-editor/__tests__/completionProvider.test.ts src/components/workflow-designer/expression-editor/__tests__/diagnosticsProvider.test.ts` - Validate diagnostics allowlist behavior and type safety: - `cd ee/server && npx vitest run src/components/workflow-designer/expression-editor/__tests__/diagnosticsProvider.test.ts` - `cd ee/server && npm run -s typecheck` - Audit persisted expression contract usage: - `rg -n '\\$expr' ee/server/src/components/workflow-designer/WorkflowDesigner.tsx shared/workflow/runtime -g'*.ts*'` - Validate runtime expression guardrails: - `npx vitest run --config shared/vitest.config.ts shared/workflow/runtime/__tests__/expressionEngine.test.ts shared/workflow/runtime/__tests__/mappingResolver.test.ts` - Audit shared path-validator usage in both designers: - `rg -n "validateSourcePaths\\(" packages/billing/src/components/invoice-designer ee/server/src/components/workflow-designer -g'*.ts*'` - Validate workflow expression editor insertion refactor: - `cd ee/server && npx vitest run src/components/workflow-designer/expression-editor/__tests__/completionProvider.test.ts src/components/workflow-designer/expression-editor/__tests__/diagnosticsProvider.test.ts && npm run -s typecheck` - Validate invoice preview interpolation behavior: - `cd server && npx vitest run ../packages/billing/src/components/invoice-designer/canvas/DesignCanvas.previewMode.test.tsx` - Validate workflow runtime expression behavior: - `npx vitest run --config shared/vitest.config.ts shared/workflow/runtime/__tests__/expressionEngine.test.ts shared/workflow/runtime/__tests__/mappingResolver.test.ts shared/workflow/runtime/__tests__/mappingValidator.test.ts` - Audit for deprecated invoice static catalog references: - `rg -n "templateVariableCatalog|TEMPLATE_VARIABLE_OPTIONS" packages/billing/src/components/invoice-designer -g'*.ts*'` - Audit for deprecated workflow scanner helpers: - `rg -n "extractExpressionPaths|validateExpressionPath|\\$\\{\\[^\\}\\]\\+\\}" ee/server/src/components/workflow-designer -g'*.ts*'` - Run shared expression-authoring foundational tests: - `npx vitest run --config shared/vitest.config.ts shared/workflow/expression-authoring/__tests__/coreContracts.test.ts shared/workflow/expression-authoring/__tests__/insertionDom.test.ts` - Compare invoice/workflow binding/expression surfaces: - `rg -n "binding|template|\{\{|\$expr|validateExpressionSource" packages/billing/src/components/invoice-designer ee/server/src/components/workflow-designer shared/workflow/runtime -g"*.ts*"` - Review workflow designer expression sections: - `sed -n '820,980p' ee/server/src/components/workflow-designer/WorkflowDesigner.tsx` - `sed -n '6060,6665p' ee/server/src/components/workflow-designer/WorkflowDesigner.tsx` - Review workflow runtime constraints: - `sed -n '1,220p' shared/workflow/runtime/expressionEngine.ts` - Review invoice AST compile/render paths: - `sed -n '104,340p' packages/billing/src/components/invoice-designer/ast/workspaceAst.ts` - `sed -n '210,290p' packages/billing/src/lib/invoice-template-ast/react-renderer.tsx` ## Links / References - Workflow designer: - `ee/server/src/components/workflow-designer/WorkflowDesigner.tsx` - `ee/server/src/components/workflow-designer/expression-editor/` - Workflow runtime: - `shared/workflow/runtime/expressionEngine.ts` - `shared/workflow/runtime/types.ts` - Invoice designer: - `packages/billing/src/components/invoice-designer/DesignerShell.tsx` - `packages/billing/src/components/invoice-designer/palette/ComponentPalette.tsx` - `packages/billing/src/components/invoice-designer/ast/workspaceAst.ts` - Invoice AST renderer: - `packages/billing/src/lib/invoice-template-ast/react-renderer.tsx` ## Open Questions - Should invoice roots stay domain-native (`invoice/customer/tenant/item`) or converge on workflow-style roots for long-term consistency? - Should workflow function metadata be hard-pruned immediately to runtime allowlist, or staged with compatibility warnings first? - Should shared validation include a true parser abstraction now, or remain an interface with domain-specific parser implementations? - Is invoice drag/drop path insertion in scope for this plan, or deferred after click-insert parity? - (2026-03-04) T015 validated by `DesignerShell.insertion.integration.test.tsx`: bindingKey-target insertions write raw `invoice.total` path tokens without moustache wrapping. - (2026-03-04) T016 validated by `DesignerShell.insertion.integration.test.tsx`: non-binding text targets insert moustache-wrapped tokens in template mode (`Before {{invoice.total}}`). - (2026-03-04) T017 validated by `DesignerShell.insertion.integration.test.tsx`: when no insert target is focused, insertion falls back to appending template token into selected text-node `metadata.text`. - (2026-03-04) T018 validated by `workspaceAst.test.ts` (`compiles edited moustache text into a dynamic path expression`): `{{invoice.total}}` compiles to AST `content: { type: 'path', path: 'total' }`. - (2026-03-04) T019 validated by `workspaceAst.test.ts` (`compiles mixed literal + moustache text into a template expression`): mixed text exports as AST `template` with args including `invoiceNumber` and `dueDate` paths. - (2026-03-04) T020 validated by `workspaceAst.test.ts` (`preserves expression and format semantics when importing existing AST templates`): import/export with unchanged imported content preserves original expression kinds/bindings (non-destructive text preview path). - (2026-03-04) T021 validated by `workspaceAst.test.ts` (`roundtrips exported AST deterministically (export -> import -> export)`): repeated export/import cycles are byte-stable for AST shape. - (2026-03-04) Added `DesignerShell.insertion.integration.test.tsx` coverage for T022: inserting `invoice.missingField` surfaces shared-validator feedback (`Unknown path ...`) and subsequent replacement with `invoice.total` succeeds without UI crash. - (2026-03-04) Introduced `workflow-designer/expressionValidation.ts` and test coverage proving `WorkflowDesigner.tsx` no longer references legacy `extractExpressionPaths`/`validateExpressionPath`; step config validation uses shared validator wiring. - (2026-03-04) T024 covered in `expressionValidation.test.ts`: `partitionStepExpressionValidations` consumes shared diagnostics contract (`severity`/`message`/`code`) and splits panel-facing error/warning/info groups deterministically. - (2026-03-04) T025 covered in `expressionValidation.test.ts`: `validateStepExpressions(...)` now flags `unknown.root` and `vars.missing.id` via shared validator diagnostics while accepting known `vars.previous.id`. - (2026-03-04) T026 validated by `expression-editor/__tests__/completionProvider.test.ts`: function completion items are constrained to runtime allowlisted helpers only. - (2026-03-04) T027 validated by `expression-editor/__tests__/diagnosticsProvider.test.ts`: disallowed/non-allowlisted functions are diagnosed and runtime-allowlisted helpers pass. - (2026-03-04) Added `shared/workflow/runtime/__tests__/types.exprPersistence.test.ts` for T028: step schemas persist expressions as `{ $expr: string }` objects (and reject bare string Expr inputs). - (2026-03-04) T029 validated by `shared/workflow/runtime/__tests__/expressionEngine.test.ts`: runtime validation rejects disallowed functions (e.g. `$sum(...)`). - (2026-03-04) T030 validated by `expressionEngine.test.ts`: evaluation timeout and max output-size guardrails remain enforced post-migration. - (2026-03-04) T031 added to `coreContracts.test.ts`: shared `validateSourcePaths(...)` returns `info`/`unknown-path` diagnostic for unresolved schema-aware path references (`payload.user.missing`). - (2026-03-04) T032 covered by `shared/workflow/expression-authoring/__tests__/insertionDom.test.ts`: Monaco-style insertion now verifies shared helper replacement semantics and cursor/selection placement at the inserted text end. - (2026-03-04) T033 validated via `expression-editor/__tests__/insertionText.test.ts`: trailing snippet placeholder (`$0`) is stripped before insertion, while non-trailing text remains unchanged. - (2026-03-04) T034 added to `DesignerShell.insertion.integration.test.tsx`: repeated template insertions with manual typing in between remain cursor-accurate and produce expected final text. - (2026-03-04) T035 validated by `DesignCanvas.previewMode.test.tsx`: preview rendering resolves known moustache tokens and leaves unresolved tokens visibly marked. - (2026-03-04) T036 validated by `DesignCanvas.previewMode.test.tsx` regression run (17 passing tests), covering field/table/totals preview scaffolds. - (2026-03-04) T037 validated by `server/src/test/unit/workflowRuntimeV2.unit.test.ts` (33 passing tests): workflow runtime v2 expression compile/evaluate/resolve unit suite remains green. - (2026-03-04) T038 validated by `server/src/test/integration/workflowRuntimeV2.control.integration.test.ts` + `workflowRuntimeV2.eventTrigger.integration.test.ts` (86 passing tests total). Required fixes: - improved `@alga-psa/auth` test mock in `server/src/test/setup.ts` to provide tenant-aware `withAuth`/`withOptionalAuth` context (using mocked db tenant where available), - added `@alga-psa/core` alias coverage in `server/vitest.config.ts` to resolve document dependency imports in server Vitest runs, - updated integration test tenant mocks to use `mockImplementation(() => tenantId)` for mutable tenant scenarios, - updated event-trigger tests to use trigger `sourcePayloadSchemaRef` and schema-aware invalid-payload expectations aligned with current runtime contract. - (2026-03-04) T039 validated by new repo-guard test `templateVariableCatalog.removal.test.ts`: static catalog file `constants/templateVariableCatalog.ts` is absent and no source files under invoice-designer reference legacy `constants/templateVariableCatalog` / `TEMPLATE_VARIABLE_OPTIONS`. - (2026-03-04) T040 validated by `ee/server/src/components/workflow-designer/__tests__/expressionValidation.test.ts` (3 passing tests), including explicit source assertions that `WorkflowDesigner.tsx` no longer references legacy `extractExpressionPaths` or `validateExpressionPath` helpers. - (2026-03-04) T041 validated by new meta-test `testImplementationStandards.meta.test.ts`: scoped React UI tests are enforced to import Vitest + Testing Library and, unless explicitly marked render-semantics-only, include role/automation-based selector signals. - (2026-03-04) T042 validated in one local session by running invoice + workflow smoke insertion tests: - `cd server && npx vitest run ../packages/billing/src/components/invoice-designer/DesignerShell.insertion.integration.test.tsx` (5 passing tests), - `cd ee/server && npx vitest run src/components/workflow-designer/expression-editor/__tests__/ExpressionEditor.smoke.test.tsx` (1 passing test). This proves both designers render and accept at least one inserted binding path without runtime errors. - (2026-03-04) T043 validated by new meta-guard `packages/billing/src/components/invoice-designer/nonTautologyAssertions.meta.test.ts`: - requires semantic assertion signals across scoped tests for AST behavior (`workspaceAst.test.ts` path/template checks), - model/feedback behavior (`DesignerShell.insertion.integration.test.tsx` store mutation, diagnostics, cursor assertions), - shared workflow diagnostics behavior (`ee/server/.../expressionValidation.test.ts` path attribution + severity partitioning). Verification run: - `cd server && npx vitest run ../packages/billing/src/components/invoice-designer/nonTautologyAssertions.meta.test.ts ../packages/billing/src/components/invoice-designer/DesignerShell.insertion.integration.test.tsx ../packages/billing/src/components/invoice-designer/ast/workspaceAst.test.ts` - `cd ee/server && npx vitest run src/components/workflow-designer/__tests__/expressionValidation.test.ts` - (2026-03-04) T044 validated by new meta-test `packages/billing/src/components/invoice-designer/featureTraceability.meta.test.ts`: - loads plan `features.json` + `tests.json`, - asserts every `Fxxx` in features has at least one reference in tests `featureIds`, - asserts no tests reference unknown feature IDs. Verification run: - `cd server && npx vitest run ../packages/billing/src/components/invoice-designer/featureTraceability.meta.test.ts`