Repository navigation
fix(scripts): the handler-key census takes the registration-WRAPPER hop (objectui#9700) - #9728
Conversation
A type-only cast on the expression that hands a child the document closed the
transitive hop, so `register('detail-view', DetailViewRenderer)` stopped at the
data-source gate and `DetailView` was never opened under that registration.
objectui#9344 peeled type-only wrappers off a READ receiver. It did not peel
them off a HOP, and the two failures are not symmetric: a hidden read still
leaves its component in the census, while a hidden hop removes the component
entirely — no finding, no census row, no ledger row — and the green then reads
as "no undeclared reads" over a file the gate never opened. objectui#9447 stood
in exactly that state.
`peelErasure` is now shared by `erasedReceiverName` and `carriesDocument`, so a
cast changes what the census READS about a node and never whether it SEES it.
The peel widens what the walk sees, never what it follows: the node underneath
still has to be a document identifier or a spread of one, and a literal writing
its own `type` is still a new document.
Measured delta on this tree: 46 -> 49 reachable reads, all three under
`detail-view`, all three read in `DetailView.tsx`. Two are objectui#9447's keys,
declared runtime slots. The third is the same `(schema as any).onTabChange` read
site the `detail` twin already ledgers, now scored under the second registration
that reaches it; both rows carry objectui#7804, which owns the disposition.
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015h79niBMyoB1xcaQje3uiz
|
changeset-claim-re-read
|
✅ 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
|
ACCEPT — in-seat review,
|
| check | result |
|---|---|
| Commit message | ✅ no model identifier; correct trailer pair; ⛔ no card trailer |
| Commit author | ✅ claude — ⛔ not a user account |
Body line 0 / Clause-② |
✅ Fixes #9700 / Clause-②: no at line start |
| Ledger | ✅ exactly one row added, self-policing, both rows carry #7804 |
| New CI context | ✅ none ⇒ ⛔ #9584 not approached |
| Counters | ✅ 46 → 49 reachable reads, all three new ones under detail-view |
⚠️ Recorded, ⛔ not held against the PR
resolveComponent'sCallExpressionbranch returns the first capitalised identifier argument and discards the wrapper call's own body, so a wrapper spelledblock(Inner)that also renders document-carrying JSX aroundInnerwould lose the outer half. ⛔ Correctly not filed: unreachable on this tree — the one such registration renders nothing around it — so no undeclared read is exposed. ⭐ Recorded in the PR's Acceptance notes with that reasoning, which is the right carrier.check:sdui-registration-pinsexits 2 (PREREQUISITE NOT MET — no console build) ⇒ NOT MEASURED locally, left to CI, and this diff adds/removes no registration. ⭐ The gate naming its own precondition is it being honest, ⛔ not silent.pnpm -s check:changeset-no-majorsurfaced exit 254 with empty output while the script itself exits 0 — a pnpm wrapper artefact that reads as a red gate.⚠️ Worth knowing: it is another instance of a wrapper's exit status not being the instrument's verdict.
Release
Flipping to ready, re-waiting for what the flip registers (measured on twelve occasions), re-reading green, then arming. ⛔ No hand-merge — the queue rebases onto main itself.
Generated by Claude Code
Fixes #9700
Clause-②: no
⭐ The fresh reading first — outcome 1, on today's
mainThe card measured this gate at
bbe57fdd52, before PR #9702 rewrote the file. Re-measured ondea17b469(branch base), with--list:detail(registersDetailViewraw)onNavigate,onAddComment,onTabChange, all atDetailView.tsxdetail-view(registers the wrapperDetailViewRenderer)⇒ the wrapper hop is still invisible. #9702's re-keying did not touch it: that change decided
which arm a registration is scored against, and this defect is about whether the component is
reached at all. Gate exit 0 either way.
The mechanism — derived, not inherited from the card
The card declined to name one. Measured with
documentCarryingChildrenon the realDetailViewRenderernode:Isolated on a minimal fixture, one token apart:
⇒ ⛔ not the
hops > 4cap, ⛔ not a failure to resolve the wrapper's reference, ⛔ notthe wrapper stopping the walk.
resolveComponentalready handles the HOC spelling and the wrapper'sinline body is walked. What closed the hop is a type-only cast on the
schema=attribute thathands the child the document:
carriesDocumenttested the raw expression, so{(bound) => <DetailView schema={bound as DetailViewSchema} />}read as "not the parent's document"and
DetailViewwas never enqueued. The data-source gate is imported from another package andcannot be followed, so that render-prop hop was the only way in.
that sees one and not the other is not describing the runtime" — and stopped there. The two
failures are not symmetric. A hidden read still leaves its component in the census; a hidden HOP
removes the component entirely: no finding, no census row, no ledger row, and the green then reads
as "no undeclared reads" over a file the gate never opened. That is the state objectui#9447 stood
in, and it is why this is a card of its own.
① The repair
peelErasureis extracted fromerasedReceiverNameand shared withcarriesDocument, so a castchanges what the census READS about a node and never whether it SEES it.
a document identifier or a spread of one, and an object literal writing its own
typeis still aNEW document — the narrowing that keeps
ViewSwitcher'sonViewChangeoffObjectViewSchema.Both directions are pinned.
Measured delta on this tree — the class is ONE instance, not a population
--listdiff, before vs after: 46 -> 49 reachable reads, and the three added rows are alldetail-view, all read inDetailView.tsx. ⇒ every otherelementDataSourceBlockwrapper in therepo (17 of them) already hopped fine;
detail-viewis the only one that hands its child a cast.The card asked for that enumeration and this is its answer: no sibling sweep is owed.
The repair surfaces one finding:
detail-view::DetailViewSchema.onTabChange.detail::DetailSchema.onTabChangerow —DetailView.tsx's(schema as any).onTabChange— scored a second time, correctly, under theother registration that reaches that component.
register('detail-view', DetailViewRenderer, { namespace: 'plugin-detail' })omitsskipFallback, so it claims the baredetail-viewkey andDetailViewSchemaIS its arm.is how many of the two registrations reaching it the gate can score — one, now both.
map's meaning; this row is a defect the blind spot was hiding, which is what the ledger is
for, and the file's own instruction text says so: "if the fix belongs to another card, add the key
to KNOWN_UNDECLARED_READS with the card that owns it."
staleExemptions()reds onthis exact row ("The defect the row waives is gone, so the row is now a live waiver for nothing").
The row is welded to the hop.
onTabChangeonDetailViewSchemawidens a published accept set ⇒Clause-②: yes⇒ not this card's to take. Thedisposition is objectui#7804's, open, and it is the card both rows carry.
② The pin that can go RED
scripts/__tests__/check-handler-key-read-sites.test.tsgains a block that rebuilds objectui#9447'sshape hop for hop:
register('detail-view', DetailViewRenderer),elementDataSourceBlock(...), adata-source gate imported from another package, and the render-prop child handed
bound as DetailViewSchema.reads/judgedfloors so the greencannot be a walk that found nothing.
document to an unrelated local declared two lines above it. The hop must NOT be taken, paired with
its lit half so the zero is a reading and not an unreached fixture.
detail-viewrows NAMED (an emptydetail-viewcensus is the blindspot restored), with the raw
detailtwin's three rows as the control — the repair only ever ADDShops, so those must be untouched.
Ablation — restore the pre-repair
carriesDocumentbody, read the pinMutation proved on disk (
peelErasurecall 2 -> 1, pre-fix paren recursion 0 -> 1; blob hashfb8841ce->b97508be), restored viagit checkout HEAD --with the hash re-compared andgit diff HEADempty.All ten wrapper legs RED, plus the ledger-honesty leg. On the repaired tree: 49 passed (49).
Verification
pnpm --filter @object-ui/types testpnpm --filter @object-ui/plugin-detail test+ the gate suiteVERDICT command-exit 0— 181/1742 and 1/49pnpm type-check:scripts--listFileshit 1)check:handler-key-readsOK … 49 reachable … 2 exemptedcheck:control-bytes·check:new-line-citations·check:changeset-no-major·check:changeset-claims·check:pending-changeset-literals·check:comment-mask-corpus·check:component-surface-parity·check:element-data-source-declaration·check:spec-symbols·check:sdui-registration-pins·check:registry-bare-namescheck:sdui-registration-pinscheck:changeset-presencecheck:sdui-registration-pinsexits 2 = PREREQUISITE NOT MET, not red: "No console build toweigh at apps/console/dist/assets." Read as NOT MEASURED; this diff adds and removes no
registration and does not touch the bundle. Left to CI, which builds the console.
pnpm -s check:changeset-no-majorreported exit 254 with no output; run directly the scriptexits 0 with
✅ No changeset declares a 'major' bump.The 254 is pnpm's wrapper, not a gate.from
--format json; ③ invariance —eslint.config.jsconfigures no type-aware linting (noproject/projectService), so this diff cannot move the verdict on any untouched file. Repo-widelint is CI's run.
Prose this change falsified, repaired in place
Two live docblocks stated "its transitive hop stops at the wrapper" and told readers ⛔ not to read
⚠️ Comment-only: no zod member,
the gate's green as evidence —
DetailViewSchema'sonNavigatemember anddetail-view-handler-slots-9447.test.tsx. Both are now false in the direction that matters (theysay the gate is blind where it is not), so both are rewritten.
no runtime behaviour, no published contract field. Surface re-measured — all four files held by
0 of 13 open PRs, 1804 filenames fully paginated, positive control
.github/workflows/ci.yml -> #9584.The changeset is therefore an empty-frontmatter declaration, which
check-changeset-presence.mjsnames as a first-class pass.Acceptance notes
resolveComponent's CallExpression branch returns the first capitalised identifier argument anddiscards the wrapper call's own body, so a wrapper spelled
block(Inner)that also rendersdocument-carrying JSX around
Innerwould lose the outer half. Not reachable on this tree — theone such registration (
register('record_picker', elementDataSourceBlock(ElementRecordPickerRenderer)))renders nothing around it. Noted, not filed: no undeclared read is exposed and no open PR passes
this file.
check:sdui-registration-pinscannot be run without a console build; nothing here is implicated.🤖 Generated with Claude Code
https://claude.ai/code/session_015h79niBMyoB1xcaQje3uiz
Generated by Claude Code