You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(spec): interface pages own their view metadata directly (revise ADR-0047 iron rule) (#2050)
* feat(spec): interface pages own their view metadata directly (revise ADR-0047 iron rule)
Airtable parity: an interface (list) page now defines its data surface DIRECTLY
as metadata — columns, base filter, sort — instead of inheriting them from a
referenced object view ("sourceView"). There is no "inherit from a separate
view" concept anymore; the page IS the view definition.
Spec (packages/spec/src/ui):
- page.zod.ts — InterfacePageConfigSchema gains its own `columns`
(string[] | ListColumnSchema[]) and `sort`; `filterBy` clarified as the
always-on base filter. `sourceView` is now @deprecated, kept only as a
runtime back-compat fallback.
- page.form.ts — the inspector follows Airtable's IA: Data (Source → Columns →
Filter By → Levels) · Appearance · User filters · User actions. The
"Source View" picker is removed and replaced by a Columns field picker
(widget: field-multi, dependsOn: source). Iron-rule helptext rewritten.
Showcase migrated to the new model (define columns directly, drop sourceView):
- task-triage.page.ts, task-workbench.page.ts.
Pairs with the objectui runtime change that reads page-owned columns/sort with
a view→object fallback. Browser-verified: the page editor shows Source/Columns/
Filter By (no Source View); picking columns re-renders the preview live; both
showcase pages render their own column sets (workbench shows Estimate (h), which
the object default view omits — proving independence).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(adr-0047): revise — interface pages own columns/sort/filterBy directly (supersede iron rule)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
> **Revision (2026-06-19) — the "iron rule" is superseded.** Validating the
6
+
> Studio config panel against Airtable showed Airtable has no "inherit from a
7
+
> separate view" concept: an interface page defines its data surface (columns,
8
+
> sort, base filter, visualizations, toolbar) **directly**. ObjectStack now
9
+
> matches this — `InterfacePageConfig` carries its own `columns`/`sort`/`filterBy`
10
+
> and the page editor edits them in place. `sourceView` is deprecated and kept
11
+
> only as a runtime back-compat fallback. TL;DR #2 below no longer holds; the
12
+
> rest of the ADR (two run modes, userFilters, visualization whitelist) stands.
4
13
**Deciders**: ObjectStack Protocol Architects
5
14
**Builds on**: [ADR-0005](./0005-metadata-customization-overlay.md) (one Zod source per type, org overlay), [ADR-0017](./0017-object-has-many-view.md) (independent view entities, `viewKind`), [ADR-0019](./0019-app-as-consumer-unit.md) (App is the consumer-facing unit — navigation decides what users see), [ADR-0027](./0027-metadata-authoring-lifecycle.md) (draft · publish lifecycle), [ADR-0033](./0033-ai-assisted-metadata-authoring.md) (**AI is the long-term author of metadata — the design center this ADR inherits**)
1.**Two run modes, both first-class.***Data mode* (navigation → object): every list view of the object renders as a switcher tab; users may create personal views; the toolbar is permissive. *Interface mode* (navigation → page): an author-curated page **references** one view as its source and exposes only the controls the author enabled — filter tabs or dropdowns, a fixed (or whitelisted) visualization, selected user actions. This mirrors Airtable's Data vs Interfaces split and Power Platform's model-driven vs canvas split.
15
-
2.**The iron rule: pages reference views, never restate them.**A page's `source` points at an object/view; columns, base filter, and sort are *inherited* from the view definition. The page schema carries presentation policy only — it has no field for columns, so the "page and view each declare columns, then drift" failure mode is unrepresentable.
24
+
2.**~~The iron rule: pages reference views, never restate them.~~***(Superseded by the 2026-06-19 revision — see banner above.)* The page now defines `columns`/`sort`/`filterBy` directly (Airtable parity). Drift is avoided not by forbidding columns on the page, but by making the page the single place they live (no second view to drift against).
16
25
3.**`userFilters` becomes spec.** The end-user quick-filter surface (Airtable "User filters": element = `tabs | dropdown | toggle`, plus per-field config) is formalized in `ListViewSchema` and `InterfacePageConfig`. The client already implements and renders it (verified live, below); today it works only by accident of raw passthrough, with no type for authors and no Studio form.
17
26
4.**Runtime visualization choice is an author-controlled whitelist.**`userActions.visualizations?: boolean | ViewType[]` at view level, `visualizations` at page level (superseding the misplaced `userFilters.elements` enum). Effective options = author whitelist ∩ types whose required field bindings resolve (kanban needs a select `groupBy`, calendar a date field, …). Data mode defaults open; interface mode defaults locked.
18
27
5.**Defaults are asymmetric on purpose.** Data mode auto-derives quick filters from select/boolean fields and allows user views; interface mode is closed until the author opens it. An AI that emits *nothing* beyond objects + views + navigation gets a correct, complete system — "omission is correct" is the strongest guardrail we can give a generative author.
description: 'ADR-0047 interface mode: bind a source view and curate the end-user surface — quick filters, locked visualizations, toolbar actions (Airtable Interfaces parity).',
71
+
description: 'Interface mode (Airtable parity): the page defines its own data surface directly — columns, filters, visualizations and toolbar — no inheriting from a separate view.',
72
72
collapsible: true,
73
73
// Primary content for a list page — open by default (still collapsible).
'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.',
81
+
'The page IS the view: source picks the object, columns/filterBy are defined directly here; appearance.allowedVisualizations whitelists renderers (one entry = locked); userActions toggles the toolbar.',
82
82
// Order: common authoring controls first, rarely-used ones last.
83
83
// Explicit sub-fields so `userFilters` can use the dedicated
/** Data binding (ADR-0047: pages REFERENCE views, never restate them) */
228
229
source: z.string().optional().describe('Source object name for the page'),
229
-
sourceView: z.string().optional()
230
-
.describe('Named list view on the source object to inherit columns/filter/sort from (ADR-0047 iron rule: the page adds presentation policy only). Omit to use the object default view'),
230
+
231
+
// ADR-0047 (revised): the page carries its OWN view metadata — columns, sort
232
+
// and base filter are defined directly here (Airtable parity: there is no
233
+
// "inherit from a named view" concept). The page IS the view definition.
0 commit comments