Repository navigation
Commit 317cddd
Fixes #11509
Clause-②: no (narrowing: the ten element-layer flat data-binding keys
and `object-grid.defaultFilters` leave the accept set, and the
component-props gate's `dataSource.object` waiver becomes a refusal)
ADR-0087 disposition, as the changeset's marker carries it: `registered
element-flat-data-binding-retired, object-grid-default-filters-retired`
(`.changeset/11509-element-flat-binding-retired.md`).
**Consumer pin rule (triage `6071460039`):** a consumer that pins both
repositories moves its objectui pin past objectui#11880 no later than
its objectstack pin moves past this retirement. The converted shape is
one only objectui after objectui#11880 reads.
**Landing gate: met.** The gate is the claim's (`6072058120`, ruling
sub-question 2), moved by the seat (`6080485027`) to #22476:
`.objectui-sha` must cover the objectui merge that repairs the designer
row named in objectstack-ai/objectui#12056.
- #22476 landed (PR #22592, `ba956070b1`), and `.objectui-sha` on `main`
is `20c6d351ad74`. This branch merged that `main` (`76bc1e03a`), and
does not move `.objectui-sha` itself.
- objectui `2a48bd408dab` (the merge of objectui PR #12064, the repair
for objectstack-ai/objectui#12056) is an ancestor of `20c6d351ad74`:
`git merge-base --is-ancestor` exits 0, and `rev-list --count` reads 7
ahead / 0 behind, agreeing with the `domain:devx` seat's REST compare
(`6093574958`). The first gate, objectui `5bc55c0c5a1e`, is an ancestor
too (exit 0).
- The gate's reason, re-measured at `20c6d351ad74` by `git show` (the
objectui checkout's HEAD did not move). Studio's page-block inspector
row for `element:repeater` is
`packages/app-shell/src/views/metadata-admin/previews/block-config.ts:344-351`.
Its Object picker (`:345`) and its Limit box (`:348`) both carry `at:
'dataSource'`. `PageBlockInspector.tsx:800` hands every `at:
'dataSource'` field to `patchBinding`, and `patchBinding` (`:483-484`)
writes `dataSource: { ...binding, [name]: value }`. No designer row
writes a retired flat key: the `element:number` row (`:357-358`) writes
`dataSource.object` as before, and `element:record_picker` has no
page-block row (the palette excludes it as page content,
`block-types.ts:208`).
## What this does
Ruling `6051498447` (A-narrow, all four sub-questions at their
recommendation), carried out as the claim states it:
1. **Tombstones.** `element:record_picker` `object` / `filter` / `sort`
/ `limit`, `element:number` `object` / `filter`, `element:repeater`
`object` / `filter` / `sort` / `limit`, and `object-grid.defaultFilters`
are `retiredKey()` tombstones in
`packages/spec/src/ui/component.zod.ts`. Each is refused at its key with
the prescription (use `dataSource.KEY` on the node; what to do where the
binding already sets it, by that element's old rule), and its input type
is `never`. The repeater's other spellings (`objectName`, `filters`,
`where`, `orderBy`, `sortBy`, `top`, `pageSize`) were aliases of the
flat keys and now point at the binding. The `object-*` block layer and
the relationship-scoped blocks are unchanged.
2. **Conversions (step 18, both retired from the load path, both ordered
at 35.5, before `page-component-filter-record-to-rule-array` at 36).**
- `element-flat-data-binding-to-data-source` follows each element's old
rule: a key the binding lacks moves there; a key the binding already set
is deleted where the binding always won (the picker, and
`element:number`'s `object`); `element:number`'s flat filter is appended
to the binding's (the two always AND-combined). It reports a TODO and
leaves the key in three cases: a picker key beside a `dataSource.view`
that sets no such key of its own, a repeater key the binding sets to a
different value or beside a `view` (the repeater read neither until the
console put its binding first, so what it applied depends on the console
version), and an `element:number` filter pair that is not two rule
arrays.
- `object-grid-default-filters-removed` is the shape `defaultSort`'s
retirement took. When `filter` is empty (absent, `null`, `[]` or `{}`),
the rules move into it. When `filter` has content, the key is deleted.
When `filter` is a value no lowering reads (a bare string), it reports a
TODO rather than overwrite it.
- A record-form value either entry moves reaches the record-form
conversion at its new door. So the absorbed arms (the element flat
`filter` and `defaultFilters` doors of
`page-component-filter-record-to-rule-array`) are dropped from that
entry's reach.
3. **Lint.** In `packages/lint/src/validate-component-props.ts` the
type-blind `suppliedByDataSource` waiver is gone. One of the three
elements with no `dataSource.object` is the rule's existing
`component-props-invalid` finding (warning tier, at
`…dataSource.object`); this is not a new rule. The docs gate's twin
waiver (`packages/spec/scripts/check-yaml-examples.ts`) turns with it.
4. **The repeater trap** is closed in this PR from both sides. A
repeater bound only through `dataSource` is the clean shape: zero
findings, and objectui reads it since objectui#11880. A repeater aimed
by a flat `object` is refused twice: the tombstone at the key and the
missing binding. Pinned in `validate-component-props.test.ts` and in the
docs gate's self-test.
5. **Registry and generated outputs.** There are eleven
`RETIRED_KEYS_BY_MAJOR[18]` rows. There are two D3 entries
(`element-flat-data-binding-retired`,
`object-grid-default-filters-retired`), each with a step-18 rationale
fragment. `authorable-surface/ui.json` (11 rows `[RETIRED]`) and
`content/docs/references/ui/component.mdx` are regenerated by the repo's
generators. The hand-edited `dropped-refinements.baseline.json` loses
three rows whose only dropped refinement was a retired `filter`. The
committed `packages/spec/spec-changes.json` and
`docs/protocol-upgrade-guide.md` are regenerated from the merged
registries (`gen:spec-changes`, `gen:upgrade-guide`); see "This round"
below.
6. **The `object-tree` `titleField` rationale, corrected (offered by the
`domain:devx` seat, `6093574958` item 2).** At objectui `20c6d351a`
nothing reads a tree `titleField`: the renderer's `getTreeConfig` rung
went on objectui#8841, and the `case 'tree'` arm's flatten rung went on
objectui#6152 round 14 (objectui `3fd862510`), so `ListView.tsx:3985`
reads `labelField: treeCfg.labelField || 'name'` and emits no
`titleField`. The `OBJECT_TREE_FLAT_CONFIG_GUIDANCE` docblock and the
last sentence of its prescription said the key is "only ever read as
that key's last fallback". Both now say the key stays in the set for its
prescription, not for a read, and the prescription still names
`tree.labelField`. The flat key set and every schema shape are
unchanged, and the in-test note beside the key-set pin
(`component.test.ts`) gains the same dated reading. Item 1 of that offer
(`kernel/functional-completeness.ts`) is outside this PR's files and is
not taken.
## Premises, verified on `origin/main` `bb4f5cc00` before the first edit
- P1 holds. The flat `object` keys were at `component.zod.ts:2602`
(`element:number`), `:3015` (`element:record_picker`) and `:4134`
(`element:repeater`). The repeater docblock's "NOT read at the pin" is
at `:4117`.
- P2 holds. `suppliedByDataSource`
(`validate-component-props.ts:169-185`) checks the issue path and
`dataSource.object`, never the component type.
- P3 holds. `defaultFilters` is `z.array(ViewFilterRuleSchema)`
(`:4563`), and the `defaultSort` precedent is
`objectGridDefaultSortRemoved` (`conversions/registry.ts:9264`).
- P4 holds. There are zero authors of the eleven keys in `examples/**`,
`skills/**` and the hand-written `content/docs/**`. The one
element-layer author, the showcase picker (`page-variables.page.ts:59`),
already writes `dataSource`. The spec, lint and metadata-protocol tests
that authored the flat keys as fixtures are re-bound here, and the
tree-scoped absence pin found two of them that grep had missed
(`page.test.ts`, `i18n-resolver.test.ts`).
`skills/objectstack-ui/rules/pages.md` names `element:number` /
`element:repeater` only as printable blocks and teaches no key, so no
`skills/**` edit is needed and no hand-written docs page needed an edit.
## Decisions the reviewer should check
- **Three step-18 narrowings are absorbed, not one.** The ruling names
`object-grid-default-filters-rule-array`.
`element-number-filter-rule-array` and
`element-record-picker-filter-rule-array` narrowed the two element flat
`filter` keys this PR retires in the same unreleased major, and their
acceptance criteria assert parses this PR refuses (`safeParse({ object,
aggregate, filter })` succeeds). That is the same-major bookkeeping of
the `spec-property-retirement` playbook (§0) and the measurement's
"same-major bookkeeping against … its semantic entries". The record-form
guidance they carried is the binding's own entry,
`element-data-source-and-object-block-filter-rule-array`. The CLI's
migrate-meta guidance pin drops the three absorbed ids from its
`REWRITTEN` floor. The absorbing entries are not added there, because
the list admits only entries rewritten off a tracker id and these two
were born without one; the printed-block case holds them with the whole
directory.
- **No new public export.** The three-element set lives privately in
`component.zod.ts`. The conversion registry and the lint rule each keep
a copy, and each copy is pinned against the tombstones by probing
`ComponentPropsMap`. Publishing it would have widened a narrowing-only
retirement.
- **Tombstones name `@objectstack/spec 17`, not 18.**
`check:future-spec-major` (ADR-0087, amended 2026-09-13) refuses a
tombstone that names the next major. The migrate sentence is `--from
17`, as with `defaultSort`.
- The `XParsed` aliases of the three element props stay, although their
two shapes now coincide. Deleting a published type name would be an
export removal of its own.
## Acceptance notes (not filed)
- `packages/lint/src/page-walk.ts:135-136` (`:137` before the merge of
`main` `76bc1e03a`) still resolves a component's object as
`dataSource.object ?? properties.object ?? page.object` for the
field-binding rules, and
`content/docs/deployment/validating-metadata.mdx:157` describes that
chain. After this PR the flat `object` on the three elements is refused
by its tombstone, so the fallback only ever reads a refused key (and
`element:metadata_viewer`'s metadata owner). This PR does not change it:
the file is outside the claim. Carrier: none named; the next card that
touches `page-walk.ts`.
- The docs gate judges only nodes that carry a `properties` mapping, so
a bare `type: element:repeater` fence with neither `properties` nor
`dataSource` is not reported there. The lint rule judges it. This is
pre-existing dispatch behaviour.
- The object-tree read-point record that `main` re-measured at
`20c6d351a` (the `OBJECT_TREE_FLAT_CONFIG_GUIDANCE` docblock header,
from #22592) is kept verbatim, as ordered, with two slips in it. It
cites the arm's `labelField` read as `:3986`, but at `20c6d351a` that
read is `:3985`, and `:3986` is `fields: treeCfg.fields ||
effectiveFields,`. Its opening sentence still cites
`ListView.tsx:3913-3932`, the range its own re-read moved to
`3965-3989`. Neither is an asserting quote, so no gate reads them. The
corrected `titleField` paragraph cites `:3985`. Carrier: the next
re-read of that record at a pin bump.
- `os validate` refuses the clean repeater on an html-tier page and
accepts the retired one. Measured through `node packages/cli/bin/run.js
validate` at this head, on a probe app carrying this repo's
`sdui.manifest.json`:
- A page whose source is one `element:repeater` tag carrying only
`dataSource={{ "object": "probe_task", "limit": 5 }}` gets exit 1 with
`jsx-missing-required-prop` (the repeater "is missing required prop
\"object\"").
- The same tag carrying a flat `object="probe_task"` instead gets no
finding.
- The cause is objectui's registration at `20c6d351a`
(`components/src/renderers/basic/data-list.tsx:320-326`, `object`
`required: true`), which `sdui.manifest.json` carries. The manifest also
still lists the flat `filter` / `sort` / `limit` inputs, and
`element:number`'s flat `object` / `filter`.
- This predates this PR (the binding-only node was legal before it), and
the fix site is in objectui. It is reported to the seat as a finding,
not fixed here.
- objectui's own tests at `20c6d351a` parse flat `object` against the
spec's element schemas (`record-picker-inputs-spec-parity.test.ts`,
`record-picker-empty-text-i18n.test.tsx`,
`types/src/__tests__/element-repeater-data-source-11880.test.ts`). They
resolve `@objectstack/spec` `^17` from npm, so nothing reds now. They
will need their fixtures re-bound when objectui moves to spec 18.
Carrier: objectui's spec-18 upgrade.
- The committed `spec-changes.json` and upgrade guide on `main` lag
`main`'s own registries. Since #22533, `check:spec-changes` and
`check:upgrade-guide` generate in memory and no longer compare the
committed copies. `check-adr-0087-registration` still reads the
committed `spec-changes.json` at HEAD as its parser witness, so a PR
that removes a registry entry reads "ledger parser drift … widen
extractIds()". The real remedy is `gen:spec-changes`. That is the red
this round fixed (below). Regenerating also carried text `main` had
changed in other entries' registries (the objectui pin readings at
`20c6d351a`, the storage-scope and flow-slot guidance). Carrier: #22485,
which deletes the committed copies.
## This round: `main` merged, the gate re-measured, the Check Changeset
red fixed
- **Merge.** `origin/main` `76bc1e03a` was merged by
`scripts/pm/os-regen-merge.sh` (merge commit `1eb154c6f`, no rebase, no
force-push). The pre-merge tip was `d8e0996f4`, and the merge base
`35ef501e1`.
- **One text conflict:**
`packages/spec/dropped-refinements.baseline.json`, only its two
`measured` totals. That ledger has no generator by design (its header
and `scripts/lib/dropped-refinements.ts` say why), so the order's
"regenerate it" has nothing to run. It was resolved by hand:
- the branch's 220/680 → 217/677 (three rows retired) and `main`'s
220/680 → 226/688 (six schemas, eight sites) stack at **223 entries /
685 sites**;
- the merged `entries` list counts exactly that;
- the spec build's ledger check, which fails on any mismatch, reads "685
refinement site(s) across 223 published schema(s) … all declared".
- **Auto-merged:** `component.zod.ts`, `component.test.ts`,
`migrations/registry.ts`, `i18n-resolver.test.ts` and
`protocol.stored-migration.test.ts`. Every line `main` added to those
files between `35ef501e1` and `76bc1e03a` is present at this head, with
one exception: the object-tree `titleField` paragraph's
`ListView.tsx:3927` line, which item 6 replaces on purpose. So `main`'s
re-measured pin readings stand verbatim, and the retirement sits on top.
One example is the repeater record, which keeps `main`'s
`data-objectstack/src/index.ts:5048-5059` with this PR's tense change
and REMOVED paragraph. `gen:migration-registry` rewrote `registry.ts`
byte-identically.
- **Step 2** kept the branch's bytes of
`content/docs/references/ui/component.mdx` and
`authorable-surface/ui.json` (branch-only edits). `check:generated`
reads 15/15 current.
- **Regeneration, `675b121bb`.** `Check Changeset` was red at
`1eb154c6f` (check-run `114130272368`, "Require an ADR-0087 disposition
on a declared-breaking changeset").
- Reproduced locally: `node scripts/check-adr-0087-registration.mjs
--base origin/main` exit 1, "ledger parser drift: 3 id(s) present in the
generated packages/spec/spec-changes.json are NOT found by
extractIds()". The three were `element-number-filter-rule-array`,
`element-record-picker-filter-rule-array` and
`object-grid-default-filters-rule-array`, the entries this PR absorbs.
`main`'s committed copy still listed them.
- `gen:spec-changes` and `gen:upgrade-guide` regenerated the committed
copies from the merged registries. At `675b121bb` the gate exits 0: "1
declared-breaking changeset(s), each carrying an ADR-0087 disposition …
registered element-flat-data-binding-retired,
object-grid-default-filters-retired".
- `check-changeset-no-major.mjs --base origin/main` exits 0 at both
heads (pre mode, tag `next`). No label and no level was changed.
- **Prose, `5faf12295`.** Item 6 above.
- **Pin citations at `20c6d351a`.**
- `pnpm --filter @objectstack/spec check:objectui-pin-citations` exit 0:
55 asserting citations match `.objectui-sha` (`20c6d351a`), and 145
historical ones are recorded and not checked.
- `--verify-anchors` (`OBJECTUI_ROOT=../objectui`, read by `git show` at
the pin) exit 0: 13 anchor content assertions verified against objectui
at `20c6d351a`.
- This PR adds or changes no asserting `.objectui-sha` citation. The
objectui anchors it adds are two:
- `data-list.tsx:320-326` (the repeater registration), byte-identical
across `f0268ad78…20c6d351a`, so it holds;
- `ListView.tsx:3985`, read at `20c6d351a`.
- No record was re-pointed. The two slips reported above are `main`'s
and stay verbatim.
- **Size:** 43 files (the 41, plus `spec-changes.json` and
`docs/protocol-upgrade-guide.md`), +2789 / -1521 = 4310 changed lines.
That is over the 3000-line human-merge line (#22490).
## Tests and gates
All readings are at head `5faf12295`, and every exit code was captured
before any pipe. Heavy runs went through `scripts/pm/os-verify-lock.sh`
on a shared box.
- `@objectstack/spec`, both tiers in full (`vitest run --project local
--project repo --maxWorkers=4`): 694 files, 20015 passed, 1 todo, 0
failed. Lock verdict: command-exit 0, held 16m44s. `pnpm --filter
@objectstack/spec typecheck`: exit 0.
- `@objectstack/lint`, full (`vitest run --maxWorkers=4`): 134 files,
6128 passed. `typecheck`: exit 0.
- `@objectstack/cli`:
- the unit tier in full (`vitest run --project unit`): 277 files, 4105
passed;
- `test/migrate-meta-engine-guidance.test.ts`, an integration-tier file
this PR edits (`--project integration`): 3 passed;
- `typecheck`: exit 0;
- the rest of the integration tier is left to CI.
- `@objectstack/metadata-protocol`
`src/protocol.stored-migration.test.ts`: 40 passed.
`examples/app-showcase` `test/my-work-visibility.test.ts`: 7 passed.
- The builds behind these: the spec build (exit 0), `turbo run build
--filter='@objectstack/cli^...'` (58/58 tasks), and the CLI build. A
locked full build followed (72/72 tasks).
- **Gates.** `node scripts/pm/dispatch-gates.mjs --commands` derived 120
at `5faf12295`.
- All 120 were run, and all 120 exit 0.
- On the first pass, `check:skill-examples` and
`check:dual-build-cjs-loads` exited 3 (PREREQUISITE NOT MET: no `dist/`
for `client-react` and eight others). After the full build both exit 0.
- `--ran`: "120 run, 0 NOT-MEASURED".
- `check:pm-dispatch-gates`: 1825 cases pass (fast tier; 6 slow sections
deferred).
- CI at `5faf12295`, read once when the report was written (not
awaited): all 35 check runs completed, 33 success and 2 skipped (Console
Pin Gate, Packed-tarball smoke opt-in), none red. `Check Changeset` is
green again.
## 维护者速读(草稿)
### 改了什么
- 页面里的三个元素组件,不再接受直接写在 `properties` 里的数据绑定键:
- 记录选择器 `element:record_picker`:`object` / `filter` / `sort` / `limit`;
- 数字 `element:number`:`object` / `filter`;
- 重复列表 `element:repeater`:`object` / `filter` / `sort` / `limit`。
- 作者改为写在组件节点的 `dataSource` 上,即
`dataSource.object`、`dataSource.filter`、`dataSource.sort`、`dataSource.limit`。repeater
原来的别名(`objectName`、`filters`、`where`、`orderBy`、`sortBy`、`top`、`pageSize`)也一并指向
`dataSource`。
- 对象表格 `object-grid` 的 `defaultFilters` 不再接受,规则改写到 `filter`。
- 旧写法在校验和保存时会被拒绝,错误信息里直接给出新写法。`os validate` 对缺少 `dataSource.object`
的这三个元素给出警告。
- 已有元数据可以用 `os migrate meta --from 17` 自动改写。改写不了的情况会留一条 TODO
提示作者手工处理:两种写法取值不同,或者 `dataSource` 指向了一个已保存的视图。
- Studio 的页面设计器在新的控制台 pin(`.objectui-sha` =
`20c6d351a`)上,已经按新形状写这三个元素。repeater 的“对象”和“条数”两栏写入 `dataSource`,作者在
Studio 里拖出来的组件不会被拒绝。
### 为什么改
- 同一个查询原来有两个入口(`properties` 和 `dataSource`),三个组件各自用不同规则合并两边,作者写下的值不一定生效。
- repeater 还有一个陷阱:只写 `dataSource` 时,`os validate` 显示通过,页面却显示“No
records”。
- 裁决 A-narrow(`6051498447`)定为:只保留 `dataSource` 一个入口,先让 objectui 读写新形状,再在
spec 里收回旧键。这一步现在已经具备条件。
### 风险与代价(含回滚)
- **破坏性变更。** 属于 v18 大版本:`@objectstack/spec` 与 `@objectstack/lint` 带
major changeset。没有跑迁移的旧元数据会在校验时报错。
- **宿主 pin 规则。** 同时 pin 两个仓库的宿主,要先移动 objectui 的 pin,越过
objectui#11880,才能读迁移后的形状;之后才能把 objectstack 的 pin 移过本次退役。Studio 设计器按新形状写
repeater,要从 objectui#12056 起;当前控制台 pin `20c6d351a` 两者都已包含。
- **已知遗留(不在本 PR 内)。**
- html 类型页面里,`os validate` 仍要求 `element:repeater` 标签写平铺的 `object`,并拒绝只写
`dataSource` 的写法。原因在 objectui 的组件注册清单,已作为发现交给席位处理。
- objectui 自己的部分测试在升级到 spec 18 时,需要改用新写法。
- **体量。** 4310 行变更,超过 3000 行人工合并线。
- **回滚。** 直接 revert 本 PR 的合并提交即可。已经迁移成 `dataSource` / `filter`
的元数据,在旧版本里本来就是合法写法,回滚后不需要再迁移回去。
### 席位意见
### 你要做的(一个动作)
审阅并亲手合并 PR #22421。
---
_Generated by [Claude
Code](https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent a800912 commit 317cddd
41 files changed
Lines changed: 2694 additions & 1435 deletions
File tree
- .changeset
- content/docs/references/ui
- examples/app-showcase/test
- packages
- cli/test
- lint/src
- metadata-protocol/src
- spec
- authorable-surface
- scripts
- src
- conversions
- migrations
- entries
- retired-keys
- semantic
- system
- ui
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| 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 | + | |
0 commit comments