Low priority. These findings surfaced during the #173 investigation, where they were the initial suspects before the real root cause was isolated. No field incident has ever been attributed to this path. Filing so the knowledge isn't lost, not as a call to action.
Context
A segment's cover thumbnail is a jpeg written at record/import/migration time; the DB row stores its relative path. Three writers persist thumbnail: null when the native frame-grab (getFrameAt) fails once:
src/features/recorder/use-recorder.ts, persistSegment (record time)
src/features/draft-transfer/unpack.ts, .pulse import (regenerates covers; 17 sequential native frame-grabs while the whole archive is held in RAM, making it the most stressed context)
src/db/legacy-migration.ts, migrated rows with no usable legacy thumb
A null row then degrades to the designed runtime fallback (src/hooks/use-thumbnail.ts → generateThumbnail): spin up a full expo-video player, extract the first frame, and cache it in memory only.
The gaps
- A single transient failure is permanent. One failed
getFrameAt means thumbnail: null forever. No retry, no later repair.
- The fallback's cost is permanent and recurring. Every null row spins up a whole video player on every draft open, for the lifetime of the row.
- The fallback never retries within a mount.
whenReady resolves silently after its 5 s timeout, so a failed or slow extraction stays blank until remount. (Cosmetic; remount covers it in practice.)
Ranked remediation (if this is ever touched)
- Backfill on fallback success: when the runtime fallback extracts a frame, persist the jpeg to the row's thumb path and repair the column. This makes the entire failure class self-healing regardless of cause, subsumes the value of any retry, converts the recurring per-open player cost into a one-time cost, and heals legacy-migrated rows too. The only item with real merit.
- One retry in the import loop (
unpack.ts), the only genuinely stressed context. Marginal.
- In-mount retry in
useThumbnail: skip unless evidence appears.
Why this is low priority
Trigger to act: the first confirmed null-thumbnail row in the wild. Then implement item 1 only.
Context
A segment's cover thumbnail is a jpeg written at record/import/migration time; the DB row stores its relative path. Three writers persist
thumbnail: nullwhen the native frame-grab (getFrameAt) fails once:src/features/recorder/use-recorder.ts,persistSegment(record time)src/features/draft-transfer/unpack.ts,.pulseimport (regenerates covers; 17 sequential native frame-grabs while the whole archive is held in RAM, making it the most stressed context)src/db/legacy-migration.ts, migrated rows with no usable legacy thumbA null row then degrades to the designed runtime fallback (
src/hooks/use-thumbnail.ts→generateThumbnail): spin up a fullexpo-videoplayer, extract the first frame, and cache it in memory only.The gaps
getFrameAtmeansthumbnail: nullforever. No retry, no later repair.whenReadyresolves silently after its 5 s timeout, so a failed or slow extraction stays blank until remount. (Cosmetic; remount covers it in practice.)Ranked remediation (if this is ever touched)
unpack.ts), the only genuinely stressed context. Marginal.useThumbnail: skip unless evidence appears.Why this is low priority
getFrameAt. The recorder: segment thumb renders invisible (blank slot) until the draft is reopened #173 incident was proven to be areact-native-sortablesrendering bug; the jpeg was on disk all along.Trigger to act: the first confirmed null-thumbnail row in the wild. Then implement item 1 only.