feat(F-070): completed feature

This commit is contained in:
chattie
2026-08-19 18:21:01 +02:00
parent 60a567d22f
commit 4b12cacd65
11 changed files with 302 additions and 59 deletions

View File

@@ -0,0 +1,70 @@
# F-070 — Implementer evidence
## Problem
Product attributes (bio, vegano, sin-gluten, etc.) are returned by the backend
`serializeProduct` (already includes `attributes: product.attributes`) but the
frontend `Product` type omits the field and the product detail page never
renders them. Customers see no attribute badges on `/products/[slug]`.
## Root cause
1. `project/frontend/src/types/api.ts``Product` interface missing `attributes` field.
2. `project/frontend/src/app/products/[slug]/page.tsx` — no attributes rendering section.
Backend already ships `PRODUCT_ATTRIBUTES` enum (16 values) in
`project/src/modules/catalog/domain/product.ts` and `serializeProduct`
returns the array. No backend changes needed.
## Changes
### Type definition
`project/frontend/src/types/api.ts`
- Added `PRODUCT_ATTRIBUTES` const array (16 values matching backend).
- Added `ProductAttribute` type = `(typeof PRODUCT_ATTRIBUTES)[number]`.
- Added `attributes?: ProductAttribute[]` to the `Product` interface.
### Attribute labels
`project/frontend/src/lib/product-attributes.ts` (new)
- `ATTRIBUTE_LABELS: Record<ProductAttribute, string>` — Spanish human-readable
labels for each attribute slug (bio → "Eco", vegano → "Vegano", etc.).
- `getAttributeLabel(attr)` helper with fallback to raw slug.
### Badge component
`project/frontend/src/components/product/ProductAttributes.tsx` (new)
- Renders attributes as `inline-flex` rounded-full badges with emerald styling.
- Returns `null` when no attributes (no DOM noise).
- Accessible: `title` attribute on each badge = full label.
### Page integration
`project/frontend/src/app/products/[slug]/page.tsx`
- Import `ProductAttributes`.
- Render `<ProductAttributes attributes={product.attributes} />` right after
the product `<h1>` title, before the price section.
## Build verification
- `npx tsc --noEmit` (frontend) — exit 0
- `npx eslint` on all 4 changed files — exit 0
- `./scripts/verify.sh` — exit 0
## Files touched
```
project/frontend/src/types/api.ts (modified — added ProductAttribute type + attributes field)
project/frontend/src/lib/product-attributes.ts (new — attribute label map + helper)
project/frontend/src/components/product/ProductAttributes.tsx (new — badge component)
project/frontend/src/app/products/[slug]/page.tsx (modified — render badges after title)
```
## Acceptance traceability
| Acceptance criterion | How it is met |
| -------------------- | ------------- |
| Product detail page renders product attributes as badges | `ProductAttributes` component renders `ATTRIBUTE_LABELS[attr]` as emerald badges on `page.tsx` |
| No regression on existing product pages | `attributes` is optional (`?`); pages without attributes render `null` — no layout change |
| `verify.sh` is green | Exit 0 |

View File

@@ -0,0 +1,15 @@
{
"feature_id": "F-070",
"agent": "leader",
"verdict": "APPROVED",
"summary": "All gates approved. F-070 adds product attribute badges to the frontend product detail page. Build, typecheck, lint, and verify.sh are all green. Closing F-070.",
"evidence": [
"work/artifacts/F-070/reviewer.json verdict=APPROVED",
"work/artifacts/F-070/security.json verdict=APPROVED",
"work/artifacts/F-070/qa.json verdict=APPROVED",
"npx tsc --noEmit (frontend) — exit 0",
"npx eslint on all 4 changed files — exit 0",
"./scripts/verify.sh — exit 0"
],
"timestamp": "2026-08-19T16:22:00Z"
}

View File

@@ -0,0 +1,16 @@
{
"feature_id": "F-070",
"agent": "qa",
"verdict": "APPROVED",
"summary": "Attributes are now typed and rendered as badges on the frontend product detail page. The backend already returns attributes via serializeProduct; the frontend Product type now includes attributes?: ProductAttribute[] and the page renders them via the ProductAttributes component. Type-safety verified: ATTRIBUTE_LABELS is a complete Record<ProductAttribute, string> covering all 16 backend enum values. No-regression: attributes is optional, component returns null when empty, existing pages without attributes are unaffected. Typecheck, lint, and verify.sh all pass.",
"evidence": [
"AC1 'Product detail page renders product attributes as badges' — ProductAttributes component renders emerald rounded-full badges from product.attributes after the h1 title; each badge has accessible title and visible label",
"AC2 'No regression on existing product pages' — attributes field is optional (?); ProductAttributes returns null when undefined or empty array; product pages without attributes have identical layout to before; typecheck green",
"AC3 'verify.sh is green' — exit 0",
"Type completeness: ATTRIBUTE_LABELS is Record<ProductAttribute, string> — TypeScript would error if any of the 16 backend PRODUCT_ATTRIBUTES values lacks a label (no error → complete coverage)",
"Backend: serializeProduct already returns attributes: product.attributes (verified in catalog.routes.ts:649) — no backend changes needed",
"Frontend typecheck: npx tsc --noEmit — exit 0",
"Frontend lint: npx eslint on all 4 changed files — exit 0"
],
"timestamp": "2026-08-19T16:21:30Z"
}

View File

@@ -0,0 +1,17 @@
{
"feature_id": "F-070",
"agent": "reviewer",
"verdict": "APPROVED",
"summary": "Product attributes now flow from backend serializeProduct through the typed frontend Product interface and render as accessible badges on the product detail page. Type-safe: PRODUCT_ATTRIBUTES const matches backend domain enum; ProductAttribute type is derived from it. The Product interface gains attributes?: ProductAttribute[] (optional, no breaking change). ATTRIBUTE_LABELS provides Spanish labels for all 16 attribute values. ProductAttributes component returns null when no attributes, preserving existing page layout. Integration is a single self-closing tag after the h1. Typecheck and lint pass green.",
"evidence": [
"git diff project/frontend/src/types/api.ts — added PRODUCT_ATTRIBUTES const, ProductAttribute type, attributes field on Product interface",
"git diff project/frontend/src/app/products/[slug]/page.tsx — import ProductAttributes + render after title",
"git diff project/frontend/src/components/product/ProductAttributes.tsx — new badge component (object-contain, accessible title attr)",
"git diff project/frontend/src/lib/product-attributes.ts — new label map matching all 16 backend PRODUCT_ATTRIBUTES values",
"npx tsc --noEmit (frontend) — exit 0, no type errors",
"npx eslint on all 4 files — exit 0",
"./scripts/verify.sh — exit 0",
"Backend already returns attributes via serializeProduct (product.attributes — no backend changes needed)"
],
"timestamp": "2026-08-19T16:20:30Z"
}

View File

@@ -0,0 +1,17 @@
{
"feature_id": "F-070",
"agent": "security",
"verdict": "APPROVED",
"summary": "Change is frontend-only: type definitions, a label map, and a badge rendering component. No backend, database, or auth changes. No new dependencies. No new API endpoints. No new env vars. No secrets introduced. The attributes field is read-only from the existing /productos/:slug API response. ProductAttributes component uses no dangerouslySetInnerHTML and no event handlers that could introduce XSS. The title attribute on badges is plain text from a static label map (no user-controlled input). verify.sh passes.",
"evidence": [
"No new dependencies (package.json unchanged — only TS types and a presentation component added)",
"No backend changes (serializeProduct already returns attributes; no new endpoints, no DB schema changes)",
"No auth/authz changes",
"No env vars introduced",
"ProductAttributes.tsx uses no dangerouslySetInnerHTML — attributes are plain text labels from static ATTRIBUTE_LABELS map",
"No user-controlled input flows into HTML; badge content is sourced from a TypeScript const assertion enum",
"No network calls in new code — data comes from existing typed API client response",
"verify.sh — exit 0"
],
"timestamp": "2026-08-19T16:21:00Z"
}