test(plugin-map,plugin-timeline): pin what these two contractEnvelope-6839 waits were standing on - #8713
Merged
Conversation
…ing on
Both `contractEnvelope-6839` pins were green for reasons their wait
expressions did not state.
`ObjectMap` carries the family's only absence-shaped wait — `queryByText('Loading
map...')` is null. An absence nothing entered is satisfied by a mount that never
started: with the component mutated to `return null` unconditionally, an
implementation strictly worse than the bug, the refusal case still passed. And
the panel is a settle signal, not a rows signal — it stands in for "the rows are
on screen" only because `setData` and `setLoading(false)` land in one React
commit, which nothing asserted. With `setData` deferred by 50ms and the
`records` arm restored to `extractRecords`, the refusal case read zero markers
and passed while the settled map plotted two.
`ObjectTimeline` waited for `getByTestId('timeline-renderer')`'s
`data-item-count` to be not-null. That clause is inert: the renderer double
writes the attribute unconditionally, so once `getByTestId` stops throwing the
attribute is always a string, and the throw was the entire gate. It named a row
count and gated on a mount, so `"0"` satisfied it — under the same deferral the
wait was satisfied at `data-item-count="0"` while the settled timeline drew two
rows.
`markersThrough` now asserts the loading panel is on screen before waiting for
it to go, and that the marker count read at the transition survives a 50ms
settle window. `itemsThrough` takes the count its arm claims, gates the wait on
that value, requires it to survive the same window, and observes the loading
skeleton first so a renderer mounted empty on the first paint is not read as a
settled zero. 50ms rather than 0ms because RTL's `asyncWrapper` drains one
macrotask, so a 0ms deferral hides inside the window meant to detect it.
Test-only; no component was changed.
Refs objectui#8709
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
os-justin
marked this pull request as ready for review
September 9, 2026 00:19
os-justin
enabled auto-merge
September 9, 2026 00:19
This was referenced Sep 9, 2026
Contributor
✅ 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
|
This was referenced Sep 9, 2026
os-justin
pushed a commit
that referenced
this pull request
Sep 9, 2026
… file
The merge before this one took this branch's side of an add/add conflict and
proved that drops no BYTE main carries. It does not prove it drops no CLAIM:
the header main landed makes five, and each needed a verdict against the AST
matcher that now sits underneath it. Four survived the rewrite. One had not.
RESTORED — the two regex modes' strict buckets are NESTED on this tree.
The rewrite kept "the modes are incomparable by construction" (M1) and dropped
the measurement that says the union of the two is just `path`'s bucket. M1 is
about shapes; on this tree ident-only is EMPTY, so a reader could take M1 as a
reason to run both modes and union them, which buys nothing. Re-measured on
both trees rather than copied forward:
at da5e4f6 ident 15 ⊂ path 18 ident-only EMPTY, 3 path-only
with main @ a9bc029 ident 12 ⊂ path 15 ident-only EMPTY, the SAME 3
and the same three `PermissionMatrixEditor` sites are the path-only ones in
both. The companion claim — that the mode choice was never the largest source
of movement — is restored as arithmetic the file can be re-run to check: the
mode separates THREE sites, D1 and D2 separate SIXTEEN (−7/+9, both verified
by diffing the site lists at da5e4f6).
ADDED — R2', the half of D1 that did NOT get repaired.
The window is scoped to the enclosing FUNCTION body. That kills the runaway
half (it can never reach the next `it`) and leaves the truncation half alive,
pointed the other way: a wait hosted in a helper the test awaits covers none of
the CALLER's statements. Forced on three probes — the same cross-recorder read
draws a flag with the wait inline and ZERO with it moved into a helper. Not a
corner: 503 of this tree's 3945 `await waitFor(...)` sites have their window
owned by an inner function, 464 of those a helper. The regex matcher was blind
here too, differently, so this is a surviving blind spot rather than a
regression — the header says which, and says the one shape where the old
matcher's runaway window did reach the read and this one does not.
The banner the reader running the script actually sees said the three error
sources were removed. Two were, plus half of the third; it now says that.
Also recorded: the counts move with the TREE alone. Merging main moved the
population 2776 → 2786 and every strict bucket down (AST 20 → 18, path 18 → 15,
ident 15 → 12) with the matcher byte-identical, because #8707/#8711/#8713 each
anchored a wait. That is claim 4 demonstrating itself.
Unchanged and re-verified: the census stays OUT of CI. No workflow and no
package script runs it; the only caller is the pin test, over six fixtures.
objectui#8704
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
akarma-synetal
pushed a commit
to akarma-synetal/objectui
that referenced
this pull request
Sep 9, 2026
… fixtures a home (objectstack-ai#8719) * test(plugin-designer): anchor the lookupKeying absence pin on the handler'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 * chore(scripts): keep objectui#8690's recorder-wait census as a script 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 * chore(scripts): record what the recorder-wait census's two matchers disagree about The census script's numbers move with an internal choice its header stated but never measured. objectui#8703 measured it, and the answer is wider than the mode choice: - The two `--recorder-match` modes are incomparable by construction — each flags a shape the other cannot see (forced on fixtures, both directions). - On da5e4f6 the buckets are nevertheless nested: ident's 15 is a subset of path's 18, ident-only is empty. "They do not contain each other" is an in-principle claim, false as a measurement of this tree. - The header's stated `path` blind spot (pushed bare, read as `host.inits[0]`) is wrong: both modes miss that shape. - Two mode-independent rules dominate the mode choice: the forward window ends at the next `await` in the FILE rather than at the end of the enclosing test, and any textual occurrence counts as a read. Seven of the eighteen strict flags on that tree point at a declaration, a destructuring, a reset or a function parameter — not at a read. Header rewritten to carry that, plus a caveat printed next to the counts. The detector's behaviour is unchanged: both modes print byte-identical site lists. Script and changeset only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S * chore(scripts): give the recorder-wait census an AST matcher, and its fixtures a home objectui#8704. The census resolved a recorder by the SPELLING of its `.push(` site, ended its forward window at the next textual `await` IN THE FILE, and counted every textual occurrence as a read. Seven of the eighteen strict flags it reported at da5e4f6 were therefore a declaration, a destructuring, a reset or a function parameter — and the same truncation went blind on a genuine cross-recorder read one ordinary `await` further on. The default matcher is now an AST pass: - identity over bindings and aliases, not names, one hop through a helper's parameter and a same-file factory's return value; - windows scoped to the enclosing test body, in statements, ending at the next awaited SETTLING anchor rather than at any `await`; - every occurrence classified read / write / declaration. The original census is kept behind `--matcher=regex`, so objectui#8690's and objectui#8703's published numbers stay reproducible from this file rather than being claims about a deleted script. Verified: at da5e4f6 it still prints 159 / 15 strict for `ident` and 167 / 18 strict for `path`, exactly as published. objectui#8703's five throwaway fixtures are committed as the script's test suite, with the sixth objectui#8704 asked for — the absence read objectui#8690 repaired, which must STAY flagged, because "flag nothing" is strictly worse than the bug and passes every other case. Both matchers are pinned over the same six files, so the repair is pinned as a direction. The header's "no count printed here is a corpus fact" caveat STAYS, with its residuals restated: no type checker, a window rule that is a judgement rather than a fact, and a flag that is still a site to read and not a defect. The census remains out of CI; the pin test runs the matcher over the fixtures only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S * chore(scripts): resolve the census header claim by claim, not file by file The merge before this one took this branch's side of an add/add conflict and proved that drops no BYTE main carries. It does not prove it drops no CLAIM: the header main landed makes five, and each needed a verdict against the AST matcher that now sits underneath it. Four survived the rewrite. One had not. RESTORED — the two regex modes' strict buckets are NESTED on this tree. The rewrite kept "the modes are incomparable by construction" (M1) and dropped the measurement that says the union of the two is just `path`'s bucket. M1 is about shapes; on this tree ident-only is EMPTY, so a reader could take M1 as a reason to run both modes and union them, which buys nothing. Re-measured on both trees rather than copied forward: at da5e4f6 ident 15 ⊂ path 18 ident-only EMPTY, 3 path-only with main @ a9bc029 ident 12 ⊂ path 15 ident-only EMPTY, the SAME 3 and the same three `PermissionMatrixEditor` sites are the path-only ones in both. The companion claim — that the mode choice was never the largest source of movement — is restored as arithmetic the file can be re-run to check: the mode separates THREE sites, D1 and D2 separate SIXTEEN (−7/+9, both verified by diffing the site lists at da5e4f6). ADDED — R2', the half of D1 that did NOT get repaired. The window is scoped to the enclosing FUNCTION body. That kills the runaway half (it can never reach the next `it`) and leaves the truncation half alive, pointed the other way: a wait hosted in a helper the test awaits covers none of the CALLER's statements. Forced on three probes — the same cross-recorder read draws a flag with the wait inline and ZERO with it moved into a helper. Not a corner: 503 of this tree's 3945 `await waitFor(...)` sites have their window owned by an inner function, 464 of those a helper. The regex matcher was blind here too, differently, so this is a surviving blind spot rather than a regression — the header says which, and says the one shape where the old matcher's runaway window did reach the read and this one does not. The banner the reader running the script actually sees said the three error sources were removed. Two were, plus half of the third; it now says that. Also recorded: the counts move with the TREE alone. Merging main moved the population 2776 → 2786 and every strict bucket down (AST 20 → 18, path 18 → 15, ident 15 → 12) with the matcher byte-identical, because objectstack-ai#8707/objectstack-ai#8711/objectstack-ai#8713 each anchored a wait. That is claim 4 demonstrating itself. Unchanged and re-verified: the census stays OUT of CI. No workflow and no package script runs it; the only caller is the pin test, over six fixtures. objectui#8704 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #8709
Test-only. No component changed. Final head
3df35c668.Both diagnoses reproduced first — one of them needed a correction
The card asked me to re-derive at the component before repairing, because its parent card's list was half wrong. Both readings hold, and the timeline one holds more narrowly than stated.
map — CONFIRMED, and worse than the card said. The absence-shaped wait (
queryByText('Loading map...')is null) is satisfied by a mount that never started. WithObjectMapmutated to return null unconditionally — "render nothing, ever", strictly worse than the bug — the refusal case still passed. And the panel is a settle signal, not a rows signal: it substitutes for "the rows are on screen" only becausesetData(capped.rows)andsetLoading(false)sit in oneawaitcontinuation and React 18 batches them. Nothing asserted that. WithsetDatadeferred by 50ms and therecordsarm restored toextractRecords, the refusal case read zero markers and passed while the settled map plotted two — the bug the file exists to catch went undetected.timeline — CONFIRMED in substance, CORRECTED in reach. The
data-item-countnot-null clause is inert exactly as described: the renderer double writes the attribute unconditionally, so oncegetByTestIdstops throwing the attribute is always a string, and the throw was the entire gate. Under the same deferral the wait was satisfied atdata-item-count="0"while the settled timeline drew two rows. But the card's framing that this is a bare mount signal overshoots in one direction:getByTestIdthrowing is a real mount gate, so this pin does not pass "render nothing, ever" — under that mutation its refusal case went red pre-fix. Its exposure is narrower and specific: mounted-but-not-yet-populated, not never-rendered. The repair is aimed at that, and the mount-transition guard is added because a renderer mounted empty on the first paint would still slip through.What changed
markersThrough(map) — chose both options the card offered, because the legs show neither alone closes it:Gate 1 alone still lets a split commit through; gate 2 alone still passes a component that renders nothing. Measured, both directions, below.
itemsThrough(timeline) — gates on the count the wait names, at the value the arm claims, then requires that value to survive the same 50ms window, with the loading skeleton observed first. The inert clause is replaced rather than deleted, per the card.50ms rather than 0ms: RTL's
asyncWrapperdrains one macrotask before returning, so asetTimeout(..., 0)commit hides inside the window meant to detect it.Evidence — refusal-arm classification, from vitest's JSON reporter
Read sites mutated, never a pin.
M1= therecordsarm restored ahead ofdatainextractRecords;M2= the data commit deferred 50ms in the component;M3= the component returns null unconditionally.Leg E is the no-regression leg: the plain bug, with commits still batched, is caught before and after — so this is a strengthening, not a relocation.
The failing assertions in leg C carry the reading themselves:
expected 2 to be 0is the whole finding in one line: the old wait read 0 while the settled component held 2.Control (the PR #8702 shape). Legs F and G restore the pre-fix pin from the base blob and run it under the identical mutation. Provenance by
git hash-object, not by assertion:Every ablation ran from a committed tree, proved its mutation on disk in both directions (anchor counts,
git hash-objectagainst the HEAD blob, and a line-total gate), and restored by state under an EXIT/INT/TERM trap with absolute paths —git diff HEADempty and disk hash equal to the HEAD blob after each leg.Harness hazards the card warned about, both carried. The
react-map-gl/maplibremock the pin owns is untouched, and every map measurement ran through the pin itself rather than a substitute. Container leakage was checked rather than assumed: three consecutive clean repetitions, all2 / 2 / 0— an accumulating count would have gone red on repetition two, since the pin asserts exact numbers.Checks run locally
vitest runon both pin files, JSON reporter: 6/6 pass on the final head; stable over three repetitions.pnpm --filter @object-ui/plugin-map --filter @object-ui/plugin-timeline run type-check: bothDone, with the dependency closure built first (turbo run build --filter=@object-ui/plugin-map^... --filter=@object-ui/plugin-timeline^...), so this is measured and not a TS2307 precondition failure. It found a real one:toBeInTheDocumentis TS2339 inplugin-timelinebecause that package'stsconfig.test.jsondoes not name@testing-library/jest-domintypes(theplugin-mapone does). Swapped fortoBeTruthy—getByTestIdthrowing is the assertion either way — and left a comment saying why. Reported as an out-of-scope finding.eslinton the two changed files: 0 errors, 9 warnings — identical to the count on the base blobs, so no new finding.node scripts/check-changeset-presence.mjs: green. Empty-frontmatter changeset, the real exemption;skip-changesetis a phantom label here and was not applied.Repo-wide lint and the full suite are CI's, not narrowed here.
🤖 Generated with Claude Code
https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
Generated by Claude Code