obsidian-formfire/docs/plans/2026-02-13-conditional-logic-design.md
tolvitty 22eeccf022 docs: add conditional logic design document
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 20:16:32 +01:00

136 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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