Skip to content

Commit 121b086

Browse files
sschabackerclaude
andcommitted
Fix a real OpenCV.js readiness race hanging the seeded demo
cv.onRuntimeInitialized (the callback OpenCV.js is supposed to call once its WASM runtime is ready) could silently never fire, even though OpenCV was actually fully loaded and usable underneath - confirmed live: cv.Mat existed and worked while the app sat frozen on "Loading OpenCV.js..." for 400+ seconds, with nothing else watching for real readiness. Every path that awaits cvReadyPromise (Compute Background, the seeded demo's skip-to-results restore, resume) hung at that exact point - "the seeded demo just loads like usual, need to retrack everything" was this same hang, not a regression in the restore logic itself. Fixed with a polling fallback alongside the callback: every 250ms, does a real `new cv.Mat().delete()` call (proof OpenCV actually answers, not just that the property exists) - whichever signal fires first wins, idempotent so neither path double-fires. Root cause not fully pinned to one line in OpenCV.js's own bootstrapping (looks like a build/timing quirk in this exact pinned version), so the fix doesn't depend on diagnosing that exactly. Verified locally end to end: a full seeded-demo run now correctly restores all 24 real overrides and completes with "tracking skipped, everything else recomputed fresh." Also this pass: the events-table legend's Poke/Entry swatches were showing the Raster tab's bold solid colors, not the pale/white colors this specific table actually uses (an earlier readability override) - legend now matches what the table really shows. Logged the fixed-size tracking crosshair/X as a real, deliberately-deferred polish item in KNOWN_LIMITATIONS.md, found during a full dry run through all three clips. 85/85 tests passing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent a1c130d commit 121b086

3 files changed

Lines changed: 47 additions & 2 deletions

File tree

‎KNOWN_LIMITATIONS.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@
4343

4444
## Deliberately out of scope
4545
<!-- things I chose not to build, and why -->
46+
- The green crosshair/red X drawn on the frame viewer to show a tracked/
47+
failed frame (`drawTrackOverlay` in `app.js`) is a fixed size and shape
48+
regardless of the video's own resolution or the mouse's actual size in
49+
frame - on some clips it may read as larger or a different shape than
50+
feels right next to the animal. Noted from watching it during a full
51+
dry run through all three clips (2026-09-07), deliberately not changed
52+
the same day it was found - a real polish item, not a correctness gap
53+
(the crosshair position itself is exactly the tracked coordinate; only
54+
its drawn size/shape is fixed). Revisit if there's time after the demo
55+
video.
4656
- RESOLVED (was: "ROI confirmation (Piece 4): resizing/repositioning the
4757
platform and hole circles is drag-only, no keyboard equivalent"). Built:
4858
a "select a shape to adjust" dropdown next to the drag instructions

‎app/index.html‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,14 @@
409409
.legend-mark { display: inline-block; width: 1.2rem; height: 0.8rem; border-radius: 2px; }
410410
.events-legend { display: flex; flex-wrap: wrap; gap: 0.75rem; margin: 0.5rem 0; font-size: 0.85rem; color: #555; }
411411
.events-legend .legend-flag-icon { color: #6b5900; font-weight: bold; }
412+
/* Real find, from her screenshot: this legend used to reuse .type-poke/
413+
.type-entry's shared solid blue/green swatches (the Raster tab's own
414+
look), but THIS table deliberately overrides both to white/pale-green
415+
for readability (see the class-collision comment above) - so the
416+
legend was showing colors that appear nowhere in the actual table.
417+
Matches the real row colors now instead. */
418+
.events-legend .legend-mark.type-poke { background: white; border: 1px solid var(--border); }
419+
.events-legend .legend-mark.type-entry { background: #eaf6ee; border: 1px solid #0d4d24; }
412420
#medianSection, #detectSection { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--border); }
413421
#medianSection h2, #detectSection h2 { font-size: 1.2rem; margin-bottom: 0.25rem; }
414422
#computeMedianBtn, #detectBtn {

‎app/js/app.js‎

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,13 +1264,40 @@ if (typeof cv !== 'undefined') {
12641264
computeMedianBtn.textContent = `Loading OpenCV.js... (${elapsed}s - large one-time download)`;
12651265
}
12661266
}, 1000);
1267-
cv['onRuntimeInitialized'] = () => {
1267+
// Real bug, found by hand-testing (2026-09-07): cv.onRuntimeInitialized
1268+
// sometimes never fires even though OpenCV's WASM runtime is genuinely
1269+
// ready to use (confirmed live: cv.Mat existed and was callable while
1270+
// the app sat stuck on "Loading OpenCV.js..." for 400+ seconds) -
1271+
// a real hang, not just slow, that broke both Compute Background and
1272+
// the seeded demo (which awaits this same promise before it can skip
1273+
// to tracking-restore). Root cause not fully pinned down (looks like a
1274+
// build/timing quirk in this exact OpenCV.js version rather than
1275+
// anything on this app's side), so the fix doesn't depend on
1276+
// diagnosing it exactly: polls for the actual readiness signal - a
1277+
// real cv.Mat construct-and-delete, the same pattern already used
1278+
// throughout detect.js, not just the property existing as a name -
1279+
// as a fallback alongside the callback. Idempotent (markCvReady no-ops
1280+
// once already ready), so whichever signal fires first wins and
1281+
// nothing double-fires.
1282+
function markCvReady() {
1283+
if (cvReady) return;
12681284
clearInterval(cvLoadTicker);
1285+
clearInterval(cvReadyPoll);
12691286
cvReady = true;
12701287
computeMedianBtn.disabled = totalFrames === 0;
12711288
computeMedianBtn.textContent = 'Compute background';
12721289
cvReadyResolve();
1273-
};
1290+
}
1291+
cv['onRuntimeInitialized'] = markCvReady;
1292+
const cvReadyPoll = setInterval(() => {
1293+
if (typeof cv.Mat !== 'function') return; // not even the stub exists yet
1294+
try {
1295+
new cv.Mat().delete(); // real call, not just a truthiness check - proves the WASM runtime actually answers, not just that the name exists
1296+
markCvReady();
1297+
} catch (err) {
1298+
// not actually callable yet - keep polling
1299+
}
1300+
}, 250);
12741301
}
12751302

12761303
// --- Reload persistence: offer to resume whatever was last worked on ---

0 commit comments

Comments
 (0)