docs(11.2-01): complete the summercms.io Nuxt landing page plan
This commit is contained in:
@@ -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*
|
||||
Reference in New Issue
Block a user