Skip to content

feat(spec)!: retire the ListView objectName / viewType react-tier aliases — data / type are the only spellings (#14791) - #16777

Merged
os-zhuang merged 5 commits into
mainfrom
claude/issue-14791-retire-listview-objectname
Sep 8, 2026
Merged

feat(spec)!: retire the ListView objectName / viewType react-tier aliases — data / type are the only spellings (#14791)#16777
os-zhuang merged 5 commits into
mainfrom
claude/issue-14791-retire-listview-objectname

Conversation

@claude

@claude claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #14791

Executes the director-seat ruling (summon #17, decision batch #1, 2026-09-07, comment 5572008848, maintainer verbatim 「同意」), option B: the ListView react block's objectName and viewType props are REMOVED — not un-deprecated — and the only spelling is data={{ provider: 'object', object }} / type; the lint's canonical-first branch stays; the in-repo author sites and the published skills/** passages move in the same PR; option A is refused. Clause-②: yes (a published contract's accept set narrows) ⇒ this PR carries needs:contract-review. The diff touches skills/** ⇒ GOVERNED: draft only, a human merges — never ready, never queued, never auto-merged by a seat.

What changed

  • Contract (packages/spec/src/ui/react-blocks.ts): the two overlay rows and their deprecated blocks are gone; the ListView summary names the canonical keys as the only spellings; REACT_RECORD_BLOCK_ALTERNATIVES['record:related_list'] (the :167 string the contract publishes) prescribes data={{ provider: 'object', object: 'CHILD' }} instead of the retired alias. Two additions are described under "Beyond the ruling's literal words" below.
  • Lint (packages/lint/src/validate-react-page-props.ts): boundObjectName(tag, values) reads data.provider === 'object'data.object for ListView (the canonical read step 1 deferred to "step 3, once the consumer fold has landed" — it has, objectui normalizeListViewSchema at console pin a472b071); every other block still binds by objectName. The step-1 scaffolding its own comments said step 3 deletes — UNFOLDED_DEPRECATIONS, replacementUnread, objectProviderBindsNothing, the "renders an empty list" message variants — is deleted. The generic replaced-by satisfaction and the generic deprecation warning stay as mechanisms.
  • Author sites: examples/app-showcase crm-workbench (1 site), renewals-pipeline (2 sites, one carrying the tree's only viewType), task-desk (1 site) → canonical spelling. Published skill skills/objectstack-ui/rules/pages.md:180 and :218 → canonical spelling. Hand-written guides content/docs/ui/react-pages.mdx (6 passages) and content/docs/deployment/validating-metadata.mdx (3 passages, caret annotations re-aligned) → canonical spelling, because this change would otherwise turn them into copy-and-fail examples. One recognizer self-test fixture in scripts/check-react-page-adapter-contract.mjs re-spelled (the assertion is about fence selection, the spelling was incidental).
  • Generated: skills/objectstack-ui/references/react-blocks.md (two rows out, data row now a required binding), packages/spec/api-surface/ui.json, packages/spec/export-origins/ui.json, packages/spec/src/migrations/registry.ts — all via check:generated --fix, only the proved-stale set; check:generated reports 15/15 up to date at the final head.
  • ADR-0087 / changeset: .changeset/react-list-view-binding-aliases-retired.md@objectstack/spec minor, @objectstack/lint minor, @objectstack/example-showcase patch; BREAKING banner; FROM → TO table and the one-line fix; disposition registered ui-react-list-view-binding-aliases-retired (a semantic entry under protocol major 18: react page source is a JSX string objectstack migrate meta cannot rewrite by key, so the prescription is by hand). D7/D8 do not apply — the diff is in packages/spec/src and the body carries a prescription. spec-changes.json and the upgrade guide are unchanged on purpose: major-18 entries are not projected yet (control: an existing 18 entry, ui-record-blocks-unknown-keys-refused, is absent from both too).

Premise check — the census the ruling ordered, by KEY IDENTITY

objectName is not one key. The discriminator used at every site: only a prop written on the JSX tag ListView in kind:'react' page source is the react-block prop this ruling retires. A metadata-tier properties.objectName on an object-metric / object-grid / object-form node (the host stamps it), a navigation item's objectName, an action target, a flow node config, a hook parameter, and the objectName on ObjectForm / ObjectChart (which keep the shared OBJECT_NAME overlay, pinned by test) are all different keys and are untouched — re-measured at 5b010e033 over WHOLE FILE TEXT rather than line by line, so a hard-wrapped occurrence cannot hide: across this whole diff versus the merge base, the objectName= prop sites attributed to ObjectForm are 16 → 16 and to ObjectChart 36 → 36, and ListView is the only tag that gains the canonical data={{ provider, object }} spelling. That claim was FALSE at 4ee841b4: one ObjectForm in the parse gate's parseable array had been re-spelled to data along with the ListView beside it in the same fragment — a prop ObjectForm does not carry, since its REACT_BLOCKS entry declares the shared OBJECT_NAME overlay and no data in either interactions or dataProps. That array asserts not.toContain(REACT_PAGE_SOURCE_UNPARSEABLE) and nothing else, so both spellings parse and no gate in the repo could see it. 5b010e033 restores that half to objectName="a" and keeps the ListView half canonical, which is what the entry was: a genuine mixed-spelling fixture.

population (at origin/main 5e53d73) raw hits in scope out of scope
skills/** (11 files) 39 2 hand-written (pages.md:180, :218) + the generated rows 37: nav items ×7 (navigation.md), action targets ×4, flow configs ×7, hook param, i18n record, ObjectChart in dashboards.md and an eval, nav-item row in SKILL.md, object-metric / ObjectForm in pages.md
examples/app-showcase pages (8 files) 29 4 ListView sites in 3 files (5 prop occurrences) 24: metadata-tier properties.objectName, ObjectForm ×5, ObjectChart, comments
examples/app-crm 12 0 — no ListView anywhere; the ruling's "CRM" is the crm-workbench showcase page 12: nav items ×5, action target, flow configs
content/docs hand-written 9 9 (2 files)
packages/spec / scripts 2 react-blocks.ts:167; one recognizer fixture
objectui 61e649ab 6 5 live author sites (guide ×4, sdui-workbench-preview.tsx:111) — a FORK, reported as objectui#8510 plugin-list/README.md:200 is the component-schema type
hotcrm d47e37ac 0 ListView control: objectName in 114 files, so the grep is live
kpi 6029352 0 ListView control: 3 files

Two PM mechanism assumptions measured the other way: (2) skills/objectstack-ui/rules/pages.md has FIVE objectName hits, not zero, and the card's :180 / :218 citations are exactly the two in-scope ones; (4) the "showcase ×3, CRM" of the ruling is three showcase files carrying four sites — examples/app-crm has none. The pinned objectui imports nothing from the react-blocks overlay (git grep REACT_BLOCKS at a472b071: a docs page and a changeset only), so the Console Pin Gate is not threatened by the removal.

Beyond the ruling's literal words — two design points to review

  1. data is restated on the ListView overlay as its REQUIRED binding, ledgered in REACT_OVERLAY_SHADOWS (the mechanism navigation already uses). Reason: ListViewShapeSchema.data is optional because a metadata page's host stamps the object; on a react page nothing does, and objectName was the contract's only required binding — removing it alone would let a bare ListView validate green and render empty, the exact class this card closes. The generated contract now marks data ✓ and the lint's missing-required check fires with the contract's own description as the hint. One-line revert if refused: drop the overlay entry and the ledger row.
  2. REACT_RETIRED_OVERLAY_PROPS is a new export of @objectstack/spec/ui, and the lint raises react-prop-retired (error) from it. Reason: the lint's unknown-prop path is a typo guess within edit distance 2, so a leftover objectName would otherwise be SILENT — and objectui's renderer still reads that key directly, so the page would keep shipping on it. The tombstone is the react-tier twin of retiredKey(); the finding carries the FROM → TO. When the retired spelling of a binding is on the element, the missing-required finding defers to it (one finding, one fix).

Serial constraint — declared, not silently taken

Files inside the predecessor seat's logical reservation for the #15442 / #15449 re-dispatch (both pm:blocked on #16626, no dev, no PR): examples/app-showcase/src/ui/pages/crm-workbench.page.ts, renewals-pipeline.page.ts, task-desk.page.ts (the four ListView sites only) and packages/lint/src/validate-react-page-props.test.ts (37 ListView fixtures re-spelled; the #11284 → #14791 describe block rewritten). The re-dispatch should rebase over these knowingly. Fence honoured: packages/spec/src/data/analytics.zod.ts and runtime/src/domains/analytics.ts untouched. Files outside the claim's declared surface, each required by a clause of the ruling itself: packages/lint/** (the lint clause), content/docs/** (copy-and-fail examples this change would create), packages/spec/src/migrations/** + .changeset/** (the ADR-0087 clause), the generated artifacts, one script fixture.

Verification (final head 50ec9f28a = my work merged with origin/main cf33adbd8; every exit captured before any pipe)

  • Builds through scripts/pm/os-verify-lock.sh (OS_VERIFY_LOCK_SLOT=issue-14791): spec (VERDICT command-exit 0, 252s), spec again after the registry regeneration (0, 183s, waited 404s), formula+lint (0, 223s), the client-react closure (0, 425s, waited 185s), spec+formula+lint on the merged tree (0, 17s cache).
  • Tests (locked): packages/spec react-blocks.test.ts + migrations.test.ts → Test Files 2 passed, Tests 129 passed; packages/lint validate-react-page-props.test.ts → 1 passed, 162 passed. Typecheck spec and lint → exit 0 including both check:test-typecheck ledgers.
  • Showcase dogfood: validateReactPageProps from the built lint over the three showcase page sources → 0 findings; control on the same call, retired spelling → 2× react-prop-retired. pnpm --filter @objectstack/example-showcase typecheck / validate / test are NOT MEASURED here: prerequisite failures (unbuilt connector/plugin closure; the CLI's validate command absent without its build; a SQLite env error in hook-body-persisted-writes); the react-page-adapter-query-contract test is not among the 12 resolve-entry failures. CI runs the closure.
  • Gates: derived with dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 50ec9f28a (127 families, no stale-tree warning); --ran reconcile: 126 run, 1 UNRUN. Exit 0 on 124; NOT MEASURED: check:dual-build-cjs-loads and check:type-check-debt (exit 3, PREREQUISITE NOT MET — every package's dist); check:react-declaration-parity (EXTERNAL_INPUT_REQUIRED — predicted delta: ListView registryOnly gains objectName / viewType on the next sdui:manifest, owned with objectui#8510); check:pm-dispatch-gates ran detached per its own header — verdict line 1552 cases pass, exit code not captured by design. After the merge origin/main moved 4 more commits (941232040: lint.yml, package.json, scripts/ci/select-gate-families.* — CI plumbing) which the derivation would add; those belong to the PR's CI, not re-chased here.
  • skills/** line budget (PM-set net +0, cap +10): net −2 — tree 13,581 → 13,579; pages.md 448 → 448; generated react-blocks.md 117 → 115. Token ratchet (check-skills-token-ratchet, ceil(bytes/4)): the canonical spelling put pages.md at 5506 against a 5501 ceiling; paid with 28 bytes of genuine deletion in the same passage (the clause "however you bind them", the placeholder lookup fieldlookup) → 5499/5501. No ceiling was raised.
  • Reverse verification (fix committed first): the lint's retired-prop branch ablated (const ret = block.retired.get(u) → undefined, marker line), mutation proven on disk (anchor 1 → 0, marker 0 → 1, blob cdbdb058 ≠ HEAD blob c2955e5d), no build leg (the suite imports the rule by relative path). Predicted RED on the retired-spelling pins only; observed: 4 failed / 158 passed, exactly the four react-prop-retired pins. Restore by git checkout HEAD -- ABSOLUTE_PATH under an EXIT/INT/TERM trap, proven by state: git diff HEAD empty, porcelain empty, blob back to c2955e5d.
  • Declared narrowing: the repo-wide pnpm lint was not run. eslint --no-inline-config --format json over the changed source files at 50ec9f28a: 19 files (count read from the JSON), 0 errors, 0 warnings; the narrowing cannot move any untouched file's verdict because eslint.config.mjs:327-328 states the config never enables type-aware linting for any file.

验收备注

维护者速读(草稿)

改了什么。 ListView 这个 react 页面组件,原来有两种绑定对象的写法:老写法 objectName="account" 和新写法 data={{ provider: 'object', object: 'account' }}(视图类型同理:viewTypetype)。本 PR 把老写法从契约里删掉,只留新写法;校验器遇到老写法会报错并直接给出改法;我们自己仓里的 4 个示例页、已发布 skill 的 2 处、文档里的 9 处全部改成新写法。

为什么改。 你 9 月 7 日的裁决(选 B,不设过渡窗口)。前端那一半(把新写法折叠到组件真正读的键上)已随 console pin a472b071 落地,两种写法今天都能渲染;老写法的仓外用户是 0,新写法与元数据层、与其它六个组件一致。

风险与代价(含回滚)。 破坏性契约变更,changeset 标 BREAKING,ADR-0087 登记为手工迁移(react 页面源码是字符串,迁移工具改不了)。仓外若有人写了老写法,校验期会被响亮拒绝(不是静默失败)。objectui 自己的文档和一个预览页还写着老写法,已立 objectui#8510 跟进。回滚:revert 本 PR 即可恢复两种写法,前端不依赖本次改动。两处超出裁决字面的设计点(data 设为必填绑定;新增退休台账导出)各可一行撤回,见上文。

席位意见。 (留空,席位定稿成评论)

你要做的。 这是受管面(skills/**)PR:请你人工确认并合并;确认前先看「Beyond the ruling's literal words」那两点是否同意。

⚠ THIS PUSH MOVES A RATCHET — check:react-declaration-parity baseline accept (head 4ee841b4e)

CI's Type Check · consumer gates reported, on head 50ec9f28a, exactly the delta this PR had declared UNRUN and predicted: ListView: new registry-only input(s) not in baseline: objectName, viewType. objectui's plugin-list registration still declares objectName (required) and viewType as designer inputs; with the overlay rows retired by the ruling, the spec no longer declares them, so the parity ratchet sees two new registry-only inputs.

What moved. packages/spec/react-declaration-parity.baseline.json — the ListView row's registryOnly goes from [] to [objectName, viewType], written by the gate's own accept path (MANIFEST=sdui.manifest.json … --baseline react-declaration-parity.baseline.json --update), never by hand-editing the row; then --strict re-run: ✓ no new DECLARATION divergence vs accepted baseline. The gate will no longer report this pair. No other block row changed.

Why this path. The gate's two other exits — declare the props in the spec schema, or put them back on the block overlay — would UNDO the maintainer ruling (#14791, 2026-09-07, option B, no deprecation window). Waiting for objectui#8510 is circular: it is Blocked-by this card. So the accept is the mechanical consequence of the ruling, recorded through the gate's sanctioned path — not a gate weakening — and it reaches the maintainer before it lands because this PR is GOVERNED and ends in a human merge.

Discharge condition — recorded in the baseline itself. The file's hand-maintained _acceptedReasons block (which --update drops and its own _note says to re-add, "an entry with no reason here is an accept nobody ruled") now carries ListView.objectName and ListView.viewType, each marked TEMPORARY with the expiry: accepted only until objectui#8510 removes the two inputs from objectui's list-view registration (the metadata host stamps objectName; normalizeListViewSchema supplies it from data at pin a472b071). When that lands and the pin moves past it, delete both reason entries and the two inputs from the ListView.registryOnly row. The PM authorised this accept explicitly as the ruling's consequence; the diff of this push is the baseline file and this disclosure, nothing else.


Generated by Claude Code

…ases — data / type are the only spellings

Maintainer ruling on #14791 (2026-09-07, director seat summon #17, decision
batch #1, option B): the two overlay props #11284 had deprecated are removed
from the ListView block with no deprecation window, now that the consumer fold
ships in the pinned console (objectui normalizeListViewSchema at a472b071).

- react-blocks.ts: objectName / viewType gone; `data` restated as the required
  binding (ledgered in REACT_OVERLAY_SHADOWS); REACT_RETIRED_OVERLAY_PROPS is
  the tombstone ledger; the record:related_list alternative writes the
  canonical spelling.
- lint: boundObjectName reads data.provider === 'object' for ListView (the
  canonical read step 1 deferred); a retired spelling is a react-prop-retired
  error carrying the prescription; the step-1 unfolded-deprecation scaffolding
  is deleted.
- showcase pages, the published objectstack-ui skill, the react-pages and
  validating-metadata guides and one recognizer fixture write the canonical
  spelling.
- ADR-0087: semantic entry ui-react-list-view-binding-aliases-retired under
  protocol major 18; changeset minor with the BREAKING banner.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
…t-origins and the migration registry; pay the pages.md token ratchet; keep the tracker id out of the lint message

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

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/lint, @objectstack/spec, touching 18 documentable anchor(s). ⚠️ 4 changed file(s) yielded no anchor (packages/lint/src/index.ts, packages/spec/api-surface/ui.json, packages/spec/export-origins/ui.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/concepts/architecture.mdx (via ListView (symbol, a field of const object REACT_OVERLAY_SHADOWS))
  • content/docs/deployment/validating-metadata.mdx (via ListView (symbol, a field of const object REACT_OVERLAY_SHADOWS))
  • content/docs/getting-started/quick-reference.mdx (via ListView (symbol, a field of const object REACT_OVERLAY_SHADOWS))
  • content/docs/protocol/objectui/concept.mdx (via ListView (symbol, a field of const object REACT_OVERLAY_SHADOWS), viewType (literal, a string literal in UNFOLDED_DEPRECATIONS; a string literal in interactions))
  • content/docs/protocol/objectui/index.mdx (via ListView (symbol, a field of const object REACT_OVERLAY_SHADOWS))
  • content/docs/ui/react-pages.mdx (via ListView (symbol, a field of const object REACT_OVERLAY_SHADOWS), REACT_BLOCKS (symbol, a top-level const object))

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

  • content/docs/releases/implementation-status.mdx (via ListView (symbol, a field of const object REACT_OVERLAY_SHADOWS))
  • content/docs/releases/v15.mdx (via ListView (symbol, a field of const object REACT_OVERLAY_SHADOWS))
  • content/docs/releases/v16.mdx (via ListView (symbol, a field of const object REACT_OVERLAY_SHADOWS))
  • content/docs/releases/v17.mdx (via ListView (symbol, a field of const object REACT_OVERLAY_SHADOWS))
  • content/docs/releases/v9.mdx (via ListView (symbol, a field of const object REACT_OVERLAY_SHADOWS))

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
  • 4 changed file(s) yielded no anchor (packages/lint/src/index.ts, packages/spec/api-surface/ui.json, packages/spec/export-origins/ui.json, …) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: objectName (literal, 34 pages)
  • 7 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 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.

Coarse fallback — 130 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 68f8f77205a32f1a0355b9a3cccf006538cfbe9fpackageMentionDocs.

Which tree this was computed on

This run read content/docs from b2299e746eebb743a502321d15746fcced229b6c — the merge of head 5b010e033266bf46d8bf4a083680963eb413fbb5 into base 68f8f77205a32f1a0355b9a3cccf006538cfbe9f, 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 b2299e746eebb743a502321d15746fcced229b6c && git checkout b2299e746eebb743a502321d15746fcced229b6c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 68f8f77205a32f1a0355b9a3cccf006538cfbe9f 5b010e033266bf46d8bf4a083680963eb413fbb5 && git checkout -B drift-repro 68f8f77205a32f1a0355b9a3cccf006538cfbe9f && git merge --no-ff 5b010e033266bf46d8bf4a083680963eb413fbb5

node scripts/docs-audit/affected-docs.mjs --json 68f8f77205a32f1a0355b9a3cccf006538cfbe9f

⚠️ 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 68f8f77205a32f1a0355b9a3cccf006538cfbe9f → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…inputs in the declaration-parity baseline, with their discharge condition

The gate's own --update path (MANIFEST=sdui.manifest.json check:react-declaration-parity
--baseline react-declaration-parity.baseline.json --update), then the hand-maintained
_acceptedReasons block re-added as the baseline's _note prescribes, with two new entries
that state the expiry: accepted only until objectui#8510 removes the two designer inputs
from objectui's list-view registration. This moves a ratchet as the mechanical consequence
of the #14791 ruling (option B, no deprecation window); declaring the props back in spec
or on the overlay would undo that ruling and is not an exit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x

Copy link
Copy Markdown
Collaborator

Contract review — REWORK, one blocking line. Head 4ee841b4, reviewed at tier, PM-verified.

domain:spec execution seat, session session_016N6xmWt5hYm94ffVEwGH8x, 2026-09-08T05:40Z.

Tier fuse. The review ran at CONTRACT_REVIEW_TIER: 124 harness-stamped "model":"claude-fable-5-1" occurrences in the subagent transcript, no other value. ⛔ Not accepted on self-description.

PASS binds to a head. The verdict is against 4ee841b4e4fdd3c4ac879d9cf97788c8e2857cdb, confirmed by git rev-parse against a namespaced fetch ref (⛔ not FETCH_HEAD, which a concurrent fetch had already re-pointed once during this review). The review was dispatched on 50ec9f28; the delta 50ec9f28 → 4ee841b4 is exactly one file, packages/spec/react-declaration-parity.baseline.json (+7/−2), and it moves no verdict row.

⛔ Blocking (1)

packages/lint/src/validate-react-page-props.test.ts:1572 re-spells a non-ListView binding. At the merge-base cf33adbd8 the fixture read:

'function Page(){ return <><ObjectForm objectName="a" /><ListView objectName="a" /></>; }'

Both halves were rewritten; only the ListView half is in ruling B's scope. ObjectForm binds by its own props — the contract says so at head, packages/spec/src/ui/react-blocks.ts:202 and :204 (<ObjectForm objectName="…" mode="view" recordId={…} fields={[…]} /> — it binds by its own props.) — so the fixture now spells a prop ObjectForm does not carry. The test still passes because that array asserts parseability only, which is precisely why it slipped.

Correction: restore <ObjectForm objectName="a" />; keep the <ListView data={{ provider: "object", object: "a" }} /> half. Then amend the PR body's census sentence (ObjectForm / ObjectChart … untouched), which this line falsifies.

PM verification of the finding (⛔ not taken on the reviewer's word)

Independent census over cf33adbd8 → 4ee841b4: 90 removed lines mentioning objectName, every one read and classified by owning JSX tag. Exactly one non-ListView re-spelling — the line above. Specifically resolved rather than assumed:

  • the two removed objectName="showcase_account" lines are paired /+ rewrites on <ListView> tags — in scope;
  • <ObjectForm objectName="showcase_account" …> survives untouched at content/docs/ui/react-pages.mdx:452,466 and examples/app-showcase/src/ui/pages/renewals-pipeline.page.ts:158,172;
  • react-pages.mdx:262, a bare objectName on its own line, belongs to an <ObjectForm> (read lines 255–270) — correctly left alone.

Repo-wide at head, <ListView …objectName> survives only where it must: changeset FROM column (2), lint pins (3), lint doc-comment (1), migration entry (1), registry (1). Zero in content/docs, examples, skills, scripts. Control terms non-zero throughout — a zero here is a measurement, not an empty grep.

Rows that hold

Ruling B executed exactly (rows removed with no deprecated block, both pinned; boundObjectName reads the canonical source; ADR-0087 registration exits 0). skills/** net −2 lines and the token ratchet unmovedpages.md computes 5499 against the unchanged ceiling 5501, so no ratchet was raised to pay for this. content/docs/releases/** is absent from the 19-file diff — ⛔ untouched, as required. Migration entry and generated artefacts consistent by reading.

⚠️ Disclosure for the human merger — a ratchet baseline moved

packages/spec/react-declaration-parity.baseline.json: ListView.registryOnly [][objectName, viewType], written by the gate's own --update, with both _acceptedReasons marked TEMPORARY and discharged by objectui#8510. The gate's other two exits would undo ruling B, and waiting on objectui#8510 is circular (it is Blocked-by this card), so the movement is the ruling's mechanical consequence rather than a weakening chosen here. It is still a moved ratchet and must be consciously accepted, not merged past. The same applies to the one new permanent export, REACT_RETIRED_OVERLAY_PROPS (+1 in api-surface/ui.json).

Non-blocking

Status

⛔ Not approved, not enqueued, not flipped ready, no auto-merge. This PR touches the governed surface (skills/**), so it stays draft and terminates in a human merge regardless of CI colour. CI at 05:30Z: 34 checks, 0 failures, 13 still running — not an enqueue reading, and it will be re-read at the ACCEPT moment because a check reading expires.

The one-line patch is queued behind this lane's dev-concurrency cap of 3 (maintainer's standing instruction); it dispatches the moment a slot frees.


维护者速读

结论:退回改一行,其余全过。

这张 PR 要做的事是:<ListView> 上两个旧写法(objectNameviewType)正式退休,改成统一的 data={{...}}。这件事做对了。

问题出在一个测试样例上。 有一行样例里同时写着 <ObjectForm><ListView> 两个组件,开发把两个都改了——但这次退休只涉及 ListViewObjectForm 本来就该用 objectName 绑定(契约文件里写得很清楚),现在这行样例教了一个 ObjectForm 根本没有的写法。测试仍然是绿的,因为那个数组只检查"能不能解析",不检查写法对不对——所以它溜过去了。改回一个词即可。

我自己重新数了一遍,没有第二处。 改动里一共 90 行提到 objectName,逐行看了归属的组件;文档和示例里的 ObjectForm 都完好没动。

两件需要你过目的事(不是错,是要你签字的扩张):

  1. 新增一个永久导出 REACT_RETIRED_OVERLAY_PROPS,公开面 +1。
  2. 一条门禁基线被上调了(react-declaration-parity)——这是执行你的裁决的必然结果:另外两条出路都会推翻裁决本身,而等 objectui#8510 是循环等待(它反过来被这张卡阻塞)。已标 TEMPORARY 并写明解除条件。

⛔ 这张 PR 动了受管面(skills/**),所以无论 CI 多绿都不入队、不自动合并,最后必须由你手工合并。 我不批准、不合并。

https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x


Generated by Claude Code

…ing fixture

The `parseable` array in `validate-react-page-props.test.ts` is the
FALSE-POSITIVE CONTROL for the syntax gate: every entry asserts only
`not.toContain(REACT_PAGE_SOURCE_UNPARSEABLE)`, so it grades the PARSE and
nothing else. One entry carries an `ObjectForm` and a `ListView` in a single
fragment. Retiring the `ListView` binding aliases re-spelled BOTH halves to
`data={{ provider: "object", object: "a" }}`, but only the `ListView` half is
in that retirement's scope: `ObjectForm` binds by its own props and carries the
shared `OBJECT_NAME` overlay (`packages/spec/src/ui/react-blocks.ts`, the
`REACT_BLOCKS` entry for `ObjectForm`), which is `objectName`, required. It has
no `data` prop at all — neither in its `interactions` nor in its `dataProps`.

The fixture therefore spelled a prop the contract does not carry. Because the
array grades parseability only, both spellings parse and CI stayed green: no
gate in the repo could see it.

Restore the `ObjectForm` half to `objectName="a"` and keep the `ListView` half
canonical, which is what the entry was — a genuine MIXED-SPELLING fragment, and
a stronger parse fixture than either uniform spelling.

Measured over whole file text (never line-oriented, so a hard-wrapped
occurrence cannot hide), across the full diff versus the merge base: the
`objectName=` prop sites attributed to `ObjectForm` are 16 -> 16 and to
`ObjectChart` 36 -> 36 — both unchanged — and `ListView` is the only tag that
gains the canonical `data` spelling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x

Copy link
Copy Markdown
Collaborator

ACCEPT — at-tier PASS on head 5b010e03. ⛔ Stays draft; a human merge is the review record.

domain:spec execution seat, session session_016N6xmWt5hYm94ffVEwGH8x, 2026-09-08T06:33Z (clock read).

The single blocking row from the 4ee841b4 verdict is discharged, and the re-review was done at tier on the head that will land — a verdict binds to a head, and this seat is not at tier, so it did not promote its own FAIL to a PASS.

Tier fuse: 129 harness-stamped "model":"claude-fable-5-1" in the delta-review transcript, no other value. ⛔ Not accepted on self-description.

The delta, measured independently by this seat before the review ran

git diff --stat 4ee841b4 5b010e03 = 1 file, +1/−1, packages/lint/src/validate-react-page-props.test.ts. The fixture reads:

'function Page(){ return <><ObjectForm objectName="a" /><ListView data={{ provider: "object", object: "a" }} /></>; }'

packages/spec/react-declaration-parity.baseline.jsonunmoved since 4ee841b4 (empty numstat). content/docs/releases/**0 files in the PR's own diff against merge-base cf33adbd8.

The reviewer's independent control is the one that settles item 3: added lines containing <ObjectForm data= = 0, against <ListView data= = 29. The positive control is what makes that zero a measurement rather than an empty grep — and the dev's own whole-text census agrees (ObjectForm 16→16, ObjectChart 36→36, ListView the only tag gaining the canonical spelling).

Row-by-row, the delta moves no PASS row: it is one string inside the parseable array of an unrelated #10653 describe, and that loop asserts only not.toContain(REACT_PAGE_SOURCE_UNPARSEABLE). Item 8 improves — the fixture is a contract-valid ObjectForm again.

Why no gate could have caught this, established by ablation rather than argued: the dev put the pre-fix spelling back on disk (blob hash 1f6fab8e6…, byte-identical to the pre-fix blob git itself named in the diff header) and the suite still passed 162/162, exit 0. Both spellings parse, and parseability is all that array grades. That is the honest answer to "why was CI green", and it is a measurement, not a theory.

Two PM rulings, recorded so they are not re-litigated

  • The body's eslint line still cites head 50ec9f28a. Ruled leave it. The line is accurate about which head it was measured on; a stale-but-honest provenance stamp is not a false one, and re-stamping would have breached the "fix only that sentence" fence for a cosmetic gain. The reading was re-taken at the new head — npx eslint --no-inline-config --format json over the changed file: exit 0, 1 file, 0 errors, 0 warnings — and is on the record here.
  • The census sentence became three sentences. Ruled keep. The expansion stays inside the fenced sentence and adds the measured counts plus a disclosure that the claim was false at 4ee841b4 and why no gate saw it. A GOVERNED PR ending in a human merge should carry its correction on its face, not only in a comment thread. ⭐ The dev flagged this rather than assuming it — the right instinct, and the reason it is a ruling instead of a discovery.

Governed-surface status — the repo's own classifier, not this seat's reading

node scripts/pm/check-governed-merges.mjs --test <19 paths>
⛔  GOVERNED — a human merge is the review record for this PR (#9495 regime).
    skills/** ×1 — skills/objectstack-ui/rules/pages.md

One hit governs the whole PR; proportion is not a question. It also correctly exempts skills/objectstack-ui/references/react-blocks.md as a pure regeneration (#11705) — byte-equal to gen:react-blocks recomputed on this tree, so it does not govern by itself.

⛔ Not approved by this seat, ⛔ not readied, ⛔ not enqueued, ⛔ no auto-merge, whatever CI says.

维护者速读

结论:内容已过审(契约复审档),只剩你手工合并这一步。

上一轮复审挑出一处问题:一个测试样例里 <ObjectForm><ListView> 写在同一段,开发把两个都改了,但这次退休只涉及 ListView。已改回,改动就一行。我自己量过:确实只有这一行,门禁基线没再动,content/docs/releases/** 一个字没碰。

为什么 CI 当时是绿的 —— 这点是量出来的,不是猜的:开发把错误写法放回磁盘重跑,162 个用例照样全过。那个数组只检查"能不能解析",两种写法都能解析,所以仓里没有任何门禁看得见它。

⚠️ 有两件事需要你在合并时有意识地签字,不是问题,是扩张:

  1. 新增一个永久导出 REACT_RETIRED_OVERLAY_PROPS,公开面 +1。
  2. 一条门禁基线被上调(react-declaration-parity),已标 TEMPORARY 并写明解除条件 objectui#8510。这是执行你那条裁决的必然结果 —— 另外两条出路都会推翻裁决本身。

⛔ 这张 PR 动了受管面(skills/objectstack-ui/rules/pages.md),仓里的门禁脚本自己判定「a human merge is the review record」。我不批准、不翻 ready、不入队、不挂自动合并。

https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x


Generated by Claude Code

Copy link
Copy Markdown
Contributor

Contract review (CONTRACT_REVIEW_TIER, isolated seat) — PR #16777 @ 5b010e03

Verdict: PASS WITH FINDINGS — nothing blocking; the PR is GOVERNED (skills/**) and terminates in a human merge. ⛔ This seat did not approve, request changes, ready, label, enqueue, or auto-merge.

Ruling implemented: YES — exactly, plus two additions beyond the ruling's literal words (F2, F3) that are disclosed on the PR's face and each revertible in one line.

Reviewed independently: PR head 5b010e033266bf46d8bf4a083680963eb413fbb5 fetched into a namespaced throwaway ref (not FETCH_HEAD), diffed against merge-base cf33adbd8; objectui read at origin/main f76f4362 and at the pinned a472b071. Every count below was re-measured, not taken from the PR body or the two prior review comments.

Ruling (card #14791, comment 5572008848, quoted verbatim)

Ruling recorded — B: step 3 retires objectName / viewType on the ListView block now; no deprecation window (director seat, summon #17, decision batch #1, 2026-09-07)

Provenance (who / verbatim / where): maintainer, live PM chat with the director seat (session_01XesLUWmuhjuRwmU618AZ1M), 2026-09-07T14:2xZ, batch #1 presented as 1A · 2A · 3B · 4A · 5A with this card as item 3 recommending B (the 5571291808 four-facet block); reply, verbatim: 「同意」. This settles the wording fork the spec seat raised at 13:22Z in the direction the standing rulings already pointed: batch #18 B (5522890150, 「同意」, 不渐进) and the 2026-08-27 ruling 「项目在创业阶段,用户也很少,短期不考虑渐进」.

Ruled. In packages/spec/src/ui/react-blocks.ts the ListView block's objectName and viewType props are removed, not un-deprecated: the only spelling is data={{ provider: 'object', object }} / type. The lint's canonical-first branch stays. The four in-repo author sites (showcase ×3, CRM) and the two published skills/** passages move to the canonical spelling in the same PR. Option A (retract the deprecation, keep both spellings) is refused.

Execution, domain:spec lane: Clause-②: yes (a published contract's accept set narrows) ⇒ dispatch at CONTRACT_REVIEW_TIER, needs:contract-review on the PR; the diff touches skills/** ⇒ governed: draft only, the terminal four-step, a human merge. Premise the dev falsifies first: a cross-repo census of the retired spelling in the consuming repos this session can see (objectui, and any kpi / hotcrm reference reachable) — a live external author site is a fork to report, not a reason to keep the old spelling silently. Changeset minor for @objectstack/spec with the BREAKING banner and an ADR-0087 disposition.

Verification

  1. Ruling clause by clause. (a) react-blocks.ts: both overlay rows and their deprecated blocks are deleted; react-blocks.test.ts:183 pins namesobjectName/viewType and interactions.filter(deprecated) = [] — a deprecated row re-added (option A) reddens it. (b) Lint canonical-first branch stays: boundObjectName(tag, values) reads data.provider === 'object'data.object for ListView; the step-1 scaffolding (UNFOLDED_DEPRECATIONS, objectProviderBindsNothing, "empty list" variants) is gone. (c) Author sites: crm-workbench.page.ts:88, renewals-pipeline.page.ts:136-137 (the only viewType) and :169, task-desk.page.ts:44 → canonical. examples/app-crm has no ListView (the ruling's "CRM" is the showcase crm-workbench page) — confirmed. (d) skills/objectstack-ui/rules/pages.md:180, :218 → canonical. (e) react-blocks.ts record:related_list alternative rewritten and pinned (not.toContain('objectName')). (f) Option A refused: yes. Precondition of step 3 verified at the pin, not on objectui main: git show a472b071:packages/core/src/utils/normalize-list-view.ts — the fold is at :315 (dataConfig?.provider === 'object' && typeof dataConfig.object === 'string') and the view kind is read at :322 (readListViewKind(s.specType) ?? readListViewKind(s.type)); :263 is the comment-only hit the card warned about. .objectui-sha at head = a472b07167a3.

  2. spec-property-retirement skill checklist (.claude/skills/spec-property-retirement/SKILL.md @ origin/main). The retired keys are react-tier OVERLAY props, not zod-schema properties (ListViewShapeSchema never declared objectName/viewType), so the ledger/forms/authorable-surface/json-schema.manifest/RETIRED_KEYS_BY_MAJOR rows have no target and the retiredKey() route does not apply — the PR substitutes the react-tier twin (REACT_RETIRED_OVERLAY_PROPS + react-prop-retired), which satisfies the skill's actual requirement that a retired spelling be REFUSED with a prescription, never silently stripped. Liveness ledger: no list-view/react row exists in packages/spec/liveness/*.json to move; "Spec property liveness" CI job green. ADR-0087: SemanticMigration entry entries/semantic/18.ui-react-list-view-binding-aliases-retired.ts + the concatenated registry.ts:9918 row under step 18, reason/acceptanceCriteria non-empty — the correct D3 semantic[] shape for "no source can be rewritten by key". Generated baselines: api-surface/ui.json +1, export-origins/ui.json +1, skills/objectstack-ui/references/react-blocks.md (two rows out, data ✓ required, schema data row deduped via the REACT_OVERLAY_SHADOWS ledger), registry.tscheck:react-blocks (lint.yml:4875) and check:generated --reconcile-only both inside the green "Lint & Repo Gates" job. spec-changes.json/upgrade guide unchanged: verified the control — the pre-existing major-18 entry ui-record-blocks-unknown-keys-refused is absent from both, so major 18 is not projected yet. Docs: content/docs/ui/react-pages.mdx (6) and deployment/validating-metadata.mdx (3, carets re-aligned) — ⛔ content/docs/releases/** absent from the diff.

  3. Files in the diff vs merge-base (19): .changeset/react-list-view-binding-aliases-retired.md · content/docs/deployment/validating-metadata.mdx · content/docs/ui/react-pages.mdx · examples/app-showcase/src/ui/pages/{crm-workbench,renewals-pipeline,task-desk}.page.ts · packages/lint/src/index.ts · packages/lint/src/validate-react-page-props.ts · packages/lint/src/validate-react-page-props.test.ts · packages/spec/api-surface/ui.json · packages/spec/export-origins/ui.json · packages/spec/react-declaration-parity.baseline.json · packages/spec/src/migrations/entries/semantic/18.ui-react-list-view-binding-aliases-retired.ts · packages/spec/src/migrations/registry.ts · packages/spec/src/ui/react-blocks.ts · packages/spec/src/ui/react-blocks.test.ts · scripts/check-react-page-adapter-contract.mjs · skills/objectstack-ui/references/react-blocks.md · skills/objectstack-ui/rules/pages.md.
    Governed paths: YESskills/** ×2: skills/objectstack-ui/rules/pages.md (hand-authored, governs on its own) and skills/objectstack-ui/references/react-blocks.md (generator-owned, [finding] skills/*/references/_index.md is generator-owned (gen:skill-refs) yet sits inside the skills/** governed-merge fence — every spec PR touching an indexed headline is forced to carry a governed-surface file #11705 carve-out). docs/adr/**, .claude/**, AGENTS.md, CLAUDE.md, content/docs/releases/**: none. ⇒ maintainer-only merge.

  4. Consumer census (surviving authoring sites of the retired spelling AFTER this PR). objectstack @ head, git grep -E "<ListView[^>]*(objectName|viewType)" over packages/** apps/** examples/** content/docs/** skills/** scripts/**: 0 authoring sites. The 7 raw hits are all non-authoring: 4 lint pins that assert REFUSAL (validate-react-page-props.test.ts:103,121,165,179), 1 doc-comment (validate-react-page-props.ts:325), 2 surface: strings in the migration entry/registry. viewType tree-wide: only the baseline, the pins, and unrelated useView(objectName, viewType) client hooks. objectui origin/main f76f4362 over packages/** apps/** examples/**: 4 sitesapps/console/src/sdui-workbench-preview.tsx:111 (live preview page source) and three test fixtures packages/components/src/__tests__/react-page-adapter.test.tsx:47, react-page-scope.test.tsx:113, :130; plus 4 in content/docs/guide/react-pages.md:42,108,134,199. The app-shell <ListView schema={…}> mounts and plugin-list/README.md schema={{ objectName, viewType }} are the component-schema key, not the react-tier prop — out of scope, correctly excluded. None of these breaks at runtime (the renderer still reads objectName; the fold is gap-fill only) — they break under objectstack validate once this lands. hotcrm/kpi: NOT reachable from this seat; the PR body's "0 ListView" there is unverified here.
    Conversion: a react page's source is a JSX string, so no key-walk conversion can rewrite it forward — the semantic registration is the honest disposition. Boot/validate notice names the remedy: yes — react-prop-retired (error) message <ListView> prop "objectName" is retired — the contract's only spelling is "data". with hint Write data={{ provider: 'object', object: '…' }} …; pinned at validate-react-page-props.test.ts:103-117 (hint toContain("data={{ provider: 'object', object: '…' }}")) and :119-128 (type="kanban"); the missing-required finding defers to it so the author gets one finding, one fix.

  5. Changeset. @objectstack/spec minor, @objectstack/lint minor, @objectstack/example-showcase patch. Level per batch [WIP] Add query enhancements and advanced validation features #35 "WHICH LEVEL" + check-changeset-no-major.mjs: accept-set narrowing ships minor under the launch-window convention (the guard refuses major); lint grew a public export (REACT_PROP_RETIRED) and a new error ⇒ minor is the floor, met; example-showcase is private but has its own CHANGELOG (precedent) and only moved spellings ⇒ patch; the [finding] No gate answers whether a changeset's LEVEL fits the surface — Check Changeset is green on patch and on minor for the same diff #16055 level axis (clause-② package graded patch) is not triggered. Title carries !, body carries **BREAKING**, FROM → TO table (3 rows) + one-line fix present. ADR-0087 disposition <!-- adr-0087: registered ui-react-list-view-binding-aliases-retired --> — the exact registered <id> shape check-adr-0087-registration.mjs:1495 parses; the id resolves in LEDGER_SOURCES[0] (registry.ts:9918) and is ADDED BY THIS DIFF, so R3/R4 cannot fire; a retirement registers a conversion rather than not-required — done. "Check Changeset" job green.

  6. Tests. Pins that redden on revert: react-blocks.test.ts:183-200 (aliases absent, no deprecated rows, data required + ledgered), :202-223 (tombstone must not coexist with a live prop of the same name; replacedBy must name a real prop; ruled pair exact); lint :102-117 and :119-128 (retired spelling REFUSED as error, not merely absent — the PR's own ablation of the block.retired.get(u) branch reddened exactly these 4 pins, 4/162), :130-140 (bare <ListView> refused, hint = contract description), :157-175 (both spellings ⇒ refusal + fields resolve on data.object), :177-182 (behind a spread). ObjectForm/ObjectChart objectName stays live: pinned in both suites; the 4ee841b4 fixture regression (ObjectForm data=) is restored at 5b010e03 — my control: added lines containing <ObjectForm data= = 0, <ListView data= = 29. .skip/.only/.todo added in the diff: 0.

  7. CI @ 5b010e03: 37 check runs — 35 success, 2 skipped (Console Pin Gate, Packed-tarball smoke opt-in), 0 failure, 0 pending; "Type Check · consumer gates" green after the baseline accept. mergeable_state: unknown (GitHub has not computed it on two reads); local git merge-tree --write-tree origin/main <head> exits 0 — no conflicts. Distance: 17 commits behind origin/main (base at PR creation 941232040).

Findings

  • F1 (non-blocking, cross-repo census gap). objectui#8510 lists 5 surviving sites; my grep at f76f4362 finds 3 more under the brief's scope: packages/components/src/__tests__/react-page-adapter.test.tsx:47, packages/components/src/__tests__/react-page-scope.test.tsx:113 and :130 — react-page source fixtures writing <ListView objectName="showcase_project">. Runtime-inert (renderer reads objectName), no objectstack gate sees them. Expectation: the objectui#8510 census is amended to 8 sites (a comment on that card); nothing in this PR changes.
  • F2 (disclosure for the merger — a ratchet moved). packages/spec/react-declaration-parity.baseline.json: ListView.registryOnly [][objectName, viewType], via the gate's --update, with _acceptedReasons marked TEMPORARY and discharged by objectui#8510. I agree the two alternative exits would undo ruling B and that waiting on [rest] record the #8039 ruling in DATA_RECORD_READ_PARAMS's docblock #8510 is circular; the accept is the ruling's mechanical consequence. Expectation: the maintainer accepts it consciously at merge; when objectui#8510 lands and the pin moves, both reason entries and the two inputs are deleted (the baseline says so itself).
  • F3 (disclosure — beyond the ruling's letter, endorsed). (i) data restated as the ListView overlay's REQUIRED binding (ledgered in REACT_OVERLAY_SHADOWS): without it a bare <ListView> would validate green and render empty — the exact class [finding] The #11284 convergence shipped only its producer half: the react-blocks contract deprecates objectName / viewType on ListView in favour of data={{ provider: 'object', object }} / type, and the lint blesses that spelling — but objectui's ListView reads neither, so the canonical spelling validates green and renders an empty list #14791 closes; the 'value' and non-static data cases still satisfy (pinned). (ii) REACT_RETIRED_OVERLAY_PROPS is a new permanent export (+1 api-surface): it is what turns "removed" into "refused with a prescription" — ADR-0049 enforce-or-remove needs it, since the lint's unknown-prop path is a typo guess and would otherwise stay silent. Expectation: the maintainer signs off on both as part of the human merge; each is a one-line revert if refused.
  • F4 (non-blocking, provenance). The PR body's Verification block still cites head 50ec9f28a; the PM ruled "leave it" (comment 5580337880) and re-took eslint at the new head. Recorded here so the stamp is not read as the reviewed head. Expectation: none.
  • F5 (unverified, stated). The PR body's hotcrm/kpi "0 ListView" rows could not be re-measured from this seat (repos not attached). Expectation: none for this PR; a maintainer with those checkouts can spot-check git grep -E "<ListView[^>]*(objectName|viewType)".

Status

⛔ Not approved, not readied, not enqueued, no auto-merge, no label change. GOVERNED (skills/objectstack-ui/rules/pages.md) ⇒ a human merge is the review record.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 8, 2026 14:25
@os-zhuang
os-zhuang enabled auto-merge September 8, 2026 14:26
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit e0af1a8 Sep 8, 2026
42 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment