Repository navigation
feat(spec)!: retire the view-level api data source — a view binds its object or static rows; an API call is the page element's binding (#23082) - #23160
Conversation
Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H Co-authored-by: Claude <noreply@anthropic.com>
… pins Regenerated: content/docs/references/** (gen:docs), the strictness counts shard (gen:strictness-ledger). The retirement pin joins the spec repo project (it walks five repo roots) and is the second witness on the absence-walk radius rows. Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H Co-authored-by: Claude <noreply@anthropic.com>
…tire-view-api-source
…rom the merged tree origin/main (287fd6e) brought PR #23101's component rows; the branch's retirement removes the view-level api arm from the same page. Regenerated with gen:docs on the merged tree per scripts/pm/os-regen-merge.sh step 4. Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 141 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c9876c263d6f588d9f934bfcc6a7dd3f08e56430 && git checkout c9876c263d6f588d9f934bfcc6a7dd3f08e56430
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin dc93781c1947b53cff50a550ad551e462a464594 12f86031637d7aaffe0ce28819166fb55bad8931 && git checkout -B drift-repro dc93781c1947b53cff50a550ad551e462a464594 && git merge --no-ff 12f86031637d7aaffe0ce28819166fb55bad8931
node scripts/docs-audit/affected-docs.mjs --json dc93781c1947b53cff50a550ad551e462a464594
|
Contract reviewServed-tier: Inputs read: card #23082 (body and all 4 comments, ruling Check-runs on the head as read for this record: 27 completed ① Derived judgmentsAccept set and public surface, each judged:
② Semver level
③ Boundary flagsDev report
Escalations raised by this review:
Required before a PASS on the next head: ① items 4, 5 and 7 (three string edits plus Implemented-by: VERDICT: FAIL |
…omponent describes, pin wording Review 6138665801 ① items 4, 5, 7: the prescription, its pin regex and the changeset quotation stamp the bare major (the house form of every prescription added since the v18 line opened; 18.0.0-next.N is the first release carrying this); ObjectGrid/ObjectMap/ObjectTree/ObjectGantt `data` describes no longer list the `api` discriminator; the ledger note and the changeset say 'measured at 7578323' instead of 'the pin'. Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H Co-authored-by: Claude <noreply@anthropic.com>
…tire-view-api-source
… the four data describes drop the api discriminator gen:docs on the merged tree; check:generated reads all 14 artifacts up to date. Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Inputs read: card #23082 (body and all 5 comments: triage Check-runs on the head as read for this record: 48 runs, every one ① Derived judgmentsThe three items
The whole net diff on this head, accept set and public surface:
Landing, stated so the record is not misread: the diff touches ② Semver level
③ Boundary flagsDev report
Escalations standing after this record: E and F (kit owner, What landing still needs beyond this record: the maintainer's authorized APPROVED review on Implemented-by: VERDICT: PASS |
维护者速读(终稿)
改了什么: 视图(列表、表单)的 为什么改: 您在 #23082 批了 C。旧的视图级 API 源能把密钥写进 风险与代价(含回滚): 风险在没测到的存量元数据。如果 cloud 或客户项目有视图写了 席位意见: 建议批准。第一轮契约复核判 FAIL:提示语里的版本号、四处组件描述仍在教已退役写法、液性账本里的 pin 引用过期。补丁轮已全部修正。第二轮复核 PASS,48 个 CI 检查全部完成,没有失败。技能文本只删不增,没有教任何已被拒绝的写法。还有一个不阻塞的缺口留作后续:技能包里还没有任何一处教页面元素 你要做的: 在本 PR 上点 Approve(请审已发给 Generated by Claude Code |
…0 and follow its contract (objectui#12030) (objectstack-ai#12337) Fixes objectstack-ai#12030 Clause-②: yes — 22 published `@object-ui/*` packages declare a consumer-facing `@objectstack/*` range that moves from `^17.x` to `^18.0.0-next.0` (21 in `dependencies`, 1 in `peerDependencies`, counted from the manifests at the head), so a release would carry a published-contract change. `node scripts/check-changeset-presence.mjs` at `0cc4e8fcc`: "102 source file(s) and 1 published-contract change(s) of 18 released package(s) changed, and this change declares 3 changeset(s)". `.changeset/12030-objectstack-next-18.md` declares the 22 packages `minor`, with the breaking-for-consumers note in its body; the changeset coverage of the other touched packages is below. follow-up: objectstack-ai#12334 **Scope: the `@objectstack/*` move to `^18.0.0-next.0` and the fixes it forces. No objectui release.** Releasing objectui 18.x (the AGENTS.md §版本号策略 sync `major`, Changesets pre mode on `next`, and the two gate edits ruled in records 6148150305 and 6149296149) moves to the follow-up card above, per the scope split recorded as 6149376719. This branch carried the sync `major` and `.changeset/pre.json` for a while (`0b942ba`); they are reverted (`7ec3c2b`), and neither gate script is edited. **Known effect after merge, intended:** `Spec Range Floors` (`scripts/check-spec-range-floors.mjs`) cannot read `^18.0.0-next.0`: at the head it exits 1 with "The scan collapsed: 0 package(s) declare @objectstack/spec in a consumer-facing field". Its nightly alarm on `main` turns red, and `pnpm changeset:publish` cancels any publish until the follow-up lands. That is the fail-safe that stops a 17.x release declaring 18 ranges. The open version PR objectstack-ai#11600 (`changeset-release/main`) refreshes toward 17.8.0 and must not be merged before the follow-up. ## What this does - Every `@objectstack/*` range in `dependencies`, `devDependencies` and `peerDependencies` moves to `^18.0.0-next.0`: 40 ranges in 32 manifests (26 / 13 / 1). Raised textually; `pnpm-lock.yaml` regenerated by `pnpm install` only, never hand-edited. `pnpm install --frozen-lockfile` exits 0 and a second plain `pnpm install` leaves the lockfile byte-identical (measured at `e8e9a35`; lockfile and manifests unchanged since). - npm dist-tags read today for the four `@objectstack/*` packages objectui names (`spec`, `client`, `formula`, `lint`): `next: 18.0.0-next.0`, `latest: 17.7.0`; every `18.0.0-next.0` tag peels to objectstack `d889448b93`. `next` had not moved when the final head was pushed, so objectstack-ai/objectstack#23160 (`1912af47ba`, compare reads `behind`: not an ancestor) is not in this release and objectstack-ai#12325's reader removal stays out of scope. - Every break the bump caused that is a mechanical follow-through of a v18 contract change is fixed here. Where a break needed a behaviour or design choice, the PR keeps today's behaviour and says so in the test; the seat judged or routed each one (record 6148150305). ## Changeset coverage Every published package this change touches, measured on the net diff from `72594f29e` (the merge base since the `main` merge; the earlier rounds measured from `cea429b98`, before it, and the set is the same 32 published packages): - `.changeset/12030-objectstack-next-18.md`: the 22 packages whose consumer-facing ranges move, `minor`. - `.changeset/12030-i18n-capability-labels.md`: `@object-ui/i18n`, `minor`. The ten locale packs drop `capability.label.manage_org_presentation` and add `capability.label.manage_mailboxes` with spec 18's capability set. A key a consumer could read is removed, so the bump is `minor`, not `patch`. - `.changeset/12030-console-bundles-spec-18.md`: `@object-ui/console` and `@object-ui/runner`, `minor`. In both, the `@objectstack/*` ranges are `devDependencies`, but each is an app build whose published `dist` bundles them. The console bundles them in its `vendor-objectstack` chunk, measured under objectstack-ai#11762 below. The runner is a Vite app build (`vite build`, no `build.lib`, no `external`) whose aliases compile `@object-ui/components`, `core`, `types` and other `@object-ui/*` packages from `src`; those import `@objectstack/spec` at runtime. The runner line was added in the second patch round, after the opus re-review 6158463307 measured it. - `@object-ui/plugin-calendar` and `@object-ui/plugin-kanban` changed only a comment, and get no line. Measured: both rebuilt from source at `2c15e40cf` (`pnpm --filter … run build`, after removing `dist`); `grep -rF` over each `dist` (their `files[]` publish `dist`, `README.md`, `CHANGELOG.md`, `LICENSE`) finds neither the changed sentence nor the unchanged comment sentence beside it, while code strings such as `startDateField` and `ObjectKanbanRenderer` are found. - `@object-ui/collaboration`, `mobile`, `plugin-charts`, `plugin-report` and `sdui-parser` get no line. The test: does the package's published `files[]` bundle anything this change touched? In each, the only manifest change is `devDependencies["@objectstack/spec"]`, and the package's own sources are unchanged. `collaboration`, `mobile` and `sdui-parser` build with `tsc`, so their `dist` holds their own sources only. `plugin-charts` and `plugin-report` are Vite lib builds whose `rollupOptions.external` externalises every bare import, so no other package is compiled into them. None of them imports `@objectstack/spec` from non-test source. ## Pending changesets on `main` reworded (patch round 3) Commit `0cc4e8fcc`. Eight pending `.changeset/*.md` that this change did not add carried sentences the spec-18 move makes false at release time. Each false clause is corrected in place, mostly by one clause pointing at objectui#12030; history is kept. In the quotes below, a type argument is spelled in words (`PublicBlockNodeOf` for `'element:repeater'`), because GitHub's body sanitizer eats angle-bracket fragments; the files spell it as code. The wording is all that changed: every file's frontmatter (packages and levels) is byte-identical to `0d457fe1f` (sha1 of each frontmatter block compared), and `check-changeset-overwrite` reports "declared at base" equal to "declares now" for all eight. - `.changeset/6152-grid-default-filters-gantt-map-filter.md` (`@object-ui/types` minor; `core`, `plugin-grid`, `plugin-map`, `plugin-view` patch) - before: "`@objectstack/spec` has typed `ComponentPropsMap['object-grid'].defaultFilters` as the same `ViewFilterRule` array as `filter`, `[{ field, operator, value }, ...]`, since 17.6.0: the legacy fallback `ObjectGrid` reads only when `filter` is absent, …" - after: "`@objectstack/spec` typed `ComponentPropsMap['object-grid'].defaultFilters` as the same `ViewFilterRule` array as `filter`, `[{ field, operator, value }, ...]`, from 17.6.0 until 18 retired it (see objectui#12030): the legacy fallback `ObjectGrid` read only when `filter` was absent, …" - before: "… so `table: { defaultFilters: [{ field: 'status', operator: 'equals', value: 'open' }] }` now parses there, on the tolerant and the strict face and through `safeValidateSchema`." - after: "… so `table: { defaultFilters: [{ field: 'status', operator: 'equals', value: 'open' }] }` parsed there through 17.x, on the tolerant and the strict face and through `safeValidateSchema`; retired in 18, see objectui#12030: the row's tombstone refuses `defaultFilters` by name, so every value, the rule array included, is refused there now." - before: "… with the protocol's own message, which computes the rule array from the record's keys. Respell `defaultFilters: { status: 'open' }` as `defaultFilters: [{ field: 'status', operator: 'equals', value: 'open' }]`, or, better, move it to `filter`, which takes the same array and wins when both are written." - after: "… with the protocol's own message, which computed the rule array from the record's keys through 17.x (since 18, see objectui#12030, it is the retired-key message, which names `filter`). Respell `defaultFilters: { status: 'open' }` as `filter: [{ field: 'status', operator: 'equals', value: 'open' }]`; the rule-array `defaultFilters` spelling this entry first offered is retired in 18 too." - before: "`ObjectGrid` lowers `defaultFilters` through the same `toFilterNode` sink as `filter`, so a rule array there sends the same `$filter` and draws the same rows as the same array written as `filter`; the sink still lowers a record or an AST that reaches the slot at runtime, …" - after: "`ObjectGrid` lowered `defaultFilters` through the same `toFilterNode` sink as `filter`, so a rule array there sent the same `$filter` and drew the same rows as the same array written as `filter` (retired in 18, see objectui#12030: `ObjectGrid` and `ObjectView` no longer read `defaultFilters`, so a view that still carries it renders unfiltered); the sink still lowers a record or an AST that reaches `filter` at runtime, …" - before: "… and `@object-ui/plugin-grid` a pin of the above through the real renderer." - after: "… and `@object-ui/plugin-grid` a pin of the above through the real renderer (since 18 it pins that `defaultFilters` is not read)." - `.changeset/11880-grid-filter-handoff.md` (`@object-ui/plugin-view` patch) - before: "… else `table.filter` unless it lowers to nothing (absent, `[]` or `{}`), else the deprecated `table.defaultFilters`." - after: "… else `table.filter` unless it lowers to nothing (absent, `[]` or `{}`), else the deprecated `table.defaultFilters` (a rung retired in 18, see objectui#12030: `ObjectView` no longer reads it)." - before: "What did not change: which filter applies (each rung and each pair sends the same query and draws the same rows), and …" - after: "What did not change: which filter applies (each rung and each pair sends the same query and draws the same rows; in 18 the `table.defaultFilters` rung is retired, see objectui#12030), and …" - Left as written: the export and page-1 bullets, which say in the past tense what a `table.defaultFilters` did before this entry's change. - `.changeset/12056-repeater-object-waiver.md` (`@object-ui/types` minor) - before: "The zod arm `ElementRepeaterBlockSchema` now applies the same `object` waiver as the spec's props gate (objectui#12056)." - after: "The zod arm `ElementRepeaterBlockSchema` now applies the same `object` waiver as the spec's props gate (objectui#12056); superseded in 18, see objectui#12030: spec 18 retires the flat `object`, so the arm is the row by reference, a node may omit `properties.object` whether or not it binds one, and a `properties.object` is refused by name." - before: "`ComponentPropsMap['element:repeater']` requires `object`." … "a node refinement puts the requirement back wherever no binding names the object." - after: "`ComponentPropsMap['element:repeater']` required `object` through 17.x." … "a node refinement puts the requirement back wherever no binding names the object. Both waivers and both refinements are gone in 18 (objectui#12030): the row has no flat `object` left to waive." - before: "**Still refused at `properties.object`.** A bag with no `object` is refused when the node has no binding that names one: … The refusal is a `custom` issue with `params.code` `ELEMENT_REPEATER_OBJECT_REQUIRED`, and its message names both remedies. A `properties.object` of the wrong type is still refused by the row itself." - after: "**Still refused at `properties.object` through 17.x.** A bag with no `object` was refused when the node had no binding that names one: … The refusal was a `custom` issue with `params.code` `ELEMENT_REPEATER_OBJECT_REQUIRED`, and its message named both remedies; retired in 18, see objectui#12030: the refinement and its code are deleted, so such a bag is accepted. A `properties.object` of the wrong type is still refused by the row itself, and since 18 so is any `properties.object`, by name." - before: "**TypeScript.** The inferred `ElementRepeaterBlockSchema` type now types `properties.object` as optional, and so does the authoring type `PublicBlockNodeOf` for `'element:repeater'` derived from it." - after: "**TypeScript.** The inferred `ElementRepeaterBlockSchema` type typed `properties.object` as optional through 17.x, and so did the authoring type `PublicBlockNodeOf` for `'element:repeater'` derived from it; since 18 (objectui#12030) both follow the row, which retires `object`, so a typed `properties.object: string` no longer type-checks." - `.changeset/7611-approver-registry-read.md` (`@object-ui/app-shell` patch) - before: "A `data` binding (`user`, `team`, `department`, and `position` under the installed spec 17.7.0) still renders the record lookup … The type that admits the registry member before the installed spec declares it is marked for removal when objectui moves to spec 18." - after: "A `data` binding (`user`, `team`, `department`, and `position` under spec 17.7.0; spec 18, which objectui installs from objectui#12030, binds `position` to the registry) still renders the record lookup … The type that admitted the registry member before the installed spec declared it was marked for removal when objectui moves to spec 18; removed in 18, see objectui#12030." - `.changeset/11880-element-datasource-only.md` (`@object-ui/components`, `app-shell`, `i18n` patch) - before: "The flat keys stay published until `@objectstack/spec` retires them in v18; each input's description now says it is not read and names the binding member that is." - after: "The flat keys stayed published, each input's description saying it is not read and naming the binding member that is, until `@objectstack/spec` retired them in 18: the record picker's four inputs left with that retirement (objectui#12030), and `element:number`'s two with objectui#12085." - `.changeset/12056-repeater-datasource.md` (`@object-ui/app-shell` patch) - before: "… and `objectui validate` accepts the node the designer now writes, because `@object-ui/types` takes the spec gate's `object` waiver for this element in the same change." - after: "… and `objectui validate` accepts the node the designer now writes: `@object-ui/types` took the spec gate's `object` waiver for this element in the same change, and since 18 (objectui#12030) the row has no flat `object` left to require." - `.changeset/11880-repeater-datasource.md` (`@object-ui/types` minor, `@object-ui/components` patch), found by the grep beyond the six named - before: "Its flat `properties.object` / `filter` / `sort` / `limit` stay read, as the fallback, until `@objectstack/spec` retires them in v18, so a repeater that carries only the flat keys reads exactly as before." - after: "Its flat `properties.object` / `filter` / `sort` / `limit` stay read, as the fallback (spec 18 retires them as authored keys, see objectui#12030, and the renderer keeps this read for metadata written before), so a repeater that carries only the flat keys reads exactly as before." - before: "… by reference, and its bag's `object` stays required. Clause-②: the strict authoring face (`StrictAnyComponentSchema`) now ACCEPTS a repeater carrying `properties.object` plus a well-formed `dataSource`, which it refused as an unrecognized key; …" - after: "… by reference, and its bag's `object` stays required (until objectui#12056's waiver, and retired in 18, see objectui#12030). Clause-②: the strict authoring face (`StrictAnyComponentSchema`) now ACCEPTS a repeater carrying `properties.object` plus a well-formed `dataSource`, which it refused as an unrecognized key (since 18 it refuses `properties.object` by name, so the accepted node carries the binding alone); …" - before: "A repeater whose bag omits `object` is refused at `properties.object` on both faces, as before." - after: "A repeater whose bag omits `object` is refused at `properties.object` on both faces, as before; retired in 18, see objectui#12030: such a repeater is accepted." - One clause here went false with objectui#12085, not with this change, and is corrected in the same edit because it sits in the same bullet: before "The registration now publishes the injected `dataSource` input; `object` stays required." after "The registration now publishes the injected `dataSource` input; `object` stays required (until objectui#12085 dropped the flat query inputs)." Evidence: the `element:repeater` registration in `packages/components/src/renderers/basic/data-list.tsx` at the head publishes `titleField`, `fields`, `emptyText`, `divided` and no `object`, and `.changeset/12085-element-binding-inputs.md` states the drop. The seat may ask for this one clause back out. - `.changeset/12093-spec-main-compile.md` (empty frontmatter, no release) - before: "… a retired-key tombstone on objectstack `main`), and `@object-ui/app-shell`'s zh-CN overlay-scope label table keys `org` beside the spec's union (objectstack `main` narrows that union to `env`)." - after: "… a retired-key tombstone on objectstack `main`; the read itself is retired in 18, see objectui#12030), and `@object-ui/app-shell`'s zh-CN overlay-scope label table keys `org` beside the spec's union (objectstack `main` narrows that union to `env`; the `org` label is dropped in 18, see objectui#12030)." The grep over all 438 pending `.changeset/*.md`, besides this change's own three: `defaultFilters` hits 6152, 11880-grid-filter-handoff and 12093; `ELEMENT_REPEATER_OBJECT_REQUIRED` hits 12056-repeater-object-waiver only; `ELEMENT_NUMBER_OBJECT_REQUIRED` and `manage_org_presentation` hit nothing; the overlay-scope `org` hits 12093 only (12171-environment-overlay-copy names the environment overlay and stays true); the flat element query keys and the record picker hit 11880-element-datasource-only, 11880-repeater-datasource, 12056-repeater-datasource, 12056-repeater-object-waiver and 12085-element-binding-inputs. Read and left as written, each still true at release: `12085-element-binding-inputs.md` (the repeater renderer still reads its flat keys as the fallback; this change does not touch it), `11903-record-picker-layout.md` (the `RecordPickerDialog`, not `element:record_picker`), `12086-edit-inline-default-on.md`, `7611-capability-registry.md` (names no curated capability), `7611-decision-output-position-registry.md`, and the "since 17.7.0" mentions elsewhere, which date a fact and do not say what is installed. ## Merge of `main` `main` at `72594f29e` is merged into this branch (merge commit `1c95e13af`): six commits, merged cleanly, none touching a manifest or the lockfile. Files both sides changed: `packages/app-shell/src/views/metadata-admin/i18n.ts`, `packages/plugin-grid/src/ObjectGrid.tsx` and the ten `packages/i18n` locale packs. The re-run after the merge is under Gates. ## The break list (measured first, at `a3f41ed`: the bump and changeset alone) Full vitest suite in five lock-held groups: 50 files, 159 tests red (types/core 15 files / 58 tests, app-shell 21 / 45, plugins A 6 / 20, plugins B 1 / 4, console + scripts 7 / 32). `turbo run build` and every package's `type-check` were green at that commit (81/81). Two repo gates were red: `check:eager-closure` (exit 2, ceilings drifted to 1.74x / 1.21x) and `check:installed-pin-claims` (23 findings). By area, and how each is closed: - **Overlay scope is `env` only** (objectstack#15206 S5; card comment 6098299437): data-objectstack overlay fixtures and docblock, two app-shell fixtures, and the zh scope label table drops its `org` row (an `org` from an older server renders raw), as the ruling's seat judgment says. - **Index `unique: true` refused at protocol 18** (objectstack#22103): the index-fallback probe pins the refusal with a `false` control, the validation-preview help (en, zh) names `unique: 'organization'` / `unique: 'global'`, and the `data-protocol.ts` docblock says refused. These are the three sites objectstack-ai#11864 names; its legacy-row rendering case still passes and is left as is. - **Capabilities**: `manage_mailboxes` in, `manage_org_presentation` out; curated label set, default label, ten locale packs. - **Page blocks**: the element:number / element:repeater / element:record_picker flat query keys and object-grid `defaultFilters` are tombstones refusing by name (objectstack#11509). The number and repeater arms take the spec row by reference; the record_picker node and registration stop publishing the four keys; the parity, arm, harvest and ratchet pins read the tombstones. - **Retired reads removed** (seat judgment under the ruling): `ObjectGrid` no longer reads `defaultFilters` and `ObjectView` no longer reads `table.defaultFilters`; no in-repo producer writes the key. The tests that pinned it as a working alias are flipped to pin it inert beside a canonical `filter` control; the plugin-view README and docs page, the registration's `filter` description and the type's JSDoc say retired. The element:repeater renderer's flat `object` / `filter` / `sort` / `limit` fallback reads stay: `apps/console/src/dev/DevLists.tsx` writes `properties.object` and `properties.limit` and is routed in `App.tsx`, a live in-repo producer, reported instead of deleted. element:number and element:record_picker already read the binding only. - **New spec checks and keys**: `checkPageSlotPair` attached to both page node schemas beside the existing page checks (and listed among the chained refinements in the import-boundary census); `checkPagePrintComposition` and `checkListViewChartBinding` read a discriminator and are listed as not attachable; ObjectSchema's 45th key `attachedOnRead`; record:details sections' experimental `count` listed as deferred; the three new record component types stay out of the block palette, as today. - **ListView `requiredPermissions`**: pinned as the one pending declaration; the seat files a declare-and-enforce card. - **Flow dialect** (ADR-0032 §3): flow fixtures move to the spellings v18 accepts. The value-role ledger now names six key/value maps; each node's own config schema was measured to refuse a malformed envelope with exactly the sentences the editor shows. A `{token}` string in a value slot is refused by the spec and the editor shows that refusal; the recipient picker's `{…}` spellings and a blank Notify title are pinned as PENDING. The Studio cutover rides objectstack-ai#11824. - **Approver `position`** is a registry binding in v18: the spec-17 loosening is removed and the identity, directory and lookup pins read the registry arm. - **Submit-redirect / url action**: the refusal pins read the spec's own message; a url action target fixture is a path. - **Installed-spec claims**: re-measured and restamped where the fact holds, one historical mention ledgered; `e2e/live/ci/backend.env` moves to `18.0.0-next.0`; `QUICK_REFERENCE.md` re-synced by its script; lockfile-integrity samples re-keyed; the declared-floor reader in core's test reads a prerelease floor by its core triple. ## objectstack-ai#11762: the eager-closure line comes down by the payback objectstack#22044 (`93125aeeb8`) is an ancestor of the `18.0.0-next.0` tag commit (compare `ahead`, behind 0; control: the 17.7.0 tag commit `4e4e881427` also reads `ahead`). Two console builds, one container, one instrument (`CI=true pnpm exec turbo run build --filter=@object-ui/console... --concurrency=2` under the verify lock, reading `apps/console/dist/eager-closure.json`): | chunk | `cea429b` (main, 17.7.0) | `5488ae7` (this branch) | delta | |---|---:|---:|---:| | `vendor-objectstack` | 1,599,281 | 1,512,690 | -86,591 | | `ui-components` | 285,771 | 284,925 | -846 | | everything else (70 names) | 1,324,632 | 1,324,848 | +216 | | aggregate | 3,209,684 | 3,122,463 | -87,221 | `MAX_EAGER_CLOSURE_GZIP_BYTES` 3,281,467 to 3,194,246 and `BASELINE` onto `5488ae7`; `vendor-objectstack` ceiling 1,623,000 to 1,536,409 and its baseline onto the same build. Lowered by the amount, not re-derived and not rounded; `REGRESSION_THIS_GATE_MUST_CATCH_BYTES` untouched. Headroom 71,783 bytes = 0.79x on the aggregate, 23,719 = 0.26x on `vendor-objectstack`: what `main` had on `cea429b`. The ruling's two later edits reach bundler inputs; the console build of `5db13ba` reads 3,122,375 (-88), inside the band, recorded in the `BASELINE` prose and not re-pinned. The upstream figure (-226,301 on the chunk) was on an injected dist; the real release nets -86,591 here. ## Held cards: their Restart-when probes at the head | card | probe | at the head (`18.0.0-next.0`) | control | |---|---|---|---| | objectstack-ai#11773 | `MetadataConflictErrorSchema` in the spec's `.d.ts` | exit 0 (next tarball and installed tree) | `@latest` (17.7.0) tarball: exit 1 | | objectstack-ai#11545 | `anonymousFormIntakeCandidates` is a function | exit 0 | 17.7.0: exit 1 | | objectstack-ai#11681 | a release carrying the objectstack#21995 change (objectstack-ai/objectstack#22021, `c5658133fc`) | met: ancestor of `d889448b93` | | | objectstack-ai#11970 | lockfile resolves `@objectstack/core` and `@objectstack/objectql` carrying `4578c56e65` | half: core `18.0.0-next.0` carries it; objectql is in no objectui manifest or lockfile, so the probe cannot be met as written | | | objectstack-ai#11958 | spec/ui exports `PagePrintSchema` and `PRINTABLE_PAGE_COMPONENT_TYPES` | exit 0 | 17.7.0: exit 1 | | objectstack-ai#11864 | spec carries `ef1fcb26a2` | met; its three sites are changed in this PR | | | objectstack-ai#11762 | spec tag commit has `93125aeeb8` as ancestor | met; executed in this PR | | | objectstack-ai#12270 | `EmptyStateSchema` link + `InterfacePageConfigSchema.emptyState` | exit 0 | 17.7.0: exit 1 | | objectstack-ai#12271 | `ListDraftsResponseSchema` reads a draft's `member` | exit 1 as written: the probe's fixture has no `label`, which the schema requires (`drafts.0.label` invalid_union). With `label` added: exit 0 | with `label`, 17.7.0: exit 1 | | objectstack-ai#12192 | `SECTION_HEADING_COUNT_HOLE` is a RegExp | exit 0 | 17.7.0: exit 1 | | objectstack-ai#12314 | `stampCommentBodyEdit` in `@objectstack/plugin-audit` dist | the probe reads `@latest` (17.7.0): exit 1. Run against `@next` (18.0.0-next.0): exit 0 | | ## Gates (final head `0cc4e8fcc`; each reading names the commit it ran on) **Patch round 3** (`0cc4e8fcc`: pending-changeset wording only, eight `.changeset/*.md`, no code, manifest or lockfile): - At `0cc4e8fcc`, one script under the verify lock, `VERDICT command-exit 0` (the script exits 1 if any gate does), each gate exit 0: `check-changeset-presence` ("102 source file(s) and 1 published-contract change(s) of 18 released package(s) changed, and this change declares 3 changeset(s)"), `check-changeset-no-major` ("No changeset declares a `major` bump."), `check-changeset-fixed`, `check-changeset-overwrite` (report-only: "3 changeset(s) added, 8 modified, 0 deleted", and for each of the eight the declared packages and levels at base equal those now), `check:changeset-claims` ("No pending changeset names a file this change touches."), `check:pending-changeset-literals` ("No test source names a pending changeset."), `check:new-line-citations` (0 new), `check:control-bytes` ("OK (scanned 8636 tracked text file(s); skipped 85 binary)"), `check:installed-pin-claims` (`OK`). - The script tests that read the live `.changeset/` or run a changeset gate against this tree, at `0cc4e8fcc` under the verify lock: `pnpm exec vitest run --project unit --maxWorkers=2` over `changeset-polarity-census`, `check-changeset-no-major`, `body-dialect-census`, `dollar-dialect-alias-census`, `check-changeset-claims`, `check-pending-changeset-literals`, `check-control-bytes`, `check-changeset-presence` and `check-changeset-overwrite`: 9 files, 376 tests passed, `VERDICT command-exit 0`. No code moved, so no package test or type-check was re-run. **Patch round 2** (`0d457fe1f`: the runner changeset line, the three objectui-side moves named in the main changeset, and the last `defaultFilters`-is-read prose reworded, in the `ObjectGridSlotKey` docblock that ships in the published `.d.ts`, the zod mirror's member comment, the grid round-10 test header, the grid registration note and the `ObjectGrid` props note): - At `0d457fe1f`, under the verify lock, each exit 0: `check-changeset-presence` ("declares 3 changeset(s)"), `check-changeset-no-major`, `check-changeset-fixed`, `check:changeset-claims`, `check:pending-changeset-literals`, `check:new-line-citations` (0 new), `check:control-bytes`, `check:spec-symbols`, `check:installed-pin-claims`. - `pnpm exec turbo run type-check --filter=@object-ui/types --filter=@object-ui/plugin-grid --concurrency=2 --continue`: 16/16. Then `grid-default-filters-gantt-map-filter-round10-6152`, `object-view-slot-key-lists` and `imported-defaults-8317`: 3 files, 392 tests passed. - `git grep defaultFilters` over non-test sources (READMEs, docs and changelogs aside): what is left says retired, not read; describes history (objectui#6152, objectui#11880); or is the declaration typed by the row's tombstone. **Patch round, after the `main` merge** (`1c95e13af`; `a1ca2ad55` only rewords the i18n changeset): - Changeset family and i18n gates at `a1ca2ad55`, under the verify lock, each exit 0: `check-changeset-presence` (above, "declares 3 changeset(s)"), `check-changeset-no-major`, `check-changeset-fixed`, `check:changeset-claims`, `check:pending-changeset-literals`, `check:i18n-keys` ("Every in-scope call-site key resolves against the en pack (3375 keys)"), `check:i18n-drift`, `check:i18n-designer-parity`, `check:i18n-dead-keys` (a report), `check:control-bytes`. - Type-check of the packages the merge touched, at `1c95e13af`: `pnpm exec turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/components --filter=@object-ui/data-objectstack --filter=@object-ui/i18n --filter=@object-ui/plugin-detail --filter=@object-ui/plugin-grid --filter=@object-ui/types --concurrency=2 --continue`, 36/36. - Tests the merge brings into contact with this diff, at `1c95e13af`. The set: every package `main` and this branch both changed (`plugin-grid`, `i18n`, `data-objectstack`, `types`) in full; every test file `main` added or changed, now running on spec 18; and every app-shell test that imports `metadata-admin/i18n`. Result: 920 files passed, 16,446 tests passed, 13 skipped. The merge touches no manifest and no lockfile, so the whole suite was not re-run; this head's CI shards are the whole-workspace reading. **Before the patch round:** - **Install:** `pnpm install --frozen-lockfile` exit 0, then a plain `pnpm install` leaves `pnpm-lock.yaml` byte-identical (sha256 compared), at `e8e9a35`; the lockfile and every manifest are unchanged since (`git diff e8e9a35 HEAD` over them is empty). Lockfile diff against `cea429b`: +106 / -106. - **Build:** `CI=true pnpm exec turbo run build --filter=@object-ui/console... --concurrency=2`, exit 0, 35/35 tasks, at `5db13ba` (the last commit touching a bundler input); the whole published set was built (43 tasks) by `pnpm check:published-dist` at `e8e9a35`. - **Type-check:** `pnpm exec turbo run type-check --concurrency=2 --continue`, 81/81, at `5db13ba` (later commits touch only scripts, tests and the changeset); `pnpm type-check:scripts && pnpm type-check:vitest-setup && pnpm type-check:e2e && pnpm type-check:vitest-config`, exit 0, at `2c15e40cf`. - **Tests** (`pnpm exec vitest run --maxWorkers=2`, five lock-held groups): - types, core, react, i18n, cli, data-objectstack and 12 more at `5488ae7`: 1019 of 1020 files; the red one (`imported-defaults-8317`, from attaching `checkPageSlotPair`) is fixed in `ef2054e` and re-ran green (267 tests). Later commits touch these packages only in one JSDoc. - app-shell at `ef2054e`: 1319 passed, 1 skipped (12,730 tests). After the `org` label drop, the four overlay-scope files re-ran green at `2e80824ea` (21 tests). - plugin-detail, plugin-grid, plugin-form, plugin-dashboard, components at `2e80824ea`: 1207 passed, 2 skipped (12,292 tests). - fields and the other 15 plugins at `7ec3c2b`: 1031 passed, 2 skipped (11,505 tests). - apps/console, scripts, eslint-rules, examples at `7ec3c2b`: 429 passed, 2 failed. `check-eager-closure-budget.test.ts` pinned the rendered `BASELINE` literal: re-taken in `2c15e40cf` and green there. `date-only-zone-pins-10183.test.ts` is red at `2c15e40cf` too, and is NOT caused by this diff: its forked child fails one case, the suite-zone control of `FormulaFunctions.dateOnlyZone-10866`, which reads `Asia/Shanghai` (11:00) instead of UTC (03:00). That is the zone another pin file sets last. The `unit` project runs `isolate: false`, and the file order comes from each worktree's vitest cache. Run alone in the same forked child at `2c15e40cf`, that file passes (39/39). The same driver passed 28/28 at `a3f41ed` in a fresh worktree. This diff touches neither the pin files nor the code they exercise. - **Repo gates at `2c15e40cf`**, exit 0 unless noted: `check-changeset-presence` (above), `check-changeset-no-major` ("No changeset declares a `major` bump."), `check-changeset-fixed`, `check:changeset-claims`, `check:pending-changeset-literals`, `check:installed-pin-claims` (`OK`), `check:spec-symbols`, `check:phantom-deps`, `check:new-line-citations` (0 new), `check:control-bytes`, `check:eager-closure` ("3049.2 KB gzipped across 72 of 2255 chunks (budget: 3119.4 KB, headroom: 70.2 KB)", every ceiling in range). `check:spec-floors` exits 1, the known effect above. `.changeset/pre.json` is absent. `check:lockfile-integrity`, `check:lockfile-dedupe`, `check:unused-deps` and `check:published-dist` were green at `2e80824ea` / `e8e9a35` and nothing they read has changed since. ## Acceptance notes - Records: the v18 ruling 6148150305 and the gate ruling 6149296149 both move to the follow-up card. This change is the dependency move only, per the split record 6149376719. The seat's judgments under 6148150305 are executed here: drop the zh `org` label, remove the retired reads. - Follow-up cards from the seat's judgments: - the list-view `requiredPermissions` mirror: objectstack-ai#12341; - the three record component types (`record:approval_decision` registration): objectstack-ai#12342; - the file field's `metadataRefused` marker: objectstack-ai#12339; - holding a blank Notify title: objectstack-ai#12343; - the `element:repeater` flat-key reads and their `DevLists.tsx` producer: objectstack-ai#12345; - the Studio flow-dialect cutover: objectstack-ai#11824; - the objectui 18.x release: objectstack-ai#12334. - Kept at today's behaviour and pinned as pending, for those cards: the recipient picker's `{…}` spellings; a blank Notify title, which is sent and shows the server's refusal; a `{token}` value-slot text cell, which commits as typed and shows the spec's refusal; the new record component types, which stay out of the palette; ListView `requiredPermissions`, still undeclared. - The element:repeater renderer keeps its flat-key fallback reads: `apps/console/src/dev/DevLists.tsx` (routed in `App.tsx`) writes `properties.object` / `properties.limit`, a live in-repo producer. - `isValueEnvelopeSlot` serves `KEY.*` ledger paths only, as before. The ledger's whole-value `value` slots (the record nodes' `filter`, loop/map `collection`, notify `recipients`) are not key/value maps, and this change leaves them alone. - objectstack-ai#11864's legacy-row case (`EmbeddedItemEditor.indexFallback`, a stored bare `unique: true` still renders) passes and is left as is: an 18 server serves such a row as `'global'`. - Two held cards' probes cannot exit 0 as written. objectstack-ai#11970 names `@objectstack/objectql`, which no objectui manifest or lockfile carries. objectstack-ai#12271's fixture lacks the required `label`. - The eager-closure readings above, and the `BASELINE` prose in `scripts/check-eager-closure-budget.mjs`, cover this branch's own commits through `2c15e40cf`. The `main` merge (`1c95e13af`) brings `main`'s own bundler inputs, which are `main`'s drift, not this change; CI's `Bundle Analysis` weighs them on this head. The console was not rebuilt locally after the merge. - AGENTS.md §版本号策略 names `OBJECTUI_ALLOW_MAJOR=1` as the sync-major switch, and nothing in `.github/` sets it. That is the follow-up card's to settle; this change leaves AGENTS.md alone. --- _Generated by [Claude Code](https://claude.ai/code/session_01CsyMRsFJBKNEMa4oPY1b5z)_ --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: os-zhuang <jack@objectstack.ai>
…ion runner, and let a list page's own emptyState win (objectstack-ai#12270) (objectstack-ai#12350) Fixes objectstack-ai#12270 Clause-②: yes. The renderers start honouring two keys the spec declares (`emptyState.link`, and a list page's own `interfaceConfig.emptyState`), so what they draw widens. A contract review is owed before the queue. ## What this does This is the renderer half of objectstack-ai/objectstack#23004 and objectstack-ai/objectstack#23005 (spec PR objectstack-ai/objectstack#23098, merge `55828c5145`). It is unblocked because `main` `c5406c2` installs `@objectstack/spec` `18.0.0-next.0`. The change covers three doors, which share one piece: - **The shared piece.** `EmptyStateLink`, in `@object-ui/components` beside `DataEmptyState`, draws the spec's `EmptyStateSchema.link` (`{ label, url }`). It follows the link as a `type: 'url'` action through the ActionRunner, so what a click opens is `classifyNavigationTarget`'s answer. It has no scheme list or URL check of its own. The `label` is a string or an inline locale map, resolved against the caller's display locale. A label that resolves to no text draws nothing. - **Door 1: `ListView`'s empty branch** (`grid` and `gallery`). `ListEmptyState` takes `link` and draws it beside the add-record action. The spec keeps the two apart, so either, both or neither may be drawn. With no link, the DOM is unchanged. - **Door 2: `InterfaceListPage`.** The relay is now `emptyState: cfg.emptyState ?? view.emptyState`. The page's own block wins, whole, on every visualization, because the precedence is set before the visualization is chosen. The source view's block is the fallback, as its `appearance` and `userFilters` already are. - **Door 3: `ObjectGrid`'s authored empty state.** It draws the link in `DataEmptyState`'s action slot. The registry `inputs` description for `object-grid.emptyState` now names `link`. Docs: the `emptyState` passages in the plugin-grid docs, README and schema reference now cover `link`. The plugin-list README gains an "Empty state" section; its typed example is checked below. Changeset: `minor` for the four packages touched. **For the spec seat (scope addition `6132635356`):** the renderer half has landed with this PR. The follow-up is the spec seat's to file: dropping the two `[PLANNED — not yet rendered]` markers, rewriting the two ledger notes, offering `interfaceConfig.emptyState` in the page form and deleting its reconciliation omit row. ## How a declared link becomes a runner navigation (measured with the real runner) | target | under the console's provider (`onNavigate` + `onToast`) | no provider | |---|---|---| | in-app `/...` or `./...` | `onNavigate(url, { external: false, newTab: false })`, in place | the runner hands back `redirect`; followed with `location.assign` | | `https://...` | `onNavigate(url, { external: true, newTab: true })` | `window.open(url, '_blank', 'noopener,noreferrer')` | | `mailto:` | `location.href = url`, in place; router and new tab untouched | the same | | refused (`//host`, `thanks`, `javascript:`) | the runner's own refusal is toasted as an error; no navigation sink is touched | the refusal is logged; no navigation | A followed link raises no success toast. The html-tier anchor and the dashboard header link raise none either. ## Deviation from the suggested route: a refused value is refused at the click, not hidden at render The suggested route said "a refused value draws no link". Here the link is drawn, and the runner refuses it at the click and reports its refusal. Reason, measured: the spec admits at parse a url that starts with a `${...}` template, and its describe says such a url "is judged when it is clicked, after interpolation". `EmptyStateSchema.safeParse` of a link whose url is `${ctx.origin}/docs` succeeds on `18.0.0-next.0`. But `classifyNavigationTarget` answers `null` for that string before interpolation. Classifying at render would therefore hide a spec-valid link, and it would also be a second judgement beside the runner's. The pin `judges nothing at render` holds this. ## Mechanism assumptions, measured 1. **Installed shapes.** `EmptyStateSchema.link` is strict `{ label: I18nLabel, url: string }`: `url` has `min(1)` and `navigationTargetRefusal` at parse. `InterfacePageConfigSchema.emptyState` is `EmptyStateSchema.optional()`. The hold's restart probe exits 0 from `apps/console`. 2. **At `c5406c2`.** `ListView` handed `ListEmptyState` `title`, `message` and `icon` only, and `ObjectGrid` read the same three. `InterfaceListPage` relayed the source view's whole block (`emptyState: view.emptyState`) and read no `cfg.emptyState`; it did not read individual members. `classifyNavigationTarget` is the runner's one classifier (`navigateTo`, `navigateOnSuccess`). 3. **`@object-ui/types` mirrors: none needed.** `ListViewSchema['emptyState']` and `ObjectGridSchema.emptyState` both take the spec's `EmptyState` by reference. The README example (a `ListViewSchema` literal with `link: { label, url }`) compiles against the built `@object-ui/types`. The same literal with an undeclared `href` inside the link is refused with TS2353. ## Tests (real renders, real runner) - `packages/components/src/__tests__/empty-state-link-12270.test.tsx` (14). Under a provider: in-app, https, mailto, three refused values (each compared with what a bare runner answers for the same target), a `${...}` template drawn, the label map, and absent or empty label. With no provider: redirect followed, https in a new tab, refusals logged. - `packages/plugin-list/src/__tests__/ListView.emptyStateLink-12270.test.tsx` (4). Grid: link beside add-record, followed in place. Gallery: https. A refused value. A control with no link. - `packages/plugin-grid/src/__tests__/ObjectGrid.emptyStateLink-12270.test.tsx` (4). Through the registry and `SchemaRenderer`: in-app, https, refused, and a control with no link. - `packages/app-shell/src/views/InterfaceListPage.emptyStatePrecedence-12270.test.tsx` (6). The real page renders the real `ListView`. The page's own sentence and link win on grid and on gallery, whole (the view's message does not fill the gap). The page's link is followed in place. With no page block, the view's is kept, on grid and on gallery. A control proves the gallery page mounts the gallery (cards, no table). ## Ablation (three legs) Each leg went through `ablation-replace.mjs`: the anchor hit exactly once, the blob changed, and the restore was proven by blob == HEAD and an empty `git diff HEAD`. The direction was red, as predicted. - `InterfaceListPage`: `cfg.emptyState ?? view.emptyState` became `view.emptyState`. 3 failed, 3 passed: the three page-precedence rows failed; the two fallback rows and the gallery control passed. - `ListView`: `link={schema.emptyState?.link}` became `link={undefined}`. 3 failed, 1 passed: the control passed. - `ObjectGrid`: the link's `action` slot was emptied. 3 failed, 1 passed: the control passed. ## Local verification (HEAD `f20667b0e`) - Closure build: `turbo run build --filter=@object-ui/plugin-list^... --filter=@object-ui/plugin-grid^... --filter=@object-ui/app-shell^...`, plus the two plugins: 28/28 tasks. - Suites (run from the repo root, path-filtered, through the verify lock): - `packages/plugin-list/`: 131 files, 1419 tests passed. - `packages/plugin-grid/`: 211 files, 1920 tests passed. - `packages/components/`: 382 files passed, 1 skipped; 3875 tests passed, 24 skipped. - The 27 app-shell files that name `InterfaceListPage` or `emptyState`: 273 tests passed. - `type-check` (`tsc --noEmit && tsc -p tsconfig.test.json`; each `tsconfig.test.json` includes `src/**/*.test.tsx`): components, plugin-list and plugin-grid all exit 0. - app-shell `type-check`, whole package: NOT MEASURED. Reason: the verify-lock queue had not reached it after about 30 minutes, behind another seat's long locked run (`issue-11773` `final2.sh`). It is declared here as a narrowing, and CI runs the whole package. The narrowed check: `tsc` with the package's own `tsconfig.test.json`, whose `files` are the three touched app-shell files (the new test, `InterfaceListPage.tsx` and the census test). That program spans 629 workspace files, 79 of them under `app-shell/src`, and exits 0. - eslint on the 12 touched TS files, with the repo config. The population comes from eslint's own JSON output: 12 files, 0 errors. Warning counts are unchanged on every touched source file, base vs head. The 5 warnings in the new app-shell test are the `no-explicit-any` mock pattern its siblings use. The narrowing is sound: the config enables no type-aware linting (no `project` / `projectService`), and no local rule reads another file, so this diff cannot move the verdict on an untouched file. - Repo checks, all exit 0: `check-changeset-presence`, `check:changeset-claims`, `check:pending-changeset-literals`, `check:new-line-citations`, `check:control-bytes`, `check:test-path-roots`, `check:vi-mock-specifiers`, `check:vi-mock-inherit`, `check:vi-mock-override-shape`, `check-doc-links`, `check:doc-json`, `check:doc-fences`, `check:doc-example-ids`, `check:doc-types`, `check:unreferenced-sources`, `check:phantom-deps`, `check:self-import`, `check:action-forward-parity`, `check:handler-key-reads`, `check:spec-symbols`, `check:esm-specifiers`, `check:i18n-keys`, `check-changeset-no-major`, `check-changeset-fixed` and `check-changeset-overwrite`. No string or i18n key was added, so the rest of the `check:i18n-*` family is not owed. - NOT MEASURED: `check:doc-snippets` and `check:readme-exports`. Reason: PRECONDITION NOT MET, because packages outside this closure are unbuilt (plugin-gantt, plugin-map, plugin-markdown, plugin-timeline and others). CI builds them. The judged rows of `readme-exports` show 0 fabricated names. The new README snippet was compiled on its own (item 3 above). ## Acceptance notes - `apps/console/src/__tests__/registry-inputs-spec-parity.test.ts`, in the `MEMBER_PINS` row `object-grid.emptyState`, still says "All three members the spec's strict `EmptyStateSchema` declares", but spec 18 declares four. It is mechanically green, because the check reads only that the pin file names the block and the key. The `link` member's pin is `ObjectGrid.emptyStateLink-12270.test.tsx`. This is prose drift, not filed. Carrier: whoever next edits that ledger row, for example the spec seat's follow-up above. - The empty state, and so its link, is drawn on the `grid` and `gallery` visualizations only, as before. The other visualizations draw their own empty surfaces. That is unchanged here; the page precedence holds whatever the visualization. - CI: `Spec Main Shape Gate` is red on `main` and on every PR (objectstack-ai/objectstack#23160; consumer objectstack-ai#12325). It is advisory and not this change's. `OAuthConsentPage.public-client-12301.test.tsx` is a known flake (objectstack-ai#12344). --- _Generated by [Claude Code](https://claude.ai/code/session_01CsyMRsFJBKNEMa4oPY1b5z)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…objectstack-ai#12361) Fixes objectstack-ai#12325 Clause-②: no The view-level `provider: 'api'` data source is retired in `@objectstack/spec` (objectstack-ai/objectstack#23082 ruling C, PR objectstack-ai/objectstack#23160). Per the maintainer's ruling A on the card (comment 6163115077), this deletes every objectui reader of a ViewData `api` block now, while objectui still resolves spec `18.0.0-next.0`, and keeps the approvals inbox's code-built `ApiDataSource`. ## What changed - `@object-ui/core` `resolveDataSource`: the `api` arm is gone. An `api` block is an unknown provider and gets that answer (the fallback, else `null`). `ResolveDataSourceOptions` loses `fetch` / `defaultHeaders`, which only that arm read. `ApiDataSource` stays a class; its docblock and its missing-config error now name `ApiDataSourceConfig`, not ViewData. - `@object-ui/core` `ViewDataProvider` (file-surface extension, see below): the `api` arm, `resolveApi`, `extractRecords` and `DataFetcher.fetchUrl` are gone; an `api` config resolves as `Unknown data provider: api`. - `@object-ui/react` `useViewData`: no `api` keying and no `apiFetch` threading. - `@object-ui/plugin-gantt` `ObjectGantt`: no `api` adapter input, no `apiFetch` dependency, the drawer's real-record fetch and the refresh button no longer branch on `api`. - `@object-ui/plugin-list` `ListView`: `ganttOwnsData` (true only for a gantt with an `api` block) and the matching skeleton branch are deleted, so such a view fetches its bound object like any list. - `@object-ui/plugin-calendar` / `@object-ui/plugin-map` (file-surface extension): their `dataProvider === 'api'` branches, which only warned "not yet implemented", are deleted. - Docs: `content/docs/plugins/plugin-gantt.mdx` and `plugin-map.mdx` sections that prescribed or described the `api` arm, the core adapters README, the gantt README, the `ViewData` docblock in `@object-ui/types`, and the approvals source's docblock. - Changeset `.changeset/12325-retire-api-view-data.md` (`minor` for the breaking packages, per the no-major rule). ## File-surface extensions (bounded in-place fixes, same defect class) The claim names `packages/core/src/adapters/`, `useViewData.ts`, `ListView.tsx`, `ObjectGantt.tsx`, `packages/types/src/` and the docs. Measurement found more readers of the same retired arm, so these were edited too, each a mechanical deletion of the same kind: - `packages/core/src/data-scope/ViewDataProvider.ts` (a second ViewData union with its own `api` arm; not caught by a `provider === 'api'` grep because it is a `case 'api'`). - `packages/plugin-calendar/src/ObjectCalendar.tsx` and `packages/plugin-map/src/ObjectMap.tsx`: `dataProvider === 'api'` (capital P, so the PM's case-sensitive grep missed them). Against spec `main` these are TS2367 once core compiles; the gate's run 38513810543 never reached them because `@object-ui/core`'s build failed first. - `packages/types/src/__tests__/object-gantt-properties-bag-10859-b6.test.ts` and `object-grid-properties-bag-11276.test.ts`: the TS2352 errors of A2. Their "the parse authors no default" pins rode on the `api` arm's `read.method` default; they now ride on `navigation`'s defaults (spec row writes `mode: 'page'`, this face writes nothing). - `apps/console/src/services/approvalRequestsDataSource.ts`: docblock only. ## Pins flipped (each asserts the new meaning) - `resolveDataSource.test.ts`: an `api` block builds no `ApiDataSource` and is answered exactly like an unknown provider. - `ViewDataProvider.test.ts`: an `api` config resolves to `Unknown data provider: api` and fetches nothing. - `useViewData.test.tsx`: an `api` block falls back to the context DataSource and requests its URL through neither `apiFetch` nor global fetch. - `ObjectGantt.apifetch.test.tsx` renamed to `ObjectGantt.retiredApiProvider-12325.test.tsx`: the `read` URL is requested through neither channel; with a context DataSource the chart queries `schema.objectName`. - `ObjectGantt.test.tsx`: the api read/write-back case is removed, pointing at the file above. - `recordSourceInputs-10394.test.tsx` (gantt), `index.recordSourceInputs-10394.test.tsx` (map), `ListView.test.tsx`: flipped to the retired meaning. - `ObjectGantt.emptyHostData-7333.test.tsx`: the host-array cases now use the bound object as the own source. - `ObjectGantt.discardedReloadIdentity-10036.test.tsx`: the discard pin moves from `api` to `value` (the constructing provider that remains); the "host swaps `apiFetch`" case is removed because `apiFetch` is no longer an adapter input. The mount baseline is measured rather than assumed (see Acceptance notes). ## Verification (head `93d4323`) - Spec Main Shape Gate reproduced locally: built `@objectstack/spec` from objectstack `b5b1bf7277d6` (contains the retirement; `VIEW_DATA_API_PROVIDER_RETIRED` present in `dist/ui/index.js`), packed, injected with `node scripts/spec-main-shape-gate.mjs inject`, then `TURBO_FORCE=true pnpm type-check --continue --concurrency=2`: 80 of 81 tasks green, the one red being `@object-ui/types` (the two TS2352 test casts). After the types-test fix, `@object-ui/types` + `@object-ui/plugin-gantt` type-check: 15/15 green on the same injected spec, and the two types test files 193/193 green there. Install then restored with `pnpm install --frozen-lockfile --force` (marker gone, retirement string 0 hits). - Installed spec (`18.0.0-next.0`): type-check of core, react, types, plugin-gantt, plugin-list, plugin-calendar, plugin-map: 22/22 tasks green; types + plugin-gantt re-run after the last commit 15/15. - vitest: every changed test file, 13 files / 460 tests green; approvals inbox (`approvalRequestsDataSource*.test.*`, 4 files) plus neighbouring `api` pins (ApiDataSource, record-source neutrality, ObjectView protocol keys, tree inputs, grid data contract, ListView 7477) green; earlier whole-package runs of plugin-gantt, plugin-map, plugin-calendar green. - Reverse validation of the narrowed `ResolveDataSourceOptions`: passing `{ fetch: undefined }` from `ObjectGantt` gives TS2353 against the rebuilt `dist` (whose `.d.ts` has no `fetch`); restored, hash equals HEAD, `git diff HEAD` empty. - Gates (exit codes): phantom-deps, self-import, unreferenced-sources, doc-example-readers, handler-key-reads, side-effects-array, element-data-source-declaration, spec-symbols, action-forward-parity, designer-field-key-parity, icon-record-names, i18n-keys, i18n-drift, i18n-designer-parity, doc-json, doc-snippets, doc-examples, new-line-citations, changeset-fixed, changeset-presence, changeset-no-major: all 0. `check:spec-floors`: 1, the known unrelated "scan collapsed" red carried by objectui#12334. - Lint narrowed to the 23 changed `.ts`/`.tsx` files (count from `eslint --format json`): 0 errors. The config enables no type-aware linting, so this diff cannot change any untouched file's verdict. ## Acceptance notes - A block that still says `provider: 'api'` is not refused by objectui at runtime: under spec `18.0.0-next.0` it parses, and the renderers answer it as an unknown provider (gantt and `useViewData` fall back to the context adapter and `objectName`; map and calendar draw nothing). The refusal lands with the spec pin bump, where `ViewDataSchema` rejects it. Measured: no objectui metadata writes `provider: 'api'`. - An inline `value` gantt reads its items more than once while mounting (measured in the moved 10036 pin). Pre-existing and unrelated to this change; the pin measures its baseline instead of hard-coding one. - Stale prose left alone, outside this file surface: `SchemaRendererContext.apiFetch`'s docblock and `packages/react/README.md` still say it serves `provider: 'api'` view data (it still serves other channels); `ConsoleShell.tsx`, `createAuthenticatedFetch.ts` and `packages/auth/README.md` comments likewise; the `DataSource required for object/api providers` error text (pinned by the schema-catalog render test); the `plugin-grid` `data` input description, which still lists `{ provider: "api", read, write }`. - Tests that parse `provider: 'api'` through the spec (`gridDataInputContract`, `objectTreeInputs-11168`, the two types probe lists) will need flipping at the spec pin bump; they are not readers. - `content/docs/plugins/plugin-calendar.mdx` lists the `api` config among shapes the calendar refuses; still true, left as is. Session: `https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB` (dispatched os-dev under the `domain:ui#2` claim). --- _Generated by [Claude Code](https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #23082
Clause-②: no (narrowing)
Ruling
6134088274(batch #331 item 5, letter C): the view-levelapidata source retires under ADR-0049 enforce-or-remove. A view binds its object or static rows; a metadata-declared API call is the page element'sdataSourcebinding (the #23016 rule, PR #23079), same-origin and credential-free. Executed per thespec-property-retirementplaybook as a VALUE retirement (the retired bare-stringsortclause's route):datasurvives one arm lighter, so there is noretiredKey()tombstone and noRETIRED_KEYS_BY_MAJORrow — the prescription ridesViewDataSchema's own discriminated-unionerrormap, keyed on the input'sprovider === 'api', so every other unknown provider keeps zod's default report (which lists the three live arms).What changed
packages/spec/src/ui/view.zod.ts— theprovider: 'api'arm ofViewDataSchema(itsread/writeHttpRequestSchemablocks and the arm's own near-miss guidance) is removed; the union carriesVIEW_DATA_API_PROVIDER_RETIREDas itserrormap. Two stale prose sites in the same file no longer teach the arm (the ganttobjectFielddescribe, the formdatadocblock).HttpRequestSchema/HttpMethodSubsetSchemaand theiruire-exports are untouched; the page-elementapibinding (ElementApiDataSourceSchema,ui/page.zod.ts) is untouched.view-data-api-provider-retired(migrations/entries/semantic/18.view-data-api-provider-retired.ts) and itsSTEP18_RATIONALEfragment (order 95). No D2 conversion, deliberately: the replacement lives on a different metadata type (a page element), and the only mechanical edit a conversion could make — deleting the view'sdataso the view falls back to querying its bound object — would silently change what the view shows. An authored source or a stored row still carrying the arm is refused at parse with the prescription (therest-api-endpoint-handler-status-retired/kernel/Manifest:loadingno-D2 precedents).packages/spec/liveness/view.json:list.dataandform.datastaylive(their surviving arms are read) and carry the arm's retirement record;list.datagainsverifiedAt/evidenceScope: cross-repowith objectui read points cited at7578323a544e. A renderer reader existed in objectui, measured at7578323a544e(.objectui-shaonmainis1fb1ced36243; the next cross-repo re-measure re-readsListView.tsx/ObjectGantt.tsxthere;ListView.tsxrouted anapiblock toApiDataSource,ObjectGantt.tsxbuilt one from the block'sread/write) and is now unreachable from authored metadata. The ledger has no per-arm row (the walk never descends a union arm), so the record sits on the key's row; the README's per-type counts do not move.packages/spec/variant-docs.json: the union key is nowprovider:object|schema|value.src/ui/view-data-api-provider-retirement.test.ts— the refusal atprovider/data.provider/list.data.providerwith its prescription, the three surviving arms value-for-value and still closed, zod's default report for any other unknown provider, the D3 registration and the absence of any key row, and a tree-scoped absence scan over the authored spelling (TS / JSON / YAML dialects; the page element'sdataSource: { provider: 'api' … }deliberately does not match). It joins the specrepovitest project and is the second witness on the spec: the rest of the incident-response, training and change-management families — every remaining key and all fifteen defs — has zero readers; whole-def enforce-or-remove is the open question left after #14477 #15513 absence-walk rows inscripts/cross-package-test-inputs.mjs. The four fixtures that pinned the arm were re-judged: two accept pins replaced by one refusal control (view.test.ts), the kanban fixture moved to thevaluearm (its point is a non-object source), the batch-18 strictness row for the arm removed, thecomponent-props-unknown-membersledger loses itsHTTP_REQUESTlines (nodatamember reaches a request block any more) and now refuses their return, and the two@objectstack/lintfixtures that used the arm to mean "a non-objectprovider" use thevaluearm.content/docs/ui/views.mdxloses the view-level example and points at Element data sources;skills/objectstack-ui/rules/list-views.mdthe same (readings below); the generated reference pages regenerate (gen:docs), the strictness counts shard regenerates (ui/view.zod.tsstrict sites 60 → 59)..changeset/23082-view-data-api-provider-retired.md:@objectstack/specminor, BREAKING, FROM → TO, one-line fix, the prescription verbatim,adr-0087: registered view-data-api-provider-retired.Measurements the ruling asked for
provider: 'api', outside documentation — each repo with a lit control in the same run:bcebd8409(this branch's base): 0 outsidepackages/spec/src/**(its own tests),CHANGELOG.md, generated reference pages, the two hand-written docs pages and the skill rule file; controlprovider: 'object'150 files. The onlycase 'api':reader in a runtime package (service-automationengine.ts) switches on a flow trigger kind, not onViewData.0f84c9c: 0; controlprovider: 'object'in 5 view files (src/*/views/*.view.ts).origin/mainf228d710: 0 authored producers.apps/console/src/services/approvalRequestsDataSource.tsconstructsnew ApiDataSource({ read: { url, method, params? }, fetch })in code — anApiDataSourceConfig, never aViewData; the phraseprovider: 'api'there is its docblock. Not a fork. Note for the objectui follow-up: its "deleteApiDataSourceand itsrelated-count-storeuse" step must keep or re-home the approvals route (three construction sites in that file). The 29 non-testprovider: 'api'/=== 'api'sites are readers (ListView, ObjectGantt,resolveDataSource,ViewDataProvider,useViewData,createAuthenticatedFetch), the objectui follow-up's population.packages/typesre-exportstype ViewData/type HttpRequestfrom the spec and declares noprovider: 'api'view type of its own;ViewDataProvider.tscarries an in-codeViewDataConfigunion with anapimember that does not derive from the spec. Control:provider: 'object'169 files.writeleg: no producer anywhere above (the fork clause's second trigger).HttpRequestSchemareaders (packages/**, excluding dist):ui/view.zod.ts(the removed arm, and theuire-export at line 108 — kept),shared/http.test.ts,ui/view.test.ts(single-source and backward-compat pins — kept),scripts/schema-index.test.ts(a fixture),scripts/build-docs.ts(a comment),ui/component-props-unknown-members.pin.test.ts(theHTTP_REQUESTlines — removed here), plus the generated shards (api-surface,authorable-surface,export-origins,declaration-map,json-schema.manifestforshared/HttpRequestandui/HttpRequest;contracts.jsonnames the unrelatedIHttpRequest). After this change no schema in the package composesHttpRequestSchema: its remaining readers are the two public re-exports and tests. Left in place per the ruling ("touches only theViewDataSchemabranch"); the orphan — and the now-stale describes onHttpMethod/HttpMethodSubsetSchemathat still say "what view data sources may request" — is recorded in the acceptance notes for a follow-up, not acted on here.scripts/build-console.shinjects this tree's spec only through the console SPA's Vite hook (OBJECTSTACK_SPEC_DIST,apps/console/vite.config.ts); objectui'stsc-building packages (types,core,react,app-shell) type-check against the published spec in objectui's own lockfile (^17.x), so the arm's removal cannot red that gate on a type, and no objectui file at the pin parses authoredprovider: 'api'metadata at build time.api-surface/,authorable-surface/(+.base.json),export-origins/,declaration-map/,json-schema.manifest/all green and unchanged (check:generatedon the built tree). What did move:content/docs/references/**(thedatatype column; the gantt's nesteddata[provider='api']section), the strictness counts shard, and (prose only)references/system/migration.mdx.skills/**readings (Tier H; net budget ≤ 0)ceil(bytes/4)) before → afterskills/objectstack-ui/rules/list-views.mdskills/*/SKILL.md(sum)skills/**/*.md(sum)The replacement pointer ("A view never calls an API: an API read is a page element's
dataSourcebinding") costs less than the example it replaces.check:skills-token-ratchetgreen.Verification
Head
be64fe664(=41045ab6f+ a merge oforigin/main287fd6e5dthroughscripts/pm/os-regen-merge.sh+ the hand-off commit regeneratingcomponent.mdx). Every exit code below was captured before any pipe; builds and tests ran throughscripts/pm/os-verify-lock.shand are quoted from itsVERDICT command-exitlines.pnpm --filter '@objectstack/lint^...' build— the diff touchespackages/spec(built,command-exit 0, three times on this head's tree) andpackages/lint(tests only; its closure spec + formula + sdui-parser built, thenturbo run build --filter=@objectstack/lint --filter=@objectstack/objectql,command-exit 0).pnpm --filter @objectstack/spec typecheck(gen:migration-registry,tsc --noEmit,check:scripts-typecheck,check:test-typecheck: OK, 52 files / 246 errors / 135 pinned signatures held — the retirement pin compiles clean) ·pnpm --filter @objectstack/spec test(thelocalproject): 660 test files passed ·pnpm --filter @objectstack/spec test:repo: 58 files / 1007 tests passed (the retirement pin's refusal, controls, D3 registration and the tree-scoped absence walk are in this project) ·pnpm --filter @objectstack/lint typecheck: OK ·pnpm --filter @objectstack/lint test: 138 files / 6649 passed, 5 skipped. Downstream importers ofViewData/ViewDataSchemaoutside spec: none in anysrc/(git grep; the two hits are release-owned CHANGELOGs), so no consumer package owes a typecheck for the narrowed type; the full closure is CI'sTypeScript Type Check.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack→ 135 commands (the PM's lead list of 100 plus 35 the tree adds; none dropped), every one run onbe64fe664with its exit recorded, then--ranreconciled with every exit code recorded (COMMAND :: exit Nper line):Run reconciliation — 135 derived, 132 run, 3 NOT-MEASURED, 0 UNRUN(the 3 derived from recorded exit 3s), reconcile exit 0. 131 exit 0. The rest, each read at its own log, none a finding against this diff:pnpm check:platform-checklist— exit 1, inherited: 8 ABSENT SYMBOL / anchor-floor problems indocs/qa/platform-checklist/areas/{access-security,attachments-storage,search}.jsoncitingpackages/metadata-protocol/src/protocol.ts#anonymousFormIntakeOrgScopeRefusal,#anonymousFormIntakeReopenRefusal,#envWideRawViewRows,#countableandservice-storage/.../attachment-access-hooks.ts#canEdit. All four protocol symbols are absent onorigin/main(287fd6e5d) and on this branch's base (bcebd8409) alike; this diff touches none of those files. A main-red for its lane (e5f9f0160/c8a9783ecare the main commits that movedprotocol.tssince the base); reported, not repaired here.pnpm --filter @objectstack/spec run check:skill-examples— exit 3, PREREQUISITE NOT MET: its client-SDK surface reads@objectstack/client-react's built output, which needs thecoreclosure built; the shared verify lock was held by other seats' builds for two full queue budgets (holders named in the logs), so it is NOT MEASURED here. Narrowing stated, not assumed: the gate type-checks onlyos:check-marked blocks, and neither edited block is marked (skills/objectstack-ui/rules/list-views.mdmarkers at 17/166/221, the Data Source block at 38–50 is unmarked;content/docs/ui/views.mdxmarkers at 13/168/426/472, the block at 295 is unmarked;view.zod.tscarries no marker), so its verdict is invariant under this diff; CI runs it.pnpm check:type-check-debt— exit 3, NOT MEASURED: it builds twelve packages itself and@objectstack/core#buildcollided with a concurrent build of mine under the lock (the batch's own log says so); spec's and lint'stsc --noEmitran green directly (②), so their rows cannot have moved; CI re-measures.pnpm check:dual-build-cjs-loads— exit 3, NOT MEASURED: it loads every package's built output and 53 of 68 workspace packages (adapters, apps) are unbuilt in this worktree; a whole-workspace build is CI's run.@objectstack/lint/@objectstack/objectqlwere not yet built, and were re-run green (exit 0) on this head once they were:check:doc-formula-expressions,check:doc-security-posture,check:docs-transcript-drift,check:lean-entry-closure.pnpm --filter @objectstack/spec check:generatedon this head: all 14 up to date (check:docs,check:strictness-ledger,check:liveness,check:test-typecheck,check:authorable-surface,check:api-surface,check:export-origins,check:declaration-map… all ✓). The 14 pure source audits it does not run were run as one group on the built tree: all ✓ exceptcheck:skill-examples(above).pnpm lintand the farm) are CI's; this PR adds no lint-relevant surface.Acceptance notes
HttpRequestSchema/HttpMethodSubsetSchemaorphaned (class b candidate,carrier:the spec seat's follow-up or the objectui follow-up card): zero schema readers in@objectstack/specafter this PR; both still exported from./sharedand./ui, and their describes still speak of "view data sources". objectui'spackages/typesimportsHttpRequestSchemafrom the spec in a test and re-exportstype HttpRequest, so a def retirement is cross-repo and belongs with the objectui follow-up (the cross-repo unlock rule). Not touched here per the ruling.ApiDataSource,resolveDataSource's'api'arm,ViewDataProvider.resolveApi,useViewData'sprovider === 'api'branches,createAuthenticatedFetch'sprovider: 'api'lane, the gantt's adapter arm,content/docs/plugins/plugin-gantt.mdx(teaches a ganttdata: { provider: 'api' }),plugin-calendar.mdx/plugin-map.mdx(mention it as not honoured), andpackages/core/src/adapters/README.md. The approvals route constructsApiDataSourcedirectly and must be kept or re-homed.sdui.manifest.json(repo root; generated from objectui's built tree at the pin bygen-sdui-manifest-node.mjs) still describes the gantt'sdataas accepting{ provider: 'api', read }; it regenerates on the next pin bump, not here.sys_metadataview rows carrying the arm (none measured; cloud unmeasured) are refused at rehydration with the prescription — loud by design, the D3 entry says what to do.维护者速读(草稿)
改了什么: 视图(列表/表单)的
data数据源不再接受provider: 'api'(自带read/write请求块、任意url与headers的那一种)。视图只绑定对象(object)或静态行(value);元数据里声明的 API 调用只剩一条路:页面元素的dataSource绑定(同源、不带凭据、用控制台登录态请求)。写了旧写法的视图在校验、发布、保存时被拒绝,错误信息里直接写明改法。对外技能文本与文档同步改成指向页面元素绑定;spec 发一个 minor 版本并带 BREAKING 说明与迁移对照表。为什么改: 您在 #23082 批了 C。旧的视图级 API 源可以把密钥写进
headers、把请求指向任意域名,这些会随视图一起存储、下发给每个读取该视图的客户端、并由控制台照原样发出;而页面元素那条路已经按 #23016 的规则锁死。同一件事两套规则,按裁决保留受治理的那一套、撤掉另一套。实测三个仓(objectstack、objectui、hotcrm)都没有视图在用它;cloud 未测,您的「同意」已覆盖。风险与代价(含回滚): 风险在未测的存量元数据:若 cloud 或某个客户项目有视图写了
provider: 'api',升级后该视图加载时会被拒绝并给出改法(不会悄悄换成别的数据),需要作者把 API 读取改到页面元素上。没有自动迁移(D2),因为机械删掉data会让视图静默改读对象。回滚:撤销本 PR 即恢复旧分支,无数据迁移需要回退。objectui 侧的删除(ApiDataSource等)是另一张卡,等 spec 发版后再做,本 PR 不动 objectui。席位意见:
你要做的: 本 PR 动了
skills/**(受治理,Tier H),需要您的批准才能合入:请审阅上面的中文说明与skills/objectstack-ui/rules/list-views.md的改动(净减 2 行),同意则在 PR 上点 Approve,由席位排队合入。Generated by Claude Code