test(app-shell): stand up the object-view relay rung census (objectui#7559) - #8864
Conversation
…wSchema (objectui#7559) The relay in `views/ObjectView.tsx` builds `fullSchema: ListViewSchema` out of the host's list schema plus a set of rungs reading the active view. The output is typed; the input (`viewDef`) is `Record<string, any>`, so a `ListViewSchema` member with NO rung is invisible to tsc, to lint and to the tests — the silence that shipped objectui#7199 (`description`), objectui#7218 (`rowColor`) and objectui#7516 (`fieldOrder`, still open). `ObjectView.relayRungCensus-7559.test.ts` re-derives the member set at test time from the zod mirror the type is built from, plus `ListViewRuntimeProps` parsed out of its declaration, and re-derives the rung set from the literal with the TypeScript parser. Every member must be either relayed or declared in an `ABSENCES` ledger with a kind and a reason, and each kind carries evidence that is checked: a nested relay's path must exist, an "the caller supplies it" must still be composed upstream, an "unread" must still have no reader in `ListView`, a `known-gap` must still have one. The write direction gets the same treatment, because the target type is `.passthrough()` and excess-property checking never covered it. Source changes are comments plus one type-only read: the `tree` block is read as `TreeViewConfig` (objectui#8253), which retires three `any` accesses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
…property (objectui#7559)
The control case ("a correctly declared absence is not a finding") restated the
global property, so it went red whenever anything else did — which is the shape
of a control that says nothing. It now asserts only that `fieldOrder`'s
card-owned absence and `quickFilters`'s deliberate suppression stay OUT of the
finding set, so it survives an ablation elsewhere in the literal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
… alone (objectui#7559)
Annotating `viewDef` as `NamedListView & { id: string }` and running the
package's `tsc --noEmit` produces 22 diagnostics, none of them a missing rung:
all 22 are keys the relay READS that the declared view type does not carry
(`rowColor`, `appearance`, `chart`, `tree`, `map`, `name`,
`editRecordsInline`) — the objectui#7483 direction. Recorded in the census
header so the ruling's basis is re-readable without re-running the probe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
PM 评审 —— PR #8864(卡 #7559):通过,已翻 ready、auto-merge 已武装
裁决 (b) 交付了,而且 (a) 被测量证明不够 —— ⛔ 不是被我论证掉的我的裁决说「(a) 只关掉『类型不匹配』那一半,⇒ 交付不了这张卡的标题句」。那是推理。dev 把它变成了测量:把 ⇒ 一个读侧的类型没法要求一次并不存在的写入。 探针已回退,blob 验回。⭐ 这就是「把裁决写成可以被测量推翻的样子」应有的样子 —— 它这次没被推翻,但它是被验过的,不是被相信的。 ⛔ 卡片的一条子前提被证伪,而我的派发原样引用了它卡片(以及我的派发)写着:「编译器会检查这个字面量写入的每个键是合法的 假的。 我复核: ⇒ 镜像是 ⇒ 这张卡的沉默两个方向都存在,而它自己只描述了一个。 读法,⛔ 不是数字派发里我给了 48 / 36 / 66 三个读数并明说「⛔ 这不是验收数字」。本 PR 全篇没有断言任何计数,给的是一条写在文件头里的读法,加上一条属性: ⭐ 台账不会静默腐坏 —— 每个 kind 的证据都被检查
⇒ 这正是把卡片选项 (c)(「缺席是刻意的」)从整体假设变成逐键可复核的主张。⭐ 采纳 (b) 没有否决 (c),而是让 (c) 变得可核对 —— dev 这句话是对这条裁决最准确的概括。 消融:三条腿,而第三条是我没要求的那条M1(删 ⛔ 我的裁决只要求了「摘掉 rung 要变红」。M3 是 dev 自己加的,而它至少同样重要:一份答案可以悄悄过期的普查是一张清单,不是一条检查。 ⭐ 对照灯被重写过,理由正是本班的标准dev 的原话:初稿的对照「restated the global property and so went red whenever anything else did, which is a control that says nothing」。⇒ 他们自己发现了它、自己改掉了它,改成只断言 ⛔ 一个主体坏了它也红的东西不是对照 —— 这条标准这一班写下来,这里是它第一次被 dev 独立地用在自己身上。 另记一处仪器意识:普查读源码用的是全树共享的 ⛔ 我在评审时自己踩了一个测量陷阱,记下来我先用 不是越界,是我的比较基准错了。 分支落后于 main, ⇒ Clause-②
|
⛔
|
…elling (objectui#7559)
objectui#8253's pin asserted the console composition's `tree` rung with
`toContain('tree?.titleField')`. objectui#7559 typed that block as
`TreeViewConfig`, which put a cast BETWEEN the two halves of the substring —
`(viewDef.tree as TreeViewConfig | undefined)?.titleField`. The READ was
untouched and the pin's claim ("the console composition reads it") stayed
true, but the text proxy for it broke and `Test (shard 2/4)` went red.
The pin now locates the rung by name (`labelField:` naming `viewDef` and
`tree` — the only seam of its kind in that file) and matches the RELATION: a
`tree` expression whose `titleField` is taken with `?.`, tolerating an
interposed `as` clause and nothing else. `||` may not appear in the tolerated
gap, so `titleField` has to be read off THIS `tree` rather than off another
term on the same line.
Its claim and its reason are unchanged — only its evidence. A CONTROL case
pins both spellings the rung has actually worn as matching, and both the
"read deleted" and "read off a different term" shapes as not, so a regex
loosened until it cannot fail is distinguishable from one that still works.
The code shape is deliberately NOT changed: hoisting the cast into a local
would restore the old substring but redden objectui#6557's convergence pin,
which requires every seam line to name `viewDef` itself.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
PM 评审 —— 补丁轮(
|
Fixes #7559
The object page's list relay (
packages/app-shell/src/views/ObjectView.tsx) buildsfullSchema: ListViewSchemafrom the host's list schema plus a set of rungs reading the active view. The output is typed; the input is not —viewDefisRecord with string keys and any values, so aListViewSchemamember with no rung is not a type error, not a lint finding and not a test failure. That silence shipped the same defect three times: #7199 (description), #7218 (rowColor), #7516 (fieldOrder).This PR lands the ruling's option (b): a standing executable census, plus the free half of (a).
What lands
packages/app-shell/src/views/ObjectView.relayRungCensus-7559.test.ts— re-derives everything at test time from four sources on disk and requires everyListViewSchemamember to be either relayed or declared absent with a reason.ObjectView.tsx— comments only, plus one type-only read: thetreeblock is read asTreeViewConfig(finding(types,plugin-tree): the per-viewtreeconfigObjectTreereads from a host-composed view (parentField,labelField,fields,defaultExpandedDepth) is declared by no exported type — only the module-localTreeConfiginObjectTree.tsx#8253, the type the back-link comment said now exists). It retires threeanyproperty accesses; eslint warnings on the file drop 173 to 170, errors 0 both ways..changeset/objectui-7559-relay-rung-census.md— empty frontmatter.node scripts/check-changeset-presence.mjsdemands a declaration (app-shellsrc/moved) and an empty one is the honest answer: the only source change is comments and a cast that compiles away.The reading, named
The card said "47 named keys" and the dispatch measured 48 / 36 / 66 under three readings, none of them 47. No count is asserted anywhere in this PR. What is asserted is a property, under a reading written out in the file's header:
.shape— the mirrorListViewInferredisz.inputof, so the TYPE is made of it — plus the members ofListViewRuntimeProps, parsed out of its declaration. Top-level keys only: a key nested inside a member's own shape is that member's business. 93 today, never written down.fullSchemaliteral whose value readsviewDef, or a local computed from it (calendarOptions; the local set is derived from the relay callback's scope, not allowed by name)....listSchema. It carries the HOST's value — which is exactly what stayed on screen in all three defects while the view's own value was dropped.Every member that is not relayed carries a
kindand areasonin anABSENCESledger, and each kind carries evidence that is checked, so a declaration cannot quietly become false:relayed-nested— the named path (options.kanban) must exist in the literal and read the view.relayed-upstream— plugin-view'srenderListView({ schema: ... })composition is re-derived from ITS source, and must still write the key fromactiveView.legacy-alias— the canonical twin must itself be carried; where a fold is named (normalizeListViewSchema), the rung must still call it.node-level— the key must really be inBaseSchema's shape.unread—ListViewmust still have NO reader for it.known-gap—ListViewmust still HAVE one. (fieldOrder,ListViewSchema.fieldOrderhas a complete reader and no producer anywhere — is it meant to be authorable at all, givencolumnsalready carries order? #7516.)host-runtime— must be aListViewRuntimePropsmember.That is what turns "the absences are deliberate" from the card's whole-literal option (c) into a per-key claim anyone can re-read — adopting (b) does not overrule (c), it makes (c) checkable.
The write direction gets a smaller ledger too, because the card's premise about it is false: the mirror is
.passthrough(), soz.inputof it carries an index signature and excess-property checking never covered this literal. The suite proves that with a parse rather than assuming it. Seven keys the relay writes are notListViewSchemamembers at all —quickFilters,showViewSwitcher,rowActionDefs,columnStateand three callbacks — each now declared with the reason it is written into a type that does not carry it.The ablation: the census can fail, by name
Ran under the shared verify lock, from the committed tree. Each leg proves the mutation reached disk (marker count and blob hash) BEFORE the census runs, and proves the restore reached disk after (
git diff HEADempty, blob back to the HEAD blob3044e9efb60ff97cc460690b777380c19b415934).3044e9ef= HEADdescriptionrung1920e1cdleaves NO member both unrelayed and undeclared·objectui#7199 — the per-view description rung is presentrowColorrung7f0791e7leaves NO member both unrelayed and undeclared·objectui#7218 — the per-view rowColor rung is presentgrouping) and leave the declaration16662fe2declares nothing that IS relayed (an absence that came back is not an absence)M1 and M2 are the two defects this class already produced; M3 is the ledger's anti-rot direction, which matters as much — a census whose answers may silently go stale is a list, not a check.
The control held.
CONTROL: a correctly declared absence is not a findingpassed in every leg, including M1 and M2. It was rewritten to make that meaningful: as first drafted it restated the global property and so went red whenever anything else did, which is a control that says nothing. It now asserts only thatfieldOrder's card-owned absence andquickFilters's deliberate suppression stay OUT of the finding set.(a) was measured, not assumed
Annotating
viewDefasNamedListView & { id: string }and running the package'stsc --noEmit: 22 diagnostics, none of them a missing rung. Every one is the opposite direction — a key the relay reads that the declared view type does not carry (rowColor,appearance,chart,tree,map,name,editRecordsInline). So (a) alone cannot deliver this card's headline sentence; a type on the read side cannot require a write that is not there. The probe was reverted; only theTreeViewConfigread (already an exported type — clause-2 staysno, no new export anywhere) is kept.Out of scope, deliberately
packages/plugin-view/src/ObjectView.tsxis only READ here (its composition literal is re-derived); not one byte is edited.ListViewSchema.fieldOrderhas a complete reader and no producer anywhere — is it meant to be authorable at all, givencolumnsalready carries order? #7516 remains open.fieldOrderis declared aknown-gapnaming that card; ⛔ no rung was added for it.Verification
pnpm --filter @object-ui/app-shell run type-check(both projects, tests included) — exit 0.pnpm exec vitest run packages/app-shell/src/views/ObjectView— 27 files, 301 tests, all passing (includes Five more consumer-side reads of the undeclared object-leveltitleFieldsurvive #6531 — inObjectViewview-config fallbacks and auseRecordSearchmemo key #6557'stitleFieldConvergenceseam pin, A list view'sdescriptionis served but never rendered — ObjectView's relay drops it on the way to ListView #7199's and Two more declared list-view keys never reach ListView:fieldOrderandrowColorare read by the renderer but relayed by neither host #7218's relay pins, and the new census).check-changeset-presence,check-changeset-fixed,check-changeset-no-major,check-control-bytes,check-comment-mask-corpus,check-vi-mock-specifiers,check-vi-mock-inherit,check-unreferenced-sources,check-phantom-dependencies,check-lint-coverage,check-governed-queue-guard --test(NOT GOVERNED).--no-inline-config: 0 errors.parserOptions.projectineslint.config.js), so this diff cannot move the verdict on any file it does not touch.The census reads source with the tree's shared comment masker (
scripts/js-comment-mask.mjs) rather than a privatestripComments: both files it scans carry regex literals and URLs, which is the shape that makes the naive form blank real code and report a clean sweep over source it never looked at.验收备注
Found while censusing; none is ridden into this PR, and each is
noted, not filedwith a named successor:NamedListViewis 7 keys short of what this host reads (rowColor,appearance,chart,tree,map,name,editRecordsInline) — measured as the 22 diagnostics above. Successor: finding(app-shell):ObjectView's entire props surface is typedany— a misspelled prop on a public route-level view raises nothing #7483, the sibling card the back-link named ("ObjectView's whole props surface is any"). Not filed: it IS that card's subject.operationsnever reachesListViewon this path.ListViewreadsschema.operations?.export !== false, plugin-view's delegation literal does not compose the key, and the relay has no rung. Not a live defect today: app-shell composes theobject-viewnode in code (objectViewSchema) and never setsoperations, so no author can reach it here. Successor: whoever owns the ObjectView 用(schema as any)从 object-view 节点读 31 个键转发给 renderListView,其中 27 个不是 ObjectViewSchema 声明成员 —— #5091 在 ObjectView 上的同形实例 #5097 host-composition ledger. Declared in the census asnode-authored, so the day the key becomes reachable the entry has to be re-read.fields/filtersspelling has no path toListView: the upstream composition reads the canonicalcolumns/filteronly, and the fold runs at the ListView boundary on whatever arrived. Unproven as a live defect (no measurement that producers write the legacy spellings into stored views), so it is an observation, not a card. Successor: the Migrate the remaining ListView legacy vocabulary to spec-canonical keys, and audit ObjectView/DetailView (#2231 phases 4–5) #2890 fold lineage.ListViewreads onlyschema.onDensityChangeandschema.onNavigateoff the schema;onSortChange/onHiddenFieldsChange/onColumnStateChangeare props, and theListViewelement passes identical handlers. Harmless, and now recorded inWRITE_EXCEPTIONSwhere the next editor will see it. Successor: this file's next editor.Generated by Claude Code