feat(ADM-018): completed feature

This commit is contained in:
chattie
2026-08-17 22:23:10 +02:00
parent cf1c69fc8b
commit d595b4871f
871 changed files with 47411 additions and 281 deletions

View File

@@ -0,0 +1,59 @@
# Architect — F-015 SEO core: structured data, sitemap, redirects
## Feature
F-015 adds SEO crawlability and structured data to the existing F-014 storefront catalog pages.
## Design
### Storefront SEO module
Create a small storefront SEO module under `project/storefront/src/lib/seo/`:
- `json-ld.ts` builds typed JSON-LD objects for Organization, BreadcrumbList and Product.
- `redirects.ts` defines a `RedirectRepository` interface and an env-backed implementation.
- Keep public URL generation centralized through existing `absoluteUrl()` / `SITE_URL`.
No new runtime dependency is required.
### Structured data
- Root layout embeds Organization JSON-LD once.
- Product pages embed:
- Product JSON-LD using product name, description, canonical URL and main image when available.
- BreadcrumbList JSON-LD for Home → Product.
- Category and brand pages embed BreadcrumbList JSON-LD.
- JSON-LD is rendered as `application/ld+json` with JSON serialization, not string concatenation.
### Sitemap and robots
Use Next.js metadata routes:
- `src/app/sitemap.ts` returns `MetadataRoute.Sitemap`.
- `src/app/robots.ts` returns `MetadataRoute.Robots`.
Sitemap source:
- Static public URLs: `/`, `/products/search`.
- Product URLs from `searchProducts({ limit: 100, offset })`, which returns active products only through backend `SearchProducts.activeOnly = true`; page through results to avoid silently truncating the first page.
- Category URLs from `listCategoryTree()` flattened recursively.
- Brand URLs from a new public `GET /brands` backend endpoint and storefront `listBrands()` client helper.
- On API failure, sitemap degrades to static URLs rather than failing the build.
### Redirect management
- Add `src/middleware.ts` using an env-backed redirect store.
- `RedirectRepository.findBySourcePath(pathname)` returns a redirect target when configured.
- Configure redirects with `REDIRECTS_JSON`, an array of `{ "from": "/old", "to": "/new" }` entries.
- Middleware returns permanent `301` redirects only for same-origin local paths. External targets are rejected by the parser to avoid open redirects.
- Redirect source and target paths must start with `/`; source cannot equal target.
### Backend brand listing
Extend the brands module minimally:
- Add `BrandRepository.list()`.
- Add `ListBrands` use case.
- Add public `GET /brands` returning `{ items: [...] }` in stable `name ASC, id ASC` order.
## Acceptance trace
- Product JSON-LD: product page embeds Product schema script.
- Sitemap: lists active product URLs via search API, category URLs, brand URLs and static pages; draft products excluded by backend active search.
- Redirect: middleware redirects stored old path with HTTP 301 to same-origin local target.
- Canonical URLs: existing F-014 canonical metadata remains on product/category/brand/search and root layout gains canonical metadata.
- `verify.sh`: must pass after gates.
## Risks / constraints
- No hreflang and no external SEO tooling by scope.
- Lighthouse/Google validator may be unavailable locally; validate structurally and through build output.
- Env redirects are enough for this slice; a database-backed admin UI is intentionally deferred.

View File

@@ -0,0 +1,18 @@
# Documenter — F-015 SEO core
## Documentation updated
Updated `project/README.md` with the F-015 public SEO behavior:
- Added public `GET /brands` route to the Brands table.
- Documented JSON-LD coverage:
- Organization JSON-LD in root layout.
- Product JSON-LD on product pages.
- BreadcrumbList JSON-LD on product/category/brand pages.
- Documented generated `sitemap.xml` and `robots.txt` behavior.
- Documented that sitemap includes static public URLs, active products, categories and brands, and degrades safely when API calls fail.
- Documented permanent redirects via `REDIRECTS_JSON` and `src/proxy.ts` using local same-origin path entries only.
## Evidence
- `project/README.md` was formatted with Prettier during implementation.
- `cd project && npm run lint` — PASS after README update.
- `./scripts/verify.sh` — PASS after README update.

View File

@@ -0,0 +1,52 @@
# Implementer — F-015 SEO core: structured data, sitemap, redirects
## Summary
Implemented SEO core for the storefront catalog and added the missing public brand listing needed by sitemap generation.
## Backend changes
- Added `BrandRepository.list()` and `ListBrands` use case.
- Added public `GET /brands` route returning `{ items: [...] }` in stable `name ASC, id ASC` order.
- The endpoint is read-only and public, matching existing public `/marca/:slug` behavior.
## Storefront SEO changes
- Added `project/storefront/src/lib/seo/json-ld.tsx`:
- Organization JSON-LD.
- Product JSON-LD.
- BreadcrumbList JSON-LD.
- Safe JSON-LD script rendering with `<` escaped.
- Added Organization JSON-LD and root canonical/OpenGraph metadata in `layout.tsx`.
- Added Product + Breadcrumb JSON-LD to `/productos/[slug]`.
- Added Breadcrumb JSON-LD to `/categoria/[slug]` and `/marca/[slug]`.
- Added `sitemap.xml` metadata route:
- Static public routes: `/`, `/products/search`.
- Active products through `searchProducts()` pagination; backend search enforces `activeOnly = true`, excluding draft content.
- Category URLs from flattened `listCategoryTree()`.
- Brand URLs from new `listBrands()`.
- API failures degrade to static URLs instead of failing the build.
- Added `robots.txt` metadata route allowing public crawl, disallowing `/api/`, and pointing to sitemap.
- Added redirect store and proxy:
- `project/storefront/src/lib/seo/redirects.ts` defines `RedirectRepository` and env-backed parser.
- `project/storefront/src/proxy.ts` returns HTTP 301 for configured same-origin local path redirects.
- `REDIRECTS_JSON` accepts an array like `[{ "from": "/old", "to": "/new" }]`.
- Parser rejects external URLs, protocol-relative URLs and self-redirects.
## Documentation
- Updated `project/README.md` with:
- Public `GET /brands` route.
- JSON-LD coverage.
- `sitemap.xml` / `robots.txt` behavior.
- `REDIRECTS_JSON` redirect format.
## Evidence
- `cd project/storefront && npm run lint` — PASS.
- `cd project/storefront && npm run typecheck` — PASS.
- `cd project/storefront && npm run build` — PASS. Build output includes `/robots.txt`, `/sitemap.xml`, and Proxy.
- `cd project && npm run lint` — PASS.
- `cd project && npm run typecheck` — PASS.
- `cd project && npm test` — PASS: 17 passed, 7 skipped; 64 assertions passed, 33 skipped.
- `./scripts/verify.sh` — PASS.
## Notes
- No new dependency was added.
- No hreflang or external SEO tooling integration was added, per scope.
- Google/Lighthouse online validators were not available in this environment; validation is structural plus Next build/typecheck evidence.

View File

@@ -0,0 +1,23 @@
{
"feature_id": "F-015",
"agent": "leader",
"verdict": "APPROVED",
"summary": "F-015 closed with SEO core implemented: structured data, sitemap.xml, robots.txt, local stored 301 redirects, public brand list, docs and gates approved.",
"gates": {
"reviewer": "APPROVED",
"security": "APPROVED",
"qa": "APPROVED"
},
"evidence": [
"work/artifacts/F-015/architect.md",
"work/artifacts/F-015/implementer.md",
"work/artifacts/F-015/reviewer.json",
"work/artifacts/F-015/security.json",
"work/artifacts/F-015/qa.json",
"work/artifacts/F-015/documenter.md",
"cd project/storefront && npm run lint/typecheck/build: PASS",
"cd project && npm run lint/typecheck/test: PASS",
"./scripts/verify.sh: PASS final close"
],
"closed_at": "2026-08-15T16:03:28Z"
}

View File

@@ -0,0 +1,46 @@
{
"feature_id": "F-015",
"agent": "qa",
"verdict": "APPROVED",
"summary": "QA approved. Acceptance criteria are traced with structural evidence and green checks: Product JSON-LD exists, sitemap/robots build routes exist and exclude drafts through activeOnly search, redirects are stored/configured via REDIRECTS_JSON and return 301, public pages have canonical metadata, and verify.sh is green.",
"acceptance": [
{
"criterion": "Product page embeds valid Product JSON-LD",
"status": "PASS",
"evidence": "json-ld.tsx builds @type Product and product page renders JsonLdScript; JSON is serialized and script-safe escaped"
},
{
"criterion": "sitemap.xml lists active public URLs and excludes draft content",
"status": "PASS",
"evidence": "sitemap.ts includes static URLs, active product pagination through searchProducts, category tree and brands; backend SearchProducts uses activeOnly=true"
},
{
"criterion": "Given stored redirect When old URL requested Then HTTP 301 to new URL",
"status": "PASS",
"evidence": "redirects.ts parses REDIRECTS_JSON into RedirectRepository; proxy.ts returns NextResponse.redirect(..., 301) for matched local path"
},
{
"criterion": "Every public page has canonical URL",
"status": "PASS",
"evidence": "layout root canonical plus product/category/brand/search generateMetadata alternates canonical"
},
{
"criterion": "verify.sh green",
"status": "PASS",
"evidence": "./scripts/verify.sh PASS"
}
],
"commands": [
"rg structured-data and metadata route patterns: PASS",
"cd project/storefront && npm run lint: PASS",
"cd project/storefront && npm run typecheck: PASS",
"cd project/storefront && npm run build: PASS; output includes /robots.txt, /sitemap.xml and Proxy",
"cd project && npm run typecheck: PASS",
"cd project && npm test: PASS; 17 passed, 7 skipped; 64 assertions passed, 33 skipped",
"./scripts/verify.sh: PASS"
],
"notes": [
"External Google Rich Results/Lighthouse validators are not installed in this environment; QA used structural JSON-LD/canonical/build evidence."
],
"timestamp": "2026-08-15T16:02:57Z"
}

View File

@@ -0,0 +1,19 @@
{
"feature_id": "F-015",
"agent": "reviewer",
"verdict": "APPROVED",
"summary": "F-015 review approved. Implementation matches the SEO core design: JSON-LD is centralized and escaped, sitemap/robots are metadata routes, redirects are behind a repository interface with permanent same-origin path redirects, canonical metadata is present on public pages, and backend brand listing supports sitemap generation.",
"evidence": [
"Read work/current.md, architect.md and implementer.md",
"Inspected JSON-LD module: Organization, Product, BreadcrumbList builders and escaped application/ld+json script rendering",
"Inspected product/category/brand pages: structured data embedded without client components; existing canonical metadata preserved",
"Inspected layout metadata: root canonical/OpenGraph and Organization JSON-LD added",
"Inspected sitemap.ts and robots.ts metadata routes: static URLs, active product search pagination, category tree flattening and brand list inclusion",
"Inspected redirects.ts and proxy.ts: RedirectRepository interface, env-backed store, same-origin local paths only, HTTP 301 via NextResponse.redirect",
"Inspected backend brands route/use case/repository: public GET /brands added in stable order",
"gentle-ai review mode status: receipt-driven development off globally, ordinary Orquestra gate used",
"Storefront lint/typecheck/build and backend lint/typecheck/test were run by implementer and passed",
"./scripts/verify.sh passed after implementation"
],
"timestamp": "2026-08-15T16:02:08Z"
}

View File

@@ -0,0 +1,16 @@
{
"feature_id": "F-015",
"agent": "security",
"verdict": "APPROVED",
"summary": "Security approved. No dependency vulnerabilities or committed secrets were found. JSON-LD rendering escapes script-breaking characters, redirects are restricted to local same-origin paths with 301 only, and public brand listing exposes only existing public SEO fields.",
"evidence": [
"cd project/storefront && npm audit --audit-level=high --omit=dev: found 0 vulnerabilities",
"Secret scan over project/storefront/src, project/src/modules/brands and README found only documented/session/revalidation identifiers; no committed credentials",
"Reviewed JSON-LD script usage: data is JSON.stringify output with < escaped before dangerouslySetInnerHTML",
"Reviewed redirect parser: rejects external URLs, protocol-relative paths, non-local paths and self-redirects",
"Reviewed proxy redirect: builds target with new URL(localPath, request.url) and returns 301",
"Reviewed GET /brands: read-only public endpoint returns brand SEO fields already exposed by /marca/:slug",
"No new runtime dependency added"
],
"timestamp": "2026-08-15T16:02:24Z"
}