Repository navigation
Commit fde7456
feat(spec)!: addRecord says what renders — enabled required, position top, mode/formView retired (#24970)
Closes #24692
Clause-②: yes (narrowing: `mode` / `formView` retire through ADR-0087
drop conversions; `enabled` becomes required)
A list's `addRecord` block now says what the renderer draws. `enabled`
is required with no default, `position` defaults to `top`, and `mode` /
`formView` are retired through the `spec-property-retirement` kit. It
ships in 18.x under the standing ruling for breaking narrowings after GA
(#6015, comment 6292005854, 「甲」), because ADR-0087 conversions cover
every body that parsed under 18.0.
## The ruling this implements (comment 6281834942, letter B′, items 1–5
verbatim)
> 1. `AddRecordConfigSchema` (`packages/spec/src/ui/view.zod.ts`, the
same schema on the page `list-view` node) keeps two keys: `enabled`,
REQUIRED with no default, so `addRecord: {}` is refused at authoring and
at publish instead of drawing nothing; and `position`, default `top`,
values `top` / `bottom` / `both` as the renderer draws them.
>
> 2. `mode` and `formView` retire through the `spec-property-retirement`
kit: ADR-0087 conversion entries that drop the two keys from authored
and stored bodies with a notice, `retiredKey()` tombstones on the strict
object, the two form composites (`view.form.ts:178`,
`page.form.ts:165`), regenerated artifacts, the liveness ledger.
`Clause-②: yes (narrowing)`, breaking, with its ADR-0087 disposition and
the FROM → TO in the changeset.
>
> 3. The producers follow in the same PR: showcase
`active-projects.page.ts:36`, `contact.view.ts:60` and the lint fixture
`showcase-shape.fixtures.ts:414` lose `mode` / `formView`;
`content/docs/ui/create-vs-edit-form.mdx:88` states the convention of
item 4 and names no binding — the docs line is part of the retirement
kit's surface and is declared in the claim as a cross-domain file.
>
> 4. objectui: the create entry selects `formViews.create` when the
object declares it, else today's `form` / `formViews.default`
(`app-shell/src/utils/recordFormNavigation.ts:218`, one selection line,
pinned by a test). objectstack-ai/objectui#13099 is that card; its
parity row (`p1-spec-alignment.test.ts:189`, the `mode` default) changes
when objectui's pin takes the spec release.
>
> 5. ADR-0050 (Proposed) Decision 2 gains a dated note that the list
add-record open mode is no longer declared on `addRecord`; how the
create form opens stays the renderer's, and a per-list open mode, if
ever wanted, is the list's existing `navigation.mode`, never a second
enum. Tier H; it may ride the spec PR (the PR is then Tier H whole) or
go as its own one-line PR — the spec seat chooses.
## What changed
**Schema (`packages/spec/src/ui/view.zod.ts`,
`AddRecordConfigSchema`).** One def serves every door: `ListViewSchema`,
`ObjectListViewSchema` (an object's or a container's `listViews`),
`InterfacePageConfigSchema` (a list page's `interfaceConfig`) and the
page `list-view` node's props row.
- `enabled` is `z.boolean()` with no default. If the key is omitted, the
parse refuses with a prescription that names the key and says how to fix
it.
- `position` defaults to `top`. The values `top` / `bottom` / `both` are
unchanged. I checked the renderer at objectui origin/main and at the
pinned sha `616df6598b`: `ListView.tsx#resolveAddRecordPlacement` maps
an absent position to top, and `showAddRecord` needs `enabled === true`.
- `mode` and `formView` are `retiredKey()` tombstones. Authoring either
key is a `tsc` error and also a parse error that carries the
prescription.
**ADR-0087.** There are two D2 conversions, both retired from the load
path with `retiredAfter: '18.0.0'`.
- They reach `stack.views[]` in all three persisted spellings,
`objects[].listViews.*`, `pages[].interfaceConfig` and every page
`list-view` node.
- `add-record-mode-form-view-removed` strips both keys and emits one
notice per key.
- `add-record-enabled-defaulted` writes `enabled: true` onto a block
that omits `enabled`. An explicit value is never touched.
- `RETIRED_KEYS_BY_MAJOR[18]` gains `ui/AddRecordConfig:mode` and
`ui/AddRecordConfig:formView`.
- The D3 entries are `add-record-mode-form-view-retired` and
`add-record-enabled-required`. Each has its own step-18 rationale
fragment.
- The two default moves are declared in `DEFAULT_CHANGES_BY_MAJOR[18]`.
**Ruling item 3 under 「甲」: the `enabled` half ships with a conversion,
not as a warning.** I measured whether any producer writes `addRecord`
without `enabled`:
- this repository at `71696121a6`: the showcase's two blocks and the
lint fixture all write `enabled: true`;
- objectui's Studio design surface at the pin: it writes `addRecord: {
enabled: true }`;
- production databases: not measured.
The measured population is zero, so the conversion is sound. It is
lossless against the contract, because it writes the value the 18.0
parse already produced. It is not lossless in pixels, and the changeset
says so: a stored block that omitted `enabled` drew no button, and once
converted it draws the one it declared. No 19.0 refusal card is needed.
**Forms.** In `view.form.ts` and `page.form.ts`, the `addRecord`
composites now list `enabled` and `position` explicitly. The new nested
labels are translated in the three locales.
**Liveness ledger.** The `liveness/view.json` `addRecord` row records
the removal. The sub-keys are one drill level below a recorded undrilled
container, so no row is added or removed. The README residue line is
updated.
**Producers and docs.**
- showcase `contact.view.ts` and `active-projects.page.ts`, and the lint
fixture `showcase-shape.fixtures.ts`, drop `mode` / `formView`.
- `content/docs/ui/create-vs-edit-form.mdx` states the convention
(`formViews.create` when declared, else `form` / `formViews.default`)
and names no binding.
- `content/docs/ui/views.mdx` drops `addRecord.formView` from where a
named form is served and states the `create` convention.
- `@objectstack/lint`: `os explain list-view-field-unknown` and one
docblock no longer name `addRecord.formView`. `lintViewRefs` never read
it.
- The reference pages under `content/docs/references/` are regenerated.
**Tests.**
- New `packages/spec/src/ui/add-record-config-retirement.test.ts`, in
the repo project. It covers every door, the prescriptions, the `tsc`
channel, the conversions over stored view, page and object rows,
idempotence, load-path retirement, the ledger rows, the forms, and a
tree-scoped absence pin over the five declared roots.
- `retired-key-removal-version.pin.test.ts` joins both new conversions.
- The old `view.test.ts` / `page.test.ts` pins on `mode` and the
`bottom` default are rewritten.
## Not in this PR
- **ADR-0050 Decision 2 note:** a Tier H file, so it goes in its own
one-line PR (ruling item 5). The exact text is in the dev report.
- **objectui** (ruling item 4) is objectstack-ai/objectui#13099, which
is already closed. Its `p1-spec-alignment.test.ts:189` parity row
(`mode` default `inline`) will go red when objectui's pin takes this
spec release, and ruling item 4 says that row changes then. No objectui
source writes `mode` or `formView` at the pin (`git grep` at
`616df6598b`).
- `content/docs/releases/` is untouched.
## Acceptance notes
- The showcase's `active-projects.page.ts` sets
`userActions.addRecordForm: false` next to `addRecord: { enabled: true
}`. objectui's `ListView` withholds the Add button on `addRecordForm ===
false`, so that page probably shows no Add button, even though its
comment says it shows one. I only read this from the code and did not
reproduce it in a browser, so it is not filed. Carrier: none.
- The 18.0 parse materialized `position: 'bottom'` into built artifacts
that carried an `addRecord` block. Those artifacts keep `bottom`
explicitly, so nothing moves on screen for them.
## Verification (final head `a05f141642`, after merging origin/main)
- `pnpm --filter @objectstack/spec build` and `check:generated`: `✓ All
14 generated artifacts are up to date`.
- `pnpm --filter @objectstack/spec test`: `Test Files 703 passed (703)`,
`Tests 21369 passed | 1 todo`.
- `pnpm --filter @objectstack/spec test:repo`: `72 passed (72)`, `1236
passed`.
- `pnpm --filter @objectstack/spec typecheck`: exit 0.
- `pnpm --filter @objectstack/lint typecheck`: exit 0.
- `pnpm --filter @objectstack/lint test`: `151 passed (151)`, `7013
passed`.
- These six ran as one `&&` chain under the verify lock, which reported
`VERDICT command-exit 0`.
- showcase: `objectstack validate` printed `✓ Validation passed`, and
`tsc --noEmit` exited 0.
- Gates:
- `check-adr-0087-registration`: registered, 4 ids new here.
- `check:i18n`: in sync.
- `check:i18n-coverage`: none new.
- `check:cross-package-test-inputs`, `check:nul-bytes` and
`check:doc-authoring`: green.
- All 120 families `dispatch-gates` derives were run on the final head
`a05f141642`, and every one exited 0. `--ran` reconciles: `120 run, 0
NOT-MEASURED`.
- Ablation of the absence pin: putting `formView: 'create'` back into
`contact.view.ts` (`ablation-replace`: mutation landed, blob changed)
turned the pin red, `1 failed | 34 passed`, naming
`examples/app-showcase/src/ui/views/contact.view.ts`. The restore was
verified: the blob equals HEAD and `git diff HEAD` is empty.
- `pnpm lint` (repo-wide eslint) is NOT MEASURED locally and left to CI.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 7532ea3 commit fde7456
36 files changed
Lines changed: 1049 additions & 92 deletions
File tree
- .changeset
- content/docs
- references
- api
- data
- ui
- ui
- examples/app-showcase/src/ui
- pages
- views
- packages
- lint/src
- platform-objects/src/apps/translations
- spec
- authorable-defaults
- authorable-surface
- liveness
- scripts/lib
- src
- conversions
- migrations
- entries
- retired-keys
- semantic
- ui
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1863 | 1863 | | |
1864 | 1864 | | |
1865 | 1865 | | |
1866 | | - | |
| 1866 | + | |
1867 | 1867 | | |
1868 | 1868 | | |
1869 | 1869 | | |
| |||
1949 | 1949 | | |
1950 | 1950 | | |
1951 | 1951 | | |
1952 | | - | |
| 1952 | + | |
1953 | 1953 | | |
1954 | 1954 | | |
1955 | 1955 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
409 | 409 | | |
410 | 410 | | |
411 | 411 | | |
412 | | - | |
| 412 | + | |
413 | 413 | | |
414 | 414 | | |
415 | 415 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
95 | 95 | | |
96 | 96 | | |
97 | 97 | | |
98 | | - | |
| 98 | + | |
99 | 99 | | |
100 | 100 | | |
101 | 101 | | |
| |||
177 | 177 | | |
178 | 178 | | |
179 | 179 | | |
180 | | - | |
181 | | - | |
182 | | - | |
183 | | - | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
184 | 184 | | |
185 | 185 | | |
186 | 186 | | |
| |||
266 | 266 | | |
267 | 267 | | |
268 | 268 | | |
269 | | - | |
| 269 | + | |
270 | 270 | | |
271 | 271 | | |
272 | 272 | | |
| |||
0 commit comments