Skip to content

Stop the Document View lazy-body tests timing out under suite load - #417

Merged
dovvnloading merged 1 commit into
mainfrom
fix/document-view-lazy-test-flake
Sep 5, 2026
Merged

dovvnloading merged 1 commit into
mainfrom
fix/document-view-lazy-test-flake

Conversation

@dovvnloading

Copy link
Copy Markdown
Owner

Problem

This was already on main. PR #416 split the Document View's markdown body
behind a React.lazy import and updated its tests to await the result. Those
awaits use testing-library's default 1000ms.

That is enough when the file runs alone. It is not enough when the full suite
runs:

result
DocumentViewPanel.test.tsx alone 45 passed
Full suite, 3 runs 2 failed

CI passed on #416 and on the merge, so this would have started failing
unrelated pull requests rather than announcing itself.

Why the wait is long

Only in the test environment. In a built bundle the lazy body is a prebuilt
chunk; under vitest it is the first transform of react-markdown plus six
remark/rehype plugins
, with 93 test files competing for the worker pool while
it happens. Nothing about the panel is slow.

Change

Wait properly, rather than reach for fake timers or stub the import away —
stubbing it would stop the tests exercising the thing #416 actually changed.
One named constant at the four call sites that await the lazy body, with the
measurement written down beside it.

Verified both directions

A longer timeout is exactly the kind of change that can quietly turn a test
into a no-op, so I checked it hasn't:

  • Five consecutive full-suite runs: 2,197 passed every time (was 2 failures
    in 3 runs).
  • With the panel's body wired to never render, seven tests in the file fail
    and the suite goes red — the wait has not made them vacuous.

Test plan

  • npm run check green.
  • Python suite: 3,285 passed, 20 skipped.

🤖 Generated with Claude Code

PR #416 split the Document View's markdown body behind a React.lazy import
and updated its tests to await the result. Those awaits use testing-library's
default 1000ms, which is enough when the file runs alone and not enough when
the full suite runs: measured, two full-suite runs in three failed on
`findByRole("heading")` or `findByText("still here")`. CI passed on #416 and
on the merge, so this was already on main and would have started failing
unrelated pull requests.

The wait is long for a reason that only exists in the test environment. In a
built bundle the lazy body is a prebuilt chunk; under vitest it is the FIRST
transform of react-markdown plus six remark/rehype plugins, and 93 test files
are competing for the worker pool while it happens. Nothing about the panel
is slow.

So the fix is to wait properly rather than to reach for fake timers or stub
the import away - stubbing it would stop the tests exercising the thing #416
actually changed. One named constant at the four call sites that await the
lazy body, with the measurement written down next to it.

Verified both directions, because a longer timeout is exactly the kind of
change that can quietly turn a test into a no-op:
 - five consecutive full-suite runs, 2,197 passed every time (was 2 failures
   in 3 runs before)
 - with the panel's body wired to never render, seven tests in the file fail
   and the suite goes red, so the wait has not made them vacuous

Test plan: npm run check green. Python suite 3,285 passed / 20 skipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dovvnloading
dovvnloading merged commit 81ea12f into main Sep 5, 2026
5 checks passed
@dovvnloading
dovvnloading deleted the fix/document-view-lazy-test-flake branch September 5, 2026 04:02
dovvnloading added a commit that referenced this pull request Sep 19, 2026
…er budget (#421)

* Send each node only the fields it uses

Every scene node crossed the wire as the full flat superset of every node
kind's fields: 157 keys per node, of which a chat node uses 13. Each new
node kind widened every node on the canvas. Against the 2026-08-11 perf
baseline the per-node payload had grown 32-47%, publish cost 40-119%, and
the nightly perf check had failed every night since 2026-08-23.

A node now sends its five identity keys plus only the fields that differ
from the contract default. The client restores the rest from the same
defaults, so every reader still sees the full row:

- backend/domain/node_wire.py builds the row from one field table per
  node state class instead of ~150 isinstance checks per node. Omission is
  type-strict (0 is not False, 480 is not 480.0), so restoring never
  changes a value.
- SceneNodeRow is marked WIRE_OMITS_DEFAULTS. codegen emits
  hydrateSceneNodeRow() from its declared defaults; validateSceneState
  restores snapshots before validating, and SceneStore restores patch
  upserts, which replace the row, so a field reset to its default comes
  back as the default rather than the old value.
- graphlink_wire_schema's schema and validator treat a sparse row's
  defaulted fields as optional, with the restored value as `default`.

Checked against the previous serializer: 22,703 nodes (the four perf
fixtures plus 20,000 randomized nodes of every state class, including
int/bool/float type confusions) restore to exactly the row it produced.
Measured locally, median of 7: publish cost -73% to -80% versus the
August baseline, full snapshot -64% to -80%, one node -65% to -94%.

Also: perf timings are now the median of 7 publishes rather than one
sample, and the Document View tests get a test timeout above the 15s
lazy-body wait #417 added. The per-test 5000ms limit still applied, so
that longer wait could never actually be used.

Co-Authored-By: Claude <noreply@anthropic.com>

* Get the initial chunk under the ADR-019 budget, and re-anchor the perf baseline

The initial JS chunk was 599,947 bytes against ADR-019's 512,000-byte budget.
A sourcemap attribution found the largest block of app source in it was the
generated wire validators: ~87,000 bytes, 47,908 of them scene-state.ts, because
codegen emitted one unrolled block of code per field (~190 bytes each).

- codegen now emits each payload's shape as a field table (~25 bytes a
  field), compiled into a checker once at load by the new
  src/lib/bridge-core/wireCheck.ts. Checked against the unrolled
  validators on 52,000 fuzzed payloads across all 13 topics: identical
  accept/reject and identical error messages. Initial chunk 599,947 ->
  529,180.
- Scalar fields are checked inline without building a path string, and
  sparse rows are restored in table order so every restored row shares one
  shape. Scene rows that already passed are not re-checked; the store
  re-validates the whole scene after every patch, and stored rows are
  replaced, never edited. Re-validating a stored 500-node scene: 0.86 ms
  with the old validator -> 0.05 ms. Client-side cost of a 500-node
  snapshot (parse + restore + validate): 17.0 ms -> 8.1 ms.
- Every chrome surface that only a click or shortcut opens (About,
  Builder/Harness launch, command palette, diagnostics, global and
  knowledge search, quick switcher, View) now loads on first open, on the
  existing LazySurface terms. Each was checked for work it does while
  closed; OnboardingDialog does some (it opens itself on a fresh machine)
  and stays eager. 529,180 -> 495,013.
- check-bundle-size: the largest-chunk ceiling is now the budget itself,
  512,000 (3.3% headroom). The total ceiling moves down to 1,440,000.

Perf baseline: re-recorded after the sparse node rows landed, because the
nightly check fails on any size change over 10% in either direction. Values
are from the nightly's own runner, not a dev machine. Each timing is the
slowest of three dispatched runs of the same commit, because runner-to-runner
spread was up to 26%. Against the 2026-08-11 baseline: publish cost -71% to
-79%, full snapshot -64% to -80%.

Co-Authored-By: Claude <noreply@anthropic.com>

* Bump anyio and soupsieve past five new advisories

pip-audit now fails every PR, main's lockfile included: anyio 4.14.1 has
three published advisories (CVE-2026-63374, CVE-2026-64847, CVE-2026-63349;
fixed in 4.14.2) and soupsieve 2.8.4 has two (CVE-2026-85999,
CVE-2026-86000; fixed in 2.9.0).

Regenerated with the lockfile's own command, upgrading only those two
packages (pip-compile --generate-hashes --upgrade-package ...), so the
diff is two versions and their hashes. 4.14.2 is a patch release on the
4.14 line, not the 4.15 line whose BlockingPortal deprecation breaks
starlette's testclient.

pip-audit on the updated lockfile: no known vulnerabilities. Full suite
on the new versions: 3,362 passed.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant