Maestro task: 8a432108-d3ba-4013-ae8c-11963981d641 (Feature 10 - Recipe Grounding and Photos).
Done-condition
recipes.CachePhoto(url) downloads via the safe Fetcher (image content types only, 8 MB image cap), decodes JPEG/PNG/WebP/GIF (first frame), rejects images <300 px on the short side, scales to 1200 px and 400 px on the long side preserving aspect ratio (golang.org/x/image/draw), encodes JPEG quality 82, stores under $MEALQUEST_DATA_DIR/media/photos/{id}-{size}.jpg, dedupes by SHA-256 of original bytes and records a photos row. GET /media/photos/{id}-{400|1200}.jpg (session required) serves with Cache-Control "private, max-age=31536000, immutable"; unknown -> 404. Tests cover each format, too-small rejection, dedupe, sizes and endpoint headers. Go quality floor passes.
Evidence
New photo cache + /media endpoint; migration/sqlc as needed; golang.org/x/image (approved) added for draw.
gofmt/vet/go test -race/build pass; sqlc-check no drift.
Note: the developer observed the existing test TestFetchCanceledWaitHoldsNoSlot (from task f4414e41) failing with EOF under heavy CPU load; it also fails on base 2fb7807, so it is a pre-existing flaky test, not caused here.
Maestro task: 8a432108-d3ba-4013-ae8c-11963981d641 (Feature 10 - Recipe Grounding and Photos).
## Done-condition
recipes.CachePhoto(url) downloads via the safe Fetcher (image content types only, 8 MB image cap), decodes JPEG/PNG/WebP/GIF (first frame), rejects images <300 px on the short side, scales to 1200 px and 400 px on the long side preserving aspect ratio (golang.org/x/image/draw), encodes JPEG quality 82, stores under $MEALQUEST_DATA_DIR/media/photos/{id}-{size}.jpg, dedupes by SHA-256 of original bytes and records a photos row. GET /media/photos/{id}-{400|1200}.jpg (session required) serves with Cache-Control "private, max-age=31536000, immutable"; unknown -> 404. Tests cover each format, too-small rejection, dedupe, sizes and endpoint headers. Go quality floor passes.
## Evidence
- New photo cache + /media endpoint; migration/sqlc as needed; golang.org/x/image (approved) added for draw.
- gofmt/vet/go test -race/build pass; sqlc-check no drift.
Note: the developer observed the existing test TestFetchCanceledWaitHoldsNoSlot (from task f4414e41) failing with EOF under heavy CPU load; it also fails on base 2fb7807, so it is a pre-existing flaky test, not caused here.
recipes.PhotoCache.CachePhoto downloads an image through the safe
Fetcher's new FetchImage (JPEG/PNG/WebP/GIF content types only, 8 MB
cap, uncached), decodes it (GIF first frame), refuses images under
300 px on the short side, scales to 1200 px and 400 px on the long side
with golang.org/x/image/draw, encodes JPEG quality 82 under
$MEALQUEST_DATA_DIR/media/photos/{id}-{size}.jpg, deduplicates by the
SHA-256 of the original bytes and records a photos row.
GET /media/photos/{id}-{400|1200}.jpg serves a stored size behind the
session with Cache-Control "private, max-age=31536000, immutable";
anything unknown is 404.
Adds golang.org/x/image v0.45.0 (approved; v0.46.0 needs Go 1.26).
Maestro task: 8a432108-d3ba-4013-ae8c-11963981d641
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
recipes.PhotoCache.CachePhoto downloads an image through the safe
Fetcher's new FetchImage (JPEG/PNG/WebP/GIF content types only, 8 MB
cap, uncached), decodes it (GIF first frame), refuses images under
300 px on the short side, scales to 1200 px and 400 px on the long side
with golang.org/x/image/draw, encodes JPEG quality 82 under
$MEALQUEST_DATA_DIR/media/photos/{id}-{size}.jpg, deduplicates by the
SHA-256 of the original bytes and records a photos row.
GET /media/photos/{id}-{400|1200}.jpg serves a stored size behind the
session with Cache-Control "private, max-age=31536000, immutable";
anything unknown is 404.
Adds golang.org/x/image v0.45.0 (approved; v0.46.0 needs Go 1.26).
Maestro task: 8a432108-d3ba-4013-ae8c-11963981d641
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
decodePhoto checked the GIF logical screen size, but gif.Decode returns
the first frame, which may be smaller; a 400x400 screen with a 1x1 first
frame passed and was scaled. The short-side check now measures the
decoded image's bounds. The pixel cap still comes from the header, before
decoding.
OpenPhoto served any file whose name was a canonical ULID and stored
size, with no photos row required. It now looks the id up with the new
GetPhoto query and reports an id with no row as fs.ErrNotExist, so
GET /media/photos/{id}-{size}.jpg is 404 for it even when a file exists.
OpenPhoto takes the request context for the lookup.
The media test fixture records a photos row for its served photo and
adds an orphan photo whose files exist without a row; both its sizes
must be 404.
Maestro task: 8a432108-d3ba-4013-ae8c-11963981d641
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Maestro task: 8a432108-d3ba-4013-ae8c-11963981d641 (Feature 10 - Recipe Grounding and Photos).
Done-condition
recipes.CachePhoto(url) downloads via the safe Fetcher (image content types only, 8 MB image cap), decodes JPEG/PNG/WebP/GIF (first frame), rejects images <300 px on the short side, scales to 1200 px and 400 px on the long side preserving aspect ratio (golang.org/x/image/draw), encodes JPEG quality 82, stores under $MEALQUEST_DATA_DIR/media/photos/{id}-{size}.jpg, dedupes by SHA-256 of original bytes and records a photos row. GET /media/photos/{id}-{400|1200}.jpg (session required) serves with Cache-Control "private, max-age=31536000, immutable"; unknown -> 404. Tests cover each format, too-small rejection, dedupe, sizes and endpoint headers. Go quality floor passes.
Evidence
Note: the developer observed the existing test TestFetchCanceledWaitHoldsNoSlot (from task f4414e41) failing with EOF under heavy CPU load; it also fails on base
2fb7807, so it is a pre-existing flaky test, not caused here.recipes.PhotoCache.CachePhoto downloads an image through the safe Fetcher's new FetchImage (JPEG/PNG/WebP/GIF content types only, 8 MB cap, uncached), decodes it (GIF first frame), refuses images under 300 px on the short side, scales to 1200 px and 400 px on the long side with golang.org/x/image/draw, encodes JPEG quality 82 under $MEALQUEST_DATA_DIR/media/photos/{id}-{size}.jpg, deduplicates by the SHA-256 of the original bytes and records a photos row. GET /media/photos/{id}-{400|1200}.jpg serves a stored size behind the session with Cache-Control "private, max-age=31536000, immutable"; anything unknown is 404. Adds golang.org/x/image v0.45.0 (approved; v0.46.0 needs Go 1.26). Maestro task: 8a432108-d3ba-4013-ae8c-11963981d641 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>recipes.PhotoCache.CachePhoto downloads an image through the safe Fetcher's new FetchImage (JPEG/PNG/WebP/GIF content types only, 8 MB cap, uncached), decodes it (GIF first frame), refuses images under 300 px on the short side, scales to 1200 px and 400 px on the long side with golang.org/x/image/draw, encodes JPEG quality 82 under $MEALQUEST_DATA_DIR/media/photos/{id}-{size}.jpg, deduplicates by the SHA-256 of the original bytes and records a photos row. GET /media/photos/{id}-{400|1200}.jpg serves a stored size behind the session with Cache-Control "private, max-age=31536000, immutable"; anything unknown is 404. Adds golang.org/x/image v0.45.0 (approved; v0.46.0 needs Go 1.26). Maestro task: 8a432108-d3ba-4013-ae8c-11963981d641 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Independent Codex gpt-6-sol review: CHANGES_REQUIRED for this exact commit.
decodePhoto checked the GIF logical screen size, but gif.Decode returns the first frame, which may be smaller; a 400x400 screen with a 1x1 first frame passed and was scaled. The short-side check now measures the decoded image's bounds. The pixel cap still comes from the header, before decoding. OpenPhoto served any file whose name was a canonical ULID and stored size, with no photos row required. It now looks the id up with the new GetPhoto query and reports an id with no row as fs.ErrNotExist, so GET /media/photos/{id}-{size}.jpg is 404 for it even when a file exists. OpenPhoto takes the request context for the lookup. The media test fixture records a photos row for its served photo and adds an orphan photo whose files exist without a row; both its sizes must be 404. Maestro task: 8a432108-d3ba-4013-ae8c-11963981d641 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Independent Codex gpt-6-sol review: APPROVED for this exact commit.