5.0 KiB
F-066 — Implementer evidence
Scope delivered
Three separate defects surfaced together; all are addressed in this ticket.
-
Image invisible on
/products/[slug]. The container infrontend/src/app/products/[slug]/page.tsxwasrelative w-full max-h-[500px]with anext/imagefillchild.next/imagewithfillrequires a non-zero-height parent;max-his only a ceiling, so the parent collapsed to zero and the absolutely positioned image filled zero pixels. F-061 had removed theaspect-[5/7]constraint that used to give the box a real height (it caused a different centering problem). This fix restores theaspect-[5/7]inside a flex centering wrapper, so the box has a height and is centred horizontally inside its grid cell. -
PATCH /api/products/:idreturned 500 when the payload includedattributes. Theattributescolumn is JSONB, butPgProductRepository.updatewas binding the JS array directly viapg-node.pg-nodeserialises JS arrays as PG array literals ({bio,keto}); the JSONB parser rejects that withinvalid input syntax for type json. The fixJSON.stringifys the array and casts the parameter as::jsonb. -
serializeProductomittedchannels,featured, andattributes. The admin editor saved these values successfully but the next GET round-tripped an object without them, so the UI never reflected the change. Added the three fields toserializeProduct.
Changes
project/frontend/src/app/products/[slug]/page.tsx
- <div className="relative w-full max-h-[500px] bg-gray-50 rounded-2xl border border-gray-100 overflow-hidden">
- <Image fill className="object-contain" ... />
- </div>
+ <div className="flex justify-center">
+ <div className="relative w-full max-w-md aspect-[5/7] max-h-[500px] bg-gray-50 rounded-2xl border border-gray-100 overflow-hidden">
+ <Image fill className="object-contain" ... />
+ </div>
+ </div>
project/src/modules/catalog/infrastructure/pg-product-repository.ts
for (const [key, column] of UPDATABLE) {
if (key in patch) {
+ if (key === 'attributes' && Array.isArray(patch[key])) {
+ values.push(JSON.stringify(patch[key]));
+ setClauses.push(`${column} = $${values.length}::jsonb`);
+ } else {
values.push(patch[key]);
+ setClauses.push(`${column} = $${values.length}`);
+ }
}
}
project/src/modules/catalog/api/catalog.routes.ts
function serializeProduct(product, images = []) {
return {
…,
state: product.state,
+ channels: product.channels,
+ featured: product.featured,
+ attributes: product.attributes,
seoTitle: product.seoTitle,
…
};
}
Acceptance traceability
| Acceptance criterion | How it is met |
|---|---|
GET /products/[slug] shows the product image (no zero-height container) |
aspect-[5/7] max-h-[500px] gives the wrapper a real height; curl on the rendered page shows <img alt="Proteina Guisante Ecologica" … style="position:absolute;height:100%;width:100%;…"> inside <div class="relative w-full max-w-md aspect-[5/7] max-h-[500px] …">. |
PATCH /api/products/:id with attributes succeeds and round-trips |
JSON.stringify + ::jsonb cast; verified with PATCH {"attributes":["bio","keto"],"featured":true,"channels":"online"} → 200 + the same shape echoed back. |
GET /api/products/:id returns channels, featured, attributes |
serializeProduct now includes them; verified the field is present in the JSON. |
verify.sh is green |
Exit 0. |
Manual verification
$ curl -X PATCH http://192.168.18.93:3004/api/products/13a65dc0-… \
-H 'Content-Type: application/json' -b /tmp/admin_cookies.txt \
-d '{"attributes":["bio","keto"],"featured":true,"channels":"online"}'
{ … "channels":"online", "featured":true, "attributes":["bio","keto"], … }
HTTP 200
$ curl http://192.168.18.93:3004/api/products/13a65dc0-… -b /tmp/admin_cookies.txt
{ … "channels":"online", "featured":true, "attributes":["bio","keto"], … }
$ curl http://192.168.18.93:3003/products/proteina-guisante-ecologica | grep -oE 'aspect-\[5/7\][^"]*'
aspect-[5/7] max-h-[500px] …
The 500 line is gone from project/.runtime/prod/backend.log; the
next PATCH with attributes is 200.
Build verification
npm run typecheck(backend) — exit 0npm test(backend) — 124 passed, 56 skippednpm run build(backend) — exit 0npx tsc --noEmit(frontend) — exit 0monolith.sh prod restart backend frontend→ backend / frontend 200./scripts/verify.sh— exit 0
Files touched
project/frontend/src/app/products/[slug]/page.tsx (image container height)
project/src/modules/catalog/infrastructure/pg-product-repository.ts (JSONB cast for attributes)
project/src/modules/catalog/api/catalog.routes.ts (serializeProduct fields)