Repository navigation
fix(lint): refuse a react-page ListView bound only by the canonical data source - #15025
zhuangjianguo merged 3 commits into
Conversation
…source
Step 1 of the maintainer's three-step ruling. The react-blocks contract
deprecates ListView's `objectName` in favour of
`data={{ provider: 'object', object }}`, and this rule treated the canonical
replacement as satisfying the required binding. No renderer reads it: objectui's
ListView queries `dataSource.find(schema.objectName, ...)` and its
`normalizeListViewSchema` has no `.object` read at all, so a page written to the
contract's own summary validated green and rendered an empty list.
- The replaced-by satisfaction no longer applies to ListView's object binding
when the author wrote the object provider. Scoped to that provider on
purpose: the fetch effect does implement `provider: 'value'`, a plain-array
shorthand and the renderer-owned `api` provider, so refusing those would
tighten past what renders.
- `boundObjectName` drops its canonical-first branch — field refs resolve
against the spelling the renderer reads, which is also the right one when an
author writes both.
- The comment claiming the precedence mirrored a one-directional fold in
objectui's `normalizeListViewSchema` is gone; that fold does not exist.
- The missing-required diagnostic and the deprecation warning now point at
`objectName` as the spelling that renders today.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
…nders The four cases that pinned the canonical metadata-tier spelling as binding a ListView are turned round rather than deleted — the deprecation window is still open, only its blessing is withdrawn. Two new pins: a canonical-only page is refused with a diagnostic naming `objectName`, and an `objectName` page passes. Three cases pin the scope: the `value` provider and a non-static `data` still satisfy (both render), and the deprecated spelling now wins when a page carries both, because that is the one objectui queries. Every other ListView fixture merely USED the canonical spelling as a convenient binding; each is re-spelled to `objectName`, which is what those suites need to keep resolving fields against a real object rather than against nothing. That adds one deprecation warning per fixture, so the suites about field resolution read past that single rule via `pastDeprecation`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
…stview-lint-stop-blessing-canonical
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 9141529d2ee8fdb7537b95febaed362c15256e10 && git checkout 9141529d2ee8fdb7537b95febaed362c15256e10
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin fddfc8db062d61ca68ba482531f5368326109554 3120681a7a489cd04825d7bd470b38442bbe4bf7 && git checkout -B drift-repro fddfc8db062d61ca68ba482531f5368326109554 && git merge --no-ff 3120681a7a489cd04825d7bd470b38442bbe4bf7
node scripts/docs-audit/affected-docs.mjs --json fddfc8db062d61ca68ba482531f5368326109554
|
|
Landing provenance (PM seat
Generated by Claude Code |
Part of #14791
Step 1 of 3 of the maintainer's ruling B (recorded on the card, 2026-09-03, verbatim 「同意」). This is the spec lane's step 1 only — the validator stops blessing a react-page ListView bound only by the canonical spelling. The card stays open: step 2 (objectui's consumer fold) and step 3 (retire the aliases, re-enable canonical binding, rename the sites) remain. objectui#7477 is step 2. #14343 remains blocked until step 3.
⛔ No site renames, no
packages/specchange, nothing in objectui — all three are later steps.The defect
The react-blocks contract deprecates ListView's
objectNamein favour ofdata={{ provider: 'object', object }}, and this rule treated a presentdeprecated.replacedByas satisfying the required binding. No renderer reads that spelling, so a page written to the contract's own summary passedos validateand then rendered an empty list with no diagnostic. The rule's own comment justified its canonical-first precedence as "mirroring the one-directional fold objectui'snormalizeListViewSchemaapplies at the component boundary" — that fold does not exist.Re-measured on the objectui tip
a27d153c, newer than the readings on the card, inpackages/plugin-list/src/ListView.tsx:data.objectdataConfig.objectprovider === 'object'specTypeschema.objectNamepackages/core/src/utils/normalize-list-view.tscontains zero.objectreads. The premise holds.What changed —
packages/lint/src/validate-react-page-props.tsobjectProviderBindsNothing, a named exception citing the ruling). The generic mechanism the contract publishes is kept for every other block.boundObjectNamedrops its canonical-first branch. Field-name props resolve againstobjectNameagain — which is also the correct object when a page carries both spellings, since that is the one the renderer queries.data={…} (canonical)for ListView; it namesobjectName, and adds one clause explaining that the canonical source would render an empty list. The deprecation warning keeps its severity and now saysobjectName/viewTypeare still the spellings to write.One measured refinement to the dispatch
The dispatch framed the tightening as ListView losing the replaced-by satisfaction. Measurement narrowed it to the object provider only.
ListView.tsx's fetch effect implementsprovider: 'value', a plain-array shorthand and the renderer-ownedapiprovider; onlyprovider: 'object'falls through all three to theobjectName-gated fetch, which clears the loading state and returns no rows. Refusing the providers that do render would tighten past what renders, against this step's own stated reason (Clause-②: no). A non-staticdatastill satisfies — unresolvable is not wrong, ADR-0072 D1. All three cases are pinned.Blast radius measured, not assumed: ListView is the only block in
react-blocks.tsdeclaring anydeprecatedprop, so a ListView-scoped exception and a general change are behaviourally identical today. The scoped one was chosen because deleting the mechanism outright would falsify the contract's own doc comment inpackages/spec, which step 3 owns and this PR may not touch.Tests
The vocabulary-window cases that pinned the canonical spelling as binding are inverted, not deleted — the window is still open, only its blessing is withdrawn. Two new pins: a canonical-only page is refused with a diagnostic naming
objectName; anobjectNamepage passes. Three more pin the scope (value provider, non-staticdata, and the deprecated spelling winning when both are present).Every other ListView fixture merely used the canonical spelling as a convenient binding; each is re-spelled to
objectName, without which those suites would resolve fields against nothing and pass vacuously. That adds one deprecation warning per fixture, so the field-resolution suites read past that single rule.No real site goes red. All five checked-in ListView sites bind with
objectName:skills/objectstack-ui/rules/pages.md:180and:218,examples/app-showcase/src/ui/pages/crm-workbench.page.ts:88,renewals-pipeline.page.ts:136and:169,task-desk.page.ts:44. Zero sites anywhere write the canonical spelling. The otherprovider: 'object'hits in the repo are metadata-tier list views, a different schema this rule never reads.Verification — all at the final commit
3120681apnpm --filter @objectstack/lint test— 93 files, 2873 passed, 5 skipped.pnpm --filter @objectstack/lint typecheck— clean, including the shrink-only test-layer ratchet (check:test-typecheck: OK).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackafter the merge, from a tree with no staleness warning: 36 of 39 pass. The other three exit 3, their own "PREREQUISITE NOT MET" code, which each states is neither a pass nor a finding:check-test-completenessneeds a saved turbo test log CI tees and this branch cannot produce, andcheck:dual-build-cjs-loads/check:type-check-debtneed every package'sdist. Recorded as NOT MEASURED; CI measures them.git diff HEADempty,git status --porcelainempty (no poisoned index), and the file byte-identical to the HEAD blobe94f2370. No build leg is involved — the suite imports the rule by relative path, so vitest reads the mutated source directly.pnpm lintwas not run;eslint --no-inline-config --format jsonran on the two changed files, 0 errors and 0 warnings, file count 2 read from the JSON output. This cannot have moved any untouched file's verdict:eslint.config.mjs:327-329states this repo "never enables type-aware linting (noparserOptions.project, no typed@typescript-eslintrules) for ANY file".🤖 Generated with Claude Code
Generated by Claude Code
Generated by Claude Code