Add compound AND/OR switch conditions #24

Closed
opened 2026-09-17 07:48:44 +02:00 by morten-olsen · 1 comment
Owner

Context

Issue #12 added a canonical typed single-comparison condition model for Switch cases while preserving advanced expressions. Users now need a structured way to combine several typed comparisons without dropping down to a hand-authored expression.

Scope

  • Extend the canonical Switch condition model with compound condition groups.
  • Support explicit all (AND) and any (OR) groups containing typed output comparisons and nested groups.
  • Provide a mobile-first builder that makes grouping and precedence visually unambiguous without becoming a desktop query builder.
  • Reuse the existing route-aware output picker, type-constrained operator catalog, typed comparison-value editors, validation boundary, evaluator, persistence format, workflow test path, node debugger, and future authoring tools.
  • Allow users to add, remove, and reorder conditions within a group and change a group between AND and OR.
  • Preserve existing single-comparison and expression conditions without migration loss. A single comparison should remain the simple default rather than being wrapped in visible group UI unnecessarily.
  • Validate every leaf and group before save, including unavailable outputs, stale declared types, unsupported operators, malformed right-hand values, and empty or structurally invalid groups.
  • Define and enforce a bounded nesting depth and condition count so evaluation, persistence, external authoring, and the phone editor remain predictable.
  • Keep evaluation deterministic and side-effect free. Short-circuiting must not change diagnostics or observable semantics between durable execution and test/dry-run paths.

Non-goals

  • Do not add arbitrary expression fragments inside structured groups; advanced expression mode remains the escape hatch.
  • Do not add node-specific operators, implicit coercion, or cross-record aggregation.
  • Do not add a general-purpose visual programming canvas.

Acceptance

  • A user can build A AND B, A OR B, and grouped combinations such as A AND (B OR C) from typed prior outputs.
  • The editor clearly communicates group boundaries and whether all or any child conditions must match.
  • Operator availability and right-hand editors remain constrained by each selected output's declared type.
  • Empty groups, invalid leaves, unavailable outputs, type changes, excessive nesting, and excessive condition counts are blocked with actionable diagnostics and are not persisted.
  • Compound conditions produce the same result in the editor test/dry-run path and the durable workflow engine.
  • Existing expression conditions and issue #12 single-comparison conditions round-trip and execute unchanged.
  • The machine-oriented authoring contract can describe, validate, create, and edit the same compound condition structure used by the Compose editor.
  • Focused tests cover AND/OR truth tables, nested precedence, unary leaves, short-circuit behavior, invalid structures, configured bounds, persistence round trips, and legacy compatibility.

Dependency

Builds on #12.

## Context Issue #12 added a canonical typed single-comparison condition model for Switch cases while preserving advanced expressions. Users now need a structured way to combine several typed comparisons without dropping down to a hand-authored expression. ## Scope - Extend the canonical Switch condition model with compound condition groups. - Support explicit `all` (AND) and `any` (OR) groups containing typed output comparisons and nested groups. - Provide a mobile-first builder that makes grouping and precedence visually unambiguous without becoming a desktop query builder. - Reuse the existing route-aware output picker, type-constrained operator catalog, typed comparison-value editors, validation boundary, evaluator, persistence format, workflow test path, node debugger, and future authoring tools. - Allow users to add, remove, and reorder conditions within a group and change a group between AND and OR. - Preserve existing single-comparison and expression conditions without migration loss. A single comparison should remain the simple default rather than being wrapped in visible group UI unnecessarily. - Validate every leaf and group before save, including unavailable outputs, stale declared types, unsupported operators, malformed right-hand values, and empty or structurally invalid groups. - Define and enforce a bounded nesting depth and condition count so evaluation, persistence, external authoring, and the phone editor remain predictable. - Keep evaluation deterministic and side-effect free. Short-circuiting must not change diagnostics or observable semantics between durable execution and test/dry-run paths. ## Non-goals - Do not add arbitrary expression fragments inside structured groups; advanced expression mode remains the escape hatch. - Do not add node-specific operators, implicit coercion, or cross-record aggregation. - Do not add a general-purpose visual programming canvas. ## Acceptance - A user can build `A AND B`, `A OR B`, and grouped combinations such as `A AND (B OR C)` from typed prior outputs. - The editor clearly communicates group boundaries and whether all or any child conditions must match. - Operator availability and right-hand editors remain constrained by each selected output's declared type. - Empty groups, invalid leaves, unavailable outputs, type changes, excessive nesting, and excessive condition counts are blocked with actionable diagnostics and are not persisted. - Compound conditions produce the same result in the editor test/dry-run path and the durable workflow engine. - Existing expression conditions and issue #12 single-comparison conditions round-trip and execute unchanged. - The machine-oriented authoring contract can describe, validate, create, and edit the same compound condition structure used by the Compose editor. - Focused tests cover AND/OR truth tables, nested precedence, unary leaves, short-circuit behavior, invalid structures, configured bounds, persistence round trips, and legacy compatibility. ## Dependency Builds on #12.
Author
Owner

Delivered on main in 3bdfca3, with 096863b and abcf262 falling out of the same work.

Model

SwitchCondition.Group(operator, conditions) combines children under all or any, wire form {"kind":"group","operator":"all","conditions":[…]}. The expression and comparison kinds are byte-identical, so saved workflows round-trip untouched and the format version stays at 4; an older build fails closed on the unknown kind rather than mis-routing, which has its own test.

Bounds are three nested groups and thirty-two conditions per branch, enforced while decoding rather than only in validation — conditions arrive from imported workflows and the authoring contract, and an unbounded tree exhausts the stack before validation runs. Validation is one recursive collector carrying the path to the failing leaf, so a runtime error names which condition broke.

Everything named in scope was reused rather than duplicated: the route-aware output picker, the type-constrained operator catalog, the typed comparison editors, the validation boundary, the evaluator, the persistence format and the authoring contract.

Editor

Summary rows with all-of/any-of headers, nested groups indented beneath. Opening a condition replaces the sheet body with a focused output/operator/value editor rather than expanding in place. A branch holding one condition shows no grouping, and deleting back to one removes it again — the collapse is enforced in the edit model, not left to callers. Bound breaches are refused at the moment of the edit, so the editor cannot build a branch that will not save.

Acceptance

  • A AND B, A OR B and A AND (B OR C) from typed prior outputs — built and verified on a Pixel 9 emulator, not only in tests.
  • Group boundaries and all/any are communicated by the header plus indentation.
  • Operator availability and right-hand editors stay constrained by declared type (unchanged from #12).
  • Empty groups, invalid leaves, unavailable outputs, type changes, excessive nesting and excessive counts are all blocked and not persisted.
  • Dry-run and durable engine agree. They share one evaluator; short-circuiting makes that worth asserting rather than assuming, so there is now a test running both paths over a group that settles before a broken leaf and one that reaches it.
  • Legacy expressions and #12 single comparisons round-trip and execute unchanged.
  • The authoring contract describes, validates, creates and edits the same structure — the emulator workflow used for verification was authored entirely through it.

Two bugs found while building this

  • 096863b — the Switch descriptor seeded item.value == true, so a freshly added Switch failed the run on any record without a value key. Predates #12. Now defaults to true: a new branch matches everything until narrowed.
  • abcf262 — the node debugger always passed an empty upstream map, so every output-based condition failed with Output <node>/<port> is not available. This affected any node with an output binding, not just Switch; #12 just made it visible. Filed separately for the record.

Carried refactors

EditorScreen.kt split (1324 → ~730 lines) into the switch editor and the schema-field renderer; four scattered operator-id branches collapsed to one dispatch; four copies of the output-comparison-type derivation reduced to one function, which had to agree for a type-mismatch diagnostic to be correct.

Not done

Nothing in scope. Non-goals were respected: no expression fragments inside structured groups, no node-specific operators or coercion, no visual programming canvas.

Delivered on `main` in 3bdfca3, with 096863b and abcf262 falling out of the same work. ## Model `SwitchCondition.Group(operator, conditions)` combines children under `all` or `any`, wire form `{"kind":"group","operator":"all","conditions":[…]}`. The `expression` and `comparison` kinds are byte-identical, so saved workflows round-trip untouched and the format version stays at 4; an older build fails closed on the unknown kind rather than mis-routing, which has its own test. Bounds are three nested groups and thirty-two conditions per branch, enforced **while decoding** rather than only in validation — conditions arrive from imported workflows and the authoring contract, and an unbounded tree exhausts the stack before validation runs. Validation is one recursive collector carrying the path to the failing leaf, so a runtime error names which condition broke. Everything named in scope was reused rather than duplicated: the route-aware output picker, the type-constrained operator catalog, the typed comparison editors, the validation boundary, the evaluator, the persistence format and the authoring contract. ## Editor Summary rows with all-of/any-of headers, nested groups indented beneath. Opening a condition replaces the sheet body with a focused output/operator/value editor rather than expanding in place. A branch holding one condition shows no grouping, and deleting back to one removes it again — the collapse is enforced in the edit model, not left to callers. Bound breaches are refused at the moment of the edit, so the editor cannot build a branch that will not save. ## Acceptance - `A AND B`, `A OR B` and `A AND (B OR C)` from typed prior outputs — built and verified on a Pixel 9 emulator, not only in tests. - Group boundaries and all/any are communicated by the header plus indentation. - Operator availability and right-hand editors stay constrained by declared type (unchanged from #12). - Empty groups, invalid leaves, unavailable outputs, type changes, excessive nesting and excessive counts are all blocked and not persisted. - Dry-run and durable engine agree. They share one evaluator; short-circuiting makes that worth asserting rather than assuming, so there is now a test running both paths over a group that settles before a broken leaf and one that reaches it. - Legacy expressions and #12 single comparisons round-trip and execute unchanged. - The authoring contract describes, validates, creates and edits the same structure — the emulator workflow used for verification was authored entirely through it. ## Two bugs found while building this - **096863b** — the Switch descriptor seeded `item.value == true`, so a freshly added Switch *failed the run* on any record without a `value` key. Predates #12. Now defaults to `true`: a new branch matches everything until narrowed. - **abcf262** — the node debugger always passed an empty upstream map, so every output-based condition failed with `Output <node>/<port> is not available`. This affected any node with an output binding, not just Switch; #12 just made it visible. Filed separately for the record. ## Carried refactors `EditorScreen.kt` split (1324 → ~730 lines) into the switch editor and the schema-field renderer; four scattered operator-id branches collapsed to one dispatch; four copies of the output-comparison-type derivation reduced to one function, which had to agree for a type-mismatch diagnostic to be correct. ## Not done Nothing in scope. Non-goals were respected: no expression fragments inside structured groups, no node-specific operators or coercion, no visual programming canvas.
Sign in to join this conversation.
No labels
bug
enhancement
feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
incubator/macros#24
No description provided.