Skip to content

fix(review): serve cached frame-set images over the review server instead of file:// URLs - #257

Merged
gfargo merged 1 commit into
mainfrom
fix/review-frame-urls
Oct 6, 2026
Merged

gfargo merged 1 commit into
mainfrom
fix/review-frame-urls

Conversation

@gfargo

@gfargo gfargo commented Oct 6, 2026

Copy link
Copy Markdown
Owner

Symptom

Reviewing a PixelLab frame set in pixelkiln pick showed a broken image for every frame. The browser console logged one error per frame:

Not allowed to load local resource: file:///var/folders/.../T/pixelkiln-pixflux/db6e26b1-a1ca-4c14-9e93-ba504b4d3e7b-5.png

We hit this on an animate-skeleton revision (animate-with-skeleton-v3) in Super Disc Golf's throw pilot. Generation was fine: gen downloaded every frame and marked the entries downloaded. Only the review page was broken. The gallery's Review sheet goes through the same code, so it was broken the same way.

Cause

  • PixelLabProvider.pollAnimateRevision (src/providers/pixellab.ts) decodes inline base64 frames into cacheDir() ($TMPDIR/pixelkiln-pixflux). It reports them as file://<cacheDir>/<jobId>-<index>.png. That is right for fetch, because download reads file:// straight off disk. pollImagePro and pollUiElement do the same for their candidates.
  • prepareReview (src/pick/server.ts) copied state.frameUrls / state.candidateUrls into each group's frameUrls without changing them, and the sheet uses those as <img src>.
  • The sheet is served from http://127.0.0.1:<port>, and a browser won't let a page served over HTTP load a file:// image. Revision sources and current art were already served through the session's assets. Frames were not.

Fix

  • prepareReview now gives each file:// frame or candidate its own exact route, ${routePrefix}/review-frame/<group>/<index>, and adds it to session.assets. This is how revision-source and current-art already work. The content type comes from the file extension, or from the PNG/GIF signature if the extension is missing.
  • runPicker's review server and the gallery's /review/<job>/… handler both serve session.assets, so the CLI sheet and the gallery sheet (with routePrefix) are both fixed.
  • Hosted http(s) URLs, plus ComfyUI view URLs and Retro Diffusion data: URLs, still reach the sheet unchanged.
  • apply keeps the provider's own URLs for its sourceUrl fallback, so a review route never ends up in the lockfile.
  • Docs: docs/CLI.md (pick) now says PixelLab animations and frame-set revisions appear as an animated strip, and that locally cached frames are served by the review server. docs/LIBRARY.md says a host embedding the sheet must serve every route in session.assets.

No lockfile, manifest or provider-call changes, and no cost impact.

Tests

Both new tests fail on main and pass with the fix:

  • test/pixellab-revision.test.ts, "serves the decoded frames to the pick sheet over HTTP, never as file:// URLs": the real PixelLabProvider with a mocked animate-with-skeleton-v3 job runs submit, poll and runPicker. The page contains no file://, each /review-frame/0/<i> returns that frame's PNG bytes as image/png, an out-of-range index is a 404, and the accepted lock entry still records the provider's file:// sources.
  • test/pick.test.ts, "serves a candidate the provider cached on disk on its own route, never as a file:// URL": a candidates review with one file:// and one https:// candidate. With routePrefix: "/review/abc" the routes are prefixed. The hosted URL is unchanged and gets no route. The standalone server serves the cached bytes. apply records the file:// URL rather than the route.

I also checked it in a real browser by opening a file:// frame-set sheet in headless Chrome. On main there is one Not allowed to load local resource error per frame. With the fix, every <img> loads (naturalWidth > 0) and that error is gone. The only console error left is a 404 for /favicon.ico, which the review server has always returned.

Checks run

Local, Node v24.20.0, npm 11.19.0:

Check Result
npm run test:release pass
npm run typecheck pass
npm run test:docs pass
npm run test:security pass
npm test 979 passed, 3 failed (timeouts, see below)
npm run test:quality pass
npm run build pass
npm run test:package pass
npm run test:gallery (Chrome) pass
npm run test:godot (Godot 4.7.2) pass
npm run website:check / website:lint / website:build pass

The repo has no separate lint step beyond typecheck for the package.

Three npm test failures, none in files this PR touches: artifacts "writes a verifiable companion through the no-manifest CLI", hand-edit "is reachable from the CLI…", and workspace "list --json and status --json agree…". Each is a 5000 ms timeout on a test that spawns the CLI. The machine was at a load average of about 300 on 10 cores while they ran. An unmodified main run under the same load failed 5 tests the same way: these three plus quality-profile and revision. Re-running all five files on this branch with --testTimeout=60000 passes 69/69. CI on this PR is the real signal for them.

…tead of file:// URLs

A PixelLab frame set reviewed in `pick` (or the gallery's Review sheet)
showed nothing but broken images. The console logged one error per frame:

  Not allowed to load local resource: file:///var/folders/.../T/pixelkiln-pixflux/<job>-<n>.png

pollAnimateRevision decodes inline frames into the provider's temp cache
and reports them as file:// URLs, which is right for fetch: download reads
them straight off disk. prepareReview copied those URLs into each group's
frameUrls unchanged, though, and the sheet is served from
http://127.0.0.1, which a browser will not let load a file:// image.
The same was true of imagePro and uiElement candidates.

prepareReview now gives every file:// frame or candidate its own exact
route, `${routePrefix}/review-frame/<group>/<index>`, in the session's
assets with a content type from its extension (or its bytes), the same
way revision sources and current art are served. runPicker's review
server and the gallery's /review/<job>/ handler both serve session.assets,
so both sheets are covered. Hosted http(s) URLs pass through unchanged.
apply keeps the provider's own URLs, so a review route never reaches the
lockfile.
@vercel

vercel Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
pixelkiln Ready Ready Preview Oct 6, 2026 7:12pm UTC

@gfargo
gfargo merged commit a8d39c1 into main Oct 6, 2026
8 checks passed
@gfargo
gfargo deleted the fix/review-frame-urls branch October 6, 2026 21:42
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 0.86.3 🎉

The release is available on:

Your semantic-release bot 📦🚀

This branch was successfully deployed

1 active deployment
Preview — 76ec3d5b Deployed Oct 6, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant