Files
mercadodevida/work/artifacts/F-059/implementer.md
2026-08-19 15:32:56 +02:00

7.6 KiB
Raw Permalink Blame History

F-059 — Implementer evidence

Scope delivered

Product images were being cropped whenever they were not square. The root cause spanned the backend resize (no max-height cap, so extreme aspects broke layouts) and the frontend CSS (every product image container forced aspect-square and used object-cover, which silently clipped non-square photos). The cached thumbnails themselves were already aspect-preserving (width-bound), but the frontend never trusted them — it tried to force the image into a square box.

Changes

Backend — bounded, aspect-preserving resize

In all four upload/thumbnail locations:

  • project/apps/admin/src/app/api/upload/route.ts
  • 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

The sharp().resize(...) call now uses fit: 'inside' together with a max-height bound:

const MAX_HEIGHT_RATIO = 1.4; // max 5:7 aspect
const maxHeight = Math.round(width * MAX_HEIGHT_RATIO);
sharp(buffer)
  .resize({ width, height: maxHeight, fit: 'inside', withoutEnlargement: true })
  .toBuffer();

This produces thumbnails that:

  • Preserve the source aspect ratio (no distortion, no cropping)
  • Fit inside a bounded box (max width × width * 1.4)
  • Stay under the size cap that the frontend container can display

Frontend — object-contain and bounded containers

Every product image container was rewritten. The aspect-square / object-cover pattern is gone; instead each container defines a bounded box (aspect-[5/7] max-h-…) and the image uses object-contain so the natural aspect ratio is preserved.

File Container before Container after
project/frontend/src/app/products/page.tsx aspect-square relative + object-cover aspect-[5/7] max-h-72 + object-contain
project/frontend/src/app/products/[slug]/page.tsx aspect-square max-h-[500px] + object-cover aspect-[5/7] max-h-[500px] + object-contain
project/frontend/src/app/brands/[slug]/page.tsx aspect-square + object-cover aspect-[5/7] max-h-72 + object-contain
project/frontend/src/app/search/page.tsx aspect-square + object-cover aspect-[5/7] max-h-72 + object-contain
project/frontend/src/app/categories/[slug]/page.tsx aspect-square + object-cover aspect-[5/7] max-h-72 + object-contain
project/frontend/src/components/home/FeaturedProducts.tsx aspect-square + object-cover aspect-[5/7] max-h-72 + object-contain
project/frontend/src/components/checkout/CheckoutClient.tsx 48×48 object-cover 40×56 object-contain, served from /uploads/40/
project/frontend/src/components/cart/CartPageContent.tsx 80×80 object-cover w-20 max-h-28, 80×112 object-contain, served from /uploads/40/
project/frontend/src/components/layout/Header.tsx 40×40 object-cover 40×56 object-contain, served from /uploads/40/
project/apps/admin/src/app/(dashboard)/products/page.tsx 40×40 object-cover w-10 h-14 wrapper, 40×56 object-contain, served from /uploads/40/
project/apps/admin/src/features/products/components/sections/ImagesSection.tsx w-full aspect-square + object-cover aspect-[5/7] max-h-72 + object-contain, served from /uploads/200/
project/storefront/src/app/productos/[slug]/page.tsx aspect-[4/3] max-h-[600px] object-cover max-h-[600px] object-contain
project/storefront/src/components/product-card.tsx aspect-[4/3] parent, child object-cover aspect-[5/7] max-h-72 parent, child object-contain

All listing cards now use the same aspect-[5/7] max-h-72 container, so visual rhythm stays consistent regardless of the source aspect.

Acceptance traceability

Acceptance criterion How it is met
Backend resize uses fit: 'inside' so cached thumbnail is bounded by max-width and max-height without crop All four resize sites use fit: 'inside' with MAX_HEIGHT_RATIO = 1.4.
40px and 200px thumbnails on disk are never square-cropped from a non-square source Verified by uploading 600×800, 400×1600, and 800×400 sources. Cached thumbnails: 40×53, 14×56, 40×20 (40px thumb) and 200×267, 70×280, 200×100 (200px thumb). Aspect preserved, never cropped.
Frontend product images render with object-contain and aspect-auto, no clipping at any viewport Every product image container uses object-contain (or object-contain on the inner <img>). Container aspect ratio is 5/7 max (or removed entirely on storefront detail), and overflow-hidden keeps the box.
Admin product list table shows the full image (not cropped to square) w-10 h-14 container with object-contain inside. The 40px thumbnail's natural 40×53 is fully visible.
verify.sh is green Exit 0.

Manual verification (with the running dev stack)

Source 600×800  (portrait)
   40px thumb → 40×53 (aspect 3:4 preserved, fits inside 40×56)
   200px thumb → 200×267 (aspect 3:4 preserved, fits inside 200×280)

Source 400×1600 (extreme portrait)
   40px thumb → 14×56 (height capped at 56, width computed from aspect)
   200px thumb → 70×280 (height capped at 280)

Source 800×400  (landscape)
   40px thumb → 40×20
   200px thumb → 200×100

Source 800×800  (square, no change)
   40px thumb → 40×40
   200px thumb → 200×200
$ curl http://192.168.18.93:3003/products | grep -oE "object-cover|aspect-square|object-contain|aspect-\[5/7\]"
aspect-[5/7]
object-contain

No object-cover or aspect-square strings left in the rendered HTML.

Build verification

  • npm run typecheck (project/) — exit 0
  • npx tsc --noEmit (apps/admin, frontend, storefront) — exit 0
  • ./scripts/verify.sh — exit 0
  • All services up (backend 3000, frontend 3003, admin 3004, storefront 3005)

Files touched

project/apps/admin/src/app/api/upload/route.ts                                            (modified)
project/apps/admin/src/app/uploads/[...path]/route.ts                                     (modified)
project/apps/admin/src/app/(dashboard)/products/page.tsx                                  (modified)
project/apps/admin/src/features/products/components/sections/ImagesSection.tsx            (modified)
project/frontend/src/app/uploads/[...path]/route.ts                                       (modified)
project/frontend/src/app/products/page.tsx                                                (modified)
project/frontend/src/app/products/[slug]/page.tsx                                         (modified)
project/frontend/src/app/brands/[slug]/page.tsx                                           (modified)
project/frontend/src/app/search/page.tsx                                                  (modified)
project/frontend/src/app/categories/[slug]/page.tsx                                       (modified)
project/frontend/src/components/home/FeaturedProducts.tsx                                 (modified)
project/frontend/src/components/layout/Header.tsx                                         (modified)
project/frontend/src/components/checkout/CheckoutClient.tsx                               (modified)
project/frontend/src/components/cart/CartPageContent.tsx                                  (modified)
project/storefront/src/app/uploads/[...path]/route.ts                                     (modified)
project/storefront/src/app/productos/[slug]/page.tsx                                      (modified)
project/storefront/src/components/product-card.tsx                                        (modified)