# 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