diff --git a/.planning/phases/33-seo-output-audit-and-content/33-13-SUMMARY.md b/.planning/phases/33-seo-output-audit-and-content/33-13-SUMMARY.md new file mode 100644 index 00000000..0b854122 --- /dev/null +++ b/.planning/phases/33-seo-output-audit-and-content/33-13-SUMMARY.md @@ -0,0 +1,250 @@ +--- +phase: 33-seo-output-audit-and-content +plan: 13 +subsystem: apps +tags: [seo, sitemap, nuxt, vue-seo-layer, grzybyfunkcjonalne, patches, deploy-decision] +status: complete + +requires: + - phase: 33-03..33-06 + provides: sitemap index and per-type files, Additional addresses migration, SEO page registry, route report, site answer, JSON-LD, breadcrumbs, pagination suffixes, per-type templates + - phase: 33-07..33-09 + provides: vue-seo-layer v0.2.0 (root files, route report, build guard, components) and the journal, menu and faq layers on it + - phase: 33-11 + provides: WebP variants (purego linking) + - phase: 33-12 + provides: P33-46 wiring pattern, scratch-33 directory +provides: + - "33-13-sm-grzybyfunkcjonalne-app.patch: Journal tag pages off and authors at /autor/:author, golem15.sitemap and golem15.seo config, both vhosts without root-file locations (P33-49), check-deploy.sh, smoke.sh (companions, route report, page names, seo-pages.csv, Additional addresses, rebuild, SMOKE_KEEP), smoke-seo.sh, urls.txt and urls-production.txt, migrate.sh with pl the only enabled locale, seo-settings.yaml (page_title_suffix, journal-author templates), grzyby:seo-settings types section, env templates, DEPLOY.md and README.md, boot, companions and console tests" + - "33-13-vue-grzybyfunkcjonalne-app.patch: nuxt.config on the seo options without @nuxtjs/seo keys, an app title template and default description for pages the CMS does not answer, no useSeoMeta in pages, SeoSitemapLink in the footer, SEO fixtures from the rehearsal under tests/fixtures/api/seo, ssr.test.ts and expect.txt checks" + - "33-13-local-wiring.md and 33-13-deploy-log.md (decision after-tags, production check list for 33-14)" +affects: [33-14] + +actuals: + tokens: 45000 + tasks: 4 + commits: 1 +plan_head_before: c59b3e61ed833b9daa46ca7cce2e0224a822a166 +plan_head_after: 7199b26bb77507349f3eeaf12c498ec64ccea89a + +tech-stack: + added: [] + patterns: + - "Scratch plugin wiring by git archive of each sibling's HEAD into the uninitialised submodule path (git refuses a symlink at a submodule path), the Nuxt worktree at the submodule path with the plan's name as a symlink, plugins.gen.go assume-unchanged in the scratch index" + - "Before/after head comparison: the live checkout's own production build rehearsed from a scratchpad copy, both stacks' heads diffed path by path" + - "Release data steps rehearsed through the management APIs with a temporary full-scope token, revoked at the end" + +key-files: + created: + - .planning/phases/33-seo-output-audit-and-content/33-13-sm-grzybyfunkcjonalne-app.patch + - .planning/phases/33-seo-output-audit-and-content/33-13-vue-grzybyfunkcjonalne-app.patch + - .planning/phases/33-seo-output-audit-and-content/33-13-local-wiring.md + - .planning/phases/33-seo-output-audit-and-content/33-13-deploy-log.md + modified: + - .planning/phases/33-seo-output-audit-and-content/deferred-items.md + +key-decisions: + - "Task 3: after-tags. 33-14 deploys the tagged v0.6.1 build to production grzybyfunkcjonalne.pl (DEPLOY.md steps 1 to 12, DB dump first); no staging install. Since the 2026-10-31 cutover the SummerCMS install is production, so there is no separate uat" + - "The static pages' titles move through the registry names and the site's title template (so page-2 suffixes land as on the PHP site); deploy/seo-pages.csv carries the home title, the descriptions and the social titles" + - "pl is the only enabled locale (migrate.sh step 3 and a DEPLOY.md step): an enabled en without content gives every page /en/ hreflang and sitemap addresses that 404" + - "The Additional addresses copied from the PHP definition (/, /blog) are removed once the route report lists the same pages" + - "All root files, robots.txt included, go to Nuxt; nginx keeps only = /_seo/api/v1/resolve of the SEO paths" + - "Demo (decided at the same time for 33-14): prefix_except_default, English at /, Polish at /pl/; reverses D-13" + +requirements-completed: [SC-1, SC-2, D-32, D-30, D-22, D-20, D-33, D-70, D-97, D-24] + +coverage: + - id: D1 + description: "Every local urls.txt line of grzybyfunkcjonalne.pl answers as expected on the new stack (full rehearsal with the companions, the route report and the release data steps)" + requirement: SC-1 + verification: + - kind: e2e + ref: "bash scripts/smoke.sh in the scratch worktree: smoke: ok (128 urls)" + status: pass + human_judgment: false + - id: D2 + description: "One H1, one canonical and one JSON-LD graph on a post, a category, /kontakt and /; breadcrumbs through the category; the sitemap index and its files, each address once with lastmod, no /en/, no tag file, no /sitemap.xsl" + requirement: D-70 + verification: + - kind: e2e + ref: "bash scripts/smoke-seo.sh http://127.0.0.1:3016: smoke-seo: ok" + status: pass + human_judgment: false + - id: D3 + description: "Both patches apply to the live repositories' HEADs" + requirement: D-32 + verification: + - kind: automated + ref: "git apply --check against sm-grzybyfunkcjonalne-app 35d3664 and vue-grzybyfunkcjonalne-app e45ffe8: patches-ok" + status: pass + human_judgment: false + - id: D4 + description: "The static pages, /blog and the author page keep the titles and descriptions production renders today" + requirement: D-33 + verification: + - kind: e2e + ref: "head diff of the Oct 31 production build and the new stack on the same rehearsal data (see Task 1)" + status: pass + human_judgment: false + - id: D5 + description: "Deploy only after Jakub's decision; production checked by Jakub at 33-14's release checkpoint" + requirement: D-32 + verification: + - kind: manual + ref: "33-13-deploy-log.md: Decision after-tags; check list for 33-14" + status: pass + human_judgment: true + +duration: 105min +completed: 2026-11-04 +--- + +# Phase 33 Plan 13: grzybyfunkcjonalne.pl on the new sitemap format and the SEO layer Summary + +**grzybyfunkcjonalne.pl rehearsed on the unreleased phase 33 stack with all 128 local URL checks passing and production's page titles kept. It now has per-type sitemaps through Nuxt, CMS-owned heads, breadcrumbs and a registry of its static pages. Two release patches cover it, and the deploy waits for the tagged build in 33-14, on production.** + +## Performance + +- **Duration:** about 105 minutes (17:28 to 19:13) +- **Tasks:** 4 (Tasks 1 and 2 executed, Task 3 decided, Task 4 recorded as the after-tags deferral) +- **Files in the patches:** 24 in the app repository, 15 in the Nuxt repository + +## Task 1: tracer on local links + +The wiring is in `33-13-local-wiring.md`. In short: +- two detached worktrees under `../../scratch-33`; +- the sibling plugins at their HEADs, copied in with `git archive`; +- the framework replaced in go.work; +- pnpm overrides `link:` to the four layers. + +**Runs:** +- `scripts/build.sh` on the wiring, with the SSR guard passing. +- `scripts/check-deploy.sh`: ok. +- `scripts/smoke.sh`, the full rehearsal: `smoke: ok (128 urls)`. It runs migrate, the companions import, the route report, the page names, the CSV import, the Additional addresses step, the rebuild, Go and Nuxt, and then every local `urls.txt` line. +- `scripts/smoke-seo.sh http://127.0.0.1:3016`: `smoke-seo: ok`. It covered 4 pages with one H1, canonical and JSON-LD each, breadcrumbs through `/cordyceps`, and the index of 5 files: posts 63, categories 8, authors 1, pages 4. +- **Baseline:** the live checkout's Oct 31 production build ran in its own rehearsal (`smoke: ok (107 urls)`), and its heads were diffed against the new stack on the same data. + - Identical: the titles and descriptions of `/`, `/blog`, the posts, the categories, `/autor/admin`, `/kontakt`, `/trwaja-prace` and `/szukaj`. + - Different: + - D-41 pagination suffixes on page 2 and later; + - og:description added on three pages; + - two small og:title differences (`/autor/admin` gains the site suffix, `/blog?page=2` shows "Artykuły"); + - `/szukaj` (noindex) without canonical and JSON-LD; + - the new `/mapa-strony`. + +## Task 2: patches + +- **Tests on the wiring:** + - `go vet` passes, untagged and tagged. + - `go test` passes with `-short`, with Docker and with `-tags summercms_mcp`, `plugins/grzyby/...` included. + - `pnpm test` 30/30, `pnpm smoke:ssr` 67 checks, `pnpm smoke:ga` 2 checks. +- **Export:** both patches came out with `git diff --binary` and `git add -N` for new files, without the wiring. +- **Check:** both pass `git apply --check` (`patches-ok`), again after the worktrees were removed. + +## Task 3: decision + +**after-tags** (Jakub, 2026-11-04). Production grzybyfunkcjonalne.pl gets the tagged v0.6.1 build in 33-14, following DEPLOY.md steps 1 to 12 with a DB dump first. No staging install is raised. There is no separate uat any more: the SummerCMS install has been production since the 2026-10-31 cutover. The user_setup secrets were checked by name before the checkpoint: the 404 token is in both `.env` files, and the optional route report token is in neither. + +## Task 4: deferral + +`33-13-deploy-log.md` records: +- the decision and why the target is production; +- that the `en` locale switch (step 5) and the emptying of the Additional addresses (step 10) are 33-14 deploy steps Jakub confirms at its release checkpoint; +- the production check list for that checkpoint: sitemap index and one file, a post's head and breadcrumbs, SEO › Pages, Settings › Sitemaps, a crawl, the visual check of the 0.8 markup, Search Console, libwebp. + +The scratch worktrees are removed (see Cleanup). **uat does not run the new stack, and neither does production: it still runs the Oct 31 build (sm-grzybyfunkcjonalne-app 35d3664).** + +## Task Commits + +1. **Task 2:** `7199b26` docs(33-13): grzyby patches for the release (both patches, the wiring note, deferred findings) +2. **Plan metadata:** `docs(33-13): complete the grzybyfunkcjonalne.pl plan` (this SUMMARY and the deploy log) + +Nothing was committed, pushed or tagged in the app repositories. + +## Decisions Made + +See `key-decisions` in the frontmatter. P33-49 was applied fully: robots.txt also leaves nginx for Nuxt. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] `/en/` hreflang links and sitemap addresses** +- **Found during:** Task 1 +- **Issue:** The translate plugin's seeded `en` row is enabled. Every record then counts as available in `en`: the posts file listed 126 addresses for 63 posts, `x-default` pointed at `/en/`, and post heads linked `/en/blog/...`. All of these answer 404. +- **Fix:** migrate.sh step 3 makes `pl` the only enabled locale and asserts it. DEPLOY.md step 5 does the same on the server. smoke-seo.sh and ssr.test.ts fail on any `/en/` link. + +**2. [Rule 1 - Bug] `/` and `/blog` listed twice in the pages file** +- **Issue:** They come in as Additional addresses (migrated from the PHP definition) and as registry pages (from the route report). +- **Fix:** smoke.sh and DEPLOY.md step 10 empty the Additional addresses after the route report, and smoke-seo.sh fails on duplicate ``. The plan expected the copied items to stay as Additional addresses: they are migrated, verified (`["/","/blog"]`), then removed. The plugin-side de-duplication stays a deferred item, also found in 33-12. + +**3. [Rule 1 - Bug] Titles would have changed on the live site** +- **Issue:** A CSV title overrides the template. `/` loses the home template once it is a registry page. The new plugin answers for `/blog` (registry) and `/autor/admin` (journal-author) with derived titles ("Blog", "admin") instead of production's "Artykuły" and "Wpisy autora admin". +- **Fix:** + - The registry names are set to "Artykuły" and "Trwają prace", and the titles come from the site template. + - The CSV carries the home title, the descriptions and the og titles. + - seo-settings.yaml gains `page_title_suffix "· Strona %page%"` and the journal-author templates. + - grzyby:seo-settings got a `types:` section (code and test) so the file can write per-type templates. + +**4. [Rule 1 - Bug] `/szukaj` lost its title suffix and description** +- **Issue:** With @nuxtjs/seo gone, pages the CMS doesn't answer lost the suffix and the site description. +- **Fix:** The app sets `app.head.titleTemplate` and a default description. error.vue keeps its old title without the suffix. + +**5. [Rule 3 - Blocking] grzyby console tests** +- **Issue:** golem15.seo now requires golem15.sitemap, so the tests' plugin lists failed to activate. +- **Fix:** The sitemap plugin is registered in both test activations, with a require/replace pair in `plugins/grzyby/go.mod`. + +**6. [Rule 3 - Blocking] Wiring** +- **Issue:** Symlinks at submodule paths break `git status`. +- **Fix:** Plugins are `git archive` copies, the Nuxt worktree sits at the submodule path, and `plugins.gen.go` is assume-unchanged in the scratch index (wiring drift only). + +### Plan assumptions adapted + +- The app was already on SummerCMS v0.6.0 with golem15.seo mounted and robots.txt served by Go (Phase 32), so summer.yaml is unchanged. +- The SEO fixtures moved from `tests/fixtures/seo` to `tests/fixtures/api/seo`, and `smoke:ssr` no longer points the SEO API at an unreachable port. +- `scripts/smoke.sh` gained `SMOKE_KEEP` and runs `smoke-seo.sh` last. +- The plan's uat deploy target no longer exists (see Task 3). + +## Issues Encountered + +New items logged in deferred-items.md "From 33-13": +- fallback locales producing addresses; +- local-time `updated_at` on registry page saves (a lastmod one hour in the future); +- the home template lost for a registry `/`; +- route-report names without diacritics; +- no token scope for the SEO or sitemap API; +- `journal:image-variants` cannot open the database from the generated CLI; +- the seeded `/uncategorized` category now in the sitemap. + +## For 33-14 + +- **Deploy grzybyfunkcjonalne.pl to production** after the tags, by `33-13-deploy-log.md` and the patch's DEPLOY.md steps 1 to 12. + - Take the DB dump first. + - Jakub confirms steps 5 (`en` off) and 10 (Additional addresses emptied) at the release checkpoint, then runs the log's check list. + - Watch the grzyby:seo-settings `--dry-run`: it must list only the three new values, or editor changes would be overwritten. +- **Apply the patches** on the release pins: `git apply` both, then bump the go.mod requires and the submodule pointers (seo, sitemap, journal, plus menu, faq and translate if tagged), the `package.json` layer tags and the lockfile. Then regenerate `plugins.gen.go` with `summer build`. The patch leaves it out because the scratch copies made it drift. +- **Linking:** the grzyby binary becomes dynamically linked against glibc (purego). It runs on the Debian server, and DEPLOY.md documents it. Decide whether to keep that or build with `-tags nodynamic`. libwebp on the server was not checked; it is item 8 of the check list. +- **WebP backfill:** `journal:image-variants` needs a `grzyby:image-variants` wrapper or a CLI fix before the backfill can run. +- **Demo, decided by Jakub on 2026-11-04:** the demo moves to `prefix_except_default`. English stays at `/` and Polish moves to `/pl/…`, which reverses D-13. 33-14 applies it on top of the 33-12 demo patches: `i18n.strategy`, `/pl/mapa-strony`, locale-aware links and LanguageSwitcher, the demo's URL and smoke expectations, and `SMOKE_SEO_STRICT=1`. It is recorded in deferred-items.md under "Decided for 33-14". + +## Cleanup + +`git worktree remove --force` was run for both worktrees, and the `scratch-33/vue-grzybyfunkcjonalne-app` symlink was removed with `/bin/rm -f`. `git -C ../sm-grzybyfunkcjonalne-app worktree list` and the Nuxt submodule's list show only the live checkouts. The live app repository is unchanged: still 35d3664, with the same untracked `.gsd/`, `docs/` and self-named entry as at the start. 33-12's files in scratch-33 were not touched. + +## Known Stubs + +None. + +## Threat Flags + +None beyond the plan's register. +- T-33-58: tokens were checked by name only, and the rehearsal tokens were throwaway and revoked. +- T-33-60: nginx exposes no `/_sitemap/` and no `/_seo/` path beyond resolve, and check-deploy.sh enforces it. +- T-33-61: nothing was deployed. + +## Self-Check: PASSED + +- FOUND: 33-13-sm-grzybyfunkcjonalne-app.patch, 33-13-vue-grzybyfunkcjonalne-app.patch, 33-13-local-wiring.md, 33-13-deploy-log.md +- FOUND: commit 7199b26 +- Both patches pass `git apply --check` against 35d3664 and e45ffe8. +- `git -C ../sm-grzybyfunkcjonalne-app worktree list` has no scratch-33 entry. +- No secret value in the patches, the logs or this summary. diff --git a/.planning/phases/33-seo-output-audit-and-content/33-13-deploy-log.md b/.planning/phases/33-seo-output-audit-and-content/33-13-deploy-log.md new file mode 100644 index 00000000..b72c0ef4 --- /dev/null +++ b/.planning/phases/33-seo-output-audit-and-content/33-13-deploy-log.md @@ -0,0 +1,55 @@ +# 33-13 deploy log + +## Decision + +**after-tags** (Jakub, 2026-11-04, Task 3 checkpoint). The production site grzybyfunkcjonalne.pl gets the tagged v0.6.1 build in 33-14. That deploy follows the patch's DEPLOY.md section "SEO pages and sitemaps (Phase 33)", steps 1 to 12, and step 1 takes a database dump before anything else. No staging install is raised. 33-13 deployed nothing and did not contact the server after the checkpoint. + +**There is no separate uat any more.** The plan was written for `uat.grzybyfunkcjonalne.pl` as a non-production environment next to the live WinterCMS site. Since the production cutover on 2026-10-31 (sm-grzybyfunkcjonalne-app 35d3664, DEPLOY.md "Production cutover"), the SummerCMS install is production: + +- the same `/srv/grzybyfunkcjonalne`, the same database and the same supervisor programs that served uat now serve grzybyfunkcjonalne.pl, with `SUMMER_ENV=production`; +- the uat vhost is switched off, and re-enabling it would put the UAT gate in front of production; +- the WinterCMS install stays on disk as the rollback, untouched. + +So 33-14 deploys to production. The staging-lock expectation of this plan (robots `Disallow: /` and noindex on uat) no longer applies. + +## Steps + +| Step | Result | +|------|--------| +| Decision recorded | after-tags, target production, no staging install | +| Read-only check of the user_setup secrets by name (before the checkpoint) | `NUXT_SEO_NOTFOUND_TOKEN` 1 line in `nuxt/.env`, `SUMMER_GOLEM15__SEO__NOTFOUND_TOKEN` 1 line in `go/.env`; both route report tokens absent (optional, the 404 token is used). Values neither printed nor compared | +| Build, upload, migrate, restart, nginx reload, rebuild, CSV import, smokes | not run in 33-13; 33-14 runs them on the tagged build | +| Scratch worktrees | removed after this log (33-13-SUMMARY.md) | + +## Part of the 33-14 deploy, confirmed by Jakub at its release checkpoint + +These two data steps of DEPLOY.md change production data. Jakub confirms them at 33-14's release checkpoint before they run: + +- **Step 5, Polish only.** `UPDATE golem15_translate_locales SET is_enabled = (code = 'pl')` (the same switch is Settings › Locales). The seeded `en` row is enabled, which would give every page an `/en/` hreflang link and every record an `/en/` sitemap address, all answering 404. Run the `SELECT` first; no translation row is deleted. +- **Step 10, Additional addresses.** Remove `/` and `/blog` from Settings › Sitemaps › Additional addresses. Migration `202610280002` copies them there from the Phase 17 definition, and SEO › Pages lists the same two pages after the route report, so without this step the pages file lists both twice. + +## Check list for 33-14's release checkpoint (production) + +Jakub runs these on https://grzybyfunkcjonalne.pl after the 33-14 deploy (steps 1 to 12) and before the release is called done: + +1. **Sitemap index and one file.** `/sitemap.xml` is a `` listing `sitemap-posts-1.xml`, `sitemap-categories-1.xml`, `sitemap-authors-1.xml`, `sitemap-pages-1.xml` and `sitemap-images-1.xml` on `https://grzybyfunkcjonalne.pl/`. `/sitemap-posts-1.xml` lists the 62 published posts, each once, with lastmod, with no `/en/` address and no hreflang. `/sitemap.xsl` answers 404. +2. **A post's head and breadcrumbs.** For example `/blog/blogchaga-wprowadzenie`: + - title and description as before; + - one canonical on the apex; + - `index, follow`; + - one JSON-LD script; + - no `hreflang="en"`; + - visible breadcrumbs Strona główna › category › post. +3. **SEO › Pages in /gfadmin:** + - lists `/`, `/blog` ("Artykuły"), `/kontakt` and `/trwaja-prace` ("Trwają prace"); + - none of them is marked Not on the site; + - their SEO tabs show the CSV descriptions. +4. **Settings › Sitemaps in /gfadmin:** + - the files table shows the five files with their counts; + - the Additional addresses are empty; + - IndexNow is off; + - the HTML sitemap is on at `/mapa-strony`, and that page lists the pages. +5. **A crawl.** SEO › Site audit, Start crawl. It finishes, and its issues are read. Expected: no `/en/` 404s and no duplicate sitemap entries. `/uncategorized` is an open editorial decision (deferred-items.md). +6. **The visual check of the 0.8 card and title markup.** Look at the home page, `/blog`, a category, a post and `/autor/admin`, on desktop and mobile. The post cards are now clickable as a whole and have no "more" link. Check the post title markup, the breadcrumbs at the top of the journal pages, and the footer link "Mapa strony". +7. **Resubmit the sitemap in Search Console.** Submit `https://grzybyfunkcjonalne.pl/sitemap.xml` (same address, now an index) and check that Google reads its files. +8. **libwebp on the server.** Run `ls /usr/lib/x86_64-linux-gnu/libwebp.so*`. If present, the Journal's WebP thumbnails use the fast encoder; if absent, the slower built-in one. Either works with the dynamically linked binary, unless 33-14 builds with `-tags nodynamic`. diff --git a/.planning/phases/33-seo-output-audit-and-content/deferred-items.md b/.planning/phases/33-seo-output-audit-and-content/deferred-items.md index 90761734..1e62d436 100644 --- a/.planning/phases/33-seo-output-audit-and-content/deferred-items.md +++ b/.planning/phases/33-seo-output-audit-and-content/deferred-items.md @@ -159,3 +159,15 @@ Out-of-scope discoveries logged during execution; not fixed in the plan that fou status: open - **Possible fix:** delete the empty seeded category on the server, or mark it noindex in its SEO tab. - **Why deferred:** an editorial data decision for Jakub. + +## Decided for 33-14 (Jakub, 2026-11-04) + +- **The demo moves to `prefix_except_default` (sm-demo-app, vue-demo-app; reverses demo D-13).** Answers 33-12's "The demo's Polish addresses answer 404". English stays at `/`, Polish moves to `/pl/…`. This matches the `/` prefix that the Journal, sitemap and SEO plugins give every non-default locale, so the hreflang links and the `/pl/…` sitemap addresses answer 200. + status: decided + - **Apply in 33-14, on top of the 33-12 demo patches:** + - `i18n.strategy: 'prefix_except_default'` in vue-demo-app; + - the HTML sitemap path `/pl/mapa-strony`; + - locale-aware links in the demo's pages and the LanguageSwitcher; + - the demo's urls and smoke expectations moved to the `/pl/…` addresses; + - run `smoke-seo.sh` with `SMOKE_SEO_STRICT=1` so the WARN lines fail. + - **Notice:** every Polish URL of the demo changes; the cookie-at-the-same-address behaviour of D-13 is gone.