Scope and evidence
The 2026-08-27 audit examined the application at commit 60de9bb, then the repair pass landed at 1b444ba. All seventeen rows below are fixed-this-session history, not an open defect list. Current source citations point at the repair mechanism so a future maintainer can tell what must not regress.
Evidence labels used during the audit were:
- E3: executed in a real browser by the orchestrator or read directly from the conflicting source paths;
- E2: independently corroborated by two agents or two source locations;
- E1: one direct observation.
The browser pass ran against the production preview at desktop and 390×844 mobile sizes. It did not infer audio correctness from compilation.
Seventeen confirmed defects and their repairs
| ID | Evidence at audit | Fixed-this-session state |
|---|---|---|
| B-1 | E3: pressing Escape closed the power dialog while powered stayed false. __engine.state() remained none; a page click plus injected note produced zero voices and zero level until reload. | The dialog onclose now invokes powerOn() whenever power is still false, so Escape itself completes the user-gesture unlock (src/App.svelte:154-171). |
| B-2 | E3: hold a, press x, release a left one voice, level about 0.44, and one lit key after 3.4 s. Keyup had recomputed the note from the new octave; blur had no drain. | held is a character-to-note map captured at keydown; keyup uses that exact note. Window blur drains notes and sustain (src/App.svelte:21-24,85-116,127-132). |
| B-3 | E3: a synthetic non-primary touch and a real two-point CDP touch both left only one sounding voice. Implicit capture also prevented sibling key entry, so glissando was dead. | The piano keeps pointer-to-note ownership, releases implicit capture, and moves ownership on pointer entry. Pads accept every button-0 pointer. Two-point touch was re-run with two voices, then drained one by one (src/components/Keys.svelte:20-82,109-113; src/components/Pads.svelte:18-26). |
| B-4 | E2/E3 source proof: engine panic set only the public bend scalar, while the synth closure retained its prior bend and applied it to new oscillators. | Panic now calls synth.setBend(0) as well as resetting the projected value. A bent-panic-new-note browser check matched the reference FFT peak (src/lib/audio/engine.svelte.ts:209-214). |
| B-5 | E2 source proof: cancelling an open-hat gain’s scheduled decay exposed the earlier peak anchor, so a closed-hat choke jumped back up before decaying; only the latest open hat was tracked. | Every open-hat gain is held in a set. Choke snapshots the current value, cancels, re-anchors, damps, and clears the set (src/lib/audio/drums.ts:25-27,103-124). |
| B-6 | E2 source proof: every voice connected the global LFO to a per-voice gain, but kill disconnected only the output gain. The LFO retained one node per note indefinitely. | kill() disconnects both sides of the vibrato gain and the voice output, with idempotent guards (src/lib/audio/synth.ts:70-95). |
| B-7 | E2 source proof: cutoff, Q, detune, and bend wrote AudioParam.value directly. A 128-step CC sweep across 80 Hz–12 kHz could zipper and ring. | Active-voice filter, Q, detune, bend, and vibrato changes use 10 ms setTargetAtTime smoothing (src/lib/audio/synth.ts:37-41,206-249). |
| B-8 | E2 source and audible mechanism: volume scaled the compressor input in master → compressor, so the 12:1 default compressor flattened the upper half of the knob. | A dedicated postGain now sits after the compressor and owns volume; the analyser remains after it (src/lib/audio/engine.svelte.ts:62-69,191-207). |
| B-9 | E2 source proof: stealing reused the user-mappable release, up to 3 s, after deleting the voice from the active map. Rapid play could leave roughly three times the nominal population sounding. | Re-trigger and oldest-voice stealing use a fixed 80 ms release; the active map still caps at 16 (src/lib/audio/synth.ts:37-40,115-128). |
| B-10 | E2 source proof: panic stopped active oscillators at their current amplitude, producing a multi-voice click. | Panic clears ownership immediately but releases audio over 30 ms before final disconnection (src/lib/audio/synth.ts:37-40,254-260). |
| B-11 | E2 source proof: the global Space handler exempted only form fields, so Space on a focused button was cancelled and also emitted sustain. | Focus inside button or a slider returns before preventDefault and CC64 dispatch (src/App.svelte:43-58,92-100). |
| B-12 | E2 source proof: pad flash used Date.now() as a keyed remount token; two hits in one millisecond reused the node and lost the second flash. | A monotonic hit sequence replaces timestamps. The live same-millisecond check observed a different flash node (src/lib/midi/manager.svelte.ts:29-37,119-123). |
| B-13 | Mutation evidence: deleting mapping’s JSON guard, stored-parameter allowlist, or input clamp survived the original suite. Corrupt browser storage could fail construction at boot. | Three tests now cover corrupt JSON, hostile parameter names, and injected values above 127 (src/lib/state/mapping.test.ts:58-92); the guarded implementation remains at src/lib/state/mapping.ts:65-90. |
| B-14 | Source and coverage evidence: settings read global storage directly at module construction, unlike mapping’s injected-store pattern; waveform and channel validation had no tests. | load(storage) accepts an injected store and guards storage access. Tests cover corrupt JSON, invalid waveform, channels 0, 17 and NaN, and round-trip save (src/lib/state/settings.svelte.ts:6-49; src/lib/state/settings.test.ts:13-54). |
| B-15 | Source mechanism: every dial pointermove reached synchronous JSON serialization and localStorage.setItem, roughly 50–200 writes per second during a latency-sensitive gesture. | Settings mutations now debounce persistence by 250 ms and flush a pending save on visibility loss or page hide (src/lib/state/settings.svelte.ts:3-4,51-83). |
| B-16 | Source proof: App had a reactive showMonitor save while TopBar already saved the same toggle, causing duplicate writes plus a mount write. | TopBar owns the one persistence path; App only binds the value and its remaining effect is the power modal (src/components/TopBar.svelte:53-56; src/App.svelte:15-20,122-124). |
| B-17 | Source proof: overlapping init() calls could each issue requestMIDIAccess, race the stored access object, and orphan a state-change subscription. | #initPromise makes the request single-flight, reuses a completed access object, and clears only in finally (src/lib/midi/manager.svelte.ts:38-39,58-95). |
The repair gate was npm run check, npx vitest run, and npm run build: all exited zero; Vitest reported 20 tests in 3 files. The production-preview browser pass then re-exercised Escape power, octave-shift release, real two-point touch, bend reset, same-millisecond pad hits, position velocity, hue consumers, dial activity, reduced motion, program change, light and retro themes, and mobile overflow.
Five plausible claims the evidence killed
These are preserved because they sound reasonable enough to waste another maintenance cycle.
| Claim | Verdict | Evidence |
|---|---|---|
| Six themes are the CSS bulk. | False. | The audited build attributed 6,847 bytes of 100,650 raw CSS bytes to the six themes: 6.8%, not the bulk. Total compressed CSS was about 16.6 kB; daisyUI component CSS dominated. Removing themes is not a material performance project. |
| Two requestAnimationFrame loops are a performance problem. | False. | Measured work was below 0.5% of a frame: roughly 2–4 µs for RMS, 5–15 µs for the FFT read, and 10–30 µs for 24 style writes. The split remains explicit in src/lib/audio/engine.svelte.ts:135-153 and src/components/Stage.svelte:41-71. |
| Key highlighting rides the 60 Hz analyser loop. | False. | Keys read midi.pressed, which dispatch replaces with a new map on note events (src/lib/midi/manager.svelte.ts:27-28,119-175; src/components/Keys.svelte:121-143,151-174). Per-frame engine.level, bend, and bins are read imperatively inside Stage’s own frame callback, not by the key template. |
| Drum channel is off by one. | False. | Parser channels are 0–15, settings are 1–16, dispatch subtracts one exactly once, generated pad events subtract one exactly once, and the monitor adds one only for display. The default UI channel 10 correctly matches status 0x99; see midi-reference. |
| The original 13 tests were decorative. | False. | Seven of ten sampled one-line mutations were killed. The three survivors were the mapping clamp, allowlist, and corrupt-JSON guard; the repair pass added focused tests for all three. One nuance remains worth knowing: the pitch-bend byte-order mutation was killed only by the center payload because the two endpoint payloads were byte-symmetric. |
Negative evidence is scoped to the measured build and workload. It does not prove all future themes are cheap or every future Stage mode is fast; it proves there was no basis for those five refactors at the audit point.
Measurement changed implementation
Two observations forced design corrections rather than merely confirming source:
- Moving the keys after a taller stage put the piano at y≈1015 on a 390×844 viewport. The owner chose the sticky mobile strip now implemented in
src/components/Keys.svelte:115-119; measured at page top, the 256 px stage and the 95 px key strip were both visible and playable. - The first dial-disc token change looked acceptable on
synthwavebut nearly disappeared onlight. Reinspection onlightandretrochanged the dial tobg-base-300with abase-contentborder mix (src/components/Knobs.svelte:255-261). A semantic token is not automatically legible on every theme.
The browser-method traps and repeatable smoke that grew from this audit live in private page (sign-in required). Deferred product choices live in roadmap.