Files
mercadodevida/work/artifacts/F-068/implementer.md
2026-08-19 17:36:09 +02:00

4.3 KiB

F-068 — Implementer evidence

Scope delivered

F-067 had replaced aspect-[5/7] max-h-72 with w-full max-h-72. The container filled the card width but the height was only bounded by max-h, so all cards ended up roughly the same height as max-h-72 (288 px) regardless of the source image aspect — they looked "flat". The product description was still rendered as plain text via {product.description}, so any HTML markup that the admin now types into the WYSIWYG editor (F-064) shows up as literal tags.

Change

Both defects share one fix: a stable square aspect ratio for the listing cards and a prose HTML renderer for the description.

project/frontend/src/app/products/page.tsx, project/frontend/src/app/brands/[slug]/page.tsx, project/frontend/src/app/search/page.tsx, project/frontend/src/app/categories/[slug]/page.tsx, project/frontend/src/components/home/FeaturedProducts.tsx

- <div className="relative w-full max-h-72 bg-white flex items-center justify-center overflow-hidden">
+ <div className="relative aspect-square w-full bg-white flex items-center justify-center overflow-hidden">
    {product.images?.[0] ? (
      <Image … fill className="object-contain" … />
    ) : (
      <span className="text-5xl">🌿</span>
    )}
  </div>
- <p className="text-gray-500 text-xs mt-1 line-clamp-2">{product.description}</p>
+ {product.description && (
+   <div
+     className="text-gray-500 text-xs mt-1 line-clamp-2 prose prose-xs max-w-none"
+     dangerouslySetInnerHTML={{ __html: product.description }}
+   />
+ )}

The container now enforces aspect-square so the card height always equals the card width (square cards in the 4-col grid). The image keeps object-contain, so non-square sources still display without distortion — they're letterboxed inside a square.

The description is rendered via dangerouslySetInnerHTML so HTML tags typed into the WYSIWYG editor (F-062 / F-064) are interpreted on the listing cards. prose prose-xs adds the small-typography Tailwind prose styles (paragraph spacing, list bullets, bold/italic) without needing extra CSS.

Acceptance traceability

Acceptance criterion How it is met
Each listing card has a 1:1 aspect ratio and the image fits inside without distortion aspect-square w-full enforces a square; object-contain keeps the visual inside without cropping.
Product description renders HTML tags on listing cards dangerouslySetInnerHTML on the description; prose prose-xs styles the inline HTML.
Existing products without HTML still display Plain text descriptions render unchanged through dangerouslySetInnerHTML (text nodes only).
verify.sh is green Exit 0.

Manual verification

$ curl -X PATCH /api/products/13a65dc0-… \
    -d '{"description":"<p>Proteína <strong>ecológica</strong> de alta calidad.</p><ul><li>Vegana</li><li>Sin gluten</li></ul>"}'
{ … description: "<p>Proteína <strong>ecológica</strong> … </ul>" … }

$ curl http://192.168.18.93:3003/products | grep -oE 'prose[^"]*"[^>]*>[^<]*'
prose prose-xs max-w-none">Aceite de Oliva Virgen Extra Bio…
prose prose-xs max-w-none">Almendras Crudas Ecologicas…
… (one per card)

# Spot-check the HTML-rendered card
$ curl … | grep -oE 'Protei.*?</div>' | head -1
Proteina Guisante Ecologica</h3><div class="text-gray-500 text-xs mt-1 line-clamp-2 prose prose-xs max-w-none"><p>Proteína <strong>ecológica</strong> de alta calidad.</p><ul><li>Vegana</li><li>Sin gluten</li></ul></div>

The description was restored to its original plain text after the test.

Build verification

  • npx tsc --noEmit (frontend) — exit 0
  • monolith.sh prod restart frontend → 200
  • ./scripts/verify.sh — exit 0

Files touched

project/frontend/src/app/products/page.tsx                                          (aspect-square + dangerouslySetInnerHTML)
project/frontend/src/app/brands/[slug]/page.tsx                                     (aspect-square + dangerouslySetInnerHTML)
project/frontend/src/app/search/page.tsx                                            (aspect-square + dangerouslySetInnerHTML)
project/frontend/src/app/categories/[slug]/page.tsx                                 (aspect-square + dangerouslySetInnerHTML)
project/frontend/src/components/home/FeaturedProducts.tsx                           (aspect-square + dangerouslySetInnerHTML)