docs(11.2-01): complete the summercms.io Nuxt landing page plan

This commit is contained in:
Jakub Zych
2026-10-01 15:56:48 +02:00
parent 88f21af923
commit 8762e92a32

View File

@@ -0,0 +1,265 @@
---
phase: 11.2-ready-to-share-summercms-io-website-and-newsletter-plugin
plan: 01
subsystem: ui
tags: [nuxt4, vue, nuxt-i18n, nuxt-seo, nuxt-fonts, ssg, node-test, imagemagick]
requires:
- phase: 11.1-summercms-documentation-for-humans-and-ai-agents
provides: the /docs pages the landing page links to (served by the Go binary, not by Nuxt)
provides:
- "vue-summercms-app repository (master, no remote) generating the summercms.io landing page with nuxt generate"
- ".output/public contract for plan 11.2-02: index.html, 200.html, 404.html, _nuxt/, _fonts/, _i18n/<hash>/en/messages.json, _payload.json, flat sitemap.xml, robots.txt, og-image.png, favicons, sun images"
- "app/data/terminal.json: the single source of the six Get started commands (groups[].comment i18n key, groups[].commands)"
- "pure utilities copyPayload (app/utils/terminal.ts) and activeSection/SPY_SECTIONS/SPY_THRESHOLD (app/utils/scrollSpy.ts)"
affects: [11.2-02 site plugin embed and link check, 11.2-02 D-40 terminal command check, 11.2-03 unit tests, 11.3 Polish locale]
actuals:
tokens: 13246 # chars/4 over the committed text files (pnpm-lock.yaml and images excluded; lockfile alone is ~97900)
tasks: 3
commits: 4 # MEASURED in vue-summercms-app: git rev-list --count master (repository created by this plan)
plan_head_before: null # vue-summercms-app did not exist before this plan; summercms.go HEAD was 88f21af923e0f4efff6a72437977f636e48923c7 and gained no code commits
plan_head_after: 17e9f923559f6c2a9f176bdb5ae3aa78af934d9f # vue-summercms-app master
commits_repo: /media/nvme/dev/golem15/summercms.io/summercms/sm-summercms-app/vue-summercms-app
tech-stack:
added: ["nuxt 4.4.8", "@nuxt/fonts 0.14.0", "@nuxtjs/i18n 10.4.0", "@nuxtjs/seo 5.2.1", "vue 3.5.35", "typescript 5.9.3 (dev)"]
patterns:
- "Plain CSS: tokens as :root custom properties (tokens.css), shared shapes in base.css, scoped component styles"
- "All copy in i18n/locales/en.json; inline code inside copy via <i18n-t> named slots, never raw HTML"
- "Width-dependent UI via CSS media queries only, so the server render never depends on the viewport"
- "Pure, erasable-TypeScript utilities under app/utils/ run directly by node --test with .ts specifiers"
- "Output tests read .output/public and en.json, resolving paths from import.meta.url"
key-files:
created:
- ../sm-summercms-app/vue-summercms-app/nuxt.config.ts
- ../sm-summercms-app/vue-summercms-app/app/app.config.ts
- ../sm-summercms-app/vue-summercms-app/app/pages/index.vue
- ../sm-summercms-app/vue-summercms-app/app/components/SiteHeader.vue
- ../sm-summercms-app/vue-summercms-app/app/components/HeroSection.vue
- ../sm-summercms-app/vue-summercms-app/app/components/WhySection.vue
- ../sm-summercms-app/vue-summercms-app/app/components/FeaturesSection.vue
- ../sm-summercms-app/vue-summercms-app/app/components/WinterSection.vue
- ../sm-summercms-app/vue-summercms-app/app/components/StartSection.vue
- ../sm-summercms-app/vue-summercms-app/app/components/TerminalCard.vue
- ../sm-summercms-app/vue-summercms-app/app/components/SiteFooter.vue
- ../sm-summercms-app/vue-summercms-app/app/assets/css/tokens.css
- ../sm-summercms-app/vue-summercms-app/app/assets/css/base.css
- ../sm-summercms-app/vue-summercms-app/app/data/terminal.json
- ../sm-summercms-app/vue-summercms-app/app/utils/terminal.ts
- ../sm-summercms-app/vue-summercms-app/app/utils/scrollSpy.ts
- ../sm-summercms-app/vue-summercms-app/i18n/locales/en.json
- ../sm-summercms-app/vue-summercms-app/scripts/derive-images.sh
- ../sm-summercms-app/vue-summercms-app/assets-src/logo.png
- ../sm-summercms-app/vue-summercms-app/public/og-image.png
- ../sm-summercms-app/vue-summercms-app/tests/output.test.ts
- ../sm-summercms-app/vue-summercms-app/tests/terminal.test.ts
- ../sm-summercms-app/vue-summercms-app/tests/scrollSpy.test.ts
modified: []
key-decisions:
- "Page title is 'SummerCMS: A new dawn in content management' with titleTemplate '%s' so the site name is not appended twice; description is the hero paragraph"
- "Header sun is the 30px original scaled 1.12 without the handoff mask (the mask only cropped the screenshot placeholder); hero badge shows the sun at 136px"
- "Header at phone widths: at 720px and below the empty links wrapper is hidden and Docs is pushed right; at 420px and below the row gap is 10px and side padding 16px, because logo + pill + Docs overflowed 375px with the handoff spacing"
- "Copy falls back to a hidden textarea + execCommand('copy') when navigator.clipboard is missing or rejects; a failed copy leaves the label unchanged"
- "Footer link group may wrap (flex-wrap) so the four links never overflow a phone width"
patterns-established:
- "en.json leaf check: every message leaf (minus interaction-only keys) must appear HTML-escaped in index.html"
- "terminal.json comment keys resolve in en.json; the output test reads commands from terminal.json, never a second list"
requirements-completed: []
coverage:
- id: D1
description: "nuxt generate prerenders index.html with header, hero #top, #why, #features, #winter, #start and the footer in handoff order"
verification:
- kind: other
ref: "pnpm generate (exit 0, no prerender errors)"
status: pass
- kind: automated_ui
ref: "tests/output.test.ts#sections appear in handoff order: top, why, features, winter, start, then the footer"
status: pass
human_judgment: false
- id: D2
description: "Every visible string comes from i18n/locales/en.json with the single en locale at /"
verification:
- kind: automated_ui
ref: "tests/output.test.ts#every en.json string is rendered on the page"
status: pass
- kind: automated_ui
ref: "tests/output.test.ts#i18n messages are emitted for the client under /_i18n"
status: pass
human_judgment: false
- id: D3
description: "Roboto and Roboto Mono self-hosted under /_fonts with no Google font host in generated HTML or CSS"
verification:
- kind: automated_ui
ref: "tests/output.test.ts#fonts are self-hosted under /_fonts"
status: pass
- kind: automated_ui
ref: "tests/output.test.ts#no generated HTML or CSS references a Google font host"
status: pass
human_judgment: false
- id: D4
description: "Static SEO: canonical, OG (absolute og:image), Twitter, schema.org JSON-LD, flat sitemap.xml and robots.txt"
verification:
- kind: automated_ui
ref: "tests/output.test.ts#the page head carries canonical, Open Graph, Twitter and schema.org tags"
status: pass
- kind: automated_ui
ref: "tests/output.test.ts#robots.txt points at the flat sitemap.xml"
status: pass
human_judgment: false
- id: D5
description: "All docs, source and credit links: eight extension-less tiles, concept map, installation guide, /docs, Source x2, golem15.com"
verification:
- kind: automated_ui
ref: "tests/output.test.ts#feature tiles and the concept map link to the docs, extension-less"
status: pass
- kind: automated_ui
ref: "tests/output.test.ts#the footer links to the source and credits Golem15"
status: pass
- kind: automated_ui
ref: "tests/output.test.ts#Get started shows the chips, the six commands, their comments and the links"
status: pass
human_judgment: false
- id: D6
description: "Terminal card renders the six terminal.json commands; Copy writes exactly those six lines"
verification:
- kind: unit
ref: "tests/terminal.test.ts#copyPayload joins the six commands in order"
status: pass
- kind: unit
ref: "tests/terminal.test.ts#every terminal.json comment key resolves to a string in en.json"
status: pass
human_judgment: false
- id: D7
description: "Scroll-spy selection: last of why/features/winter/start with top < 140, none above #why"
verification:
- kind: unit
ref: "tests/scrollSpy.test.ts#the last section above the threshold wins"
status: pass
- kind: unit
ref: "tests/scrollSpy.test.ts#a top of exactly the threshold is not active"
status: pass
human_judgment: false
- id: D8
description: "Visual fidelity at 1280/721/720/375px, sticky header while scrolling, live scroll-spy highlight, Copy -> Copied for 1.5s and clipboard contents, hero rays and sun badge vs the handoff"
verification: []
human_judgment: true
rationale: "Pixel and interaction fidelity in a real browser is the end-of-phase UAT backstop (plan must_haves); headless screenshots at 1280/375/360/720/721 were only a sanity check"
duration: 12min
completed: 2026-10-01
status: complete
---
# Phase 11.2 Plan 01: summercms.io Nuxt landing page Summary
**Nuxt 4 static landing page in the new `vue-summercms-app` repo. It has self-hosted Roboto, a single `en` locale holding every string, static SEO and sitemap output, the original sun in every image, and a Get started terminal card driven by one `terminal.json`. It is verified by 29 node:test assertions over the utilities and the generated `.output/public`.**
## Performance
- **Duration:** 12 min
- **Started:** 2026-10-01T13:43:15Z
- **Completed:** 2026-10-01T13:55:16Z
- **Tasks:** 3 (tracer + auto + TDD)
- **Files created:** 35 tracked files in vue-summercms-app (no summercms.go code changes)
## Accomplishments
- New repository `/media/nvme/dev/golem15/summercms.io/summercms/sm-summercms-app/vue-summercms-app` (branch `master`, no remote). Packages are pinned exactly to the fonoteka versions, `pnpm-lock.yaml` is committed, and `allowBuilds` is limited to `@parcel/watcher`, `esbuild` and `vue-demi`.
- `nuxt generate` exits 0 and writes real prerendered HTML: the sticky header, the hero `#top`, `#why`, `#features`, `#winter`, `#start` and the footer, in that order. `/docs` is ignored by the prerender crawler.
- `@nuxt/fonts` self-hosts 4 woff2 files under `/_fonts/`. The generated HTML and CSS name no Google font host.
- `@nuxtjs/seo` writes the canonical link, OG tags (absolute `https://summercms.io/og-image.png`), Twitter tags, schema.org `@graph`, a flat `sitemap.xml` and a `robots.txt` with a Sitemap line. `ogImage` is disabled and a static PNG is shipped instead.
- `scripts/derive-images.sh` (ImageMagick only) derives `sun-logo.webp` (60px), `sun-badge.webp` (272px), `favicon.ico` (16/32/48), `apple-touch-icon.png` (180, on navy) and `og-image.png` (1200x630: sun, wordmark, tagline) from the committed original `assets-src/logo.png` (746x744). `sun-crop.png` is not shipped.
- `app.config.ts` has `landing.showRays` and `landing.scrollSpy`. Setting `showRays: false` was checked to remove the rays div from the output.
- Scroll-spy uses a passive scroll listener, computes on mount and is cleaned up on unmount. The server render has no active link.
- Copy uses `copyPayload` (six lines, no `$` or comments) with a textarea fallback, and shows `Copied` for 1500 ms.
## Task Commits
All in `vue-summercms-app`:
1. **Task 1: Tracer: scaffold with header, hero, fonts and SEO**: `c41cdac` (feat)
2. **Task 2: Why, Features, From WinterCMS and footer**: `a29b2d2` (feat)
3. **Task 3 (TDD): terminal card, copy button and scroll-spy**: RED `d2ffb9b` (test), GREEN `17e9f92` (feat). No refactor was needed.
**Plan metadata:** committed in summercms.go (docs commit for this SUMMARY)
## TDD Gate Compliance
- RED `d2ffb9b`: tests plus interface-only stubs (correct exports and signatures, placeholder bodies), so the failures are assertion failures, not load crashes. `node --test tests/terminal.test.ts tests/scrollSpy.test.ts` gave exit 1 with 8 of 13 failing. `gsd-tools check tdd-red-evidence` returned `RED_EVIDENCE_OK` for the targets "the last section above the threshold wins" (expected `winter`, got `''`) and "copyPayload skips a group without commands" (expected `one\ntwo\nthree`, got `''`).
- GREEN `17e9f92`: 29 of 29 pass in `pnpm test`, and 13 of 13 in the two unit files.
## Files Created/Modified
- `nuxt.config.ts`: modules, fonts, i18n (`prefix_except_default`, `prerenderMessages`), site, `ogImage` off, flat sitemap, link-checker exclusion, `nitro.prerender.ignore: ['/docs']`.
- `app/components/*.vue`: `SiteHeader` (sticky bar, scroll-spy, CSS-hidden links at 720px or less), `HeroSection` (rays behind the flag, glow, badge, gradient H1, CTAs), `WhySection` (`<i18n-t>` slots for the file names), `FeaturesSection` (eight extension-less docs tiles), `WinterSection` (concept table), `StartSection` (chips, note, buttons), `TerminalCard`, `SiteFooter` (with the Golem15 credit).
- `app/assets/css/tokens.css`, `base.css`: tokens, globals with a reduced-motion scroll override, and the shared heading, button and chip shapes.
- `app/data/terminal.json`, `app/utils/terminal.ts`, `app/utils/scrollSpy.ts`: the single command source and the pure helpers.
- `i18n/locales/en.json`: `meta`, `header`, `hero`, `why`, `features`, `winter`, `start`, `terminal`, `footer`.
- `tests/output.test.ts`, `tests/terminal.test.ts`, `tests/scrollSpy.test.ts`: node:test, with paths resolved from `import.meta.url`.
## Decisions Made
See `key-decisions` in the frontmatter. In short: the full page title with a `%s` template, the unmasked sun scaled 1.12 in the header, the phone-width header fix, the clipboard fallback, and footer links that can wrap.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] The header overflowed phone widths**
- **Found during:** Task 3 (headless screenshot sanity check at 375px)
- **Issue:** With the handoff spacing (an empty `flex: 1` links wrapper and three 16px gaps), logo + pill + Docs needed about 414px. At 375px the Docs button was clipped and the right padding was lost. The handoff markup has the same overflow.
- **Fix:** At 720px and below, `.links-wrap` is hidden and `.docs` gets `margin-left: auto`. At 420px and below, `.bar` uses `gap: 10px; padding: 0 16px`. Above 720px nothing changes.
- **Files modified:** `app/components/SiteHeader.vue`
- **Verification:** Headless screenshots at 360, 375, 720 and 721px. The header fits, links are hidden at 720 and visible at 721. 29 of 29 tests pass.
- **Committed in:** `17e9f92`
**2. [Rule 1 - Bug] Footer links could overflow a phone width**
- **Found during:** Task 2
- **Issue:** The D-12 credit makes four footer links. The handoff's non-wrapping link row is close to the 327px content width at 375px.
- **Fix:** `flex-wrap: wrap` on the footer link group. Nothing changes when they fit.
- **Files modified:** `app/components/SiteFooter.vue`
- **Committed in:** `a29b2d2`
**3. [Process] Task 3 committed as RED + GREEN instead of one commit**
- The plan asked for one `feat(site)` commit holding the tests and the implementation. Following the TDD reference, the tests went in a `test(site)` RED commit (with interface-only stubs so RED fails on assertions) and the implementation in the `feat(site)` commit with the planned message. `pnpm test` is red at `d2ffb9b` by design.
**4. [Minor] Small structural choices inside the plan's latitude**
- The en.json leaf-check helper lives inside `tests/output.test.ts`, not in a separate helper file, so `tests/*.test.ts` matches only test files. It takes an `except` list for `terminal.copied`, which only appears after a click.
- The `Docs →` label is two keys (`header.docs`, `header.docsArrow`) so the arrow sits in its own span, as in the handoff.
- `<html lang>` renders as `en-US` (from `language: 'en-US'`). The test matches `<html\s+lang="en`.
---
**Total deviations:** 2 auto-fixed bugs (Rule 1), 1 process deviation (TDD commit split), and minor structural choices.
**Impact on plan:** The responsive fixes keep D-23 fidelity above 720px and make SC1's "responsive down to phone width" true. There is no scope creep.
## Issues Encountered
- A bash `pkill -f` pattern matched its own shell and killed the screenshot command after the screenshots were written. Later runs stopped the server by PID instead. No effect on the repository.
## User Setup Required
None. Creating the `vue-summercms-app` remote on git.golem15.com is a D-43 launch item listed for plan 11.2-02.
## Next Phase Readiness
- Plan 11.2-02 can rsync `.output/public/` (not the `dist` symlink) into the plugin's `public/site/` and embed it with `all:` (the output has `_nuxt/`, `_fonts/`, `_i18n/`, `_payload.json` and `__sitemap__/`). `_nuxt/builds/` is not content-hashed.
- `app/data/terminal.json` is ready for the D-40 Go check and the HTML drift guard. Commands render contiguously after a `<span class="prompt">$ </span>`.
- The page uses extension-less `/docs/...` hrefs (D-47), so the plugin must 301 them to `.html`.
- End-of-phase UAT: visual fidelity at 1280, 721, 720 and 375px, the sticky header, the live scroll-spy, and the Copy label and clipboard (coverage D8).
## Self-Check: PASSED
- Files: all 35 tracked files are present in `vue-summercms-app` (`git ls-files`), including every `key-files.created` entry.
- Commits: `c41cdac`, `a29b2d2`, `d2ffb9b` and `17e9f92` are in `vue-summercms-app` master. The commit count of 4 was measured with `git rev-list --count master`.
- Plan verification: `pnpm install --frozen-lockfile && pnpm generate && pnpm test` gives 29 pass and 0 fail. The SITE working tree is clean. summercms.go shows only its pre-existing `STATE.md`, `state.json` and zip changes. No co-author trailers.
---
*Phase: 11.2-ready-to-share-summercms-io-website-and-newsletter-plugin*
*Completed: 2026-10-01*