Skip to content

Commit 569d4d2

Browse files
feat(spec)!: form layout accepts only vertical | horizontal — the inline and grid arms retired (#20221) (#20262)
Fixes #20221 Clause-②: no (narrowing) Retires the `inline` and `grid` arms of form `layout` (ADR-0049 enforce-or-remove), per the triage direction on the card (comment `5855767378`) under the maintainer's family criterion on #18900: the capability `grid` names already exists under another key (`columns`), and `inline` is not a record-form layout, so both are redundant vocabulary and retire with no alias window. ## What changed - **Both schemas narrowed to `'vertical' | 'horizontal'`.** `ObjectFormPropsSchema.layout` (the `object-form` page component) and `FormViewSchema.layout` (`view.form`, `view.formViews.*`, a form view item's `config`). Each enum carries a per-value error map keyed on the input (the `record:chatter` `position` precedent): `'grid'` and `'inline'` are refused with `invalid_value` at `layout` and a prescription (write `'vertical'` or omit `layout`; for multi-column set `columns`), while a never-vocabulary value keeps zod's own enum refusal. - **ADR-0087 D2 conversion `form-layout-inline-grid-to-vertical`** (protocol 18, `retiredFromLoadPath: true`): rewrites both values to `'vertical'` and leaves `columns` untouched, on `object-form` page components (regions, slots, nesting), on every form payload a `views` entry carries (`mapViewPayloads`, form kind only), and on the assembled-manifest `viewItems` channel (record `config` or flattened form overlay). Wired into the protocol-18 chain step; the step's `rationale` is extended. - **One D3 entry for the family**, `ui-form-layout-inline-grid-retired` (ruling B on #17152: one semantic entry per retirement family even when D2 is lossless). It carries the one judgement the chain cannot make: whether a form that said `grid` without `columns` wanted more than one column. `registry.ts` regenerated with `gen:migration-registry`. - **Descriptions**: the `columns` describe on `object-form` no longer says "grid layout"; both `layout` describes name `columns` as where multi-column lives. - **Regenerated, never hand-edited**: `content/docs/references/{api/protocol,ui/component,ui/view}.mdx` and `skills/objectstack-ui/references/react-blocks.md` (the `layout` row of the `ObjectForm` block). - **Hand-written**: `content/docs/protocol/objectui/layout-dsl.mdx` (one sentence listed the four-arm enum), the `form.layout` liveness row in `packages/spec/liveness/view.json` (still `live`; note re-anchored at the pin, `verifiedAt` stamped), and the changeset. - **Pins**: `packages/spec/src/ui/form-layout-inline-grid-retired.test.ts` (29 tests). ## Mechanism hypotheses, measured - **H1 — only these two sites declare the four-arm form enum: CONFIRMED.** `git grep` over `packages/spec/src` at the merge base `ab820016b3` for a `layout` enum with `'inline'` / `'grid'` arms: exactly `ui/component.zod.ts` (`ObjectFormPropsSchema.layout`) and `ui/view.zod.ts` (`FormViewSchema.layout`). The other `'grid'` hits are the view-type vocabulary (`ListView.type`, `inlineEdit`, the object designer), left untouched. - **H1, the flattened overlay: CONFIRMED, it inherits the narrowing.** `VIEW_METADATA_MEMBERS.formOverlay` is built from `FormViewSchema` (`.extend` at the base; `.safeExtend` after #20186 landed and was merged into this branch), so the flattened form overlay refuses both values with the same prescription. Pinned through `VIEW_METADATA_MEMBERS.formOverlay`; the union and its members were not edited. - **H2 — producers: zero, re-measured with lit controls.** Matcher: `layout` followed by `grid` or `inline` as a value, quoted or bare, across every tracked file (TS, JSON, YAML, Markdown), tests excluded. At `ab820016b3`: 0 hits. Controls on the same tree: the same matcher with `vertical|horizontal` hits 2 (conversion fixtures); `layout:` under `examples/**` hits 53; `formViews` under `examples/**` hits 35; the one authored `object-form` in `examples/` and `apps/` (the showcase create wizard) authors no `layout`. At the merged head `2d0b7a6bcb` the only subject hits are this PR's own prose and fixture. - **Renderer read points, re-measured at the `.objectui-sha` pin `f8a9d0fb0`** (recorded beside the prescriptions): the simple arm folds both values to `vertical` (`ObjectForm.tsx:1406-1410`); the drawer/modal hand-off (`:463`, `:499`), `DrawerForm.tsx:575` and `ModalForm.tsx:597` pass only `vertical` / `horizontal`; `TabbedForm.tsx:556`, `SplitForm.tsx:445`, `WizardForm.tsx:1075` hard-code `vertical`. The pinned sibling imports neither enum for a value assignment, so the pin build is unaffected. ## Verification (merged head `2d0b7a6bcb`) - `pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2`: **550 files passed, 16177 tests passed, 2 todo.** - `pnpm --filter @objectstack/spec typecheck` (tsc, scripts-typecheck, test-typecheck): exit 0; test layer "53 file(s) / 255 error(s) / 142 pinned signature(s) held". - `pnpm --filter @objectstack/spec build` then `check:generated`: **"All 15 generated artifacts are up to date"**. `authorable-surface/`, `api-surface/` and `json-schema.manifest/` are byte-unchanged, as expected for an enum-value narrowing (the ratchets record key and export existence, not value sets). `spec-changes.json` and the upgrade guide are unchanged because the protocol-18 step is not yet cut. - Gate families derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at `2d0b7a6bcb` (117 families, the same set as before the merge): **115 run, all exit 0; 2 NOT MEASURED; 0 unrun** (`--ran` reconciliation). Among them: `check:adr-0087-registration` ("registered form-layout-inline-grid-to-vertical, ui-form-layout-inline-grid-retired (new here: both)"), `check:changeset-no-major`, `check:empty-changeset`, `check:doc-authoring`, `check:liveness`, `check:objectui-pin-citations`, `check:react-blocks`, `check:skill-examples`, `check:nul-bytes`, `check:cross-package-test-inputs`. - NOT MEASURED: `check:dual-build-cjs-loads` and `check:type-check-debt`. Both refuse with their own prerequisite (exit 3): every workspace package's built output, which this worktree did not build on the shared box. The diff adds no import, export or entry point, and no ledgered package reads or writes the retired values (census above). CI builds that closure. - **Ablation** (one-time proof, taken at `41ceb7e5d6` before the merge, which touched neither enum nor the pin file): both enums re-admitted `'inline'` and `'grid'` through `scripts/ablation-replace.mjs` (anchor 1 to 0 on each file, blob `b6d256b45a8a` to `0787a83d3806` and `33d6fd43be7c` to `8514b7afecb3`), then the pin file ran: **14 failed, 15 passed**. Red: all 10 refusal pins (2 values times 5 doors), the two-surface vocabulary pin, and the 3 conversion pins that assert the input is refused before the rewrite. Green: the 5 `vertical`/`horizontal` control pairs, the 5 never-vocabulary pins, the load-path, non-carrier and registration pins. Restore proven: both blobs equal HEAD and `git diff HEAD` is empty. - `skills/**` readings: `skills/objectstack-ui/references/react-blocks.md` 115 lines before and after (one row regenerated in place); the published catalog's SKILL.md files, 10 files, 4402 lines before and after. ## Acceptance notes - **objectui follow-through (on the objectui pin bump, not filed here):** the page-designer inspector's `object-form` layout select (still offers Inline and Grid), the `object-form` registry `inputs` enum (`plugin-form/src/index.tsx:238` at the pin), `ObjectFormSchema.layout` and its docblock, the fold sites that become plain pass-through, the `FormSchema` mirror's `'grid'` (its `WiderThanDeclared` row), and the docs listing four arms. This is objectui#7759 group C. - **Rewrite, not strip.** The card's own option R proposed stripping the key; the triage direction and the dispatch rule a rewrite to `'vertical'`. Both are behaviour-preserving (`'vertical'` is the renderer default); the rewrite was followed. - **Clause-② line.** Triage's note 4 wrote `yes`; the claim wrote `no`, since the accept set only narrows. This PR and its changeset carry `no (narrowing)`. - **Governed path.** `skills/objectstack-ui/references/react-blocks.md` is regenerated output under `skills/**` (Tier H), so this PR waits on a maintainer approval. It is not split out: CI's react-blocks gate re-derives it from the schema. - **Hand-written docs page beyond the claim's file list:** `content/docs/protocol/objectui/layout-dsl.mdx`, one sentence that enumerated the four arms. - **Observation, no carrier:** `horizontal` renders byte-identical to `vertical` on the drawer, modal and tabbed presentations, and on the simple form changes only the action row's justification (the card's "Not in this card" measurement). Not in scope here. ## 维护者速读(草稿) **改了什么**:表单的 `layout`(字段布局)只保留 `vertical`(纵向)和 `horizontal`(横向)两个取值,删掉 `inline` 和 `grid`。两处都改:页面上的 `object-form` 组件,以及视图里的表单定义。写了这两个值的元数据现在会在保存/校验时被明确拒绝,报错里直接告诉作者该怎么写:改成 `vertical`,想要多列就设 `columns`。已存的旧数据会被自动改写成 `vertical`(`columns` 原样保留),不会因此加载失败。 **为什么改**:这两个值从来没有生效过。前端所有表单形态都把它们当成 `vertical` 渲染,效果完全一样。也就是说,作者(尤其是 AI)写了一个看起来有意义、实际被丢掉的配置,还能通过校验。多列表单本来就由 `columns` 控制,而 `inline` 是工具栏/筛选行的形态,不是记录表单的布局。按您在 #18900 定下的判据,这属于重复词汇,直接退役,不留过渡期。 **风险与代价(含回滚)**:仓库内与示例应用里没有任何地方写过这两个值(已实测,并有对照),所以对现有应用没有影响。外部如有人写了,会在校验时看到明确的修改提示;已存数据由转换层自动改写,渲染结果与之前完全相同。代价是前端(objectui)的设计器下拉框和组件注册里还列着这两个选项,要在下次同步 objectui 版本时一并删掉,本 PR 已在 Acceptance notes 里写明。回滚就是 revert 本 PR,没有数据迁移需要撤销。 **席位意见**: **你要做的**:批准此 PR。 --- _Generated by [Claude Code](https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2972097 commit 569d4d2

13 files changed

Lines changed: 700 additions & 25 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
feat(spec)!: retire the `inline` and `grid` arms of form `layout` — every renderer folded both to `vertical`, and multi-column is `columns` (ADR-0049)
6+
7+
<!-- adr-0087: registered form-layout-inline-grid-to-vertical, ui-form-layout-inline-grid-retired -->
8+
9+
**BREAKING** — form `layout` accepts exactly `'vertical' | 'horizontal'` on both surfaces
10+
that declared the four-arm enum: the `object-form` page component
11+
(`ObjectFormPropsSchema.layout`) and the form view (`FormViewSchema.layout` — `view.form`,
12+
`view.formViews.*`, a form view item's `config`, and the flattened form overlay, which
13+
spreads the form view's shape). `'inline'` and `'grid'` are refused at parse, each with a
14+
prescription naming what to write instead.
15+
16+
| surface | before | after |
17+
|:--|:--|:--|
18+
| `object-form` `layout: 'grid'` | parsed clean, rendered as `vertical` | refused — write `'vertical'` (or omit `layout`); for multi-column set `columns` |
19+
| `object-form` `layout: 'inline'` | parsed clean, rendered as `vertical` | refused — write `'vertical'` (or omit `layout`) |
20+
| form view `layout: 'grid'` | parsed clean, rendered as `vertical` | refused — write `'vertical'` (or omit `layout`); for multi-column set `columns` |
21+
| form view `layout: 'inline'` | parsed clean, rendered as `vertical` | refused — write `'vertical'` (or omit `layout`) |
22+
| `layout: 'vertical'` / `'horizontal'` | accepted | **unchanged** |
23+
| `columns` | honoured under every layout | **unchanged** — the key multi-column always lived under |
24+
25+
**What was actually wrong.** No renderer ever gave either value a behaviour of its own.
26+
Measured at the `.objectui-sha` pin this repo builds against (`f8a9d0fb0`): the simple
27+
`object-form` arm folds both to `vertical` (`ObjectForm.tsx:1406-1410`, under the comment
28+
"Map 'grid' and 'inline' to 'vertical' as fallback"); the drawer and modal arms
29+
(`ObjectForm.tsx:463`, `:499`) and `DrawerForm.tsx:575` / `ModalForm.tsx:597` pass only
30+
`vertical` / `horizontal` through; `TabbedForm.tsx:556`, `SplitForm.tsx:445` and
31+
`WizardForm.tsx:1075` hard-code `vertical`. So both values were a green parse for a value the
32+
renderer threw away. The spec had admitted them from two declarations — the designer palette
33+
and the registry `inputs` offered all four — never from a read.
34+
35+
Under the maintainer's ADR-0049 family criterion (the capability exists on mainstream
36+
platforms ⇒ build the consumer; it does not ⇒ retire), multi-column — what `grid` would
37+
mean — already exists here under another key, `columns`, which the renderer honours under
38+
every arm; `inline` is a toolbar / filter-row pattern, not a record-form layout. The two arms
39+
are redundant vocabulary, retired with no alias window.
40+
41+
## What to write instead
42+
43+
```ts
44+
// before — parsed clean, rendered single-column 'vertical'
45+
{ type: 'object-form', properties: { objectName: 'crm_lead', layout: 'grid' } }
46+
// after — what it rendered; add `columns` if a multi-column form was the intent
47+
{ type: 'object-form', properties: { objectName: 'crm_lead', layout: 'vertical', columns: 2 } }
48+
```
49+
50+
`'inline'` → `'vertical'` (or delete `layout`: `'vertical'` is the renderer default). A form
51+
that wrote `'grid'` without `columns` always rendered single-column; only its author knows
52+
whether more columns were meant — set `columns` to the count you meant.
53+
54+
Existing sources: `os migrate meta --from 17` lists the mechanical edits; apply them by hand.
55+
56+
The retirement kit:
57+
58+
- both enums narrowed to `'vertical' | 'horizontal'`, each with a per-value error map keyed on
59+
the input (the `record:chatter` `position` precedent), so only a value that used to be legal
60+
is told it "was removed"; a never-vocabulary value keeps zod's own enum refusal
61+
- the D2 conversion `form-layout-inline-grid-to-vertical` (protocol 18, retired from the load
62+
path) rewrites both values to `'vertical'` and leaves `columns` untouched — on `object-form`
63+
page components, on every form payload a view carries, and on the assembled-manifest
64+
`viewItems` channel, so stored rows and assembled artifacts replay clean; wired into the
65+
protocol-18 chain step
66+
- one D3 entry for the family, `ui-form-layout-inline-grid-retired`, carrying the one judgement
67+
the chain cannot make: whether a form that said `grid` wanted columns it never declared
68+
- pin tests (`ui/form-layout-inline-grid-retired.test.ts`): both values refused with the
69+
prescription on five doors (`ObjectFormPropsSchema`, the `object-form` props-map row,
70+
`FormViewSchema`, a view container's `formViews`, the flattened form overlay), `vertical` /
71+
`horizontal` green on each as controls, the conversion's rewrite parsing green on the door
72+
that refused its input, and the chain registration
73+
- the `columns` descriptions no longer say "grid layout"; the generated references
74+
(`content/docs/references/**`, `skills/objectstack-ui/references/react-blocks.md`, the JSON
75+
Schema) print the two-arm enum; the hand-written `layout-dsl` page and the `form.layout`
76+
liveness row (still `live`) are updated
77+
- `api-surface/` and `authorable-surface/` are unchanged, correctly: they ratchet export and
78+
key existence, and no export or key leaves — `layout` is still declared, two values narrower
79+
80+
Clause-②: no (narrowing)

‎content/docs/protocol/objectui/layout-dsl.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -544,8 +544,8 @@ The tab *is* the section, so a section's keys are the whole surface: `name`,
544544
`visibleWhen` — plus `pane`, which split forms alone accept. There is **no**
545545
per-tab `lazy`, `source`, `badge` or `badgeVariant` key, and no
546546
`layout: { mode: tabbed, ... }` wrapper: `FormViewSchema.layout` is a string
547-
enum (`vertical` / `horizontal` / `inline` / `grid`) and the form schema
548-
declares no `tabs` key at all. `FormViewSchema` and `FormSectionSchema`
547+
enum (`vertical` / `horizontal` — multi-column is the separate `columns` key,
548+
not a layout value) and the form schema declares no `tabs` key at all. `FormViewSchema` and `FormSectionSchema`
549549
(`packages/spec/src/ui/view.zod.ts`) are `.strict()`, so authoring any of them
550550
is a **parse failure** — a loud rejection, not a silent no-op.
551551

‎content/docs/references/api/protocol.mdx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1644,9 +1644,9 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
16441644
| **label** | `string \| Record<string, string>` | optional | Human-readable label shown in metadata lists. |
16451645
| **object** | `string` | optional | Object this container binds to — how a stack-level `views: [...]` entry says which object its views belong to; read by `getViewsByObject()` / `GET /meta/view?object=`. |
16461646
| **list** | `{ name?: string; label?: string \| Record<string, string>; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: object \| … +3 more; … }` | optional | |
1647-
| **form** | `{ type?: Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>; layout?: Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>; columns?: integer; title?: string; … }` | optional | |
1647+
| **form** | `{ type?: Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>; layout?: Enum<'vertical' \| 'horizontal'>; columns?: integer; title?: string; … }` | optional | |
16481648
| **listViews** | `Record<string, { name?: string; label?: string \| Record<string, string>; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: object \| … +3 more; … }>` | optional | Additional named list views (views mode — dropdown userFilters allowed, no tabs; ADR-0047) |
1649-
| **formViews** | `Record<string, { type?: Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>; layout?: Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>; columns?: integer; title?: string; … }>` | optional | Additional named form views |
1649+
| **formViews** | `Record<string, { type?: Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>; layout?: Enum<'vertical' \| 'horizontal'>; columns?: integer; title?: string; … }>` | optional | Additional named form views |
16501650
| **protection** | `{ lock: Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>; reason: string; docsUrl?: string }` | optional | Package author protection block — lock policy for this view. |
16511651
| **_lock** | `Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>` | optional | Item-level lock — controls overlay & delete (ADR-0010). |
16521652
| **_lockReason** | `string` | optional | Human-readable reason shown when a write is refused by _lock. |
@@ -1716,7 +1716,7 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
17161716
| Property | Type | Required | Description |
17171717
| :--- | :--- | :--- | :--- |
17181718
| **type** | `Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>` | optional (default: `"simple"`) | |
1719-
| **layout** | `Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>` | optional | Field layout direction |
1719+
| **layout** | `Enum<'vertical' \| 'horizontal'>` | optional | Field layout direction — 'vertical' (the renderer default) or 'horizontal'. Multi-column is not a layout value: set `columns` |
17201720
| **columns** | `integer` | optional | Number of columns for the form body |
17211721
| **title** | `string` | optional | Form title |
17221722
| **description** | `string` | optional | Form description |
@@ -1801,7 +1801,7 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
18011801
| Property | Type | Required | Description |
18021802
| :--- | :--- | :--- | :--- |
18031803
| **type** | `Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>` | optional (default: `"simple"`) | |
1804-
| **layout** | `Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>` | optional | Field layout direction |
1804+
| **layout** | `Enum<'vertical' \| 'horizontal'>` | optional | Field layout direction — 'vertical' (the renderer default) or 'horizontal'. Multi-column is not a layout value: set `columns` |
18051805
| **columns** | `integer` | optional | Number of columns for the form body |
18061806
| **title** | `string` | optional | Form title |
18071807
| **description** | `string` | optional | Form description |

‎content/docs/references/ui/component.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,8 +362,8 @@ Sort field and direction pair
362362
| **recordId** | `string \| number` | optional | Record to load (edit/view modes) |
363363
| **mode** | `Enum<'create' \| 'edit' \| 'view'>` | optional | Form mode |
364364
| **formType** | `Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>` | optional | Form presentation |
365-
| **layout** | `Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>` | optional | Field layout |
366-
| **columns** | `number` | optional | Field columns in grid layout |
365+
| **layout** | `Enum<'vertical' \| 'horizontal'>` | optional | Field layout — 'vertical' (the renderer default) or 'horizontal'. Multi-column is not a layout value: set `columns` |
366+
| **columns** | `number` | optional | Number of field columns (multi-column forms), honoured under either `layout` |
367367
| **fields** | `any[]` | optional | Limit/order the fields shown |
368368
| **customFields** | `any` | optional | Custom field definitions merged into the generated set |
369369
| **sections** | `any[]` | optional | Form sections (`{ label, description?, fields }` — wizard steps / tab panes) |

‎content/docs/references/ui/view.mdx‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ Form-view select option — the object-field option shape minus the per-option `
401401
| Property | Type | Required | Description |
402402
| :--- | :--- | :--- | :--- |
403403
| **type** | `Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>` | optional (default: `"simple"`) | |
404-
| **layout** | `Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>` | optional | Field layout direction |
404+
| **layout** | `Enum<'vertical' \| 'horizontal'>` | optional | Field layout direction — 'vertical' (the renderer default) or 'horizontal'. Multi-column is not a layout value: set `columns` |
405405
| **columns** | `integer` | optional | Number of columns for the form body |
406406
| **title** | `string` | optional | Form title |
407407
| **description** | `string` | optional | Form description |
@@ -1778,9 +1778,9 @@ Tab configuration for multi-tab view interface
17781778
| **label** | `string \| Record<string, string>` | optional | Human-readable label shown in metadata lists. |
17791779
| **object** | `string` | optional | Object this container binds to — how a stack-level `views: [...]` entry says which object its views belong to; read by `getViewsByObject()` / `GET /meta/view?object=`. |
17801780
| **list** | `{ name?: string; label?: string \| Record<string, string>; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: object \| … +3 more; … }` | optional | |
1781-
| **form** | `{ type?: Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>; layout?: Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>; columns?: integer; title?: string; … }` | optional | |
1781+
| **form** | `{ type?: Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>; layout?: Enum<'vertical' \| 'horizontal'>; columns?: integer; title?: string; … }` | optional | |
17821782
| **listViews** | `Record<string, { name?: string; label?: string \| Record<string, string>; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: object \| … +3 more; … }>` | optional | Additional named list views (views mode — dropdown userFilters allowed, no tabs; ADR-0047) |
1783-
| **formViews** | `Record<string, { type?: Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>; layout?: Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>; columns?: integer; title?: string; … }>` | optional | Additional named form views |
1783+
| **formViews** | `Record<string, { type?: Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>; layout?: Enum<'vertical' \| 'horizontal'>; columns?: integer; title?: string; … }>` | optional | Additional named form views |
17841784
| **protection** | `{ lock: Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>; reason: string; docsUrl?: string }` | optional | Package author protection block — lock policy for this view. |
17851785
| **_lock** | `Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>` | optional | Item-level lock — controls overlay & delete (ADR-0010). |
17861786
| **_lockReason** | `string` | optional | Human-readable reason shown when a write is refused by _lock. |
@@ -1850,7 +1850,7 @@ Tab configuration for multi-tab view interface
18501850
| Property | Type | Required | Description |
18511851
| :--- | :--- | :--- | :--- |
18521852
| **type** | `Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>` | optional (default: `"simple"`) | |
1853-
| **layout** | `Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>` | optional | Field layout direction |
1853+
| **layout** | `Enum<'vertical' \| 'horizontal'>` | optional | Field layout direction — 'vertical' (the renderer default) or 'horizontal'. Multi-column is not a layout value: set `columns` |
18541854
| **columns** | `integer` | optional | Number of columns for the form body |
18551855
| **title** | `string` | optional | Form title |
18561856
| **description** | `string` | optional | Form description |
@@ -1935,7 +1935,7 @@ Tab configuration for multi-tab view interface
19351935
| Property | Type | Required | Description |
19361936
| :--- | :--- | :--- | :--- |
19371937
| **type** | `Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>` | optional (default: `"simple"`) | |
1938-
| **layout** | `Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>` | optional | Field layout direction |
1938+
| **layout** | `Enum<'vertical' \| 'horizontal'>` | optional | Field layout direction — 'vertical' (the renderer default) or 'horizontal'. Multi-column is not a layout value: set `columns` |
19391939
| **columns** | `integer` | optional | Number of columns for the form body |
19401940
| **title** | `string` | optional | Form title |
19411941
| **description** | `string` | optional | Form description |
@@ -2187,7 +2187,7 @@ This schema accepts one of the following structures:
21872187
| Property | Type | Required | Description |
21882188
| :--- | :--- | :--- | :--- |
21892189
| **viewKind** | `'form'` | ✅ | |
2190-
| **config** | `{ type?: Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>; layout?: Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>; columns?: integer; title?: string; … }` | ✅ | Form view configuration. |
2190+
| **config** | `{ type?: Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>; layout?: Enum<'vertical' \| 'horizontal'>; columns?: integer; title?: string; … }` | ✅ | Form view configuration. |
21912191
| **name** | `string` | ✅ | Globally-unique view id, `<object>.<viewKey>`. |
21922192
| **object** | `string` | ✅ | Bound object name — the foreign key used to aggregate views. |
21932193
| **label** | `string \| Record<string, string>` | optional | Display label (supports i18n). |
@@ -2210,7 +2210,7 @@ This schema accepts one of the following structures:
22102210
| Property | Type | Required | Description |
22112211
| :--- | :--- | :--- | :--- |
22122212
| **type** | `Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>` | optional (default: `"simple"`) | |
2213-
| **layout** | `Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>` | optional | Field layout direction |
2213+
| **layout** | `Enum<'vertical' \| 'horizontal'>` | optional | Field layout direction — 'vertical' (the renderer default) or 'horizontal'. Multi-column is not a layout value: set `columns` |
22142214
| **columns** | `integer` | optional | Number of columns for the form body |
22152215
| **title** | `string` | optional | Form title |
22162216
| **description** | `string` | optional | Form description |
@@ -2370,7 +2370,7 @@ This schema accepts one of the following structures:
23702370
| Property | Type | Required | Description |
23712371
| :--- | :--- | :--- | :--- |
23722372
| **viewKind** | `'form'` | ✅ | |
2373-
| **config** | `{ type?: Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>; layout?: Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>; columns?: integer; title?: string; … }` | ✅ | Form view configuration. |
2373+
| **config** | `{ type?: Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>; layout?: Enum<'vertical' \| 'horizontal'>; columns?: integer; title?: string; … }` | ✅ | Form view configuration. |
23742374
| **name** | `string` | ✅ | Globally-unique view id, `<object>.<viewKey>`. |
23752375
| **object** | `string` | ✅ | Bound object name — the foreign key used to aggregate views. |
23762376
| **label** | `string \| Record<string, string>` | optional | Display label (supports i18n). |
@@ -2396,7 +2396,7 @@ This schema accepts one of the following structures:
23962396
| Property | Type | Required | Description |
23972397
| :--- | :--- | :--- | :--- |
23982398
| **type** | `Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>` | optional (default: `"simple"`) | |
2399-
| **layout** | `Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>` | optional | Field layout direction |
2399+
| **layout** | `Enum<'vertical' \| 'horizontal'>` | optional | Field layout direction — 'vertical' (the renderer default) or 'horizontal'. Multi-column is not a layout value: set `columns` |
24002400
| **columns** | `integer` | optional | Number of columns for the form body |
24012401
| **title** | `string` | optional | Form title |
24022402
| **description** | `string` | optional | Form description |

‎packages/spec/liveness/view.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,8 @@
264264
},
265265
"layout": {
266266
"status": "live",
267-
"note": "objectui: ObjectForm.tsx:826, ModalForm.tsx:468, DrawerForm.tsx:384 (vertical/horizontal)."
267+
"verifiedAt": "2026-09-27",
268+
"note": "objectui @ f8a9d0fb0: plugin-form ObjectForm.tsx:1408 (simple arm) and :463/:499 (drawer/modal hand-off), DrawerForm.tsx:575, ModalForm.tsx:597 — each reads vertical/horizontal only. The enum was narrowed to those two arms 2026-09-27 (#20221, ADR-0049 enforce-or-remove): every arm folded 'inline' and 'grid' to vertical, so both were a declared value the renderer threw away; multi-column is the sibling `columns` key. The key itself stays live."
268269
},
269270
"columns": {
270271
"status": "live",

0 commit comments

Comments
 (0)