docs(33-08): complete vue-seo-layer build guard, noindex routes and components plan
This commit is contained in:
@@ -0,0 +1,406 @@
|
||||
---
|
||||
phase: 33-seo-output-audit-and-content
|
||||
plan: 08
|
||||
subsystem: vue-seo-layer
|
||||
tags: [nuxt-layer, build-guard, ssr, noindex, route-report, drift-check, breadcrumbs, pagination, html-sitemap]
|
||||
|
||||
requires:
|
||||
- phase: 33-07
|
||||
provides: vue-seo-layer runtime (resolve middleware, useCmsSeo, site answer cache, labels, tokens, smoke harness, fixture API)
|
||||
- phase: 33-03
|
||||
provides: sitemap index /sitemap.xml and per-type files /sitemap-<slug>-<n>.xml through /_sitemap/files/{file} (unchanged here)
|
||||
provides:
|
||||
- SSR build guard (lib/guard.ts) with options seo.noindexRoutes and seo.indexable, guard:check over four fixture apps
|
||||
- automatic noindex for declared routes and for pages whose meta sets requiresAuth or seo.robots (app plugin seo-route-robots)
|
||||
- route artefact #seo-routes, the route report (POST /registry/routes) and the startup drift check
|
||||
- GET /api/__seo__/site and useSeoSite()
|
||||
- <SeoBreadcrumbs>, <SeoPagination> + usePagination(), HTML sitemap pages (SeoHtmlSitemapPage, GET /api/__seo__/sitemap) and <SeoSitemapLink>
|
||||
- options seo.routeReport, seo.pagination, seo.htmlSitemap
|
||||
affects: [33-09, 33-12, 33-13, 33-14]
|
||||
|
||||
actuals:
|
||||
tokens: 42200
|
||||
tasks: 3
|
||||
commits: 7
|
||||
plan_head_before: 6636cde70a6d866e6d7511c24f5d3b70258455f9
|
||||
plan_head_after: b051bda0809b9a8ca58f2c55c55d12d6477e8127
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Build-time checks in the local module: nitro:config (rules merged) runs before pages:resolved, so the guard runs once the pages are resolved; a production build throws, nuxt dev and nuxt prepare warn"
|
||||
- "Build-time route data for runtime plugins through addServerTemplate (#seo-routes), no network call at build time"
|
||||
- "Pagination and breadcrumb links through NuxtLink's custom slot, so the router's aria-current does not mark every page of the same path"
|
||||
- "Helpers in app/utils carry the seo prefix (auto-imported next to the journal layer's paginationWindow)"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- ../vue-seo-layer/lib/guard.ts
|
||||
- ../vue-seo-layer/lib/routes.ts
|
||||
- ../vue-seo-layer/lib/options.ts
|
||||
- ../vue-seo-layer/testing/guard-check.mjs
|
||||
- ../vue-seo-layer/testing/guard/{indexable-ssr-false,noindex-ok,client-page,global-ssr-false}/
|
||||
- ../vue-seo-layer/app/plugins/seo-route-robots.ts
|
||||
- ../vue-seo-layer/server/plugins/seo-route-report.ts
|
||||
- ../vue-seo-layer/server/plugins/seo-drift.ts
|
||||
- ../vue-seo-layer/server/lib/drift.ts
|
||||
- ../vue-seo-layer/server/lib/routeReport.ts
|
||||
- ../vue-seo-layer/server/lib/htmlSitemap.ts
|
||||
- ../vue-seo-layer/server/seo-routes.d.ts
|
||||
- ../vue-seo-layer/server/api/__seo__/site.get.ts
|
||||
- ../vue-seo-layer/server/api/__seo__/sitemap.get.ts
|
||||
- ../vue-seo-layer/app/composables/useSeoSite.ts
|
||||
- ../vue-seo-layer/app/composables/usePagination.ts
|
||||
- ../vue-seo-layer/app/components/Seo/Breadcrumbs.vue
|
||||
- ../vue-seo-layer/app/components/Seo/Pagination.vue
|
||||
- ../vue-seo-layer/app/components/Seo/SitemapLink.vue
|
||||
- ../vue-seo-layer/app/components/Seo/SitemapList.vue
|
||||
- ../vue-seo-layer/app/components/global/SeoHtmlSitemapPage.vue
|
||||
- ../vue-seo-layer/app/seo-pages/SitemapPage.vue
|
||||
- ../vue-seo-layer/app/seo-pages/SitemapGroupPage.vue
|
||||
- ../vue-seo-layer/app/utils/paginationWindow.ts
|
||||
- ../vue-seo-layer/app/utils/pageHref.ts
|
||||
- ../vue-seo-layer/app/utils/breadcrumbClaim.ts
|
||||
- ../vue-seo-layer/app/utils/sitePath.ts
|
||||
- ../vue-seo-layer/testing/expect-seo-drift.txt
|
||||
- ../vue-seo-layer/testing/fixtures/seo/site-drift.json
|
||||
- ../vue-seo-layer/testing/fixtures/seo/html.json
|
||||
- ../vue-seo-layer/.playground/app/pages/{konto,szkic}.vue, prywatne/index.vue, blog/index.vue
|
||||
- ../vue-seo-layer/tests/{guard,routes,paginationWindow,pageHref,breadcrumbClaim,htmlSitemap}.test.ts
|
||||
modified:
|
||||
- ../vue-seo-layer/lib/seo-module.ts
|
||||
- ../vue-seo-layer/server/lib/config.ts
|
||||
- ../vue-seo-layer/app/composables/useCmsSeo.ts
|
||||
- ../vue-seo-layer/app/composables/useSeoLabels.ts
|
||||
- ../vue-seo-layer/app/utils/seo.ts
|
||||
- ../vue-seo-layer/app/utils/types.ts
|
||||
- ../vue-seo-layer/app/assets/css/seo-base.css
|
||||
- ../vue-seo-layer/package.json
|
||||
- ../vue-seo-layer/README.md
|
||||
- ../vue-seo-layer/testing/smoke-ssr.mjs
|
||||
- ../vue-seo-layer/testing/fixture-api.mjs
|
||||
- ../vue-seo-layer/testing/expect-seo.txt, expect-seo-off.txt, expect-seo-down.txt, expect-seo-notoken.txt
|
||||
- ../vue-seo-layer/testing/fixtures/seo/site.json, resolve.json
|
||||
- ../vue-seo-layer/.playground/nuxt.config.ts, app/app.vue, app/pages/[...slug].vue
|
||||
|
||||
key-decisions:
|
||||
- "The build guard runs when the pages are resolved (pages:resolved), not inside nitro:config: Nuxt fires nitro:config first; the rules are read from the final Nitro config (nitro:init), and nitro:build:before is the fallback for an app without pages"
|
||||
- "The guard throws only in a production build or generate; nuxt dev and nuxt prepare (run on install) warn, so an install never fails"
|
||||
- "Declared noindex and page-meta robots win over the CMS robots (tagPriority -10 against the head entry's critical -8), except on staging"
|
||||
- "The route report retries once after 60 s only on no answer or a non-404/401/403 status; a 404 (no page registry) and a refused token are logged, not retried"
|
||||
- "A root catch-all page does not count as serving a CMS content-type pattern in the drift check; a prefixed catch-all does"
|
||||
- "app/utils helpers are seo-prefixed (seoPaginationWindow, seoPageHref, seoClaimBreadcrumbs, seoSitePath): the journal layer auto-imports its own paginationWindow with another return shape"
|
||||
- "HTML sitemap pages answer 404 unless the CMS's site answer has the HTML sitemap on with a path for the locale, 503 when the backend has not answered; an app page at the same path keeps it"
|
||||
|
||||
patterns-established:
|
||||
- "Smoke grammar: routereport N [has=PATH] [lacks=PATH], log TEXT:N, --site-file NAME"
|
||||
- "Split commits verified on the staged tree: git checkout-index into a scratch dir, then test, build and smoke there"
|
||||
|
||||
requirements-completed: [SC-2, D-70, D-23, D-21, D-33, D-20c, D-86, D-40, D-41, D-42, D-71, D-34, D-97]
|
||||
|
||||
coverage:
|
||||
- id: D1
|
||||
description: "nuxt build fails with the D-70 message for a Nuxt route rule, a Nitro route rule, a .client.vue page and a global ssr: false, and passes when the route is declared in seo.noindexRoutes; the declared route is served with X-Robots-Tag: noindex, nofollow and / without it; inline route rules are refused; seo.indexable false checks nothing"
|
||||
requirement: "D-70"
|
||||
verification:
|
||||
- kind: integration
|
||||
ref: "npm run guard:check (testing/guard-check.mjs, 16 checks)"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "tests/guard.test.ts"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D2
|
||||
description: "A page with requiresAuth meta, a page with seo.robots and a path under noindexRoutes get the robots meta tag and X-Robots-Tag without page code; an ordinary page keeps the CMS robots"
|
||||
requirement: "D-23"
|
||||
verification:
|
||||
- kind: integration
|
||||
ref: "npm run smoke:ssr (expect-seo.txt /konto, /szkic, /prywatne, /plain lines)"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "tests/routes.test.ts#pageMetaRobots"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D3
|
||||
description: "One route report at server start with the token and the static paths (not /konto, /szkic, /prywatne, the sitemap pages); none without a token or with routeReport false"
|
||||
requirement: "D-33"
|
||||
verification:
|
||||
- kind: integration
|
||||
ref: "npm run smoke:ssr (routereport lines in expect-seo.txt, expect-seo-notoken.txt, expect-seo-drift.txt)"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "tests/routes.test.ts#staticRoutes, routeReportBody, routeReportOutcome"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D4
|
||||
description: "Startup drift check: no warning when CMS and build agree, three warnings (URL pattern, pagination form, pl HTML sitemap path) against site-drift.json, and the server still serves pages"
|
||||
requirement: "D-20c"
|
||||
verification:
|
||||
- kind: integration
|
||||
ref: "npm run smoke:ssr (log lines in expect-seo.txt and expect-seo-drift.txt)"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "tests/routes.test.ts#driftWarnings"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D5
|
||||
description: "<SeoBreadcrumbs>: one nav per page with a layout and a page instance, server-rendered links, last item aria-current text; none on the home page or a page without a trail"
|
||||
requirement: "D-86"
|
||||
verification:
|
||||
- kind: integration
|
||||
ref: "npm run smoke:ssr (expect-seo.txt L1 lines, count=<nav class=\"seo-breadcrumbs\":1)"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "tests/breadcrumbClaim.test.ts"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D6
|
||||
description: "<SeoPagination> + usePagination(): window 1 … 3–7 … 14, page 1 unnumbered, other query parameters kept, rel prev/next anchors and head links, %pages% filled in the CMS title and description, nothing with one page"
|
||||
requirement: "D-42"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "tests/paginationWindow.test.ts, tests/pageHref.test.ts"
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: "npm run smoke:ssr (expect-seo.txt L2 lines)"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D7
|
||||
description: "HTML sitemap: overview with H1 and an H2 per non-empty group, nested categories, sitemap.all link, sitemap.empty; group pages with built trail, page suffix and pagination; 404 unknown group / past the last / malformed page, 301 ?page=1, 503 on API failure and with the backend down, 404 with the layer off; non-http URLs never rendered as links"
|
||||
requirement: "D-34"
|
||||
verification:
|
||||
- kind: integration
|
||||
ref: "npm run smoke:ssr (expect-seo.txt L3 lines, expect-seo-off.txt, expect-seo-down.txt)"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "tests/htmlSitemap.test.ts"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D8
|
||||
description: "<SeoSitemapLink> renders the locale's sitemap link only while the CMS has the HTML sitemap on and the layer serves it"
|
||||
requirement: "D-34"
|
||||
verification:
|
||||
- kind: integration
|
||||
ref: "npm run smoke:ssr (L4 line in expect-seo.txt and expect-seo-drift.txt; none in expect-seo-off.txt and the --no-site v0.1.1 run)"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D9
|
||||
description: "Default styles of L1–L3 (only .seo-* selectors, tokens only, long text wraps)"
|
||||
verification:
|
||||
- kind: other
|
||||
ref: "app/assets/css/seo-base.css review; no bare-element selector"
|
||||
status: pass
|
||||
human_judgment: true
|
||||
- id: D10
|
||||
description: "v0.1.1 compatibility: the v0.1.1 smoke suite unchanged; the scratch build of the existing consumer against this layer passes its own checks"
|
||||
verification:
|
||||
- kind: integration
|
||||
ref: "testing/expect.txt (31 checks, --no-site) on every smoke run; scratch copy of the consumer app with node_modules/vue-seo-layer = git archive aee6957: nuxt build, smoke:ssr 52/52, smoke:ga 2/2, npm test 28/28"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
|
||||
duration: 27min
|
||||
completed: 2026-11-04
|
||||
status: complete
|
||||
---
|
||||
|
||||
# Phase 33 Plan 08: vue-seo-layer build guard, noindex routes, route report and public components Summary
|
||||
|
||||
**vue-seo-layer now fails a production build whose indexable routes would render only in the browser, marks declared and signed-in routes noindex without page code, reports its static routes to the CMS page registry and logs CMS/build drift at start. It also ships server-rendered breadcrumbs, pagination in the CMS URL form and an HTML sitemap with its footer link. All of it is additive to v0.1.1.**
|
||||
|
||||
## Context rulings applied
|
||||
|
||||
- **Framework version:** the framework is at v0.6.0 and this phase delivers v0.6.1. This plan changes no framework code, so D-90 holds.
|
||||
- **The layer:** vue-seo-layer was extended in place on main after 33-07 (6636cde).
|
||||
- Nothing was pushed or tagged.
|
||||
- `package.json` stays at 0.1.1; the next tag is v0.2.0, in 33-14.
|
||||
- **Sitemap paths (33-03):** the plan's layer code proxies no XML sitemap path, so no alignment was needed.
|
||||
- The HTML sitemap pages call the sitemap plugin's HTML API at `/_sitemap/api/v1/html` and `/_sitemap/api/v1/html/{group}`, the contract planned in 33-04.
|
||||
- 33-04 is not built yet, so these calls run against fixtures. The same is true of the SEO plugin's `POST /registry/routes` (33-05): 33-03 and 33-05 code had not landed in the plugins when this plan ran.
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** about 27 min (2026-11-04T10:37:49Z to 11:04:32Z)
|
||||
- **Tasks:** 3 (tracer, then 2 TDD tasks)
|
||||
- **Commits:** 7 in ../vue-seo-layer
|
||||
- **Files:** 68 changed in ../vue-seo-layer
|
||||
|
||||
## Task Commits
|
||||
|
||||
All in /media/nvme/dev/golem15/summercms.io/summercms/vue-seo-layer (branch main, not pushed):
|
||||
|
||||
1. **Task 1 (tracer):** `b4680af` feat: the build fails when an indexable route renders only in the browser (tracer)
|
||||
2. **Task 2:**
|
||||
- A `9cc2cda` feat: noindex for declared and signed-in routes
|
||||
- B `3abc986` feat: route report and drift check at server start
|
||||
3. **Task 3:**
|
||||
- A `ebb0c80` feat: breadcrumbs component
|
||||
- B `f2d13d6` feat: pagination component and composable
|
||||
- C `aee6957` feat: HTML sitemap pages and footer link
|
||||
- D `b051bda` docs: build guard, noindex routes and components
|
||||
|
||||
The tracer gate ran in interactive end-of-phase mode with automated checks only. Its `<verify>` was re-run (tests plus `guard:check`) and passed before expansion.
|
||||
|
||||
## Accomplishments
|
||||
|
||||
### Build guard (D-70)
|
||||
- `lib/guard.ts` holds the pure checks: `isNoindex` and `patternCovers` (Nitro route-rule semantics), `pagePattern`, `flattenPages`, `findOffenders` and `guardMessage`.
|
||||
- The module collects every merged route rule from the final Nitro config, plus the `.client.vue` pages. It refuses `experimental.inlineRouteRules`.
|
||||
- A production build throws `[vue-seo-layer] indexable routes must render on the server (D-70): …`.
|
||||
- Each declared noindex pattern gets an X-Robots-Tag route rule.
|
||||
- `testing/guard-check.mjs` builds four fixtures (about 14 s in total) and checks the header on the noindex fixture.
|
||||
|
||||
### Noindex routes (D-23, D-21)
|
||||
- The module adds `requiresAuth` and `seo` to Nuxt's page-meta extraction keys.
|
||||
- The app plugin `seo-route-robots` applies the robots meta tag (priority above the CMS head entry) and, during SSR, the header once navigation is done.
|
||||
- Staging keeps the CMS's noindex.
|
||||
|
||||
### Route report and drift check (D-33, D-20c)
|
||||
- `#seo-routes` is written from the resolved pages at bundle time.
|
||||
- `seo-route-report` posts the static paths once per process.
|
||||
- `seo-drift` compares URL patterns, pagination and HTML-sitemap paths with the site answer and logs one warning per mismatch.
|
||||
- `GET /api/__seo__/site` and `useSeoSite()` share the cached site answer.
|
||||
|
||||
### Components (D-86, D-40 to D-42, D-34, D-71)
|
||||
- `<SeoBreadcrumbs>`: one trail per page, claimed by the first instance per path.
|
||||
- `<SeoPagination>` with `usePagination()`: hrefs in the CMS form, head prev/next links, and `%pages%` filled in the CMS title and description (`useCmsSeo` with `seoFillPages`).
|
||||
- HTML sitemap pages, registered per locale through `extendPages` and rendering the overridable global `SeoHtmlSitemapPage`. They load data from `GET /api/__seo__/sitemap`.
|
||||
- `<SeoSitemapLink>`.
|
||||
- Default styles for L1 to L3.
|
||||
|
||||
### README
|
||||
The README documents everything above.
|
||||
|
||||
## Compatibility notes for v0.1.1 consumers
|
||||
|
||||
All new behaviour is additive or tied to a declaration or a v0.2.0 backend answer:
|
||||
|
||||
- **v0.1.1 options:** every v0.1.1 option, runtime key and exported helper keeps its meaning. The v0.1.1 smoke suite (`testing/expect.txt`, 31 checks against a backend without `/site`) passes unchanged on every run.
|
||||
- **Build guard:** it only fails an app with an indexable `ssr: false` route, a `.client.vue` page or a global `ssr: false`.
|
||||
- The existing consumer has none of these and builds.
|
||||
- The demo app's `/p/**` and `/presentations/**` must be declared in `seo.noindexRoutes` when it moves to v0.2.0, as 33-12 plans. Otherwise its build fails by design.
|
||||
- `nuxt prepare` and `nuxt dev` only warn, so an install never fails.
|
||||
- **Noindex:** applied only to routes the app declares, and to pages whose meta sets `requiresAuth: true` or `seo.robots`. An app that already uses `requiresAuth` for its own guard gets `noindex, nofollow` on those pages, which is the intended D-23 behaviour.
|
||||
- **Route report:**
|
||||
- It needs a token. It falls back to `NUXT_SEO_NOTFOUND_TOKEN`, so an app that set that token will start posting the report.
|
||||
- A v0.1.1-era backend answers 404, which is logged once and not retried.
|
||||
- `seo.routeReport: false` switches it off.
|
||||
- **HTML sitemap pages:** registered by default at `/mapa-strony` and `/sitemap`.
|
||||
- **If the app already has a page at one of these paths:** the app's page is kept.
|
||||
- **CMS has no HTML sitemap** (no site answer, or the sitemap off or without a path for the locale): the pages answer 404. In the existing consumer these paths answered 404 before as well, through the journal's `/:category` route.
|
||||
- **Backend unreachable since start:** they answer 503 instead of 404.
|
||||
- The layer also statically claims these two paths ahead of a dynamic route such as `/:category`.
|
||||
- `seo.htmlSitemap: false` registers none.
|
||||
- **New global names:** components `SeoBreadcrumbs`, `SeoPagination`, `SeoSitemapLink`, `SeoSitemapList` and `SeoHtmlSitemapPage`; composables `usePagination`, `useSeoPages` and `useSeoSite`; utils prefixed `seo*`. None of them collides with the journal, menu or FAQ layers.
|
||||
- **Consumer check:** the existing consumer app was copied to a scratch directory with `node_modules/vue-seo-layer` replaced by `git archive` of aee6957. The real repository was not touched. Results:
|
||||
- `nuxt build` passed, guard included.
|
||||
- `smoke:ssr` 52/52, `smoke:ga` 2/2, `npm test` 28/28.
|
||||
|
||||
## Decisions Made
|
||||
|
||||
See key-decisions in the frontmatter.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Adapted to the actual repository and Nuxt
|
||||
|
||||
**1. Guard timing.**
|
||||
- **Plan:** throw in `nitro:config`.
|
||||
- **What happens in Nuxt 4.4.8:** `nitro:config` fires before `pages:resolved`, so the pages are not known there.
|
||||
- **What I did:**
|
||||
- `nitro:config` only adds the noindex header rules.
|
||||
- The rules are read from the final Nitro config (`nitro:init`).
|
||||
- The check runs in `pages:resolved`, with `nitro:build:before` as the fallback.
|
||||
- The acceptance grep still prints the two hook lines.
|
||||
|
||||
**2. Dev and prepare only warn.** Throwing in `nuxt prepare`, which pnpm runs on install, would break installs. Production `build` and `generate` fail as planned.
|
||||
|
||||
**3. Helper names are prefixed.**
|
||||
- **Plan names:** `paginationWindow`, `pageHref`, `breadcrumbClaim`.
|
||||
- **Why changed:** vue-journal-layer already auto-imports a `paginationWindow` with another return shape. A second auto-import of that name in the same app would shadow it.
|
||||
- **What I did:** the files keep the plan's names, and the exports are `seoPaginationWindow`, `seoPageHref` / `seoPageNumber` and `seoClaimBreadcrumbs` / `seoReleaseBreadcrumbs`.
|
||||
|
||||
**4. Extra files.** Each was split out of a planned file or task:
|
||||
- `lib/options.ts` and `server/lib/{drift,routeReport,htmlSitemap}.ts`: pure parts, so they can be unit-tested.
|
||||
- `app/utils/sitePath.ts`: same-origin URLs become paths; anything that is not http(s) is never a link.
|
||||
- `app/components/Seo/SitemapList.vue`: the recursive category tree.
|
||||
- `server/seo-routes.d.ts`: types for `#seo-routes`.
|
||||
- `tests/routes.test.ts` and `tests/htmlSitemap.test.ts`.
|
||||
|
||||
**5. Fixture site answer.** `testing/fixtures/seo/site.json` changed its post pattern from `/blog/:slug` to `/posts/:slug`. The playground serves posts at `posts/[slug].vue`, while `/blog/<slug>` is rendered by the root catch-all, which by design does not count for the drift check. Only the drift check reads `routes`.
|
||||
|
||||
**6. Playground HTML sitemap paths.** The playground sets `htmlSitemap.paths` to `{pl: '/mapa-strony', en: '/en/sitemap'}`, matching the 33-07 fixture site answer. The layer default stays pl `/mapa-strony`, en `/sitemap`.
|
||||
|
||||
**7. Fixture for the "/blog with one page" case.** The blog listing takes `?pages=N`. "One page" is `/blog?pages=1`, because the fixture's `/blog` has 14 pages.
|
||||
|
||||
**8. HTML sitemap behaviour beyond the plan:**
|
||||
- **Breadcrumbs:** the overview's trail comes from the resolver, as on any page.
|
||||
- **`?page=1`:** the 301 is enforced in the layer page, since the resolver does not know these pages.
|
||||
- **Path pagination form:** an extra route `<path>/:group/<segment>/:page` is registered.
|
||||
- **Gating:** the pages answer only while the site answer has the HTML sitemap on with a path for the locale.
|
||||
|
||||
**9. [Rule 1 - Bug] `setResponseHeader` is not auto-imported in app code.**
|
||||
- **Found during:** the Task 2 smoke (`/konto` answered 500).
|
||||
- **Fix:** the header is set with `useResponseHeader` inside `nuxtApp.runWithContext`.
|
||||
- **Files:** app/plugins/seo-route-robots.ts. Fixed before commit 9cc2cda.
|
||||
|
||||
**10. [Rule 1 - Bug] The router marked every query variant as the active page.**
|
||||
- **Found during:** a Task 3 probe. NuxtLink's active match ignores the query, so every `/blog?page=N` link carried `aria-current="page"`.
|
||||
- **Fix:** pagination and breadcrumb links render through NuxtLink's `custom` slot. They still navigate on the client, and only the current item carries `aria-current`. Fixed before commit f2d13d6.
|
||||
|
||||
**11. [Rule 2 - Security] Non-http(s) sitemap URLs.** A `javascript:` URL from the sitemap API renders as text, never as a link. The smoke covers it with a fixture entry.
|
||||
|
||||
**12. Smoke expectations adjusted to real markup:**
|
||||
- **aria-current count:** `aria-current="page"` appears twice on `/blog?page=5` (the current page and the breadcrumbs' last item).
|
||||
- **Payload:** the raw `%pages%` stays in the payload, so the check targets the title and description tags.
|
||||
|
||||
**13. Commit splits:**
|
||||
- Task 2 A/B and Task 3 A/B/C share files.
|
||||
- Intermediate versions of those files were staged with `git update-index`.
|
||||
- Each staged tree was checked out into a scratch directory and passed `npm run test`, `build:playground` and `smoke:ssr` there before its commit.
|
||||
|
||||
### TDD gate
|
||||
|
||||
- Tests were written alongside each unit before running (guard, routes/drift/report, window/href/claim, htmlSitemap).
|
||||
- **RED observed:**
|
||||
- `pagePatterns` (a `?` stripped as a query, fixed in lib/guard.ts before the Task 1 commit).
|
||||
- The smoke's `/konto` 500 (deviation 9).
|
||||
- No separate RED commits, because the plan requires `npm run test` green at every commit (same as 33-07).
|
||||
|
||||
## Deferred Issues
|
||||
|
||||
- **The `scripts/check-phase33.sh --layers` stage cannot pass.**
|
||||
- **Cause:** its `npm_vitest` step demands a vitest summary, while all four layers run `node --test` TAP ("refuse: no all-passing vitest summary", `test=0 detect=3`).
|
||||
- **What I ran instead:** every step of the stage by hand: unit tests, playground builds and smokes for all four layers, and `guard-check.mjs`. All passed.
|
||||
- **Logged in:** deferred-items.md, for a gate fix outside this plan.
|
||||
- **No typecheck script.** The layer has no typecheck script, and its devDependencies lack `vue-tsc`. Types were not machine-checked, as in 33-07. The build (vite + oxc) compiled every file.
|
||||
|
||||
## Known Stubs
|
||||
|
||||
None. The sitemap HTML API (33-04) and the page registry (33-05) are not built yet. The layer code follows their planned contracts and is covered by fixtures, so it does not depend on stubs.
|
||||
|
||||
## Threat Flags
|
||||
|
||||
| Flag | File | Description |
|
||||
|------|------|-------------|
|
||||
| threat_flag: new-endpoint | ../vue-seo-layer/server/api/__seo__/site.get.ts | Public GET /api/__seo__/site returns the backend's public site answer (cached); no-store, noindex, never route-cached |
|
||||
| threat_flag: proxy | ../vue-seo-layer/server/api/__seo__/sitemap.get.ts | Public GET /api/__seo__/sitemap proxies the sitemap HTML API on the server-only backendBase; locale, group slug and page are checked by regex before use, only these reach the backend, the answer is reduced to known fields (T-33-38) |
|
||||
| threat_flag: outbound-token | ../vue-seo-layer/server/plugins/seo-route-report.ts | The route-report token (server-only runtime config) is sent only to the configured apiBase (T-33-36) |
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- **Files:** all key files listed above exist in ../vue-seo-layer.
|
||||
- **Commits:** b4680af, 9cc2cda, 3abc986, ebb0c80, f2d13d6, aee6957 and b051bda are on main in ../vue-seo-layer (7 since 6636cde).
|
||||
- **Unit tests:** `npm run test` 106 pass, 0 fail.
|
||||
- **Smoke:** `npm run smoke:ssr` passed all seven runs (31, 64, 11, 11, 7, 3 and 7 checks).
|
||||
- **Purge:** `npm run purge:check` 18 + 53 checks.
|
||||
- **Guard:** `npm run guard:check` 16 checks.
|
||||
- **Other layers:** the remaining steps of the gate's layers stage passed: journal, menu and FAQ unit tests, playground builds and smokes.
|
||||
- **Acceptance greps:**
|
||||
- The D-70 message is in lib/guard.ts.
|
||||
- `nitro:config` and `pages:resolved` appear once each in seo-module.ts.
|
||||
- `requiresAuth` is in seo-route-robots.ts and `drift:` in seo-drift.ts (the message prefix itself lives in server/lib/drift.ts).
|
||||
- `registry/routes` in seo-route-report.ts prints two lines, not the planned one: the header comment and the call.
|
||||
- `aria-current` is in Breadcrumbs.vue and Pagination.vue.
|
||||
- No `ClientOnly` or `onMounted` in the components and pages.
|
||||
- expect-seo.txt has `count=<nav class="seo-breadcrumbs":1`.
|
||||
@@ -22,3 +22,11 @@ Out-of-scope discoveries logged during execution; not fixed in the plan that fou
|
||||
- **Possible fix:** support `datepicker` (mode date) in repeater rows in cabana and the SPA's RepeaterField, then switch the field type in `sm-sitemap-plugin/models/settings/fields.yaml`.
|
||||
- **Why deferred:** 33-03 commits only to sm-sitemap-plugin; the gap is generic framework work (additive, but its own change).
|
||||
- **A partial cannot link off the admin's origin (by design).** cabana's partial renderer keeps only root-relative `a[href]` values (`safePartialURL`), so the S1 files table links each file to the backend route that serves it (`/sitemap.xml`, `/_sitemap/files/<name>`) instead of the public site URL; the note under the table names the public index address. No change needed unless public links in partials become a requirement.
|
||||
|
||||
## From 33-08 (vue-seo-layer)
|
||||
|
||||
- **`scripts/check-phase33.sh --layers` cannot pass (gate script).** Its `npm_vitest` step runs `npm run test` in each layer and then requires a vitest summary (`detect_vitest`). All four layers (vue-seo-layer, vue-journal-layer, vue-menu-layer, vue-faq-layer) run `node --test --test-reporter=tap`, so the stage refuses on the first layer although every test passes ("refuse: no all-passing vitest summary", `test=0 detect=3`).
|
||||
status: open
|
||||
- **What 33-08 ran instead:** each step of the stage by hand: `npm run test`, `npm run build:playground` and the smokes of all four layers, `testing/guard-check.mjs` in vue-seo-layer. All passed.
|
||||
- **Possible fix:** a `node_tap` helper in the gate (TAP `# fail 0` and no `not ok`) used for the layers, keeping `npm_vitest` for sm-seo-plugin and sm-journal-plugin.
|
||||
- **Why deferred:** 33-08 commits only to vue-seo-layer; the gate script is framework planning tooling of 33-01.
|
||||
|
||||
Reference in New Issue
Block a user