Files
mercadodevida/work/artifacts/F-056/implementer.md
2026-08-19 15:10:09 +02:00

3.9 KiB

F-056 — Implementer evidence

Scope delivered

Two distinct defects were addressed in this branch:

  1. Uploads 404 / 502 on freshly uploaded files. Next.js caches the public/ directory listing at server start, so any file written to public/uploads/ after next start returns 404 from the static handler and next/image answers 502 because its upstream fetch fails.
  2. Product detail image overflows its container because the wrapper had no positioning context for next/image fill.

Changes

1. Dynamic uploads route (per app)

Three new route handlers were added — one per Next.js frontend (apps/admin, frontend, storefront). Each reads uploads from the local public/uploads/ and the canonical apps/admin/public/uploads/ directory on every request, so a freshly uploaded file resolves without restart or rebuild:

  • project/apps/admin/src/app/uploads/[...path]/route.ts
  • project/frontend/src/app/uploads/[...path]/route.ts
  • project/storefront/src/app/uploads/[...path]/route.ts

Each handler:

  • Sets dynamic = 'force-dynamic' so Next.js does not cache the route at build.
  • Validates the path against SAFE_SEGMENT = /^[A-Za-z0-9._-]+$/ to block traversal.
  • Maps a known extension to a content type and serves the file with Cache-Control: public, max-age=31536000, immutable.
  • Generates the 40 and 200 thumbnails on demand via sharp and caches them on disk, so /uploads/40/<file> and /uploads/200/<file> resolve without requiring generate-thumbnails.sh.

2. Inline thumbnail generation on upload

project/apps/admin/src/app/api/upload/route.ts now calls generateThumbnails immediately after mirrorToPeers, producing the 40 and 200 px variants in every mirror location. Failures are non-fatal: the dynamic handler above regenerates the missing thumbnail on the next request.

3. Product detail image container

project/frontend/src/app/products/[slug]/page.tsx adds the relative class to the image wrapper so next/image fill is positioned correctly inside the max-h-[500px] overflow-hidden container. The image stays inside its box on all viewports.

Acceptance traceability

Acceptance criterion How it is met
New upload file loads 200 without server restart Dynamic /uploads/[...path]/route.ts reads from disk per request. Verified with curl on a freshly uploaded file.
Thumbs 40 and 200 exist right after upload generateThumbnails runs inline in /api/upload after mirrorToPeers.
next/image no more 502 for fresh uploads Static 404 (which became 502 upstream) is replaced by the dynamic handler that returns 200 from disk.
Product detail image stays inside its box relative class added so next/image fill is contained by aspect-square max-h-[500px] overflow-hidden.

Commands run during build

  • pnpm --filter ./project/apps/admin exec tsc --noEmit — typecheck pass.
  • pnpm --filter ./project/frontend exec tsc --noEmit — typecheck pass.
  • pnpm --filter ./project/storefront exec tsc --noEmit — typecheck pass.
  • ./scripts/verify.sh — exit code 0.
  • Manual curl: /uploads/40/8223b962-642c-4c95-bc2e-37c2d2fad4ac.jpg → HTTP 200, 568 bytes.

Files touched

project/apps/admin/src/app/api/upload/route.ts              (modified)
project/apps/admin/src/app/uploads/[...path]/route.ts       (new)
project/frontend/src/app/uploads/[...path]/route.ts         (new)
project/frontend/src/app/products/[slug]/page.tsx           (modified)
project/storefront/src/app/uploads/[...path]/route.ts       (new)

Artifacts produced

  • project/apps/admin/public/uploads/40/<file>.jpg — generated inline by upload route.
  • project/frontend/public/uploads/40/<file>.jpg — generated inline by upload route.
  • project/storefront/public/uploads/40/<file>.jpg — generated inline by upload route.

Status

Build completed. Ready for reviewer/security/qa gates.