# 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` ```diff -
+
{product.images?.[0] ? ( ) : ( 🌿 )}
-

{product.description}

+ {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":"

Proteína ecológica de alta calidad.

"}' { … description: "

Proteína ecológica … " … } $ 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.*?

' | head -1 Proteina Guisante Ecologica

Proteína ecológica de alta calidad.

``` 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) ```