Skip to content

Commit bd8dc4e

Browse files
os-zhuangclaude
andauthored
docs(adr): ADR-0050 — split overloaded FormView.type into layout vs presentation (#1914)
FormView.type conflates layout (simple/tabbed/wizard/split) with presentation container (drawer/modal), so "a modal containing a tabbed form" is inexpressible — which is why modal create/edit can only render simple. Presentation is already modelled elsewhere (NavigationMode, addRecord.mode, action type:'modal') and the drawer/modal/split form-type values have zero real usage (5 demo named views). Decision: FormView.type = layout only (simple/tabbed/wizard); drop drawer/modal (caller-supplied containers) and split (covered by subforms + list split-detail); ObjectForm drops the retired branches. Spec-major; implement after sign-off, bundled with modal/drawer layout-forwarding so "modal + tabbed" ships. Refs #1890 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b0df09c commit bd8dc4e

1 file changed

Lines changed: 62 additions & 0 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# ADR-0050: Form layout vs presentation — split the overloaded `FormView.type`
2+
3+
**Status**: Proposed (2026-06-15)
4+
**Deciders**: ObjectStack Protocol Architects
5+
**Builds on**: [ADR-0014](./0014-record-form-field-type.md) (record form field type), [ADR-0017](./0017-object-has-many-view.md) (object-bound views), [ADR-0049](./0049-no-unenforced-security-properties.md) (declared ≠ enforced discipline)
6+
**Consumers**: `@objectstack/spec` (`ui/view.zod.ts` `FormViewSchema`), `@object-ui/plugin-form` (`ObjectForm` + `TabbedForm`/`WizardForm`/`SplitForm`/`DrawerForm`/`ModalForm`), `@object-ui/app-shell` (`RecordFormPage`, `AppContent`, `useActionModal`), examples + templates form views.
7+
**Surfaced by**: framework #1890 (viewschema liveness audit) → objectui #1762 (full-page form layout fix) → the modelling debt found while browser-verifying it.
8+
9+
---
10+
11+
## TL;DR
12+
13+
`FormViewSchema.type` is a single enum holding **two orthogonal dimensions**:
14+
15+
| value | real meaning | dimension |
16+
|---|---|---|
17+
| `simple` / `tabbed` / `wizard` | the form's internal **layout** | **Layout** |
18+
| `drawer` / `modal` | where the form is **opened** | **Presentation / container** |
19+
| `split` | a list+detail master-detail mode | neither — a *view mode* |
20+
21+
Because it's one field, **"a modal containing a tabbed form" is inexpressible**`type` can only be `modal` *or* `tabbed`. That's exactly why the real modal create/edit entry points (`AppContent`, `useActionModal`) set `formType:'modal'` and the form inside can only ever be `simple`.
22+
23+
**Decision: split the dimensions.** `FormView.type` becomes **layout only** (`simple` | `tabbed` | `wizard`). Presentation (drawer/modal/inline/page) is already a *caller* concern and already modelled elsewhere — reuse it. `split` is removed (master-detail is already `subforms` + the list's split-detail open mode).
24+
25+
---
26+
27+
## Context — the presentation dimension already exists (3 places)
28+
29+
The audit + the #1762 browser verification established that `ObjectForm` **already implements every variant** (real `TabbedForm`/`WizardForm`/`SplitForm`; `drawer`/`modal` fall through to `DrawerForm`/`ModalForm`). The gap was entry wiring (fixed for the full-page route in #1762).
30+
31+
Crucially, "how a form is opened" is **already** spec'd, independently of `FormView.type`:
32+
33+
- **Detail open mode**`NavigationModeSchema`: `page` / `drawer` / `modal` / `split` / `popover` (`view.zod.ts`).
34+
- **Add-record mode** — list `addRecord.mode`: `inline` / `form` / `modal` + `formView`.
35+
- **Action open** — action `type:'modal'` + `target`.
36+
37+
So `drawer`/`modal`/`split` *as `FormView.type` values* are **redundant with — and orthogonal to — these**. And they have **zero real business usage**: the only definitions are 5 showcase/template *named* views built to demo variants (`app-showcase task.view` `split`/`quick`; `hotcrm lead.view` `split`/`drawer`/`modal`). No default form view, no business flow depends on them.
38+
39+
## Decision
40+
41+
1. **`FormView.type` = layout only**: `z.enum(['simple','tabbed','wizard'])` (default `simple`).
42+
2. **Remove `drawer` / `modal` from `FormView.type`.** A form is *placed in* a drawer/modal by the **caller** (list `addRecord.mode`, `NavigationMode`, action `type:'modal'`), and the container renders an `ObjectForm` whose `type` is a *layout*. This makes "modal + tabbed" expressible — the whole point.
43+
3. **Remove `split` from `FormView.type`.** Master-detail is already `subforms` (single-record parent/child) + the list's `NavigationMode:'split'` (list+detail). A form-level `split` is a third, redundant spelling.
44+
4. **`ObjectForm` drops its `drawer`/`modal`/`split` branches** (`DrawerForm`/`ModalForm` become caller-supplied containers; `SplitForm` retires in favour of `subforms`). It keeps `simple`/`tabbed`/`wizard`.
45+
46+
## Migration (low cost — breaking spec, tiny blast radius)
47+
48+
- Spec: narrow the `FormViewSchema.type` enum (spec-major: the removed values are a breaking surface change).
49+
- Renderer: remove the 3 retired `ObjectForm` branches.
50+
- The 5 demo named views (showcase `task` split/quick, hotcrm `lead` split/drawer/modal): convert to `tabbed`/`simple` layout demos, and demo the *open modes* via the list's `NavigationMode` / `addRecord.mode` instead. No business metadata changes.
51+
- Reconcile with the deferred entry wiring: modal/drawer create/edit (`AppContent`/`useActionModal`) should forward the form view's *layout* into the container (so a modal can host a tabbed form) — the concrete follow-up this re-model unlocks.
52+
53+
## Consequences
54+
55+
- **Positive.** Authors describe *structure* with `type` (layout) and *placement* with the existing open-mode fields — no overloaded field, and modal/drawer forms can finally be tabbed/wizard. Smaller, honest `FormView` surface; one master-detail spelling.
56+
- **Negative / cost.** Breaking enum narrowing (spec-major). Requires a coordinated spec + `plugin-form` + examples/templates change. Mitigated by the near-zero real usage of the removed values.
57+
- **Sequencing.** This ADR is the design; implementation is a spec-major change (like ADR-0021's cutover) and should land after architect sign-off, ideally bundled with the modal/drawer layout-forwarding follow-up so the "modal + tabbed" capability ships demonstrably.
58+
59+
## Non-goals
60+
61+
- Re-modelling `NavigationMode` / `addRecord.mode` / action `modal` — they already carry presentation; this ADR *reuses* them.
62+
- The viewschema key-drift cleanup (objectui#1763) — separate.

0 commit comments

Comments
 (0)