Skip to content

feat(spec,types,plugin-list,app-shell)!: retire the list view's options bag; stored bags fold onto the top-level blocks, and the renderers read the blocks alone - #27206

Merged
os-zhuang merged 12 commits into
mainfrom
claude/project-thread-eqo5sw
Oct 28, 2026
Merged

os-zhuang merged 12 commits into
mainfrom
claude/project-thread-eqo5sw

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Yibo Wang · project thread

Part of #25923

Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: skills/objectstack-ui/references/react-blocks.md regenerated by pnpm gen:react-blocks; the list-view table drops its options prop row. No other skills/** file teaches the bag: skills/objectstack-ui/rules/list-views.md and skills/objectui/guides/page-builder.md already write the top-level blocks only.
ADR-0087: registered list-view-options-bag-folded, ui-list-view-options-bag-folded

维护者速读

  • 改了什么:列表视图的旧 options 配置包退役。规范的视图写入口按名拒收 options 并提示改写为顶层 kanban / calendar 等配置块;已存储的配置包由 ADR-0087 D2 转换在下发前折叠到顶层配置块;objectui 的类型镜像直接引用规范的墓碑,ListView、对象页中继、界面页、Studio 预览和检查器都只读写顶层配置块。
  • 为什么改:同一个配置块有两种写法,每个渲染器各自实现一遍合并(Add comprehensive test suite for Zod schema validation #12);普查在所有可达元数据里找到 0 行存储的配置包,席位裁定走 A 方案一个 PR 落地(claim amendment 6460225523)。
  • 风险与回滚:直接写 options 的 PUT 请求、页面 list-view 节点或 ListView 宿主会被拒收或不再生效;已存储的行由服务端折叠,渲染不变。回滚即还原本 PR;D2 转换只在读取时折叠,不改写存储数据。
  • 先看:packages/spec/src/ui/view.zod.ts(墓碑)、packages/spec/src/conversions/registry.ts(list-view-options-bag-folded)、ui/packages/app-shell/src/views/objectViewListRenderer.tsx(顶层中继)、ui/packages/plugin-list/src/ListView.tsx。
  • 两分钟验证:cd ui && pnpm exec vitest run packages/types/src/__tests__/list-view-options-bag-6152.test.ts packages/plugin-list/src/__tests__/ListView.readersRetired-6152.test.tsx packages/app-shell/src/views/ObjectView.storedRowParity-10380.test.tsx,预期全绿:配置包在每个入口被拒收并给出规范原文,源码普查没有 schema.options 读取,存储的配置包折叠后两页渲染一致。
  • 需要你做的:无,席位落地

What changes

  • @objectstack/spec: the flattened list overlay's options is a retiredKey() tombstone that refuses a bag by name with the move-each-options.KIND-to-KIND prescription. The D2 conversion list-view-options-bag-folded folds a stored or source bag onto the top-level blocks before a row is served (top-level wins per key; for chart, whose block replaced the bag whole, the top-level block wins whole). It leaves options.grid, a non-object block and a bag on a form view in place with a TODO. The D3 entry ui-list-view-options-bag-folded names what the fold cannot decide (a folded block can still miss a required key). The page list-view component row shares the tombstone, and react-blocks.ts drops the options prop row.
  • @object-ui/types: ListViewSchema.options is the spec's tombstone by reference, so the mirror, the tolerant and strict faces and safeValidateSchema refuse a bag at options with the spec's text, unreworded. NamedListView.options is a ?: never tombstone naming the top-level blocks, moved from held to retired in the object-view-unmirrored-keys-7779 census (22 tombstones, was 21). The dataset-chart filter refusal reads the chart block alone. A page node's flat options is refused with the bag text rather than pointed at properties.options.
  • @object-ui/plugin-list: ListView reads every kind from its top-level block alone (render branches, capability gate, projection and expand collectors, fetch effect dependencies). The list-view registration publishes no options input.
  • @object-ui/app-shell: the object page relays each per-kind block at the top level of the list schema, each held by value through isSameOptionsValue so an equal rebuild keeps its identity, and forwards no stored bag (storedLegacyOptions removed). The interface page sends one map block (its derivation with the authored keys laid over) and no bag. ViewPreview and the list-view inspector fold nothing; the server does.
  • @object-ui/plugin-view: the host list schema carries the active view's blocks at the top level instead of under options. @object-ui/plugin-map: two development warnings name the map block.
  • @object-ui/console, @objectstack/rest, @objectstack/metadata-protocol, @objectstack/lint: test pins only.

Tests

  • Refusal on the authoring face: list-view-options-bag-6152 pins the mirror IS the spec tombstone, and every door refuses five bag shapes at options with the spec's text, beside a CONTROL that the same blocks written top-level parse. The named alias refusals the bag carried are pinned at the top-level block.
  • No read left: ListView.readersRetired-6152 gains a SOURCE census (TypeScript AST over every non-test plugin-list file) that finds no schema.options read, with a firing control on schema.kanban and a probe of each retired shape. The same scanner finds 39 reads in ListView.tsx on origin/main. The object page relay census (ObjectView.relayRungCensus-7559) declares options unread and every block relayed; the interface page census (InterfaceListPage.relayCensus-11572) writes map and not options.
  • Stored rows convert: the spec and metadata-protocol pins replay list-view-options-bag-folded on stored rows; in ui, ObjectView.storedRowParity-10380 and ViewPreview.foldedOnRead-25923 render rows folded with applyConversionsToStoredItem and pin that an unfolded bag renders nothing.
  • The test fixtures that mounted a bag (79 by the seat's count in the ruling) moved to the top-level KIND form, not loosened. Tests that pinned bag behaviour now pin that the bag is refused or reaches nothing, each beside a CONTROL with the block.

Census

TypeScript AST + JSON + YAML walk of every object literal: views carrying a top-level options bag with a kind key.

sites where
origin/main 4b8f8cd (19,856 files) 158 157 tests + 1 the object page composer, objectViewListRenderer.tsx
this branch (21,425 files) 109 106 tests that pin the refusal, the fold or the inversion + 3 in packages/spec/src/conversions/registry.ts (the fold's own doc and fixtures)
stored (examples, apps, the console's shipped metadata, ui/examples, seeds, platform-objects, fixtures) 0 before and after

Not reachable, so not counted as zero: hotcrm metadata, tenant sys_metadata rows, cloud.

Acceptance notes

Verification

Head 3f56e4cfe (main b322b5da6 merged in). Every run below was under os-verify-lock.

ui tests
  • Full ui suite on the branch before the last main merge, in three batches with --maxWorkers=3 (a single vitest run hit the container's memory limit at 1h49m and was killed, so it measured nothing): app-shell Test Files 1826 passed | 1 skipped · Tests 16795 passed | 9 skipped; components, plugin-list, plugin-view, plugin-map, types, core, react, plugin-grid, plugin-detail Test Files 2041 passed | 2 skipped · Tests 28433 passed | 69 skipped; the other packages, apps, scripts, eslint-rules and examples Test Files 1843 passed | 4 skipped · Tests 25842 passed | 291 skipped. Each batch exit 0.
  • After merging main b322b5da6 (fix(plugin-list): show list view sharing type and lock owner as read-only text, drop the dead Share button #27013 touched ListView.tsx and ListView.test.tsx): plugin-list + types Test Files 560 passed (560) · Tests 12306 passed, exit 0.
spec and server packages
  • pnpm --filter @objectstack/spec test after the last merge: Test Files 741 passed (741) · Tests 22244 passed | 1 todo, exit 0. typecheck: exit 0.
  • pnpm --filter @objectstack/spec check:generated after the spec closure build: all 14 generated artifacts up to date, exit 0.
  • @objectstack/metadata-protocol after the last merge and its closure build: Test Files 277 passed | 4 skipped · Tests 21752 passed | 26 skipped, exit 0. (Run before the closure build, one test from feat(spec, formula, plugin-email, lint): a mail template formats an amount in the record's own currency with currency:@path #26937, runtime-authoring-gate.email-template-writes, failed on a stale @objectstack/lint dist; it passes once that dist is built.)
  • @objectstack/rest Test Files 332 passed · Tests 6552 passed | 326 skipped; @objectstack/lint Test Files 170 passed · Tests 7516 passed. Both exit 0.
type-check and lint
changeset checks
  • check-changeset-no-major.mjs --base origin/main with this body as the event: exit 0; it reads Clause-②: no (narrowing) and Metadata-protocol: narrowed against the minor grade on @objectstack/spec.
  • check-adr-0087-registration (registers list-view-options-bag-folded, ui-list-view-options-bag-folded), check-empty-changeset, check:changeset-fixed, check:nul-bytes: exit 0.
dispatch gates
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 100 commands, each run with its exit code recorded.
  • --ran: 100 derived, 99 run, 1 NOT-MEASURED, 0 UNRUN. All 99 measured exit 0.
  • NOT-MEASURED: pnpm check:dual-build-cjs-loads exit 3 (its prerequisite is a full pnpm build). CI runs it.
  • node scripts/check-platform-checklist.mjs --changed-from origin/main: OK, exit 0.
dogfood battery
  • Head 3f56e4cfe (after the b322b5da6 merge and the round-1 fixes): closure pnpm --filter '@objectstack/dogfood^...' build exit 0, then pnpm --filter @objectstack/dogfood test: Test Files 348 passed | 3 skipped (351) · Tests 2804 passed | 20 skipped (2824), exit 0.
  • Earlier reading, on the 07d18e692 merge: Test Files 348 passed | 3 skipped (351) · Tests 2803 passed | 20 skipped (2823), exit 0.
review round 1 (contract review 6467465885 on ef9b178)
  • Build & E2E UI: sdui.manifest.json still listed the list-view options input. node scripts/gen-sdui-manifest-node.mjs drops that row (5 lines, the same diff CI printed) and re-records scripts/sdui-manifest.record.json; node scripts/check-sdui-manifest.mjs exit 0.
  • Test Core 2/8: scripts/conversions-major18-merge.test.ts rule 2 wants a conversion added after the rule defined directly above the next entry's definition. Reproduced locally (3 failed of 12, naming listViewOptionsBagFolded and listViewSortStringClauseToArray). The fold and its LIST_VIEW_OPTIONS_BAG_KINDS constant now sit directly above listViewSortStringClauseToArray: 12 passed. Spec repo project Test Files 81 passed · Tests 1324 passed; spec local conversions, migrations and ui Test Files 167 passed · Tests 6308 passed.
  • Test Core 6/8: the D3 entry's printed reason cited objectui#10380 and #25923. Reproduced locally with the old text and a spec build: migrate-meta-engine-guidance.test.ts fails with "ui-list-view-options-bag-folded: the printed guidance cites a tracker id: expected '[finding] Two PASSING @objectstack/runtime tests print Acquire connection error and ~150 ERROR-level sql-driver DATABASE_ERROR lines into the shared Test Core log — they were adopted verbatim as a p1 flake signature #10380' to be undefined". Rephrased without ids: 3 passed. Full @objectstack/cli test Test Files 521 passed | 1 skipped · Tests 6484 passed | 9 skipped, exit 0; check:generated exit 0.
  • Changeset: the "renders as before" sentence is qualified. A stored bag folds onto the top-level blocks, which on the object page pass through the relay's defaults (the kanban titleField: 'name' floor, for one), so a folded row can draw differently where its bag left such a key unset; 0 reachable rows, pinned by ObjectView.storedRowParity-10380.

Generated by Claude Code

…onto the top-level blocks

The flattened list overlay's options member is a retiredKey tombstone naming
the move to the top-level KIND block. The ADR-0087 D2 conversion
list-view-options-bag-folded replays on stored rows (top-level wins per key,
chart whole); the D3 entry ui-list-view-options-bag-folded carries what it
cannot decide. Pins move in spec, rest, metadata-protocol and lint.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzevL8Q6rTfGz2XHXGT
… readers; the mirror takes the spec tombstone

ListViewSchema.options takes the list overlay's retiredKey tombstone by
reference, and ListView, the object page relay, the interface page,
ViewPreview, the list-view inspector and plugin-view read and write the
per-kind blocks at the top level only. The server folds a stored bag before
the row is served. Tests that mounted a bag move to the top-level block;
the ones that pinned the bag now pin that it is refused or reaches nothing.
A source census in ListView.readersRetired-6152 pins that no plugin-list
source reads schema.options. react-blocks.md regenerates without the prop.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzevL8Q6rTfGz2XHXGT
…t from held to retired

The member was held under objectui#7928's Q2 (i) while the bag was a live
protocol spelling. With the bag retired and the fold on the server, it is a
?: never tombstone naming the top-level blocks, and the listViews docblock
names the server fold instead of ViewPreview.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzevL8Q6rTfGz2XHXGT
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 24 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/spec/src/ui/component.zod.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

15 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/plugin-endpoints.mdx (via /api/v1/meta/view (route, a path literal in surface))
  • content/docs/automation/hook-bodies.mdx (via crm_deal (literal, a string literal in fixture))
  • content/docs/concepts/metadata-lifecycle.mdx (via /api/v1/meta/view (route, a path literal in surface))
  • content/docs/data-modeling/analytics.mdx (via close_date (literal, a string literal in fixture))
  • content/docs/data-modeling/formulas.mdx (via close_date (literal, a string literal in fixture))
  • content/docs/data-modeling/seed-data.mdx (via close_date (literal, a string literal in fixture))
  • content/docs/kernel/services-checklist.mdx (via /api/v1/meta/view (route, a path literal in surface))
  • content/docs/protocol/objectql/query-syntax.mdx (via close_date (literal, a string literal in fixture))
  • content/docs/protocol/objectui/concept.mdx (via close_date (literal, a string literal in fixture))
  • content/docs/protocol/objectui/index.mdx (via /api/v1/meta/view (route, a path literal in surface))
  • content/docs/protocol/objectui/layout-dsl.mdx (via close_date (literal, a string literal in fixture))
  • content/docs/ui/dashboards.mdx (via close_date (literal, a string literal in fixture))
  • content/docs/ui/forms.mdx (via /api/v1/meta/view (route, a path literal in surface))
  • content/docs/ui/pages.mdx (via close_date (literal, a string literal in fixture))
  • content/docs/ui/reports.mdx (via close_date (literal, a string literal in fixture))

⛔ 3 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via close_date (literal, a string literal in fixture))
  • content/docs/releases/v17/17-5.mdx (via retiredAfter (symbol, a field of const object listViewOptionsBagFolded), retiredFromLoadPath (symbol, a field of const object listViewOptionsBagFolded))
  • content/docs/releases/v17/17-6.mdx (via /api/v1/meta/view (route, a path literal in surface))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/ui/component.zod.ts) — pages documenting those are invisible to this run
  • 7 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 56 of 208 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 165 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json ebfadb02c3a06153ff05af1cb620dfdc29bb6157 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 1a130d0c0faa075b3532a43b5c149d6dcff68de0 — the merge of head 8a4ad1fba714e18cc91c305587abbe9936bf1142 into base ebfadb02c3a06153ff05af1cb620dfdc29bb6157, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 1a130d0c0faa075b3532a43b5c149d6dcff68de0 && git checkout 1a130d0c0faa075b3532a43b5c149d6dcff68de0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ebfadb02c3a06153ff05af1cb620dfdc29bb6157 8a4ad1fba714e18cc91c305587abbe9936bf1142 && git checkout -B drift-repro ebfadb02c3a06153ff05af1cb620dfdc29bb6157 && git merge --no-ff 8a4ad1fba714e18cc91c305587abbe9936bf1142

node scripts/docs-audit/affected-docs.mjs --json ebfadb02c3a06153ff05af1cb620dfdc29bb6157

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs ebfadb02c3a06153ff05af1cb620dfdc29bb6157 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…ons input

The list-view registration publishes no options input since the bag retired,
so the console registry manifest drops that row.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzevL8Q6rTfGz2XHXGT
…ry that follows it

The major-18 merge pin wants a conversion added after its rule defined
directly above the next entry's definition (listViewSortStringClauseToArray);
the bag kinds constant moves with it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzevL8Q6rTfGz2XHXGT
os migrate meta prints the reason verbatim, and author-shown guidance carries
no tracker number.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzevL8Q6rTfGz2XHXGT
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ef9b1785432610dd082b7673ac188ae3474d7c63
Local-runs: none
Scope: packages/spec/src/conversions/registry.ts, packages/spec/src/migrations/entries/semantic/18.ui-list-view-options-bag-folded.ts, packages/spec/src/ui/component.zod.ts, packages/spec/src/ui/react-blocks.ts, packages/spec/src/ui/view.zod.ts, skills/objectstack-ui/references/react-blocks.md (the hits: schema face + the governed skills path), then — narrowing face, Clause-②: no (narrowing) — every other path of the net diff: .changeset/25923-list-view-options-bag-retired.md, docs/audits/2026-07-unknown-key-strictness-ledger.counts/ui.md, packages/lint/src/validate-component-props-list-view-22444.test.ts, packages/metadata-protocol/src/protocol.graft-folded-form-sections.test.ts, packages/metadata-protocol/src/protocol.stored-conversions.test.ts, packages/rest/src/meta-view-chart-binding.test.ts, packages/rest/src/meta-view-overlay-options-bag.test.ts, packages/spec/src/shared/retired-key-removal-version.pin.test.ts, packages/spec/src/ui/component-list-view-row.test.ts, packages/spec/src/ui/view-chart-binding.test.ts, packages/spec/src/ui/view-overlay-options-bag.test.ts, packages/spec/src/ui/view-overlay-options-type.test.ts, packages/spec/src/ui/view-overlay-viewkind-arm.test.ts, ui/apps/console/src/tests/registry-inputs-spec-parity.test.ts, ui/apps/console/src/tests/timelineAxisRefusalReach-7070.test.tsx, ui/packages/app-shell/src/tests/listKanbanEveryRecord-12619.test.tsx, ui/packages/app-shell/src/views/InterfaceListPage.mapConfig.test.tsx, ui/packages/app-shell/src/views/InterfaceListPage.relayCensus-11572.test.ts, ui/packages/app-shell/src/views/InterfaceListPage.treeChartRelay-11572.test.tsx, ui/packages/app-shell/src/views/InterfaceListPage.tsx, ui/packages/app-shell/src/views/ObjectView.chartRelay-7823.test.tsx, ui/packages/app-shell/src/views/ObjectView.densityModeThroughFold-7924.test.tsx, ui/packages/app-shell/src/views/ObjectView.fallbackTabSessionOnly-11643.test.tsx, ui/packages/app-shell/src/views/ObjectView.fieldOrderRelay-7516.test.tsx, ui/packages/app-shell/src/views/ObjectView.relayRungCensus-7559.test.ts, ui/packages/app-shell/src/views/ObjectView.relayedBlocksParse-6152.test.ts (renamed from ObjectView.optionsBagParses-6152), ui/packages/app-shell/src/views/ObjectView.rowColorRelay-7218.test.tsx, ui/packages/app-shell/src/views/ObjectView.storedRowParity-10380.test.tsx (renamed from ObjectView.storedOptionsBag-10380), ui/packages/app-shell/src/views/ObjectView.titleFieldConvergence.test.tsx, ui/packages/app-shell/src/views/ObjectView.toolbarWritesCompose-11642.test.tsx, ui/packages/app-shell/src/views/ObjectView.tsx, ui/packages/app-shell/src/views/ObjectView.viewDescriptionRelay-7199.test.tsx, ui/packages/app-shell/src/views/ObjectView.viewRowDeclaredReads-11013.test.tsx, ui/packages/app-shell/src/views/metadata-admin/previews/ViewPreview.foldedOnRead-25923.test.tsx (renamed from ViewPreview.optionsFold-7928), ui/packages/app-shell/src/views/metadata-admin/previews/ViewPreview.tsx, ui/packages/app-shell/src/views/objectViewListRenderer.tsx, ui/packages/app-shell/src/views/objectViewOptions.ts, ui/packages/app-shell/src/views/studio-design/ObjectListViewInspector.target.test.tsx, ui/packages/app-shell/src/views/studio-design/ObjectListViewInspector.tsx, ui/packages/plugin-list/src/ListView.tsx, ui/packages/plugin-list/src/index.tsx, ui/packages/plugin-list/src/tests/ListView.calendar-binding-7029.test.tsx, ListView.calendarAliasRefused-8355.test.tsx, ListView.chart-capability-7544.test.tsx, ListView.datasetChartToolbarFilter-10327.test.tsx, ListView.galleryBlockCanonical-6152.test.tsx (renamed from galleryOptionsBagCanonical), ListView.gantt-binding-7070.test.tsx, ListView.kanbanBlockCanonical-8193.test.tsx (renamed from kanbanOptionsBagCanonical), ListView.kanbanOwnsLanes-12619.test.tsx, ListView.mapFlatten.test.tsx, ListView.mapLocationSelect-10370.test.tsx, ListView.mapViewLevelConfig.test.tsx, ListView.readersRetired-6152.test.tsx, ListView.rowAdornment-13330.test.tsx, ListView.speculativeFls-7216.test.tsx, ListView.strayGroupByRefused-8365.test.tsx, ListView.test.tsx, ListView.timeline-binding.test.tsx, ListView.timelineMetaFieldsRetired-10222.test.tsx, ListViewBlock.specInputs-13047.test.tsx, ui/packages/plugin-map/src/ObjectMap.listViewMapConfigReach.test.tsx, ObjectMap.schemaAlignment.test.tsx, ObjectMap.topLevelStyle.test.tsx, ObjectMap.tsx, ui/packages/plugin-view/src/ObjectView.tsx, ui/packages/plugin-view/src/tests/ObjectView.namedViewProtocolKeys-8980.test.tsx, ui/packages/types/src/objectql.ts, ui/packages/types/src/zod/objectql.zod.ts, ui/packages/types/src/tests/calendar-date-alias-refusal-8355.test.ts, imported-defaults-8317.test.ts, kanban-stray-group-by-refusal-text-9653.test.ts, list-view-dataset-chart-filter-refusal-10327.test.ts, list-view-options-bag-6152.test.ts, list-view-properties-bag-12670.test.ts, object-chart-legacy-axis-keys-retired-10608.test.ts, object-kanban-group-by-limit-7322.test.ts, object-view-unmirrored-keys-7779.test.ts + cited, read at the head or on origin/main 4265176c as named: sdui.manifest.json (repo root, committed; its list-view.inputs row), scripts/gen-sdui-manifest-node.mjs (docblock :5-92), .github/workflows/ci.yml (:4272-4290, the manifest step of Build & E2E UI), AGENTS.md (§Frontend "A registry change has a second half"; Post-Task Checklist 3), packages/spec/src/conversions/stored.ts (:17, :102-113 includeRetired: true), packages/spec/src/conversions/apply.ts (:140-157), packages/spec/src/shared/retired-key.ts (docblock), packages/spec/src/migrations/entries/semantic/18.view-overlay-options-bag-judged.ts and 18.view-item-options-bag-refused.ts (origin/main, unedited), packages/spec/scripts/build-migration-registry.ts (:145 directory discovery), packages/spec/scripts/build-react-blocks-contract.ts (:29, :174 — the one output), packages/spec/src/ui/view.zod.ts on origin/main (:6357-6411 ListViewOverlayOptionsSchema, :6593, :2784-2863 chart-binding check), packages/spec/package.json (version: 18.2.0), ui/packages/plugin-list/src/ListView.tsx on origin/main (kanbanBlockOf :1232, case 'kanban' :4168-4215, every options?. read), ui/packages/plugin-view/src/ObjectView.tsx at the head (:237-250 hostViewKindBlocks, :2081-2130 viewOptions, :3000-3060 the list-schema literal) and on origin/main (:2985-3040, options: activeView at :3036 with no top-level canonical block), ui/packages/app-shell/src/views/objectViewListRenderer.tsx on origin/main (:400-420, the ...listSchema spread) and at the head (:141-160, :399-410 holdBlock, :410-440), ui/packages/app-shell/src/views/objectViewOptions.ts at the head (every producer, isSameOptionsValue :378-398), ui/packages/app-shell/src/views/InterfaceListPage.tsx at the head (:265-271 defaultMapFromObject, :439-470), ui/packages/plugin-map/src/ObjectMap.tsx at the head (:463-466, :518-551 getMapConfig binding precedence), ui/packages/app-shell/src/views/ObjectView.storedRowParity-10380.test.tsx at the head (:300-420, every case), ui/apps/console/src/tests/registry-inputs-spec-parity.test.ts at the head (list-view.kanban…list-view.tree member pins), scripts/check-changeset-no-major.mjs (:895-930 ruling A, :1879-1890 verdict table), scripts/check-adr-0087-registration.mjs (:60-63 marker grammar, :137-145 registered, :526-528 entry dirs, :2018-2049 parser), scripts/pm/record-recognisers.mjs (--template --paths over the 83 files), .changeset/25923-ui-types-mirrors-passthrough.md on origin/main (work item 2's landed half), skills/** at the head (git grep for a view-level options: { KIND — none), content/docs/releases/v17/17-6.mdx:919 (release-owned, read only), packages/spec/package.json (test / test:repo scripts), packages/spec/vitest.config.ts and vitest.repo-tests.json (the repo project's population), packages/spec/scripts/conversions-major18-merge.test.ts (:46 ENTRY, :50 DEFINITION, :61-80 PLACED_BEFORE_THE_RULE, :143-160 placementFindings, :289-327), packages/spec/scripts/step18-rationale-merge.test.ts, strictness-ledger.test.ts, strictness-ledger-doc.test.ts (:168-260), packages/spec/src/shared/retired-key-migrate-sentence.test.ts (:1-230, the house-sentence regexes), packages/spec/src/conversions/conversion-conflict-channel-retirement.test.ts and src/ui/view-list-tabs-retirement.test.ts (the retirement-pin pattern and its tree walk), docs/audits/2026-07-unknown-key-strictness-ledger.md:949 (the view.zod.ts row), packages/cli/package.json (test), packages/cli/test/migrate-meta-engine-guidance.test.ts (:73 TRACKER_ID, :290-330 REWRITTEN, :378-416), packages/cli/test/migrate-meta-default-range-window.test.ts, packages/cli/src/commands/migrate/meta.report-order.test.ts, packages/cli/src/utils/sdui-manifest.test.ts (headers), packages/spec/src/conversions/registry.ts at the head in full for the entry list, the 101 definitions and retiredAfter values (:10759, :12387, :12564), the check-runs of the eight most recently updated open PRs (Test Core (2/8) / (6/8) green where complete: #27179, #27205, #27212).

Inputs read: card #25923 body and all 24 comments, in particular the claim 6456306486, the amendments 6456384663, 6456494843 and 6460225523, the rulings 6412606977 and 6409923362, the premise checks 6430806588, the landings 6416705898 / 6437594464 / 6448239783 / 6462217108, and every os-dev-report (6412566640, 6432621418, 6445173979, 6458717005 — no os-dev-report for PR #27206 is on the card as of 10:42Z); PR #27026 (merged 06:18:03Z as d8a9cc5), its body, its six comments including both contract-review records (6458913292 FAIL r1, 6459158552 PASS r2) and the seat's ACCEPT 6461454849, its 44-file list, and its changeset on the branch; PR #27206 as it stands (head ef9b17854, created 10:29:49Z, updated 10:31:13Z, draft, no auto-merge, GitHub counts 83 paths = 88 with the 5 renames counted twice as dispatch-gates counts them, +1,875 / −1,808), its body, its one comment (docs-drift bot 6466662057), zero review comments, zero reviews, its 5 commits (4 on the branch + the merge of main b322b5da6; merge-base with today's origin/main 4265176c is b322b5da6), and the net diff against main (application/vnd.github.diff, 6,428 lines, read in full; git diff --stat origin/main...ef9b17854 = the same 83 files); the head files named in Scope via git show ef9b17854: after a read-only git fetch; the check-runs on ef9b17854, first read 10:32:22Z and re-read until the readings in ③; the failed check-run's annotations and job steps (/check-runs/122765066930/annotations, /actions/jobs/122765066930). The dispatch order and the seat's own conclusions beyond the named amendments were not read.

① Derived judgments

  • D1 — WRONG, the diff is one registry change short, and the gate that enforces the rule is red on this head for exactly that reason. ui/packages/plugin-list/src/index.tsx deletes the options row of LIST_VIEW_INPUTS (diff :5106-5113), which is a registry-inputs change; AGENTS.md §Frontend says such a change "has a second half: regenerate the committed manifest with node scripts/gen-sdui-manifest-node.mjs … CI's Build & E2E UI runs it too and fails when the committed sdui.manifest.json differs". The diff touches no sdui.manifest.json (0 hunks), and the committed repo-root sdui.manifest.json at the head still lists options between bulkActionDefs and kanban in the list-view entry's inputs. The check-run Build & E2E UI (122765066930) on this head completed failure at 10:38:15Z at step 10 "The committed SDUI manifest is the one this tree generates" (ci.yml :4285-4287), with the annotation sdui.manifest.json is not the manifest this tree generates. Run 'node scripts/gen-sdui-manifest-node.mjs' and commit what it writes.; every later step (Console build, eager-closure, test:dist, the E2E run) was skipped. The same job is green on origin/main 4265176c (10:26:55Z) and on the merged b322b5da6 (10:06:38Z), so the red is this diff's. The dev's local gate sweep could not see it: the manifest step is not in dispatch-gates' derived set (the dev reports 100 derived, and the regen needs the built ui registry), and the PR body does not mention the manifest. FIX: run node scripts/gen-sdui-manifest-node.mjs on the branch and commit what it writes (the script also refreshes its record under scripts/sdui-manifest.sources/); the queue-leg regen clause does not apply, because the committed file is what the step compares. This is the first FAIL reason.

  • D1b — WRONG, Test Core (2/8) is red on this head (check-run 122765680521, completed 10:51:56Z, step 11 "Run this shard's tests": command (packages/spec) pnpm run test:repo exited (1)), green on origin/main 4265176c (10:36:20Z). The shard's log blob is not reachable from this seat (the proxy forbids the log host), so the failing case is derived by reading, not read off the log: packages/spec/scripts/conversions-major18-merge.test.ts is in the repo project (packages/spec/vitest.repo-tests.json), and its rule 2 (placementFindings, :143-160; the exemption set PLACED_BEFORE_THE_RULE is closed at 46 names, :325) requires a conversion added after the rule to be DEFINED directly above the definition of the entry that follows it in the sorted MAJOR_18_CONVERSIONS list. At the head the entry list is sorted (jobTimeoutToTimeoutMs, listViewOptionsBagFolded, listViewSortStringClauseToArray, …, 86 entries, one each), but const listViewOptionsBagFolded: MetadataConversion is defined at packages/spec/src/conversions/registry.ts:12387, directly above viewListFilterableFieldsRemoved (:12564), while listViewSortStringClauseToArray is defined at :10759. Replaying the test's regexes over the head file gives the finding "listViewOptionsBagFolded is defined above viewListFilterableFieldsRemoved; define it directly above listViewSortStringClauseToArray's definition, the entry that follows it" (the two neighbours are exempt, so no other finding). Nothing else in the repo list is touched by this diff in a way I can trace to a red: the house os migrate meta sentence is verbatim (D3), the strictness-ledger counts shard agrees with the tree (strictObject( sites in view.zod.ts 56 → 55, so 59 → 58 and 217 → 216 are the fresh render's numbers), the entry list is sorted and order 78 unique. The dev's local sweep ran spec's test project (741/741), which is the local project; test:repo is the other turbo task. FIX: move the definition (with its doc comment) to directly above listViewSortStringClauseToArray at :10759.

  • D1c — WRONG, Test Core (6/8) is red on this head (check-run 122765680713, completed 10:53:40Z, step 11: command (packages/cli) pnpm run test exited (1)), green on main. Same caveat on the log; derived: packages/cli/test/migrate-meta-engine-guidance.test.ts:395-416 runs os migrate meta --from <floor> --to 18 --all over a family fixture and asserts, for EVERY semantic entry, that the printed block is verbatim and block.match(TRACKER_ID) (/#\d{4,5}\b/, :73) is undefined — "the guidance of every semantic entry carries no tracker number". The new D3 entry packages/spec/src/migrations/entries/semantic/18.ui-list-view-options-bag-folded.ts carries two in its author-shown reason: objectui#10380 (:24) and #25923 (:25). At the head it is the only semantic entry whose fields carry one (git grep over entries/semantic/ finds exactly those two lines plus the file's own // header, which the test does not print). The same house rule is what every spec retirement pin states for its own entry ("carries its D3 entry beside the D2 (ruling B), with no tracker number in any author-shown field"); the D2 conversion's own literals (summary, notices, TODO text) carry none. FIX: reword reason to name the ruling in words ("the maintainer's ruling on the objectui options.map read", "this retirement") with no #NNNNN.

  • D2 — RIGHT, the fold is honest under ADR-0087 D2. listViewOptionsBagFolded (registry.ts, new :437-624, order 78 in MAJOR_18_CONVERSIONS, the only order: 78 at the head) gives each of the eight bag kinds exactly one live home: options.KIND with no KIND becomes KIND; both present ⇒ { ...under, ...top } (top-level wins per key, the precedence ListViewOverlayOptionsSchema's JSDoc declared on main :6500-6516 and ListView applied for kanban/calendar/gallery/timeline/gantt/map); chart alone is replaced whole, matching main's schema.chart || schema.options?.chart read (main ListView :4176-region, main view.zod.ts :2836-2838). Nothing is silently dropped: a non-object bag, options.grid, a non-kind key, a non-object block (or one beside a non-object top-level block) and any bag on a form overlay are left in place with a reportTodo naming the key and the remedy (:509-519, :545-554); an empty residue removes the key with a notice. The fold is scoped to the flattened spelling (config, list/form, listViews/formViews all bail, :505-507) and runs over both views and the assembled viewItems channel (:556-560). It is retiredFromLoadPath: true, retiredAfter: '18.2.0', so the authoring funnel refuses and teaches while applyConversionsToStoredItem replays it (stored.ts :102-113 passes includeRetired: true; apply.ts :157); the metadata-protocol pin (protocol.stored-conversions.test.ts :268-282) proves both getMetaItem and getMetaItems serve the row folded and leave the row at rest unrewritten. Idempotence is pinned (view-overlay-options-bag.test.ts :1169-1174): a second read changes nothing and emits nothing, and the code agrees (no options ⇒ return view at :504; a TODO residue with changed === false returns the view unchanged, :552). The hand-listed LIST_VIEW_OPTIONS_BAG_KINDS equals the eight kinds the bag declared on main (main's declaredBagKinds pin listed the same eight), and spelling it out is right for a conversion that replays a frozen shape (:437-443). The one thing the fold cannot supply — a key the top-level block REQUIRES that the bag never had — is named in the D2 docblock, the D3 entry ui-list-view-options-bag-folded (reason, acceptanceCriteria), the changeset's Fix paragraph (kanban.columns, gantt.titleField, chart.dataset/values) and pinned (view-overlay-options-bag.test.ts :1158-1164, a gantt bag folded into a block that diagnoseViewMetadata refuses). kanban.columns and groupByField, gantt's titleField, chart's dataset/values are indeed required on the spec blocks (view.zod.ts head :2057-2084; ListChartConfigSchema).

  • D3 — RIGHT, the tombstones and refusals are complete and each prescription points at a live destination. VIEW_METADATA_MEMBERS.listOverlay.shape.options is retiredKey(LIST_OVERLAY_OPTIONS_REFUSED) (view.zod.ts head :6473); the text names the move (options.KIND → KIND), the top-level-wins rule, the grid exception, the folded-on-read fact and the standardized os migrate meta --from 17 sentence (:6637-6643), and "18.3.0" is spec 18.2.0 + the minor this changeset grades, pinned through the new JOIN row of retired-key-removal-version.pin.test.ts (:699). ComponentPropsMap['list-view'].options shares the member by reference (component.zod.ts :773-778; component-list-view-row.test.ts :755-758 pins toBe), so the page node, the lint rule (COMPONENT_PROPS_INVALID at properties.options, lint test :119-124) and the REST door (422 INVALID_METADATA at options, rest test :378-404) all refuse by name with one text. @object-ui/types takes the member by reference (stripImportedDefaults(SpecViewMetadataMembers.listOverlay.shape.options), objectql.zod.ts :2398; list-view-options-bag-6152.test.ts :5749 pins toBe(SPEC_OPTIONS)), so every published door — mirror, tolerant, strict, safeValidateSchema — refuses a bag, empty or full, with the spec's text, beside a LIT CONTROL that the same eight blocks written top-level parse (:5800-5821). NamedListView.options is ?: never with a @deprecated RETIRED (objectstack#25923) docblock naming the eight top-level blocks (objectql.ts :6189-6195); the 7779 census moves it from BUCKET_LOCAL_ONLY_HELD (now empty) to RETIRED_BY_25923, 21 → 22 tombstones, with the inverted pin asserting tombstones contains options and the old options?: Record<string, any>; line is gone (:6154-6164). The TypeScript face is pinned in the spec (view-overlay-options-type.test.ts, Equal<ListOverlayIn['options'], z.input<RetiredKeySchema>> plus five @ts-expect-errors) and in ui (:5943-5955). parse(parse(x)): a bag never passes the first parse; a folded row parses and re-parses with no options key (view-overlay-options-bag.test.ts :1067-1071, :1105-1131). No prescription names a retired value: FORM_OVERLAY_OPTIONS_REFUSED and VIEW_ITEM_OPTIONS_REFUSED are untouched and point at their own live homes.

  • D4 — RIGHT, the reader deletion is complete, and the census that says so cannot pass vacuously. On origin/main, ListView.tsx reads schema.options?.KIND in kanbanBlockOf, resolveListMapConfig, resolveListChartBinding, resolveTimelineDateBinding (two rungs), both collectViewFields passes (×5 each), the fetch-effect deps (×1 in the dependency list and ×1 in the projection memo), the capability gate (kanban, gallery, calendar, gantt, tree), and the render branches (calendar ×5, gallery ×2, timeline ×1, gantt ×4, tree ×1); every one is deleted in the diff (:3338-3345, :3388-3391, :3422-3428, :3447-3450, :3453-3476, :3481-3486, :3493-3516, :3538-3539, :3554-3620, :3639-3675, :3683-3695, :3700-3706, :3723-3747, :3784-3785). At the head, grep -n options ListView.tsx returns only comments, select-field options, kanbanLaneOptions (an object-field read) and i18n options — no bag read. The new SOURCE census (readersRetired-6152.test.tsx :4645-4729) walks every non-test .ts/.tsx under plugin-list/src with the TypeScript AST and reports a schema.options / schema?.options / (schema as any).options / props.schema.options / schema['options'] read by file and line; it cannot pass vacuously because (a) the POPULATION pin requires ListView.tsx and index.tsx to be in the walked set, (b) the FIRING CONTROL finds schema.kanban in ListView.tsx with the same scanner, and (c) the probe pin shows the scanner hits lines 2, 3, 4, 5, 7 of a seven-line fixture and skips the comment and field.options. What it does not cover is a read through an aliased variable (const s = schema; s.options), which no file in the package has at the head (read). Outside plugin-list, every other reader named in the ruling is gone: storedLegacyOptions (objectViewOptions.ts) and the options: rung of objectViewListRenderer.tsx; InterfaceListPage's legacyOptions gate and mapCfg; foldStoredListOptions in ViewPreview.tsx and its import in ObjectListViewInspector.tsx; plugin-view's options: activeView; checkListViewDatasetChartFilter's legacyBag; the spec's checkListViewChartBinding bag arm. A repo-wide git grep at the head for options?.KIND / options.KIND in non-test ui source finds only select-field options arrays. Each deleted read's behaviour has a home: a stored bag reaches every one of these pages already folded (every stored-row read replays the chain, D2), and a bag written inline is refused at every door (D3), so a bag can reach a renderer only through a hand-built ListView/ObjectView host schema cast past the types — which is the one declared break (changeset BREAKING paragraph, ListView host).

  • D5 — RIGHT with one precision note, on "a config that rendered one way on main and renders differently at this head". I looked for one across the three producers. (a) Object page, no bag: on main plugin-view handed options: activeView and no top-level canonical block (main ObjectView.tsx :3036, and no currentNamedViewConfig?.kanban rung in that literal), and objectViewListRenderer then overwrote options wholesale with its synthesized bag — so ListView rendered from the synthesized blocks alone. At the head the same synthesized blocks go out top-level (holdBlock(...), diff :2962-3082); kanbanViewOptions/timelineViewOptions/galleryViewOptions/ganttViewOptions are unchanged, the map rung is the same six-key projection, chart is viewDef.chart whole. Same inputs to ListView, so the same render; the isSameOptionsValue hold now applies per block (heldListOptions typed Record), which keeps objectui#10046's identity promise for the fetch effect that names each block. (b) Object page, stored bag row (census: 0 reachable): on main the bag's block REPLACED the synthesized block (kanban: legacyOptions.kanban ?? kanbanViewOptions(...)); at the head the served row arrives folded and the synthesized producer runs over it, so the floors now apply (titleField: 'name' on kanban/gantt/gallery/timeline/map, labelField: 'name' on tree). This IS a main→head change for such a row, and it is declared — the PR's "Kanban title floor" acceptance note and the inverted parity pin (storedRowParity-10380.test.tsx :2366-2374, "Pinned as observed") — though the changeset's "A view already stored with one is served folded, so it renders as before" over-states it for the object page; see ③. (c) Interface page, stored bag row with options.map and map whitelisted: on main mapCfg = bag.map ?? derived suppressed the derivation; at the head mapBlock = { ...derivedMap, ...authoredMap } lays the folded block over the derivation, so a bag with only latitudeField/longitudeField gains a derived locationField beneath it. ObjectMap.getMapConfig reads the lat/lng pair first (:541-551) and hasBinding is unchanged, so the markers draw the same; the no-bag case already behaved this way on main. Benign, undeclared, noted. (d) Interface page, other kinds: derives(kind) on main was false when the bag carried the kind, which left the top-level block absent and the bag to ListView; at the head the folded view.KIND is relayed through view.KIND ?? derived — same effective block. (e) plugin-view host views entry: hostViewKindBlocks(activeView) relays the same eight blocks options: activeView used to carry, now top-level; the canonical named-view blocks were never in that literal on main, so no precedence moved. (f) ViewPreview / ObjectListViewInspector: the fold moved from the door to the server; the pins are the same lane cases run on applyConversionsToStoredItem output (foldedOnRead-25923.test.tsx), with a CONTROL that an UNFOLDED body no longer resolves the lane (:2685-2704). I found no undeclared rendering change beyond (c).

  • D6 — RIGHT, the main merges carry only main. The PR's commits are four on the branch (ad68bae, 6fee4e7, 1e40f39, 2e74671) plus the merge ef9b17854 of b322b5da6; the merge-base with today's origin/main 4265176c is b322b5da6, and git diff --stat origin/main...ef9b17854 is the same 83 files / +1,875 / −1,808 the API reports, so the three-dot diff is PR 2's work alone. git diff --name-only 07d18e692 b322b5da6 (37 first-parent commits; the dev wrote 21) intersected with the PR's 83 paths is exactly ui/packages/plugin-list/src/ListView.tsx and ui/packages/plugin-list/src/__tests__/ListView.test.tsx (fix(plugin-list): show list view sharing type and lock owner as read-only text, drop the dead Share button #27013), as the body says; refactor(plugin-calendar,plugin-gantt)!: retire the flat fallback read; the calendar and gantt renderers read the block only #27026 (d8a9cc5) and feat(app-shell,plugin-list,plugin-grid,i18n): the Display menu's Open records section writes the reader's drawer | split preference; the name cell's ↗ opens the record as the full page #27031 are both ancestors of the head.

  • D7 — RIGHT, the declarations are internally consistent and match the diff. PR body and changeset both carry Clause-②: no (narrowing) and Metadata-protocol: narrowed, and narrowed is the measured kind: an authorable key (options on the list overlay, the page list-view node and the ListView mirror) is tombstoned. The ADR-0087 marker is one <!-- adr-0087: registered list-view-options-bag-folded, ui-list-view-options-bag-folded -->; both ids are new in this diff and resolve — the D2 in conversions/registry.ts, the D3 in migrations/entries/semantic/18.ui-list-view-options-bag-folded.ts, which build-migration-registry.ts discovers by directory (:145), so no index edit is owed — and the PR body's ADR-0087: line names the same two. The Skills-docs: line is true: build-react-blocks-contract.ts has exactly one output (:29, :174), skills/objectstack-ui/references/react-blocks.md; the regenerated file drops exactly the options row the REACT_BLOCKS list-view entry drops (react-blocks.ts :790 ↔ react-blocks.md :1690), nothing else moves, and git grep over skills/** at the head finds no view-level options: { KIND teaching (the two files the body names write the blocks top-level). Part of #25923 is present and correct — the card stays open — but the remainder sentence is imprecise; see ③. check-adr-0087-registration and check-changeset-no-major are CI's Check Changeset, green on this head (10:32:10Z).

  • D8 — RIGHT, no test was deleted or weakened without being re-pinned to the new truth; each one that moved: (1) view-chart-binding.test.ts loses the bag-as-binding pins and the "asks of the bag what the block requires" derivation, replaced by three refusal arms at options and a CONTROL; the block's required keys are still pinned by its own strict schema (chart.dataset / chart.values, list-view-dataset-chart-filter-refusal-10327.test.ts :5516-5528). (2) view-overlay-options-bag.test.ts is inverted whole: refusal ×5 kinds of bag, authoring-funnel does-not-fold, stored-row fold (per-key, chart-whole, missing-required, idempotent), TODO residues, form/record controls. (3) view-overlay-options-type.test.ts: six @ts-expect-errors become five on a tombstone type plus one Equal pin. (4) view-overlay-viewkind-arm.test.ts W2 drops the one options row (no longer list-legal); the other three rows stand. (5) storedRowParity-10380 renamed and re-pointed: every case runs on the served (folded) row; the kanban-lane case replaces toEqual(interfacePage) with per-field pins and a pinned titleField divergence (declared); a new block pins that an unfolded bag reaches neither page. (6) ViewPreview.optionsFold-7928 → foldedOnRead-25923: the same lane cases on the served row, foldStoredListOptions pins become served() pins, the "input not mutated" pin is dropped (the function is gone; the conversion's non-mutation is applyConversionsToStoredItem's contract and structuredClone is used), and the CONTROL is strengthened (asserts the unfolded body has no kanban). (7) The four plugin-list bag CONTROLs (chart-capability, gallery/kanban canonical, gantt-binding, mapViewLevelConfig, mapLocationSelect, timeline-binding, timelineMetaFieldsRetired, datasetChartToolbarFilter, readersRetired describe 2/3/H2) are inverted to RETIRED arms asserting the bag binds nothing, each beside the top-level CONTROL. (8) ListViewBlock.specInputs-13047's options member pin becomes a door-refusal pin, and the console's MEMBER_PINS['list-view.options'] row is removed while list-view.kanban…list-view.tree rows remain (head :2949-3061). (9) strayGroupByRefused-8365 moves its distinguishing fixture to the served form and adds a pin on the fold that produces it. (10) calendar-date-alias-refusal-8355 surface 2 and kanban-stray-group-by-refusal-text-9653 move the bag channel to the top-level slot. (11) relayRungCensus-7559 adds an options: unread ledger row and re-derives the eight top-level rungs. Nothing is loosened: every former ACCEPT of a bag is now a REFUSE with a CONTROL, and every former "renders from the bag" is now "renders from the block".

  • D9 — RIGHT, the changeset covers every published package whose src/** moves, in the root .changeset/ as ui/** changesets must: @objectstack/spec minor, @object-ui/types minor, @object-ui/plugin-list minor, @object-ui/app-shell minor, @object-ui/plugin-view patch, @object-ui/plugin-map patch, and @object-ui/console, @objectstack/rest, @objectstack/metadata-protocol, @objectstack/lint patch (test pins only). The BREAKING banner carries Before / Now / Fix with the FROM → TO mapping, the required-keys caveat and the os migrate meta --from 17 sentence.

② Semver level

  • Kind: Metadata-protocol: narrowed — right. The list overlay's options key, the page list-view node's options and the ListView mirror's options are tombstoned; a stored bag is converted on read (D2), an authored one refused by name (D3). Clause-②: no (narrowing) — right: no accept set widens anywhere in the diff (the only ACCEPTs added are CONTROLs on shapes that already parsed).
  • Level: minor on @objectstack/spec, @object-ui/types, @object-ui/plugin-list, @object-ui/app-shell satisfies ruling A ("a kind takes at least minor on a package the diff moves") and the gate's discharged arm (check-changeset-no-major.mjs :1886-1887: a kind declared, a moved package graded minor+, others patch). patch on @object-ui/plugin-view and @object-ui/plugin-map is acceptable under that arm: plugin-view's change relays the host views entry's eight blocks top-level instead of as a bag the renderer read the same keys from (no authorable key moves on its face), plugin-map's are two warning strings. The four test-only patch entries are the right grade. Check Changeset green on this head confirms the gate's reading.
  • Prescription version: "removed in @objectstack/spec 18.3.0" = 18.2.0 + minor, pinned (D3).

③ Boundary flags

  • FAIL reasons, three gate reds on the head, each this diff's (green on main at 4265176): (1) D1 — ui/packages/plugin-list/src/index.tsx drops the options registry input and the committed sdui.manifest.json was not regenerated; Build & E2E UI red at the manifest step (annotation quoted in D1); node scripts/gen-sdui-manifest-node.mjs, commit the result. Because that job gates the Console build, eager-closure, test:dist and the ui E2E run, none of those ran on this head; after the regen they are unread, not green. (2) D1b — packages/spec/src/conversions/registry.ts:12387 defines listViewOptionsBagFolded above viewListFilterableFieldsRemoved instead of directly above listViewSortStringClauseToArray (:10759), the entry that follows it; scripts/conversions-major18-merge.test.ts rule 2 → Test Core (2/8) red (spec test:repo). (3) D1c — packages/spec/src/migrations/entries/semantic/18.ui-list-view-options-bag-folded.ts:24-25 cites objectui#10380 and #25923 in the author-shown reason; packages/cli/test/migrate-meta-engine-guidance.test.ts:409-415 → Test Core (6/8) red (cli test). (2) and (3) are derived by reading the test and the head file, because the shard logs are unreachable from this seat; the shard annotations name only the package and the exit code, so a second failing case in either shard would be invisible to this reading — the dev's rerun after the fixes is the measurement.
  • The dev's gate sweep (100 derived / 99 exit 0) did not include the three steps above: the manifest step needs the built ui registry, and spec's test:repo and cli's test are turbo tasks the derivation did not select for these paths. Not a flag against the dev; recorded so the seat does not read "99/100" as covering CI.
  • Dev flag "Kanban title floor … pre-existing": ANSWERED, with a precision note for the seat. The page-to-page difference is pre-existing for every no-bag kanban row, as the body says; but for a row that DID carry a bag, the object page's render moves main → head (the synthesized floors now apply over the folded block instead of being replaced by the bag's block, D5(b)). It is pinned "as observed" and bounded by the census (0 reachable stored bags; hotcrm, tenant rows and cloud unreachable), so I accept it, but the changeset's "A view already stored with one is served folded, so it renders as before" should read "… renders as a view that declared the same blocks top-level", which is the true sentence on both pages. Seat's call whether to amend the changeset before landing (the rest of the body is exact).
  • D5(c), interface page + stored options.map without locationField: the derivation now underlies the folded block; ObjectMap reads the pair first, so the draw is the same. Noted, no action.
  • Dev flag "the D3 entries 18.view-item-options-bag-refused.ts and 18.view-overlay-options-bag-judged.ts are left unedited": ACCEPTED. They are dated ledger entries of earlier steps (the latter's acceptanceCriteria "every stored overlay carrying options saves again unchanged" is now false as a present-tense statement), the new D3 entry names the supersession ("supersedes direction A on objectui#10380"), and the upgrade guide compiles them in order. The seat may prefer a one-line "superseded by ui-list-view-options-bag-folded" note in the judged entry's reason; not a defect.
  • Dev flag "Part of #25923 … Left for types: the strict authoring face accepts spellings the spec refuses (object-calendar flat keys), 74 zod mirrors silently strip unknown keys, and the list view's options bag is a second contract #25923: work item 2": the keyword and the open card are right; the remainder sentence is loose. Work item 2's strip-mode half landed in fix(types): the @object-ui/types/zod mirrors keep an unknown key instead of silently dropping it #26019 (.changeset/25923-ui-types-mirrors-passthrough.md is on main, landing 6416705898); what remains is ruling 6412606977's deferred FormField index signature and the immutable render-or-refuse follow-up from 6448239783. The landing comment should say so.
  • Dev flag "88 files, 5,341 changed lines, over the 3,000-line human-merge threshold": VERIFIED as 83 GitHub paths = 88 with renames counted twice; the PR is governed on two counts (size, and skills/objectstack-ui/references/react-blocks.md is a Tier H register row), so the landing is the maintainer's whatever this record says; the regenerated skills file is a byte-exact projection of react-blocks.ts (D7).
  • Dev flag "feat(spec)!: the action blocks spell the destructive variant danger, the word ActionSchema.variant uses #26966 (open) adds its own conversion to registry.ts; whichever lands second takes the next free order": VERIFIED order 78 is unique at the head; ordinary concurrency.
  • Dev flag "dogfood not repeated after the b322b5da6 merge": does NOT carry mechanically — the span 07d18e692..b322b5da6 is 37 first-parent commits touching 18 packages/spec files, 6 packages/lint, 3 packages/metadata-protocol among others — but no path of this diff moved on main except the two ListView files (D6), the dev reran spec, metadata-protocol, rest, lint, plugin-list and types after the merge, and CI's Dogfood Regression Gate on this head is path-skipped (success, three shards skipped). Recorded as the dev's own reading over a tree that is not the head, not as a head measurement.
  • Dev flag "check:dual-build-cjs-loads NOT-MEASURED (exit 3)": CI's Build Core on this head is the reading; see the gate list below.
  • Dev's local runs (ui suite in three batches, spec 741/741, metadata-protocol, rest, lint, type-check ×6, lint ×6, 99/100 gates exit 0, check:generated 14/14): read as the dev's own measurements, not re-run; CI's contexts below are the readings of record. Note the ui suite "before the last main merge" is on a tree that differs from the head by the two ListView files, which the dev did rerun (plugin-list + types, 560 files).
  • Escalation to the seat: the SOURCE census (D4) covers plugin-list only, by design; the other readers are pinned individually (relayCensus-11572 options not written, relayRungCensus-7559 options: unread, foldedOnRead, Inspector target test). No repo-wide "no options bag read" instrument exists; acceptable for this card, noted for the next bag-shaped retirement.
  • Governance: draft, head repo = base repo, no auto-merge, governed (size + skills/**), Tier H — the landing click is the maintainer's; this record is the contract-tier reading the seat owes before asking for the word.
  • Gate readings on the head ef9b17854 (UTC, 2026-10-28). First read 10:32:22Z: 33 check-runs, 10 success, 8 skipped, 15 in_progress. 10:36:34Z: 61 runs, 22 success, 8 skipped, 31 in_progress. 10:40:17Z: 23 success, 8 skipped, 1 failure (Build & E2E UI, D1), 29 in_progress. 10:42:27Z: 26 success (Build Core, Type Check · consumer gates, Type Check · debt ledger joined Lint & Repo Gates, Check Changeset, Governed Surface Queue Guard, Spec property liveness, Dogfood Regression Gate (shards path-skipped), Plan Test Core shards, Build UI Deps, the nine Lint · lanes, Type Check · source gates, Check PR Size, Auto Label, the PR-body guards, Check Documentation Links, Flag docs affected by code changes, filter), 8 skipped, 1 failure, 26 in_progress. 10:48:59Z: 46 success, 7 in_progress. 10:59:15Z, every run completed: 64 check-runs, 52 success, 8 skipped, 4 failure — Build & E2E UI (10:38:15Z, D1), Test Core (2/8) (10:51:56Z, D1b), Test Core (6/8) (10:53:40Z, D1c) and the Test Core roll-up (10:59:07Z: "2 of 8 declared shard(s) of test published no positive attestation (test-2-of-8, test-6-of-8)"). Green on this head: Test UI 1-16 and its roll-up (so main's Test UI (3/16) red, the PermissionMatrixEditor signature of Queue-flake anchor: packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.objectGrantInherited-27010.test.tsx #27097/Queue-flake anchor: packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.packageDoorDraftBase-26837.test.tsx #27104, did not reproduce here), Test Core 1, 3, 4, 5, 7, 8, Lint UI, Type Check UI, Type Check · workspace, Build Core, Check Changeset, Governed Surface Queue Guard, Spec property liveness, Dogfood Regression Gate (shards path-skipped), the nine Lint · lanes, Type Check · source/consumer/debt, Check PR Size, the PR-body guards, Check Documentation Links, Flag docs affected by code changes. On origin/main 4265176c Build & E2E UI and all eight Test Core shards are green, so the four reds are this diff's. GATE_FINAL_READING (11:09Z). Shard logs: not readable from this seat (/actions/jobs/<id>/logs and the artifacts redirect to a host the proxy forbids); the readings above are the check-run conclusions, their annotations and the job step list.

Implemented-by: claude/project-thread-eqo5sw
Reviewed-by: session_01WGufzgbJhFoXYsLSe5WSPP

VERDICT: FAIL

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Round 2, on the round-1 FAIL (6467465885). Round 1's judgments D2–D9 and ② stand for every file the four new commits do not touch (the net diff against origin/main is the round-1 83 files plus sdui.manifest.json and scripts/sdui-manifest.record.json; no new main merge).

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 3f56e4cfea2f49a8c86c6dd2105272add044627f
Local-runs: none
Scope: the round-2 range ef9b1785…3f56e4cf (compare API, application/vnd.github.diff, 453 lines, read in full; 4 single-parent commits d9deed5, 9d1966c, 57897ed, 3f56e4c; 5 files): .changeset/25923-list-view-options-bag-retired.md, packages/spec/src/conversions/registry.ts (+189/−189), packages/spec/src/migrations/entries/semantic/18.ui-list-view-options-bag-folded.ts, scripts/sdui-manifest.record.json, sdui.manifest.json — plus, read at 3f56e4cf to judge them: packages/spec/src/conversions/registry.ts in full (the 86-entry list, every const …: MetadataConversion definition, :10721 LIST_VIEW_OPTIONS_BAG_KINDS, :10769 listViewOptionsBagFolded, :10948 listViewSortStringClauseToArray), packages/spec/scripts/conversions-major18-merge.test.ts (:46 ENTRY, :50 DEFINITION, :143-160 placementFindings, :325), packages/cli/test/migrate-meta-engine-guidance.test.ts (:73 TRACKER_ID, :395-416), sdui.manifest.json in full (parsed; the list-view inputs), scripts/gen-sdui-manifest-node.mjs (docblock, :103-152 the record's sha256), ui/packages/types/src/form.ts:1942-1945, ui/packages/plugin-form/src/sectionFields.ts:417, every non-test immutable read under ui/packages/*/src, the PR body as it stands (updated 11:53:15Z), PR #27026's title and body (work item 3's PR 1), card comments 6412606977 (ruling A) and 6448239783 (the item-4 landing note, "Still open on this card"), git diff --stat origin/main...3f56e4cf after a read-only fetch, and the check-runs on 3f56e4cf read at 11:53:55Z and 11:57:21Z.

① Derived judgments

  • D1 (manifest) — FIXED, faithfully, nothing else moved. Commit d9deed5 changes sdui.manifest.json by exactly the five lines of the list-view options input (the row CI's own diff printed in round 1) and nothing else; the list-view inputs at the head are the 40 remaining names, with kanban, calendar, gantt, gallery, timeline, map, tree, chart all present and options gone, which is what LIST_VIEW_INPUTS in ui/packages/plugin-list/src/index.tsx now declares. scripts/sdui-manifest.record.json moves only generatedAt (2026-10-27 → 2026-10-28) and sha256; the recorded digest 6b9a62b5…263b79 is the sha256 of the committed sdui.manifest.json bytes at this head (computed over git show 3f56e4cf:sdui.manifest.json), components stays 106, source/generator/inTree unchanged — the record moved with the artefact, as its own note requires. The reading of record: Build & E2E UI on 3f56e4cf completed success at 11:39:06Z (the manifest step compares the committed bytes with a fresh generation, so no unrelated drift is possible past it), and the steps round 1 left unread — Console build, eager-closure, test:dist, the ui E2E run — ran green inside it.

  • D1b (placement) — FIXED, byte-identical apart from position. Commit 9d1966c is +189/−189 in registry.ts; the 189 added lines equal the 189 removed lines exactly (compared line by line after stripping the diff sign), so the fold, its doc comment and LIST_VIEW_OPTIONS_BAG_KINDS moved together and nothing in them changed. The block now sits after viewPageMountRemoved closes and directly above the [#17053] doc of listViewSortStringClauseToArray: replaying the test's DEFINITION and ENTRY regexes over the head file, listViewOptionsBagFolded is defined at :10769, the next definition is listViewSortStringClauseToArray (:10948), which is the entry that follows it in the still-sorted 86-entry list — placementFindings returns no finding for it, and its two neighbours remain exempt as before. MAJOR_18_CONVERSIONS is untouched (not in the diff), order 78 stands. The reading of record: Test Core (2/8) on 3f56e4cf completed success at 11:47:51Z (spec test:repo), and the Test Core roll-up success at 11:51:59Z with all eight shards attested.

  • D1c (tracker id) — FIXED, minimal. Commit 57897ed changes two lines of the D3 entry's reason (:24-25): "Ruling direction A on objectui#10380 kept it … which types: the strict authoring face accepts spellings the spec refuses (object-calendar flat keys), 74 zod mirrors silently strip unknown keys, and the list view's options bag is a second contract #25923 retires" → "An earlier ruling kept it only for objectui's pinned options.map read, which this change retires with every other reader." No other field moved; surface, replacement, acceptanceCriteria, conversionIds are as round 1 read them. The only #NNNNN left in the file is the // header at :5, which is source comment, not a printed field, and the engine-guidance test prints fields only. The sentence stays true (the ruling exists, 6412606977's lineage on objectui#10380 is recorded on the card) and still names the mechanism in words. The reading of record: Test Core (6/8) on 3f56e4cf completed success at 11:49:57Z (cli test).

  • D5/③ (changeset prose) — FIXED, and the new sentence is the true one. The Now bullet reads: "A view already stored with one is served folded onto its top-level blocks and renders from them. On the object page those blocks now pass through the same relay defaults as a block written top-level (the kanban titleField: 'name' floor, for one), so a folded row can draw differently from its bag where the bag left such a key unset. No reachable source stores a bag (census: 0 rows), and ObjectView.storedRowParity-10380 pins the folded rendering." Each clause matches what round 1 D5(b) measured: the object page's synthesized producers (kanbanViewOptions and the others) now run over the folded block instead of being replaced by the bag's block, the divergence is pinned "as observed" in storedRowParity-10380 (:2366-2374 at round 1's head, unchanged here), and the census is 0. Check Changeset ran twice on this head (11:32:29Z after the changeset commit, 11:55:10Z after the body edit), both success, so the gate still reads Clause-②: no (narrowing) + Metadata-protocol: narrowed against the minor grades.

  • D7 (PR body, "what remains") — TRUE, verified clause by clause at this head. (a) FormField's index signature: ui/packages/types/src/form.ts:1945 [key: string]: any; under "Additional field-specific props" is still there; ruling 6412606977 (2026-10-26) says exactly what the body says — "PR fix(types): the @object-ui/types/zod mirrors keep an unknown key instead of silently dropping it #26019 keeps FormField's index signature. Dropping it is a narrowing that breaks components form.tsx (×5), plugin-form (×2 …) and plugin-grid (×2), so it rides a follow-up on this card". (b) immutable: ui/packages/plugin-form/src/sectionFields.ts:417 if (fd.immutable != null) base.immutable = fd.immutable; copies it, and no file under ui/packages/plugin-form/src or any other ui form renderer reads .immutable off a FormField — the only renderer reads of an immutable key are Studio's metadata-admin SchemaForm.tsx (:1623, :3010) on its own form-spec.ts field type, a different type, so "no form renderer reads it, so an edit-mode form leaves an immutable field editable" is right for the FormField mirror the follow-up is about; the follow-up is 6448239783's "Render it, or record a refusal" verbatim. (c) The one other item 6448239783 left open — "retire the calendar/gantt flat fallback reads and the hand-off types" under work item 3 — is PR refactor(plugin-calendar,plugin-gantt)!: retire the flat fallback read; the calendar and gantt renderers read the block only #27026 (merged d8a9cc5, "retire the flat fallback read; the calendar and gantt renderers read the block only", removing ObjectCalendarFlatFields / ObjectCalendarHandoff), and its "census must also count stored views whose options.gantt carries a GanttConfigSchema-refused value" is discharged by a census of 0 stored bags. So "Work item 3 is complete with refactor(plugin-calendar,plugin-gantt)!: retire the flat fallback read; the calendar and gantt renderers read the block only #27026 and this PR" and the two named remainders are the whole remainder; Part of #25923 is the right keyword. The body's round-1 section describes the three fixes as the diff shows them, and its dogfood paragraph now carries a reading on 3f56e4cfe (348 files / 2,804 tests, exit 0) beside the earlier one — recorded as the dev's own measurement; CI's Dogfood Regression Gate on this head is success with its shards path-skipped, as before.

  • D6 (merges) — unchanged. The four round-2 commits are single-parent; git diff --stat origin/main...3f56e4cf is 85 files / +1,877 / −1,815, the API's numbers, and git diff --name-only ef9b1785 3f56e4cf is exactly the five files above.

② Semver level

  • Unchanged from round 1 and still right: Metadata-protocol: narrowed, Clause-②: no (narrowing); minor on @objectstack/spec, @object-ui/types, @object-ui/plugin-list, @object-ui/app-shell; patch on @object-ui/plugin-view, @object-ui/plugin-map and the four test-only packages. Round 2 changes no graded surface: the manifest is a generated artefact of the already-graded plugin-list change, the registry move is positional, the D3 reword is prose. Prescription version "18.3.0" still equals spec 18.2.0 + minor.

③ Boundary flags

  • None blocking. Gate readings on 3f56e4cf (UTC, 2026-10-28), first read 11:53:55Z: 68 check-runs, 57 success, 10 skipped, 1 in_progress (Check Changeset, re-run for the 11:53 body edit). 11:57:21Z: 68 completed, 58 success, 10 skipped, 0 failure — Build & E2E UI 11:39:06Z, Build Core 11:40:46Z, Test Core 1-8 and roll-up 11:51:59Z, Test UI 1-16 and roll-up 11:47:00Z, Check Changeset ×2, Dogfood Regression Gate (shards path-skipped), Spec property liveness, Governed Surface Queue Guard, the Lint · lanes, Type Check · lanes, Lint UI, Type Check UI. The 10 skipped are the opt-in / schedule / docs lanes plus the skipped re-runs of Auto Label and Check PR Size. The dev's "64/64 completed, 0 failures" was read before the body edit added the re-runs; the head's state now is 68/68 completed, 0 failures, every run on 3f56e4cfea. GATE_FINAL_READING (11:57Z).
  • Precision, not a defect: the D3 entry's // header (:5) still names #25923 and objectui#10380 — source comment, outside the printed fields and the house rule; fine to leave.
  • Precision, not a defect: Studio's metadata-admin SchemaForm does honour an immutable flag on its own form-spec.ts type (readOnly={… (f.immutable && !createMode)}), so a reader who searches ui/ for immutable will find a renderer that reads one; the body's claim is about the FormField mirror and plugin-form, where it holds.
  • Governance unchanged: draft, head repo = base repo, no auto-merge, governed on two counts (5,341 lines over 88 dispatch-gates paths — 85 GitHub paths with renames counted twice — and skills/objectstack-ui/references/react-blocks.md, a Tier H row, untouched in round 2 and still the generator's byte-exact output). The landing click is the maintainer's; this record is the contract-tier PASS the seat owes before asking for the word.
  • Round-1 flags carried, all addressed or accepted: changeset prose (fixed); "what remains" (fixed, verified); the unedited 18.view-overlay-options-bag-judged.ts acceptanceCriteria (accepted in round 1 as a dated ledger entry; the new entry names the supersession in words); dogfood (now re-read on this head by the dev; CI path-skips it); D5(c) interface-page map derivation under a folded block (benign, noted).

Implemented-by: claude/project-thread-eqo5sw
Reviewed-by: session_01WGufzgbJhFoXYsLSe5WSPP

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读 (席位终稿)

  • 改了什么:列表视图的 options 杂物袋退役了(types: the strict authoring face accepts spellings the spec refuses (object-calendar flat keys), 74 zod mirrors silently strip unknown keys, and the list view's options bag is a second contract #25923 第 3 项第 2 个 PR,按路线 A)。
    • 袋里每种配置都折叠到它唯一的正式位置(ADR-0087 D2 转换 list-view-options-bag-folded)。已存的行在读取时自动转换。
    • 写 options 会被拒绝,拒绝信息给出迁移方法。NamedListView.options 变成 never。
    • objectui 里 39 处读 schema.options 的地方全部删除,改读正式位置。源码普查从 39 处降到 0 处。
  • 为什么改:同一个配置能写在两个地方,渲染器两处都读,作者和 AI 不知道该写哪里,也会出现两边不一致。
  • 风险与回滚:这是收窄,旧写法会被拒,但已存的行会自动转换,显示不变。一个已声明的例外:对象页上,已存的袋式行会套用系统合成的标题字段兜底。按测试统计,能触达的行有 0 条,并有测试钉住。回滚 = revert 本 PR。
  • 先看:packages/spec/src/conversions/registry.ts 的 listViewOptionsBagFolded,以及 ui/packages/plugin-list/src/ListView.tsx。
  • 两分钟验证:pnpm --filter @objectstack/spec exec vitest run conversions 预期全绿(转换一次与转换两次结果相同,袋里每种配置都落到正式位置)。
  • 需要你做的:请审批(Approve)本 PR。 它超过 3000 行,并重新生成了 skills/objectstack-ui/references/react-blocks.md,属于受治理面,需要 os-zhuang 或 hotlong 批准。批准后由席位合并。

席位意见:接受。契约复核第一轮 FAIL(6467465885),三处 CI 红灯都是机械问题:SDUI 清单没重新生成、转换的位置不对、迁移说明里写了编号。第二轮 PASS(6468417300),三处都已修好,说明文字也已核实为真。#25923 剩下的事:FormField 的 [key: string]: any(裁决 6412606977 已延后),以及 immutable 的后续。

席位核对 · ACCEPT (Tier H, awaiting an authorized approval) · domain:spec seat 3 · os-support-ai · session session_01WGufzgbJhFoXYsLSe5WSPP · 2026-10-28T12:01Z

Head: 3f56e4cfea2f49a8c86c6dd2105272add044627f · card #25923 (item 3, PR 2 of 2; PR 1 was #27026) · claim amendments 6456384663 (route A) and 6460225523 (PR 2 and PR 3 merged)

  1. 形态: a draft PR to main carrying Part of #25923. 85 paths, +1877 / −1815. check-governed-merges --pr 27206 says GOVERNED on two counts: the 3,692 changed lines exceed 3,000, and it regenerates skills/objectstack-ui/references/react-blocks.md. So this is the Tier H final package: this record, needs-user-decision on the PR, and review requests to GOVERNED_APPROVERS (os-zhuang and hotlong are already requested). The PR stays draft until an authorized APPROVED review. After that, the seat runs the pre-landing checks, clears the label, readies the PR and arms auto-merge.

  2. 前提实测: the premise holds on main. The list-view options bag duplicated keys that each have a first-class home, and objectui read both places. Round 1 (6467465885, head ef9b1785) judged the substance RIGHT:

    • the fold honours ADR-0087 D2: one live home per kind, the chart moved whole, TODOs for residue that cannot fold, idempotent, replayed only for stored rows;
    • the tombstones and refusals are by reference across spec, types, lint and rest;
    • reader deletion is complete, with a non-vacuous AST census (39 → 0);
    • NamedListView.options: never, and the 7779 census moved;
    • the main merges are clean;
    • the changeset levels are valid;
    • the skills regeneration is byte-faithful.

    It failed on three CI reds. Round 2 (6468417300, this head) confirms all three fixed:

    • the manifest drops exactly the 5-line options input, and the record sha matches the committed bytes;
    • the conversion moved byte-identical, 189 lines out and 189 in, and now sits directly above listViewSortStringClauseToArray;
    • the D3 reason prints no tracker id.
  3. 门禁 check-runs (on this head): 68 runs read to completion at 2026-10-28T11:57Z: 58 success, 10 skipped, none red or pending. Build & E2E UI, Test Core (2/8) and Test Core (6/8), all red on ef9b1785, are green here. Dev-side readings on this head:

    • full dogfood: 348 files passed and 3 skipped, 2,804 tests;
    • spec repo project 1,324 tests; cli 6,484 tests;
    • check:generated exit 0;
    • dispatch gates (round 1): 100 derived, 99 exit 0, and 1 NOT-MEASURED (dual-build-cjs-loads, which CI runs).
  4. 接受的偏差:

    • (a) PR 2 and PR 3 of item 3 merged into one PR (amendment 6460225523).
    • (b) Stored bag rows on the object page now take the synthesized titleField floors, a declared divergence from "renders as before". The census finds 0 reachable rows, and it is pinned (storedRowParity-10380).
    • (c) Flags noted by round 2, not owed: the D3 file's // header comment still names tracker ids (a comment, outside the printed-text rule); Studio SchemaForm reads an immutable flag of its own, on a different type.
  5. 范围外去向: types: the strict authoring face accepts spellings the spec refuses (object-calendar flat keys), 74 zod mirrors silently strip unknown keys, and the list view's options bag is a second contract #25923 keeps two items: FormField's [key: string]: any in ui/packages/types/src/form.ts (deferred by ruling 6412606977), and the immutable follow-up from 6448239783 (sectionFields.ts ~:417 copies it, and no FormField renderer reads it). The card therefore stays pm:dispatched after this lands.

  6. 落地条款: the seat checked the prose faces against the diff on this head: the PR body (r2, applied and read back), the changesets, the D2/D3 text and the regenerated react-blocks.md. It now waits for an APPROVED review from os-zhuang or hotlong, then lands through the queue and follows it to MERGED.

…-eqo5sw

# Conflicts:
#	packages/spec/src/shared/retired-key-removal-version.pin.test.ts
#	scripts/sdui-manifest.record.json
#	ui/packages/plugin-list/src/ListView.tsx
…ons input retired

main's #27179 generates content/docs/ui/components/ from sdui.manifest.json;
the merged manifest no longer publishes list-view's options input.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzevL8Q6rTfGz2XHXGT
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit 8eeb3b3 Oct 28, 2026
65 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-eqo5sw branch October 28, 2026 20:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants