From 22eeccf02212a24c2fc534f8606b66b118c29d59 Mon Sep 17 00:00:00 2001 From: tolvitty Date: Fri, 13 Feb 2026 20:16:32 +0100 Subject: [PATCH] docs: add conditional logic design document Co-Authored-By: Claude Opus 4.6 --- .../2026-02-13-conditional-logic-design.md | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 docs/plans/2026-02-13-conditional-logic-design.md diff --git a/docs/plans/2026-02-13-conditional-logic-design.md b/docs/plans/2026-02-13-conditional-logic-design.md new file mode 100644 index 0000000..f4b1961 --- /dev/null +++ b/docs/plans/2026-02-13-conditional-logic-design.md @@ -0,0 +1,136 @@ +# Conditional Logic Design Document + +**Date**: 2026-02-13 +**Status**: Approved +**Plugin**: obsidian-formfire +**Phase**: 3 — Conditional Field Visibility + +## Problem + +Forms with many fields become overwhelming when not all fields are relevant in every context. A "Meeting Notes" form shouldn't show "Book Author" fields. Currently all fields are always visible — users must skip irrelevant ones manually. + +## Solution + +Add per-field conditional visibility rules with AND/OR logic. Fields can reference other fields' values to determine whether they should be shown or hidden. Hidden fields are excluded from submission. + +## Approach + +**Rule-per-Field** — each `FormField` gets an optional `conditions` object containing rules and a logic combinator. This keeps conditions co-located with the field they control and avoids a separate rules data structure. + +## Data Model + +### New Types + +```typescript +type ConditionOperator = + | 'equals' | 'not_equals' // all field types + | 'contains' | 'not_contains' // text, textarea, tags + | 'is_empty' | 'is_not_empty' // all field types + | 'greater_than' | 'less_than'; // number, rating, slider + +interface ConditionRule { + fieldId: string; // reference to another field + operator: ConditionOperator; + value?: unknown; // not needed for is_empty/is_not_empty +} + +interface FieldConditions { + logic: 'and' | 'or'; // how rules are combined + rules: ConditionRule[]; // at least 1 rule +} +``` + +### FormField Extension + +```typescript +interface FormField { + // ... existing fields ... + conditions?: FieldConditions; // optional, undefined = always visible +} +``` + +### Operator Availability by Field Type + +| Operator | text/textarea | number | toggle | date/time | dropdown | tags | note-link/folder | rating | slider | color | +|----------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| equals / not_equals | yes | yes | yes | yes | yes | — | yes | yes | yes | yes | +| contains / not_contains | yes | — | — | — | — | yes | — | — | — | — | +| is_empty / is_not_empty | yes | yes | — | yes | yes | yes | yes | yes | yes | yes | +| greater_than / less_than | — | yes | — | — | — | — | — | yes | yes | — | + +Note: `toggle` doesn't support `is_empty` since it always has a value (true/false). + +## Evaluation Engine + +New module: `src/utils/condition-engine.ts` + +```typescript +evaluateConditions( + conditions: FieldConditions, + values: Record +): boolean +``` + +### Logic + +1. Each `ConditionRule` is evaluated against the current value of the referenced field +2. Results are combined: `and` = all must be true, `or` = at least one must be true +3. Return: `true` = show field, `false` = hide field + +### Operator Semantics + +- `equals` / `not_equals` — string coercion for comparison, strict for booleans +- `contains` / `not_contains` — substring check for strings, element check for arrays (tags) +- `is_empty` / `is_not_empty` — `null`, `undefined`, `""`, `[]` count as empty +- `greater_than` / `less_than` — numeric comparison (parseFloat) + +### Cycle Prevention + +Fields can only reference fields that appear **before** them in the field list. This prevents circular dependencies by design and is enforced in the builder UI (field dropdown only shows preceding fields). + +## Reactivity in Form Modal + +1. **Change listeners** on every field — value changes trigger `reevaluateVisibility()` +2. `reevaluateVisibility()` iterates all fields with `conditions`, evaluates them, toggles `display: none` +3. **Hidden fields excluded from submit** — values not written to frontmatter +4. **Validation skips hidden fields** — a required field that is hidden does not block submission + +## Builder UI + +### Per-Field Conditions Editor + +Located below existing field settings, collapsible: + +**Collapsed**: Shows summary like `"Show if: type = Meeting"` or `"Always visible"` + +**Expanded**: +1. **Logic dropdown** at top: `ALL conditions match` (AND) / `ANY condition matches` (OR) +2. **Rule rows**, each containing: + - Field dropdown (only fields before current field) + - Operator dropdown (filtered by selected field's type) + - Value input (type-dependent: text input, dropdown with options, number input, toggle; hidden for is_empty/is_not_empty) + - Delete button (×) +3. **"+ Add condition"** button + +### Live Preview Behavior + +Fields with unmet conditions are shown **grayed out with a "conditional" badge** in the builder preview (not fully hidden, so the builder user can see all fields exist). + +## Files Changed + +| File | Change | +|------|--------| +| `src/types.ts` | New types: `ConditionOperator`, `ConditionRule`, `FieldConditions`; add `conditions?` to `FormField` | +| `src/utils/condition-engine.ts` | **New** — `evaluateConditions()`, operator logic, helpers | +| `src/utils/validators.ts` | Accept visibility map, skip hidden fields | +| `src/ui/form-modal.ts` | Change listeners, `reevaluateVisibility()`, exclude hidden fields from submit | +| `src/ui/form-builder.ts` | Conditions editor UI per field (collapsed/expanded, rule editor) | +| `src/ui/field-renderers.ts` | No changes — conditions operate one level above | +| `styles.css` | Condition editor styles, conditional badge in preview, show/hide transitions | + +## Out of Scope + +- Nested condition groups (AND inside OR) — one level is sufficient +- Form-level conditions (hide entire form) +- Actions other than show/hide (e.g. set value, change required) +- Migration — `conditions` is optional, existing forms work unchanged