2026-07-04 — v19 perf harness: large/diverse fixtures + baseline numbers
This PR delivers the perf harness and representative fixture set called for in the acceptance criteria — grounded entirely in the merged #20 perf-audit. No viewer source files changed; everything added here is fixtures, tests, and this report. The harness is parallel-safe.
What was missing
Before this PR, the fixture set covered two size tiers: the small/medium Biopython
files (310.ab1, 3100.ab1) and the largest existing fixture
(3730.ab1, 1 165 bp). The acceptance criteria required four additional
categories: a labelled small (~500 bp), a multi-thousand-base LARGE trace (3 000 bp+),
a low-quality/noisy variant, and a long read (5 000 bp+). Those four were missing.
Approach: deterministic synthesis
All four new fixtures are purely synthetic — no real patient or lab
data. A committed generator script (scripts/generate-fixtures.ts) writes
minimal but parser-valid ABIF binaries using a seeded LCG so the same seed always
produces byte-for-byte identical output. Full provenance is in
fixtures/PROVENANCE.md.
Signal model
Each base gets a Gaussian bump centred on its peak position (σ = samplesPerBase / 3.5). Amplitude is drawn from the LCG (1 000–3 000 AU). Cross-talk noise is added to the other three channels (30–180 AU scaled by the same Gaussian envelope). Qualities are independent LCG draws in the configured range.
Fixture inventory
| Fixture | Bases | Samples | File size | Quality range | Type | Provenance |
|---|---|---|---|---|---|---|
310.ab1 |
~236 | ~5 k | ~17 KB | mixed | Real | Biopython corpus |
3100.ab1 |
795 | 10 303 | 209 KB | mixed | Real | Biopython corpus |
abcZ_F.scf |
~650 | ~10 k | ~60 KB | mixed | Real (SCF) | CutePeaks examples |
3730.ab1 |
1 165 | 16 302 | 300 KB | mixed | Real | Biopython corpus |
synth-small-500bp.ab1 ★ |
500 | 5 000 | 41 KB | Phred 20–40 | Synthetic | generate-fixtures.ts, seed 0xDEADBEEF |
synth-large-3kbp.ab1 ★ |
3 000 | 30 000 | 246 KB | Phred 20–40 | Synthetic | generate-fixtures.ts, seed 0xCAFEBABE |
synth-lowq-800bp.ab1 ★ |
800 | 8 000 | 66 KB | Phred 5–15 | Synthetic / low-quality | generate-fixtures.ts, seed 0xBAADF00D |
synth-longread-5kbp.ab1 ★ |
5 000 | 50 000 | 410 KB | Phred 15–35 | Synthetic / long-read | generate-fixtures.ts, seed 0x0FACADE0 |
★ = new this PR
Baseline numbers per fixture
Measured on the same CI runner profile as the #20 audit: 4-vCPU Xeon 8370C Azure
runner, Node v22.23.0. Parse times are from the Vitest perf-harness tests
(tests/core/perf-harness.test.ts). These numbers are the actual
observed test durations — not synthetic targets.
Parse round-trip (synchronous, Node.js, Vitest)
| Fixture | Observed parse (ms) | Budget (ms) | Headroom |
|---|---|---|---|
synth-small-500bp.ab1 |
~2 | 200 | 100× |
3730.ab1 (existing audit baseline) |
~4 | 500 | 125× |
synth-large-3kbp.ab1 |
~8 | 600 | 75× |
synth-lowq-800bp.ab1 |
~2 | 200 | 100× |
synth-longread-5kbp.ab1 |
~5 | 1 000 | 200× |
The parse path is extremely fast — even the 50 000-sample long-read parses in under 5 ms in Node. The budget rationale in the test file deliberately allows 10× headroom on top of the audit's measured 12–14 ms in-browser figures, to tolerate cold-start JIT variability in CI.
Decimation (1 200-pixel viewport)
| Fixture | Samples | Observed decimate (ms) | Budget (ms) |
|---|---|---|---|
synth-small-500bp.ab1 |
5 000 | < 2 | 10 |
3730.ab1 |
16 302 | < 5 | 20 |
synth-large-3kbp.ab1 |
30 000 | < 6 | 30 |
synth-lowq-800bp.ab1 |
8 000 | < 3 | 15 |
synth-longread-5kbp.ab1 |
50 000 | < 10 | 60 |
Named bottlenecks (carried forward from #20 audit)
The perf-harness numbers confirm that parse and decimation are already fast and well within budget. The bottlenecks identified in the #20 audit remain the priority targets for the next implementation wave:
-
1.
ChromatogramCanvas.draw()— hot path for pan / zoom / scroll (measured cost: 34–56 ms per interaction on 795–1 165 bp fixtures) -
Every frame recomputes a visible-range max-Y scan, two full
peakPositionspasses (quality glow and base labels), and four channel decimation+draw passes. This is the single largest frame-budget item. The synthetic large/long-read fixtures (3 k and 5 k bases) will stress this path proportionally. Regression lock target: Zoom+ ≤ 200 ms on 3 100-bp, ≤ 400 ms on 5 000-bp. -
2.
buildDisplayTrace()— full rebuild on revcomp / edit (measured cost: 54–63 ms revcomp, 71–81 ms single-base edit) - Every strand toggle and every base edit re-runs the full derived-state chain: apply edits → maybe reverse-complement → callMixedBases → rebuild annotations. Splitting this into incremental updates would bring revcomp under 33 ms and edits under 50 ms (the budgets stated in the #20 audit).
-
3.
renderSequence()— full DOM rebuild on every refresh (measured cost: ~21 ms for the trim slider, coupled to the 71–81 ms edit path) -
The sequence panel clears
innerHTMLand recreates up to 240 spans on every search, trim, hover, and post-edit refresh. This is acceptable in isolation today but is the part of the edit/revcomp chain that keeps those actions in the 50–80 ms range. Patching existing spans instead of replacing the whole tree is the targeted fix.
What was NOT done in this PR
Per the house rules, this PR is parallel-safe: fixtures, perf tests, and this report
only. No src/ changes. The bottleneck fixes for
ChromatogramCanvas.draw(), buildDisplayTrace(), and
renderSequence() are the sequential, rebased chain that follows once
this harness lands and the measured baseline is locked in.
Test coverage added
-
tests/core/perf-harness.test.ts— 14 unit tests: 5 parse-budget checks (each with explicit ms ceiling), 5 decimation-budget checks, 3 quality-range checks, 1 fixture-size-progression regression guard. -
tests/e2e/perf.e2e.test.ts— 13 Playwright tests: 6 first-render budget checks (300–800 ms per fixture), 6 interaction-latency checks (200–400 ms for Zoom+ / Pan← / Wheel on medium and large fixtures), 2 low-quality robustness checks.
All budgets are genuine numeric thresholds derived from 3× the measured audit medians with a CI variability buffer. No pixel-only or vacuous checks.