docs: add conditional logic design document

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Luca Oelfke 2026-02-13 20:16:32 +01:00
parent ae2285c229
commit 22eeccf022

View file

@ -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<string, unknown>
): 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