feat(studio): clip thumbnail modules#2214
Conversation
c243deb to
8a91e44
Compare
5c41357 to
6f9c592
Compare
8a91e44 to
7808aec
Compare
miga-heygen
left a comment
There was a problem hiding this comment.
Reviewed as part of the 25-PR NLE overhaul stack. Full stack review on #2205 (the keystone). No blockers on this PR. — Miga
vanceingalls
left a comment
There was a problem hiding this comment.
R1 — Batch E · clip thumbnails
Reviewed as PR 6 of 6 (thumbnail module addition, non-swap). Head SHA 7808aecb41bf55582e58a84ac4e2a17f28f55729.
Verdict: 🟢 LGTM
Presentational addition on top of the new engine. 5 files: 3 net-new (ImageThumbnail.tsx +160, ImageThumbnail.test.tsx +173, thumbnailUtils.{ts,test.ts} +54/+127), 1 tiny modify (VideoThumbnail.tsx +3/-3). Zero interaction with the swap correctness verified for #2211/#2212/#2213.
Verification
- Landing point: sits atop
studio-dnd/pr22-app-shell-swap(#2213 head). - Scope: no touches to timeline glue, store, or engine code — purely thumbnail extraction and helper utilities.
- Coverage: 173 lines of ImageThumbnail tests + 127 lines of util tests = solid coverage for a new module.
- No deletions, no fallow changes, no cross-package touches.
Nothing to block on. If reviewers upstream in Batch E flag anything, it wouldn't cascade here.
R1 by Via
6f9c592 to
bc7ce78
Compare
4f76fc2 to
ff8f3bf
Compare
jrusso1020
left a comment
There was a problem hiding this comment.
Re-review — uncaught canvas.toDataURL SecurityError kills thumbnails silently
VideoThumbnail.tsx:122:
const dataUrl = canvas.toDataURL("image/jpeg", 0.6);video.crossOrigin = "anonymous" is set at line 75, but resolveMediaPreviewUrl (thumbnailUtils.ts:52) passes absolute http(s): URLs through untouched. An external asset whose host doesn't send CORS response headers will taint the canvas → toDataURL throws SecurityError inside the seeked DOM listener.
Failure mode: no try/catch on the toDataURL call. The extractor dies mid-flight, the seeked handler never resolves, extractNext() never advances — the strip stays in shimmer forever with no user-facing error. Not memory-corrupt, not a crash — just silently broken thumbnails.
Reproduces reliably with any external asset whose CDN doesn't serve Access-Control-Allow-Origin. HeyGen assets should be safe (same-origin proxy path), but user-linked or externally-hosted assets from arbitrary sources will trip this.
Fix: wrap drawImage + toDataURL in try/catch. On SecurityError, either fall through to the error handler (which continues to the next timestamp), OR emit a placeholder frame for that timestamp. Either way, keep the extractor loop alive.
Bonus tail-batch nit: AssetCard hover-video has no debounce, no abort, no preload="metadata"
At AssetCard.tsx:190-203, every pointerenter mounts a fresh <video autoPlay> against the preview API. Dragging the cursor across a 40-tile assets grid triggers 40 sequential in-flight fetches; on slow networks this hammers the server and burns decoder init cost.
Fix: add a ~100ms hover-in delay (setTimeout guarded by an in-flight ref), or reuse the VideoFrameThumbnail element.
— Rames Jusso
2700f84 to
d5242de
Compare
ff8f3bf to
47e2df7
Compare
What: ImageThumbnail (+tests) and thumbnailUtils (+tests) — frame decode with SVG/AVIF format fallbacks and rounded-corner clipping — plus VideoThumbnail updates. Why: the decode layer for timeline clip thumbnails, ahead of the visual refresh that renders them. How: new modules + one modified file; purely presentational. Test plan: bunx vitest run on both test files; tsc --noEmit; fallow audit clean.
47e2df7 to
51e0240
Compare
d5242de to
2e1046f
Compare

What
ImageThumbnail (+tests) and thumbnailUtils (+tests) — frame decode with SVG/AVIF format fallbacks and rounded-corner clipping — plus VideoThumbnail updates.
Why
the decode layer for timeline clip thumbnails, ahead of the visual refresh that renders them.
How
new modules + one modified file; purely presentational.
Test plan
bunx vitest run on both test files; tsc --noEmit; fallow audit clean.
Stack position 23/25 — studio NLE overhaul (CapCut-parity timeline + canvas). Graphite manages bases; merge from #2192 upward. Temporary
TEMP(studio-dnd)fallow entries (unwired-component windows) are all removed by #2213 (app-shell swap), whose tree is byte-identical to the fully-verified integration branch.