feat(F-068): completed feature

This commit is contained in:
chattie
2026-08-19 17:36:09 +02:00
parent f62bd6a578
commit ba2ac939c7
12 changed files with 235 additions and 23 deletions

View File

@@ -0,0 +1,96 @@
# 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
- <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)
```

View File

@@ -0,0 +1,13 @@
{
"feature_id": "F-068",
"agent": "leader",
"verdict": "APPROVED",
"summary": "All gates approved. Closing F-068.",
"evidence": [
"work/artifacts/F-068/reviewer.json verdict=APPROVED",
"work/artifacts/F-068/security.json verdict=APPROVED",
"work/artifacts/F-068/qa.json verdict=APPROVED",
"./scripts/verify.sh exit 0"
],
"timestamp": "2026-08-19T15:45:00Z"
}

View File

@@ -0,0 +1,14 @@
{
"feature_id": "F-068",
"agent": "qa",
"verdict": "APPROVED",
"summary": "End-to-end trace. Cards have aspect-square so they look uniform; description renders HTML; verify.sh passes.",
"evidence": [
"AC1 'Each listing card has a 1:1 aspect ratio and the image fits inside without distortion' — aspect-square w-full in HTML; image renders with object-contain",
"AC2 'Product description renders HTML tags on listing cards' — curl PATCH with HTML desc → public page shows the HTML inside the prose container",
"AC3 'Existing products without HTML still display' — plain text passes through dangerouslySetInnerHTML unchanged",
"AC4 'verify.sh is green' — exit 0",
"Regression: typecheck green; frontend 200"
],
"timestamp": "2026-08-19T15:45:00Z"
}

View File

@@ -0,0 +1,18 @@
{
"feature_id": "F-068",
"agent": "reviewer",
"verdict": "APPROVED",
"summary": "Five listing-card pages now use aspect-square (1:1) so all cards line up uniformly, and the description is rendered via dangerouslySetInnerHTML with prose-xs styling so admin-typed HTML tags appear in the card preview.",
"evidence": [
"git diff project/frontend/src/app/products/page.tsx — aspect-square + dangerouslySetInnerHTML + prose-xs",
"git diff project/frontend/src/app/brands/[slug]/page.tsx — same",
"git diff project/frontend/src/app/search/page.tsx — same",
"git diff project/frontend/src/app/categories/[slug]/page.tsx — same",
"git diff project/frontend/src/components/home/FeaturedProducts.tsx — same",
"curl /products shows aspect-square w-full in HTML for every card",
"curl PATCH /api/products/13a65dc0-… with HTML description → public /products renders the HTML",
"npx tsc --noEmit (frontend) — exit 0",
"./scripts/verify.sh — exit 0"
],
"timestamp": "2026-08-19T15:45:00Z"
}

View File

@@ -0,0 +1,14 @@
{
"feature_id": "F-068",
"agent": "security",
"verdict": "APPROVED",
"summary": "dangerouslySetInnerHTML is now used on the listing-card description. The description was already writable via the admin editor and was already rendered via dangerouslySetInnerHTML on the product detail page (F-064); the same string is now also rendered on the listing cards. The admin role is is the only role that can PATCH product descriptions, so the source of HTML is fully gated.",
"evidence": [
"PATCH /api/products/:id requires admin role (unchanged)",
"The admin product editor (F-062 / F-064) is the only path that produces HTML on the description",
"dangerouslySetInnerHTML is also used on the product detail page (F-064) — same string source",
"Public endpoint GET /products/search is read-only",
"No new endpoints, no new dependencies, no new env vars"
],
"timestamp": "2026-08-19T15:45:00Z"
}