Plan 001: Return image bytes when capturing a generative preview
Executor: follow every step and verification gate. Modify only listed files. Stop and report if a STOP condition occurs.
Status
- Priority: P1
- Effort: S
- Risk: LOW
- Depends on: none
- Category: bug
- Planned at: commit
c16bc4f, 2026-07-16
Why this matters
getCaptureImageFile fetches an image, but passes the Response object itself to File instead of its body bytes. Capture/download consumers can therefore receive a file whose contents are not the fetched PNG.
Current state
app/utils/generativePreview.ts:27-40, getCaptureImageFile, validates the hash and calls fetch(imageDataPayload.image).
- Line 40 constructs
new File([res], 'image.png', ...); res is a Response, not a Blob or ArrayBuffer.
- The function is a focused utility and has no visible regression test beside the existing utility test files.
Commands and expected results
| Purpose |
Command |
Expected result |
| Tests |
pnpm run test -- app/utils/generativePreview.spec.ts |
New capture tests pass |
| Lint |
pnpm run lint -- app/utils/generativePreview.ts |
exit 0 |
| Full tests |
pnpm run test |
all tests pass |
Scope
In scope:
app/utils/generativePreview.ts
- A colocated test file for capture behavior
Out of scope:
- Image-provider routing and hash generation semantics
- UI changes to capture controls
Steps
Step 1: Consume the response body
Use await response.blob() (or an equivalent byte-oriented body method) and preserve the response failure behavior by checking response.ok or mapping failures to the existing error. Keep the existing hash mismatch guard.
Verify: Add a test with a mocked successful Response and assert the returned File has the expected byte size/type.
Step 2: Cover failed fetches and mismatched hashes
Add regression cases for a mismatched hash and an unsuccessful response. Keep error messages actionable and avoid logging image contents.
Verify: pnpm run test -- app/utils/generativePreview.spec.ts → all cases pass.
Test plan
- Valid matching hash returns a PNG file containing the response body.
- Hash mismatch rejects before fetch.
- Non-OK response rejects or follows the chosen documented error contract.
Done criteria
STOP conditions
- Current response/file API behavior differs materially from the evidence.
- A test requires browser-only APIs unavailable under the repository's Vitest setup without an approved test-environment change.
Maintenance notes
Keep this utility's network error behavior stable for callers unless tests demonstrate that callers already distinguish error types.
Issue body
Bug Description
getCaptureImageFile passes a fetched Response object directly to File, so generated preview capture may not contain the downloaded image bytes.
Evidence
app/utils/generativePreview.ts:39-40
const res = (await fetch(...)) as any
new File([res], 'image.png', { type: 'image/png' })
Expected Behavior
The returned File contains the response body bytes and has the expected image type.
Acceptance Criteria
- Add regression tests for successful capture, hash mismatch, and failed response.
- Use
response.blob() or equivalent byte extraction.
pnpm run test and pnpm run lint pass.
Environment
- Repository:
chaotic-art/app
- Baseline:
c16bc4f
- Node: package declares 24.x; audit host had Node 22.22.1
- pnpm was unavailable during audit
Plan 001: Return image bytes when capturing a generative preview
Status
c16bc4f, 2026-07-16Why this matters
getCaptureImageFilefetches an image, but passes theResponseobject itself toFileinstead of its body bytes. Capture/download consumers can therefore receive a file whose contents are not the fetched PNG.Current state
app/utils/generativePreview.ts:27-40,getCaptureImageFile, validates the hash and callsfetch(imageDataPayload.image).new File([res], 'image.png', ...);resis aResponse, not aBloborArrayBuffer.Commands and expected results
pnpm run test -- app/utils/generativePreview.spec.tspnpm run lint -- app/utils/generativePreview.tspnpm run testScope
In scope:
app/utils/generativePreview.tsOut of scope:
Steps
Step 1: Consume the response body
Use
await response.blob()(or an equivalent byte-oriented body method) and preserve the response failure behavior by checkingresponse.okor mapping failures to the existing error. Keep the existing hash mismatch guard.Verify: Add a test with a mocked successful
Responseand assert the returnedFilehas the expected byte size/type.Step 2: Cover failed fetches and mismatched hashes
Add regression cases for a mismatched hash and an unsuccessful response. Keep error messages actionable and avoid logging image contents.
Verify:
pnpm run test -- app/utils/generativePreview.spec.ts→ all cases pass.Test plan
Done criteria
ResponseobjectSTOP conditions
Maintenance notes
Keep this utility's network error behavior stable for callers unless tests demonstrate that callers already distinguish error types.
Issue body
Bug Description
getCaptureImageFilepasses a fetchedResponseobject directly toFile, so generated preview capture may not contain the downloaded image bytes.Evidence
app/utils/generativePreview.ts:39-40const res = (await fetch(...)) as anynew File([res], 'image.png', { type: 'image/png' })Expected Behavior
The returned
Filecontains the response body bytes and has the expected image type.Acceptance Criteria
response.blob()or equivalent byte extraction.pnpm run testandpnpm run lintpass.Environment
chaotic-art/appc16bc4f