Skip to content

Commit 33f53b5

Browse files
committed
Merge origin/main (a88a1bb) into claude/issue-19992-currency-config-precision-retire
Two hand-resolved conflicts, both intents stacked: - conversions/registry.ts: main's form-layout-inline-grid-to-vertical definition kept, currency-config-precision-removed appended after it; CONVERSIONS_BY_MAJOR keeps main's order and appends this branch's entry. - migrations/registry.ts: step-18 rationale keeps main's text and appends this branch's sentence; conversionIds keeps main's list in order and appends currency-config-precision-removed. Generated regions are regenerated after. Claude-Session: https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN Co-authored-by: Claude <noreply@anthropic.com>
2 parents cdfbc5f + a88a1bb commit 33f53b5

14 files changed

Lines changed: 705 additions & 30 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)

‎.claude/skills/spec-property-retirement/SKILL.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ preview renderer 不算消费者)与 AGENTS.md §"Touched `packages/spec`?"(八
4040
`rls.enabled` 曾是「证据错误的 live」而实际**无人读取** —— disabled 的
4141
policy 仍在贡献它的授权。那个键最后是**被强制执行**,不是被删除。功能存在时,强制
4242
执行赢。
43-
- [ ] **它是文档形状的吗?** `hook.label`、`hook.description`、`flow.description`
44-
没有运行时消费者,但被**有意保留** —— 它们为下一个读者(按 ADR-0033,常是模
45-
型)记录意图。把豁免写进台账 `note`,下次审计不再重审。良性展示元数据
46-
(`description`、`tags`、`icon`)永远谈不上「误导」;不要标 `authorWarn`,也
47-
不要退役它。
43+
- [ ] **主流平台有没有这个能力?** 声明而未执行的键每族问这句,按维护者原话(裁决评论
44+
`5727134555`):「有 ⇒ 补消费端(一次做对);没有 ⇒ 退役,而不是看仓里有没有人读」。
45+
文档形状的键(`hook.label`、`flow.description`)为下一个读者记录意图(ADR-0033),是「有」的
46+
一例:补渲染、不退役,判定写进台账 `note`,下次审计不再重审。`description`、`tags`、`icon`
47+
不自动豁免:RLS 策略的 `tags` 主流没有 ⇒ 退役。`authorWarn` 另按 README 判。
4848
- [ ] **有已承诺的路线图吗?** 那就是 `experimental` + `.describe()` 里的
4949
`[EXPERIMENTAL — not enforced]` 标记,不是删除。
5050
- [ ] **同 major 记账。** 同一个*未发布 major* 里更早的 conversion 改名了你现在要删
@@ -58,7 +58,7 @@ preview renderer 不算消费者)与 AGENTS.md §"Touched `packages/spec`?"(八
5858
`live-elsewhere` 一节。
5959
- [ ] **零编写实例普查要并跑一个同族已知存活的键作对照**,两读数都报;同得零即没测出。
6060
- [ ] **拿 `cross-repo` 行当论据,就当刻重核路径与行号**:漂了读作重新取证,⛔ 不是 `dead`。
61-
- [ ] **「零编写实例」≠「没有代码读它」。** 前者答的是示例应用,后者才是退役的证据。
61+
- [ ] **「零编写实例」≠「没有代码读它」。** 前者答的是示例应用,后者才证 `dead`。
6262

6363
## 1. 裁判是构建,不是台账
6464

‎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) |

0 commit comments

Comments
 (0)