Skip to content

Commit 8248501

Browse files
os-zhuangclaude
andcommitted
feat(spec): page form uses filter-mode widget for userFilters + ADR-0047 §3.4a
page.form.ts: the Interface section's interfaceConfig composite now lists its sub-fields explicitly so userFilters can use the dedicated 'filter-mode' selector widget (None / Tabs / Dropdown / Toggle, objectui). ADR-0047 §3.4a records the design decision: 'no filter bar' is the ABSENCE of userFilters, not a literal element: 'none'. Rationale — declarative-metadata hygiene (off = key absent, like every other optional capability), no orphaned sub-config, clean overlay diffs, presence and style are orthogonal axes. The authoring UI provides the explicit None/Tabs/Dropdown/Toggle affordance; storage stays clean. Pairs with objectui#1694 (the filter-mode widget). An unknown widget name degrades gracefully to the prior composite rendering, so this is independently mergeable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 060467a commit 8248501

2 files changed

Lines changed: 51 additions & 1 deletion

File tree

docs/adr/0047-object-ui-run-modes.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,35 @@ have; we do not rely on lint to forbid what the type can simply not express.
153153
| User-created views | Allowed, governed by permissions + view `sharing` | Never |
154154
| Filter state | Session-scoped (URL-param sync is a later, orthogonal step) | Session-scoped |
155155

156+
### 3.4a "No filter bar" is omission, not a literal `element: 'none'`
157+
158+
Airtable's User-filters control is a single tri-state selector
159+
(**None / Tabs / Dropdown**). We deliberately do **not** mirror that as a
160+
literal enum value. The `userFilters.element` enum stays
161+
`dropdown | tabs | toggle`; **"none" is the ABSENCE of `userFilters`**.
162+
163+
Rationale (declarative-metadata hygiene):
164+
165+
- **Consistency.** Every optional capability in the protocol is "off = key
166+
absent" (`kanban`, `grouping`, …). A literal `element: 'none'` would be the
167+
one special case authors and tooling must learn.
168+
- **No dead config.** `element: 'none'` would leave an object whose `fields` /
169+
`tabs` are orphaned — undefined semantics for validation, overlay merge, and
170+
AI generation. Omission has one unambiguous meaning.
171+
- **Cleaner diffs / overlays.** Disabling the bar is a key deletion (ADR-0005
172+
overlay semantics), not a value mutation dragging stale sub-config along.
173+
- **Orthogonal axes.** "Is there a filter bar?" (presence) and "what style?"
174+
(`element`) are independent; one enum would couple them, and we carry four
175+
styles (`+toggle`) to Airtable's three.
176+
177+
**Storage and authoring UI are separate layers.** The Studio editor exposes a
178+
first-class **None / Tabs / Dropdown / Toggle** segmented selector (the
179+
`filter-mode` widget) — selecting *None* writes `onChange(undefined)`, removing
180+
the key. Authors get Airtable's explicit affordance; the protocol stays clean.
181+
182+
If a "disable but remember the configured fields/tabs" need ever arises, the
183+
right shape is a separate `enabled: false` flag — never `element: 'none'`.
184+
156185
### 3.5 AI authoring rules (inherits ADR-0033's draft gate)
157186

158187
1. **Default output**: objects + list views + navigation → objects. *No pages.*

packages/spec/src/ui/page.form.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,28 @@ export const pageForm = defineForm({
5050
field: 'interfaceConfig',
5151
type: 'composite',
5252
helpText:
53-
'source/sourceView bind the object view (columns, base filter and sort are inherited — the iron rule); userFilters picks the element style (dropdown / tabs / toggle) and exposed fields; appearance.allowedVisualizations whitelists renderers (one entry = locked); userActions toggles the toolbar.',
53+
'source/sourceView bind the object view (columns, base filter and sort are inherited — the iron rule); appearance.allowedVisualizations whitelists renderers (one entry = locked); userActions toggles the toolbar.',
54+
// Explicit sub-fields so `userFilters` can use the dedicated
55+
// filter-mode selector (None / Tabs / Dropdown / Toggle, ADR-0047).
56+
// None maps to ABSENCE of userFilters — the protocol stores
57+
// "no filter bar" as omission, not a literal element: 'none'.
58+
// Keep this list in sync with InterfacePageConfigSchema.
59+
fields: [
60+
{ field: 'source' },
61+
{ field: 'sourceView' },
62+
{ field: 'levels' },
63+
{ field: 'filterBy', type: 'repeater' },
64+
{ field: 'appearance', type: 'composite' },
65+
{
66+
field: 'userFilters',
67+
widget: 'filter-mode',
68+
helpText: 'End-user filter bar: None (no bar) / Tabs (named presets) / Dropdown (per-field) / Toggle. None removes the config.',
69+
},
70+
{ field: 'userActions', type: 'composite' },
71+
{ field: 'addRecord', type: 'composite' },
72+
{ field: 'showRecordCount' },
73+
{ field: 'allowPrinting' },
74+
],
5475
},
5576
],
5677
},

0 commit comments

Comments
 (0)