test: audit the nine recorder-wait sites — one repaired, eight measured sound - #8702
test: audit the nine recorder-wait sites — one repaired, eight measured sound#8702os-justin wants to merge 2 commits into
Conversation
…dler's reload `expect(deletes).toEqual([])` was dated to the first PUT by `waitFor(puts)`. That covers the delete scan only because the scan happens to run before the save loop in `handleObjectsChange` — a property of the page, not one the file asserts. Anchor it on `reload()`, the handler's last statement, so the emptiness is read after every write the handler makes. Measured: with a stray `reset` issued after the saves, the `puts` wait leaves this assertion green while C0/H1/H2 in the same file — which already anchor on the reload — go red. objectui#8690 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
The detector answers where to look, never what is wrong, so it is a census tool and NOT wired into CI: a gate on this list would institutionalise the batch repair the card exists to prevent. Two recorder-matching modes, because the choice moves the numbers, and the header records what each one measured on the card's base commit rather than quoting the card's counts as reproduced. Changeset declares an empty frontmatter: test-only plus one repo script, nothing published moves. objectui#8690 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
✅ 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
|
|
Closing: every file this PR delivers is already on 1. All three files entered Two of the three are byte-identical to this branch, by blob hash rather than by eye:
2. Merging would conflict, not add. The script is absent at the merge base ( Resolving that conflict has exactly one correct answer — take 3. The red check is not this PR's. That is the defect #8689 fixed — "wait on the array the assertion reads" — landed on The audit itself — nine recorder-wait sites, one repaired, eight measured sound — is on Generated by Claude Code |
Pull request was closed
Part of objectui#8690 — the audit it asks for. Nine strict-shape sites read and classified: 1 repaired, 8 sound. Every verdict is a measurement, not an inspection: for each site the unfavourable ordering was forced on disk and the outcome observed.
Base:
da5e4f69e(the card's own base, so its line numbers land unchanged).The nine readings
da5e4f69e)app-shell/…/PermissionMatrixEditor.packageDoorFacets.test.tsx:241(and:191, same construction)server.saved, readsserver.savedOpts[0]savedoubleplugin-charts/src/ObjectChart.optionColors.test.tsx:285host.calls, readshost.inits[0]plugin-dashboard/…/DatasetWidget.relabel.test.tsx:206host.calls, readshost.inits[0]plugin-designer/…/MetadataObjectsPage.lookupKeying.test.tsx:380puts, reads the delete log — an absenceplugin-list/…/ListView.objectProviderBinding-7477.test.tsx:161, :191, :231loadingflips falseplugin-list/…/ListView.timeline-binding.test.tsx:82findCalls, readscapturedcapturedis itself awaited two lines above; the detector only inspects the nearest waitplugin-tree/…/ObjectTree.settledSchemaKeying-6481.test.tsx:169findCalls, readsschemaRequestsThe one repair
MetadataObjectsPage.lookupKeying— theconstructorround-trip case asserted "no delete was issued" right afterwaitFor(puts.length === 1). That covers the delete scan only because the scan happens to run before the save loop inhandleObjectsChange— a property of the page, not one the file asserts. The pin now waits for the manager to receive the re-read list (reload()is the handler's last statement) and reads the delete log after that.labeland notname, because a rename leaves the name unchanged: only the relabelled row is a value the pre-reload state cannot answer with. This is the anchor C0/H1/H2 in the same file already use.Evidence (all three legs, from a committed tree, mutation proved on disk in both directions — anchor counts,
git hash-objectagainst the HEAD blob, and a line-total gate — restored by state withgit diff HEADempty):client.reset('object', 'ghost_mut8690')at the end ofhandleObjectsChange, 50ms late (RTL'sasyncWrapperdrains one macrotask, so a 0ms deferral would sit inside its own window). Pre-repair pin: PASSES. The C0/H1/H2 cases in the same file, already anchored on the reload: RED (expected [ 'contact', 'ghost_mut8690' ] to deeply equal [ 'contact' ]).expected [ 'ghost_mut8690', 'ghost_mut8690' ] to deeply equal [].The control that separates a strengthening from a relocation (copied from PR #8689): under the identical mutation, the pre-repair pin restored from
da5e4f69epasses. Provenance was checked by blob, not by eye — file on disk76b074a1…,da5e4f69eblob76b074a1…, shipped blob53ea1edd…, and zero occurrences of the anchor comment in the restored copy.And the reachable defect class is still caught: breaking the delete scan itself so it misses every name reds the repaired pin (
expected [ 'constructor' ] to deeply equal []). Nothing was traded away.Why the other eight were left alone
The three
calls/inits-shaped pairs (app-shell, charts, dashboard). Both recorders are filled by one statement pair inside the same double, so no ordering exists in between — single-threaded, no interleaving point. Forced two ways:expected undefined to match object { mode: 'draft', packageId: 'app.a' }, and the same for bothinits[0]reads). That is also the vacuity check the card warns about — these reads discriminate, they do not degrade to comparing undefined with undefined.ListView.timeline-binding:82. The read's own recorder is awaited two lines above the flagged wait; the detector only looks at the nearest one. Measured: defer the spy's record by 50ms and the file stays green (17 passed) — the wait absorbs it; remove that wait under the same forced ordering and the read isundefined(TypeError: Cannot read properties of undefined (reading 'schema')). The guard is real and it is load-bearing.ObjectTree.settledSchemaKeying-6481:169. A query for object X can only be issued after X's schema request was recorded — the record is the first statement of the getter whose promise gates the query. Defer that push by 50ms and the pin goes RED (expected [] to deeply equal [ 'business_unit', 'territory' ]), so it is non-vacuous and genuinely depends on the recorder being filled. The residual — a duplicate arriving after the read — was measured too, and it is not the detector's shape: a late duplicate is invisible on the WAITED recorder (findCalls, whose exact-equality assertion sits one line above) exactly as it is on the unwaited one. Rewriting the wait would change nothing.The three
objectProviderBinding-7477absences. These are the kind objectui#8664 solved forObjectTree.contractEnvelope-6839by anchoring on the "No records" panel, which only renders afterloadingflips false. The anchor here already has that property, and it was measured rather than assumed:ListViewrenderslist-loadingwhileloading && data.length === 0, so the sibling renderer's first props arrive only after the load settles. Probe inserted at each of the three anchors —kanbanProps[0].loading/gridProps[0].loading— allfalse. The emptiness is therefore read on a settled render, not on an unrendered one.Two forcing attempts, reported as measured:
currentViewstartsgrid, flips 50ms after the load settles) was intended to make the grid mount before the anchor. It never mounted at all — a diagnostic probe waiting up to 3s forgridPropstimed out. The wrong kind is replaced before the surface is ever shown, so the leg did not produce the scenario and proves nothing about the pin. Reported VOID, not green.The detector
Kept as
scripts/census-recorder-wait-shape.mjs, re-derived from the card's four-step description. Not wired into CI, deliberately — it answers where to look, never what is wrong, and a gate on this list would institutionalise the batch repair the card exists to prevent.Its header records what it measured rather than quoting the card as reproduced. On
da5e4f69e, 2776 tracked test files:--recorder-match=identgives 159 flags / 15 strict in 10 files,--recorder-match=pathgives 167 / 18 in 12. The card reported 160 / 9. One flag off the total, wider in the strict bucket, and the two modes' strict buckets do not contain each other —identmisses a wait written as a member of a host object,pathmisses a recorder pushed bare and read through one. The card's nine sit inside the union.The repair also removes its own flag from the census:
MetadataObjectsPage.lookupKeyingno longer appears in the strict list.Scope
Test-only plus one repo script.
packages/fieldsandpackages/permissionsare untouched (objectui#8434 and PR #8689 are live there). The 151 flags whose wait named no recorder were not re-litigated. The census sites outside the card's nine were not audited and not folded in — reported separately as a finding.Verification
pnpm exec vitest runfrom the repo root with paths, per-test classification from the JSON reporter (--reporter=json), never the text one.pnpm --filter @object-ui/plugin-designer type-checkgreen, with the dependency closure built first (an unbuilt tree reports TS2307 for every workspace import — a precondition, not a result).tsc -p tsconfig.test.json --listFilesconfirms the edited file is in the checked set, so this is a measurement and not an empty pass.node scripts/check-changeset-presence.mjs,check-changeset-no-major.mjs,check-control-bytes.mjs,eslinton the new script: all green. The changeset declares an empty frontmatter — the real exemption;skip-changesetis a phantom label here.Generated by Claude Code