feat(ADM-018): completed feature
This commit is contained in:
59
work/artifacts/F-015/architect.md
Normal file
59
work/artifacts/F-015/architect.md
Normal 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.
|
||||
18
work/artifacts/F-015/documenter.md
Normal file
18
work/artifacts/F-015/documenter.md
Normal 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.
|
||||
52
work/artifacts/F-015/implementer.md
Normal file
52
work/artifacts/F-015/implementer.md
Normal 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.
|
||||
23
work/artifacts/F-015/leader-close.json
Normal file
23
work/artifacts/F-015/leader-close.json
Normal 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"
|
||||
}
|
||||
46
work/artifacts/F-015/qa.json
Normal file
46
work/artifacts/F-015/qa.json
Normal 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"
|
||||
}
|
||||
19
work/artifacts/F-015/reviewer.json
Normal file
19
work/artifacts/F-015/reviewer.json
Normal 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"
|
||||
}
|
||||
16
work/artifacts/F-015/security.json
Normal file
16
work/artifacts/F-015/security.json
Normal 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"
|
||||
}
|
||||
Reference in New Issue
Block a user