Skip to content

Add GET /api/video-gen/history/:id so a video id resolves without downloading the whole history - #4268

Merged
atomantic merged 1 commit into
mainfrom
claim/issue-4165
Aug 15, 2026
Merged

Add GET /api/video-gen/history/:id so a video id resolves without downloading the whole history#4268
atomantic merged 1 commit into
mainfrom
claim/issue-4165

Conversation

@atomantic

Copy link
Copy Markdown
Owner

Summary

Gives video id → filename resolution a real home: GET /api/video-gen/history/:id.

A history entry's id is not its filename stem. videoGen/local.js names a clip <jobId>.mp4 (so path reconstruction happens to work for per-scene renders), but the video-timeline renderer — a Creative Director project's stitched final cut — mints timeline-<project>-<ts>.mp4 beside an independent randomUUID() id. A CD finalVideoId is therefore a history id, and /data/videos/<id>.mp4 404s for it. The thumbnail is keyed the other way (generateThumbnail always writes <jobId>.jpg), which is what made the mismatch easy to miss: the poster renders perfectly while the mp4 behind it does not exist.

With no by-id route, useVideoFileSrc resolved that by downloading the entire render history and scanning it client-side — on four surfaces (CD list cards, CD Overview, EpisodeVideoStage, MusicVideo).

  • getHistoryItem(id) (server/services/videoGen/history.js) — the single-entry read, next to the existing loadHistory/saveHistory/mutateVideoHistory primitives. Returns null (not a throw) for an absent id so the route owns the 404, while the strict loadHistory read still throws on an unreadable history file rather than reporting a bogus "no such entry".
  • GET /api/video-gen/history/:id — returns the entry, 404 NOT_FOUND for an unknown id.
  • getVideoHistoryItem(id, options) (client/src/services/apiImageVideo.js) — the wrapper, and useVideoFileSrc now calls it instead of listVideoHistory. The hook's public shape ({ src, resolving, retry }), its synchronous resolving, its enabled laziness, and its silent-failure contract are all unchanged.

Two decisions the issue left open, recorded in full at #4165:

  1. By-id endpoint, not a 302 resolver. Every consumer wants the entry, not just a redirect target; a resolver would also introduce a second URL space for the same bytes that the poster convention (/data/video-thumbnails/<jobId>.jpg) wouldn't follow.
  2. The id is validated as a length-capped string, not with the existing UUID historyIdSchema. Ids this install mints are randomUUID(), but history entries also arrive with a caller-supplied download id (videoDownload.js: const jobId = id || randomUUID()) and from federated peers, so a .guid() gate would 400 rows that are legitimately in the list. Nothing is interpolated into a filesystem path — the value is only compared against stored ids.

ScenePreview keeps its <jobId>.mp4 reconstruction — deliberately. The issue notes the endpoint "would also let ScenePreview stop reconstructing paths at all". Its one remaining reconstructing caller is SegmentsTab, which renders a ScenePreview per treatment scene; those are per-scene clip renders where <jobId>.mp4 is correct by construction. Resolving internally would turn one render into N by-id requests on mount for a 10-scene treatment, buy nothing for those ids, and change autoplay timing inside a component shared by four surfaces. The reconstruction stays the documented fallback for callers that pass no src; every surface where the id genuinely is not the filename stem already passes a resolved src.

Test plan

  • cd server && NODE_ENV=test npx vitest run routes/videoGen services/videoGen — 586 passed. New coverage:
    • server/services/videoGen/history.test.jsgetHistoryItem resolves the timeline case (filename unrelated to id), returns null (not a throw, not undefined) for an absent id and an empty history, matches ids exactly rather than by filename stem, and survives a malformed row sitting ahead of the target.
    • server/routes/videoGen.test.js — the route returns the one entry and calls getHistoryItem with exactly the requested id while never touching loadHistory; 404s an unknown id; decodes a percent-encoded id before the lookup; 400s an over-length id without reaching the service.
  • cd client && npx vitest run src/pages src/hooks src/components/pipeline src/services src/components/creative-director — 2226 passed. useVideoFileSrc.test.js now drives a by-id mock that rejects with a 404-shaped error for an unknown id (matching what request() really does), and adds a guard that the hook asks for exactly one id and never the whole list.
  • cd client && npx biome lint --error-on-warnings on the touched files — clean.

Closes #4165

…nstead of downloading the whole list (#4165)

A history entry's id is not its filename stem: videoGen/local.js names a clip
<jobId>.mp4, but the timeline renderer mints timeline-<project>-<ts>.mp4 beside
an independent randomUUID() id. So a Creative Director finalVideoId can only be
resolved through the stored filename — and with no by-id endpoint, useVideoFileSrc
pulled the ENTIRE render history to find one row, on four surfaces (CD cards, CD
Overview, EpisodeVideoStage, MusicVideo).

Adds getHistoryItem() as the single-entry read in videoGen/history.js and the
route on top of it, 404ing cleanly for an unknown id. The id is validated as a
length-capped string rather than with the UUID historyIdSchema: entries also
arrive with a caller-supplied download id and from federated peers, so a .guid()
gate would 400 rows that are legitimately in the list. Nothing is interpolated
into a path.

ScenePreview keeps its <jobId>.mp4 reconstruction on purpose. Its remaining
reconstructing caller is SegmentsTab, whose per-scene clip renders ARE named
after their job; resolving internally would turn one render into N by-id
requests per treatment and buy nothing for those ids.
@atomantic
atomantic merged commit 91eaa85 into main Aug 15, 2026
7 checks passed
@atomantic
atomantic deleted the claim/issue-4165 branch August 15, 2026 05:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a by-id video-gen history resolver endpoint (retire full-list fetches)

1 participant