Files
summercms/.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-01-PLAN.md

44 KiB
Raw Blame History

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, assumption_delta_decision, specless_probe_fallback, user_setup, estimate, must_haves
phase plan type wave depends_on files_modified autonomous requirements assumption_delta_decision specless_probe_fallback user_setup estimate must_haves
11.2-ready-to-share-summercms-io-website-and-newsletter-plugin 01 execute 1
../sm-summercmsio-app/vue-summercmsio-app/package.json
../sm-summercmsio-app/vue-summercmsio-app/pnpm-lock.yaml
../sm-summercmsio-app/vue-summercmsio-app/pnpm-workspace.yaml
../sm-summercmsio-app/vue-summercmsio-app/.nvmrc
../sm-summercmsio-app/vue-summercmsio-app/.gitignore
../sm-summercmsio-app/vue-summercmsio-app/README.md
../sm-summercmsio-app/vue-summercmsio-app/tsconfig.json
../sm-summercmsio-app/vue-summercmsio-app/nuxt.config.ts
../sm-summercmsio-app/vue-summercmsio-app/app/app.config.ts
../sm-summercmsio-app/vue-summercmsio-app/app/app.vue
../sm-summercmsio-app/vue-summercmsio-app/app/pages/index.vue
../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue
../sm-summercmsio-app/vue-summercmsio-app/app/components/HeroSection.vue
../sm-summercmsio-app/vue-summercmsio-app/app/components/WhySection.vue
../sm-summercmsio-app/vue-summercmsio-app/app/components/FeaturesSection.vue
../sm-summercmsio-app/vue-summercmsio-app/app/components/WinterSection.vue
../sm-summercmsio-app/vue-summercmsio-app/app/components/StartSection.vue
../sm-summercmsio-app/vue-summercmsio-app/app/components/TerminalCard.vue
../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteFooter.vue
../sm-summercmsio-app/vue-summercmsio-app/app/assets/css/tokens.css
../sm-summercmsio-app/vue-summercmsio-app/app/assets/css/base.css
../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json
../sm-summercmsio-app/vue-summercmsio-app/app/utils/terminal.ts
../sm-summercmsio-app/vue-summercmsio-app/app/utils/scrollSpy.ts
../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json
../sm-summercmsio-app/vue-summercmsio-app/assets-src/logo.png
../sm-summercmsio-app/vue-summercmsio-app/scripts/derive-images.sh
../sm-summercmsio-app/vue-summercmsio-app/public/sun-logo.webp
../sm-summercmsio-app/vue-summercmsio-app/public/sun-badge.webp
../sm-summercmsio-app/vue-summercmsio-app/public/favicon.ico
../sm-summercmsio-app/vue-summercmsio-app/public/apple-touch-icon.png
../sm-summercmsio-app/vue-summercmsio-app/public/og-image.png
../sm-summercmsio-app/vue-summercmsio-app/tests/output.test.ts
../sm-summercmsio-app/vue-summercmsio-app/tests/terminal.test.ts
../sm-summercmsio-app/vue-summercmsio-app/tests/scrollSpy.test.ts
true
no-change skipped: phase has no requirement IDs to probe (visible skip); ROADMAP SC1-SC5 and the D-IDs are the acceptance contract
tokens raw_tokens tasks confidence
120000 120000 3 low
truths artifacts key_links
Per D-04 and SC1, `pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run generate` exits 0 and writes `.output/public/index.html` as real prerendered HTML holding, in order, the sticky header, the hero `#top`, `#why`, `#features`, `#winter`, `#start` and the footer.
Per D-23, colors, type, spacing, breakpoints, copy and the two interactions match design/README.md and `SummerCMS Landing.dc.html`; the only copy deviations are the D-12 credit, the D-39 clone step and the D-26 `PostgreSQL 15+` chip.
Per D-33, every visible string on the page comes from `i18n/locales/en.json` through @nuxtjs/i18n with the single locale `en` served at `/` (strategy `prefix_except_default`), so Phase 11.3 adds `pl.json` without touching component markup.
Per D-34, Roboto 300/400/500/700 and Roboto Mono 400/500 are self-hosted by @nuxt/fonts under `/_fonts/` and the generated HTML and CSS reference no Google font host.
Per D-35, styles are plain CSS: the handoff tokens are custom properties on `:root` in `tokens.css`, used by scoped component styles; no CSS framework package is installed.
Per D-36, every sun image (header logo, hero badge, favicons, OG image) is derived from the original `logo.png` by a committed ImageMagick script, and the handoff `sun-crop.png` placeholder is not shipped.
Per D-37, @nuxtjs/seo emits title, description, canonical, Open Graph, Twitter and schema.org tags, a flat `sitemap.xml` and `robots.txt` at generate time; `og:image` is the absolute `https://summercms.io/og-image.png`; nothing is rendered at runtime.
Per D-45, `app.config.ts` has `landing.showRays` and `landing.scrollSpy`, both `true`; setting either to false removes the hero rays layer or the scroll-spy listener.
Per D-39 and D-40, the terminal card shows three groups and six commands read from the single source `app/data/terminal.json`, and Copy writes exactly the six command lines joined by newlines (no `$`, no comments), then shows `Copied` for 1500 ms.
Per D-47 and D-38, the eight feature tiles, the concept-map link and the Installation guide keep the handoff's extension-less `/docs/...` hrefs, the Docs links point to `/docs`, and both Source links point to `https://git.golem15.com/golem15/summercms`.
Per D-09, the From WinterCMS section carries the handoff copy and concept table unchanged, with no added seasons copy; per D-12, the footer has a `Built by Golem15` link to `https://golem15.com` styled as a footer link.
Per SC1, at viewport widths of 720px or less the header section links are hidden by a CSS media query (rendered in SSR, no width-dependent markup), and every grid collapses to one column on phone widths.
Scroll-spy marks the last of why, features, winter and start whose top is less than 140px from the viewport top; above `#why` no link is active.
statement verification
Visual fidelity at 1280, 721, 720 and 375px, the sticky header, and the Copy label change are confirmed against the handoff in the end-of-phase UAT. backstop
path provides contains
../sm-summercmsio-app/vue-summercmsio-app/nuxt.config.ts Nuxt 4 SSG config: fonts, i18n, seo, prerender ignore for /docs prerender
path provides contains
../sm-summercmsio-app/vue-summercmsio-app/app/app.config.ts D-45 landing flags scrollSpy
path provides contains
../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json all page copy (D-33) A new dawn in content management
path provides contains
../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json single source of the six terminal commands (D-40) ./bin/hello greeter:hello
path provides contains
../sm-summercmsio-app/vue-summercmsio-app/app/components/TerminalCard.vue terminal card with Copy copyPayload
path provides contains
../sm-summercmsio-app/vue-summercmsio-app/app/utils/scrollSpy.ts pure scroll-spy selection export function activeSection
path provides contains
../sm-summercmsio-app/vue-summercmsio-app/app/utils/terminal.ts pure copy payload builder export function copyPayload
path provides contains
../sm-summercmsio-app/vue-summercmsio-app/tests/output.test.ts SC1 assertions on the generated output index.html
path provides contains
../sm-summercmsio-app/vue-summercmsio-app/scripts/derive-images.sh D-36 image derivation from the original sun magick
from to via pattern
../sm-summercmsio-app/vue-summercmsio-app/app/components/TerminalCard.vue ../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json static JSON import; the card renders and copies only these commands data/terminal.json
from to via pattern
../sm-summercmsio-app/vue-summercmsio-app/app/components/TerminalCard.vue ../sm-summercmsio-app/vue-summercmsio-app/app/utils/terminal.ts copyPayload(groups) builds the clipboard text copyPayload(
from to via pattern
../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue ../sm-summercmsio-app/vue-summercmsio-app/app/utils/scrollSpy.ts scroll listener calls activeSection with section tops activeSection(
from to via pattern
../sm-summercmsio-app/vue-summercmsio-app/nuxt.config.ts /docs links on the page nitro.prerender.ignore keeps generate from crawling /docs, which the Go binary serves ignore:.*'/docs'
Build `vue-summercmsio-app`, the Nuxt 4 static site for summercms.io, matching the claude.ai/design handoff exactly (D-23) with the decided deviations (D-12, D-26, D-39). `nuxt generate` produces real prerendered HTML (D-04) with self-hosted fonts (D-34), plain-CSS tokens (D-35), the original sun artwork (D-36), static SEO output (D-37), a single `en` locale ready for Phase 11.3 (D-33), the app-config flags (D-45), and a terminal card fed from one command list (D-39, D-40). Docs links stay extension-less (D-47). The superseded decisions apply in their superseded form: D-06 English only (Polish is Phase 11.3, via D-33), D-08 the handoff's section list, D-10 the "Alpha 0.1 is out" tone, and D-11 Source links on the page (D-38).

Purpose: SC1. The Go binary in plan 11.2-02 embeds this output, so the generate output contract here (file layout, _nuxt/, _fonts/, _i18n/, flat sitemap.xml) is what the site handler serves.

Output: a new git repository at /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app (it becomes a submodule of sm-summercmsio-app in plan 11.2-02), with node:test assertions over the generated output and the two pure utilities.

<execution_context> @/.claude/gsd-core/workflows/execute-plan.md @/.claude/gsd-core/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @CLAUDE.md @.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-CONTEXT.md @.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-RESEARCH.md @.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-PATTERNS.md @.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/design/README.md @.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/design/SummerCMS Landing.dc.html @/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/nuxt.config.ts @/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/package.json @/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/pnpm-workspace.yaml

Paths. Commands run from the summercms.go root. SITE = ../sm-summercmsio-app/vue-summercmsio-app (absolute: /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app). Every file in this plan is written to the SITE repository, which this plan creates. Nothing in summercms.go changes in this plan. Do not run git add in the meta repository summercms/ or in summercms.go; sm-summercmsio-app/ stays a plain directory until plan 11.2-02 makes it a repository.

Commits. Commit inside SITE only, one logical change per commit, conventional messages such as feat(site): ..., and never a co-author tag.

Design source of truth. design/SummerCMS Landing.dc.html is authoritative for punctuation and details not in design/README.md (RESEARCH "Details found in SummerCMS Landing.dc.html"): typographic apostrophes in "each plugin’s YAML" and "the caller’s transaction", the straight apostrophe in "WHAT'S IN THE BOX", gaps, paddings and the terminal layout. Ignore its support.js runtime and its JS width state.

Pure utilities consumed by components and by node:test (use only erasable TypeScript syntax so Node 22 runs the files without a build: no enums, no namespaces, no parameter properties; type-only imports use `import type`):
  • app/utils/terminal.ts: export interface TerminalGroup { comment: string; commands: string[] } and export function copyPayload(groups: readonly TerminalGroup[]): string returning every command in order joined by \n, with no trailing newline, no $ and no comment text.
  • app/utils/scrollSpy.ts: export const SPY_SECTIONS = ['why', 'features', 'winter', 'start'], export const SPY_THRESHOLD = 140, and export function activeSection(tops: ReadonlyArray<{ id: string; top: number }>, threshold?: number): string returning the id of the last entry whose top is strictly less than the threshold, or '' when none is.
  • app/data/terminal.json: { "groups": [ { "comment": "<i18n key>", "commands": [ ... ] } ] } with exactly these groups in order: terminal.getFramework → git clone https://git.golem15.com/golem15/summercms, cd summercms; terminal.installCli → go install ./cmd/summer; terminal.buildExample → cd examples/hello, summer build, ./bin/hello greeter:hello. Plan 11.2-02 reads this file from Go (D-40 check and the HTML drift guard), so the key names and shape are a contract.
  • app/app.config.ts: defineAppConfig({ landing: { showRays: true, scrollSpy: true } }).
Task 1: Tracer: `nuxt generate` turns en.json into a prerendered page with the sticky header and hero, self-hosted fonts, the real sun and static SEO output `node --version` prints v22.18 or newer, `pnpm --version` prints 11.x, and `magick -version` succeeds with WEBP and ICO delegates (RESEARCH Environment Availability). ../sm-summercmsio-app/vue-summercmsio-app/package.json, ../sm-summercmsio-app/vue-summercmsio-app/pnpm-lock.yaml, ../sm-summercmsio-app/vue-summercmsio-app/pnpm-workspace.yaml, ../sm-summercmsio-app/vue-summercmsio-app/.nvmrc, ../sm-summercmsio-app/vue-summercmsio-app/.gitignore, ../sm-summercmsio-app/vue-summercmsio-app/README.md, ../sm-summercmsio-app/vue-summercmsio-app/tsconfig.json, ../sm-summercmsio-app/vue-summercmsio-app/nuxt.config.ts, ../sm-summercmsio-app/vue-summercmsio-app/app/app.config.ts, ../sm-summercmsio-app/vue-summercmsio-app/app/app.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/pages/index.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/components/HeroSection.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/assets/css/tokens.css, ../sm-summercmsio-app/vue-summercmsio-app/app/assets/css/base.css, ../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json, ../sm-summercmsio-app/vue-summercmsio-app/assets-src/logo.png, ../sm-summercmsio-app/vue-summercmsio-app/scripts/derive-images.sh, ../sm-summercmsio-app/vue-summercmsio-app/public/sun-logo.webp, ../sm-summercmsio-app/vue-summercmsio-app/public/sun-badge.webp, ../sm-summercmsio-app/vue-summercmsio-app/public/favicon.ico, ../sm-summercmsio-app/vue-summercmsio-app/public/apple-touch-icon.png, ../sm-summercmsio-app/vue-summercmsio-app/public/og-image.png, ../sm-summercmsio-app/vue-summercmsio-app/tests/output.test.ts - .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/design/README.md (Global, 1. Header, 2. Hero, Design Tokens, Assets) - .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/design/SummerCMS Landing.dc.html (header and hero markup and inline styles) - .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-RESEARCH.md sections "Standard Stack", "Package Legitimacy Audit", "Nuxt 4 (question 4)", "Pattern 1", "Design Handoff Inventory", Pitfalls 1-4 and 6, Open Questions 4-5 - .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-PATTERNS.md section "SITE/nuxt.config.ts, package.json, pnpm-workspace.yaml, i18n/locales/en.json" - /media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/nuxt.config.ts (fonts, i18n and site blocks; copy shapes only) - /media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/package.json and pnpm-workspace.yaml (scripts, engines, allowBuilds) Create the repository and the thinnest production-quality slice through every layer the site has: package config, Nuxt modules, i18n copy, tokens, components, static assets and the generate output, checked by a test over the output.
  1. Repository (per D-01 and D-43): mkdir -p /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app, then git init -b master inside vue-summercmsio-app only. Add .gitignore (node_modules/, .nuxt/, .output/, .data/, dist, .env), .nvmrc with 22.22.0, and a short README.md (what the site is, pnpm install --frozen-lockfile, pnpm generate, pnpm test, scripts/derive-images.sh, and that the root app embeds .output/public).
  2. Packages (T-11.2-SC): package.json with "name": "vue-summercmsio-app", "private": true, "type": "module", "engines": { "node": ">=22.6" }, scripts build (nuxt build), dev (nuxt dev), generate (nuxt generate), preview (nuxt preview), prepare (nuxt prepare), test (node --test tests/*.test.ts), and no install-time lifecycle script. Install with exact pins via pnpm add --save-exact nuxt@4.4.8 @nuxt/fonts@0.14.0 @nuxtjs/i18n@10.4.0 @nuxtjs/seo@5.2.1 vue@3.5.35 and pnpm add -D --save-exact typescript@5.9.3. These are the versions running in vue-fonoteka-app and approved in RESEARCH "Package Legitimacy Audit". If pnpm cannot resolve any of these exact versions, stop and return a checkpoint:human-verify with gate="blocking-human" naming the package and version instead of installing another version. Copy fonoteka's pnpm-workspace.yaml allowBuilds entries for @parcel/watcher, esbuild and vue-demi only (no protobufjs, no sharp, no patchedDependencies). Commit pnpm-lock.yaml. Add tsconfig.json extending ./.nuxt/tsconfig.json as fonoteka does.
  3. nuxt.config.ts (the spike-verified config in RESEARCH Pattern 1, per D-04, D-33, D-34, D-37): compatibilityDate: '2025-07-15', devtools: { enabled: false }, modules: ['@nuxt/fonts', '@nuxtjs/i18n', '@nuxtjs/seo'], css loading ~/assets/css/tokens.css then ~/assets/css/base.css; fonts.families Roboto weights 300/400/500/700 and Roboto Mono weights 400/500, each with styles: ['normal'], subsets: ['latin', 'latin-ext'], global: true (Roboto 900 from the prototype is not loaded, per D-34); i18n with strategy: 'prefix_except_default', defaultLocale: 'en', one locale { code: 'en', language: 'en-US', file: 'en.json', name: 'English' }, langDir: 'locales/', baseUrl: 'https://summercms.io', experimental: { prerenderMessages: true }; site: { url: 'https://summercms.io', name: 'SummerCMS', description: <the hero description>, defaultLocale: 'en' }; ogImage: { enabled: false }; sitemap: { autoI18n: false } (Pitfall 2: one flat file); linkChecker: { excludeLinks: ['/docs', '/docs/**'] }; nitro: { prerender: { ignore: ['/docs'] } } (Pitfall 1: generate otherwise exits 1 on /docs links); app.head.link with /favicon.ico, /apple-touch-icon.png (sizes 180x180).
  4. app/app.config.ts per D-45 (both flags true). app/app.vue renders <NuxtPage />. app/pages/index.vue renders SiteHeader and HeroSection inside a wrapper whose horizontal overflow is clipped with overflow-x: clip, never hidden (Pitfall 3: a hidden-overflow ancestor breaks the sticky header), and sets the page head with useSeoMeta: title SummerCMS: A new dawn in content management with a titleTemplate that does not append the site name again (spike finding 4), description = the hero description, ogImage /og-image.png with width 1200, height 630 and alt text, twitterCard: 'summary_large_image'. Title, description and alt live in en.json under meta.*.
  5. i18n/locales/en.json (D-33): nested keys for meta, header (logo aria label, wordmark parts "Summer" and "CMS", pill ALPHA 0.1, nav labels Why, Features, From WinterCMS, Get started, Docs →), hero (tagline "A new dawn in content management", description "A content management framework for Go, inspired by WinterCMS. Plugins, the admin SPA and console commands compile into a single binary.", CTA labels "Read the docs" and "Alpha 0.1 is out"). Copy every string verbatim from the handoff. Use no vue-i18n special characters (@, |, {, }) in values except deliberate slot placeholders (Pitfall 6).
  6. Styles per D-35: tokens.css defines on :root the fourteen color tokens from RESEARCH "Tokens → :root custom properties" (--navy-900 #18223a through --text-4 #8a94a3), white border alphas 6/7/8/10/12/14% as --border-06 … --border-14, radii --radius-sm 6px, --radius-card 16px, --radius-pill 999px, and font stacks --font-sans (Roboto, system-ui, sans-serif) and --font-mono (Roboto Mono, ui-monospace, monospace). base.css holds the globals from design/README.md "Global": page background #233148, text #e9edf3, html { scroll-behavior: smooth; scroll-padding-top: 72px } plus @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto } } (discretion choice), selection colors, link colors and hover, .container (max-width 1120px, padding 0 24px, auto margins), the shared section heading pattern (.eyebrow, .section-title), and the shared button shapes (primary, secondary, outline) and chip shape used by later sections. Component styles are <style scoped> and reference the tokens. No CSS framework package.
  7. SiteHeader.vue per design/README.md "1. Header" and the dc.html details: sticky 64px bar, translucent navy with blur, logo link to #top (30px sun image /sun-logo.webp sized to match the handoff visually, wordmark "Summer" white plus "CMS" #fbd77e), the ALPHA 0.1 pill, the section links in a flex: 1; min-width: 0; overflow-x: auto; scrollbar-width: none wrapper with the inner row pushed right, and the Docs → button linking /docs with the arrow in its own span. Section links are always rendered and hidden by @media (max-width: 720px) (Pitfall 4). Give each nav link a data-section attribute; active styling is wired in Task 3.
  8. HeroSection.vue per "2. Hero": <section id="top">, the rays layer rendered only when useAppConfig().landing.showRays is true (D-45), the glow, the 180px sun badge holding /sun-badge.webp at about 136px (RESEARCH Open Question 5), the H1 with the gradient "CMS", tagline, description, and the two CTAs (/docs and #start, the secondary with its 8px dot).
  9. Images per D-36 with scripts/derive-images.sh (bash, set -euo pipefail, ImageMagick magick only, run from the SITE root): download the original once with curl -fsSL https://summercms.io/logo.png -o assets-src/logo.png and commit it (the live URL disappears at cutover), check it is 746x744 (magick identify), then write public/sun-logo.webp (60x60, 2x of 30px), public/sun-badge.webp (272x272, 2x of 136px), public/favicon.ico (16, 32 and 48 px layers), public/apple-touch-icon.png (180x180 on #233148), and public/og-image.png (1200x630 on #233148 with the sun, the wordmark "Summer" white and "CMS" #fbd77e, and the tagline in #c5ccd6, set in Roboto from /usr/share/fonts/TTF/). Commit the script and every output. Do not copy design/assets/sun-crop.png.
  10. tests/output.test.ts (node:test; every test file resolves paths from import.meta.url, never from the working directory, so it runs from any cwd; reads files under .output/public and fails with a clear message when index.html is missing): index.html contains <section id="top", the hero tagline and description, href="/docs", a <link rel="canonical" href="https://summercms.io/", og:image with content https://summercms.io/og-image.png, twitter:card summary_large_image, <html lang="en and a JSON-LD @graph; _fonts/ holds at least one .woff2; robots.txt contains Sitemap: https://summercms.io/sitemap.xml; sitemap.xml is a regular file containing <loc>https://summercms.io/</loc>; at least one _i18n/**/en/messages.json exists (Pitfall 7 contract for plan 11.2-02); no generated .html or .css file mentions either Google font host named in the acceptance criteria. Build the en.json leaf check as a helper reused by later tasks: every leaf string without a { placeholder, HTML-escaped the way Vue escapes text (&, <, >, ", '), appears in index.html.

Commit as feat(site): scaffold the Nuxt 4 landing page with header, hero, fonts and SEO. pnpm -C ../sm-summercmsio-app/vue-summercmsio-app install --frozen-lockfile && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run generate && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app test <fails_when>non-zero exit, "Exiting due to prerender errors" in the generate log, a "not ok" line in the node:test TAP output, or "# fail" with a count above 0</fails_when> <acceptance_criteria> - node -p "const p=require('/media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app/package.json'); [p.dependencies.nuxt,p.dependencies['@nuxt/fonts'],p.dependencies['@nuxtjs/i18n'],p.dependencies['@nuxtjs/seo'],p.dependencies.vue,p.devDependencies.typescript].join(' ')" prints 4.4.8 0.14.0 10.4.0 5.2.1 3.5.35 5.9.3. - test -f ../sm-summercmsio-app/vue-summercmsio-app/.output/public/index.html && test -f ../sm-summercmsio-app/vue-summercmsio-app/.output/public/sitemap.xml succeeds (a regular file, not a directory). - ls ../sm-summercmsio-app/vue-summercmsio-app/.output/public/_fonts/*.woff2 | wc -l prints at least 1. - grep -c "fonts.googleapis.com\|fonts.gstatic.com" ../sm-summercmsio-app/vue-summercmsio-app/.output/public/index.html prints 0. - grep -n "ignore: \['/docs'\]" ../sm-summercmsio-app/vue-summercmsio-app/nuxt.config.ts finds a match. - magick identify ../sm-summercmsio-app/vue-summercmsio-app/public/og-image.png reports 1200x630, and magick identify ../sm-summercmsio-app/vue-summercmsio-app/assets-src/logo.png reports 746x744. - git -C ../sm-summercmsio-app/vue-summercmsio-app log --format='%(trailers:key=Co-authored-by,valueonly)' master prints only empty lines. </acceptance_criteria> A new vue-summercmsio-app repository generates a prerendered page with the handoff header and hero from en.json, self-hosted Roboto fonts, the original sun in every derived image, static SEO tags, flat sitemap and robots, and its output test passes.

Task 2: Visitors read the Why, Features and From WinterCMS sections and the footer, with every docs, source and credit link from the handoff ../sm-summercmsio-app/vue-summercmsio-app/app/components/WhySection.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/components/FeaturesSection.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/components/WinterSection.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteFooter.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/pages/index.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/assets/css/base.css, ../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json, ../sm-summercmsio-app/vue-summercmsio-app/tests/output.test.ts - ../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json, app/pages/index.vue, app/assets/css/base.css and tests/output.test.ts (as written in Task 1) - .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/design/README.md sections "Section heading pattern", "3. Why", "4. Features", "5. From WinterCMS", "7. Footer" - .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/design/SummerCMS Landing.dc.html (the same sections: grids, gaps, concept-row borders, footer spacer) - .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-RESEARCH.md "Every link on the page" table and Pitfall 6 Add the three content sections and the footer as components, all copy in `en.json` (D-33), styled with the tokens (D-35), matching the handoff exactly (D-23).
  1. WhySection.vue (<section id="why">): eyebrow WHY SUMMER, H2 "What you tested is exactly what you deploy." (max-width 720px), and the three cards (number in Roboto Mono, title, body) on a repeat(auto-fit, minmax(260px, 1fr)) grid. Card 2's body has inline code for fields.yaml and columns.yaml (Roboto Mono 13px, #e9edf3): render it with the <i18n-t> component and named slots for the two file names (Pitfall 6). Do not use Vue's raw-HTML binding directive anywhere in the project (T-11.2-01).
  2. FeaturesSection.vue (<section id="features">, full-bleed #1f2b42 band with top and bottom borders): eyebrow WHAT'S IN THE BOX, H2 "The services a content app needs.", and the eight tiles as links on the 1px-divider grid (gap: 1px over the border color, outer border, radius 16px, overflow: hidden). Tile hrefs per D-47, exactly as the handoff writes them, extension-less: /docs/backend/admin-spa, /docs/database/models, /docs/services/routing, /docs/services/jobs, /docs/services/realtime, /docs/services/search, /docs/services/mail, /docs/console/introduction. Keep tile titles and bodies verbatim, including "the caller’s transaction" with its typographic apostrophe. Keep the hrefs in the component (or a small typed array next to it) and the copy in en.json.
  3. WinterSection.vue (<section id="winter">), per D-09 with no added seasons copy: two columns repeat(auto-fit, minmax(300px, 1fr)), gap 48px; left column eyebrow COMING FROM WINTERCMS, H2 "Same shape. Compiled and typed.", the paragraph, and "See the full concept map →" linking /docs/setup/coming-from-wintercms (display: inline-flex; margin-top: 24px); right column the concept table with header WINTERCMS | SUMMERCMS and the seven rows from design/README.md (minmax(0,1fr) minmax(0,1.3fr) columns, Roboto Mono 13px, every row including the last with a bottom divider).
  4. SiteFooter.vue: top border, 32px 24px padding, 13px #8a94a3; left "© 2026 SummerCMS. Something bright is here."; a flex: 1 spacer; the link group (gap 20px, #a3adbd, hover #f5c55a) with Docs → /docs, Source → https://git.golem15.com/golem15/summercms (D-38), the D-12 credit "Built by Golem15" → https://golem15.com styled as a footer link, and "Back to top ↑" → #top.
  5. index.vue renders header, hero, Why, Features, Winter, then the footer (the Get started section is added in Task 3 between Winter and the footer).
  6. Extend tests/output.test.ts: section ids why, features, winter present after top; the eight tile hrefs plus /docs/setup/coming-from-wintercms each appear as href="…"; the Source href and https://golem15.com appear; the en.json leaf check passes for the new keys; the seven concept rows (both columns) appear.

Commit as feat(site): add the Why, Features and From WinterCMS sections and the footer. pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run generate && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app test <fails_when>non-zero exit, "Exiting due to prerender errors" in the generate log, or a "not ok" line in the TAP output</fails_when> <acceptance_criteria> - grep -o 'href="/docs/[a-z/-]*"' ../sm-summercmsio-app/vue-summercmsio-app/.output/public/index.html | sort -u | wc -l prints at least 9 (eight tiles plus the concept map). - grep -c 'href="https://golem15.com' ../sm-summercmsio-app/vue-summercmsio-app/.output/public/index.html prints at least 1. - grep -c 'caller’s transaction' ../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json prints 1. - grep -rn 'v-html' ../sm-summercmsio-app/vue-summercmsio-app/app prints nothing. - grep -c '<i18n-t' ../sm-summercmsio-app/vue-summercmsio-app/app/components/WhySection.vue prints at least 1. </acceptance_criteria> The Why, Features and From WinterCMS sections and the footer render from en.json with the handoff's links (extension-less docs hrefs, Source, Golem15 credit), and the output test covers them.

Task 3: Visitors copy six working commands from the Get started terminal card, and the header highlights the section they are reading ../sm-summercmsio-app/vue-summercmsio-app/app/components/StartSection.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/components/TerminalCard.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json, ../sm-summercmsio-app/vue-summercmsio-app/app/utils/terminal.ts, ../sm-summercmsio-app/vue-summercmsio-app/app/utils/scrollSpy.ts, ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/pages/index.vue, ../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json, ../sm-summercmsio-app/vue-summercmsio-app/tests/terminal.test.ts, ../sm-summercmsio-app/vue-summercmsio-app/tests/scrollSpy.test.ts, ../sm-summercmsio-app/vue-summercmsio-app/tests/output.test.ts - ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue, app/pages/index.vue, i18n/locales/en.json and tests/output.test.ts (as written in Tasks 1-2) - .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/design/README.md sections "6. Get started", "Interactions & Behavior", "State" - .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/design/SummerCMS Landing.dc.html (terminal title bar, line blocks, group spacing, chips row, buttons row) - .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-RESEARCH.md "Pattern 3: Single-source terminal commands", "Breakpoints/interactions", Pitfall 5 - .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-CONTEXT.md D-26, D-38, D-39, D-40, D-45 - copyPayload over the three terminal.json groups returns exactly `git clone https://git.golem15.com/golem15/summercms\ncd summercms\ngo install ./cmd/summer\ncd examples/hello\nsummer build\n./bin/hello greeter:hello` (six lines, no trailing newline, no `$`, no `#`). - copyPayload over an empty list returns `''`; over a group with no commands contributes nothing. - terminal.json has three groups, six commands, and each group's `comment` key resolves to a string in en.json. - activeSection([]) is `''`; when every top is at or above 140 it is `''`; with why at 100 and features at 300 it is `why`; with why -900, features -200, winter 120, start 600 it is `winter`; a top of exactly 140 is not active; a custom threshold is honoured. - The generated index.html contains `id="start"`, the chips `Go 1.27` and `PostgreSQL 15+`, each of the six commands, the three comment texts, `href="/docs/setup/installation"`, and the Source href. Write the tests from the behavior block first (they fail), then implement until they pass.
  1. app/data/terminal.json exactly as the interfaces block defines it (D-39: the clone step comes first; D-40: this file is the single source the page renders and plan 11.2-02's Go check reads). The comment keys resolve in en.json to get the framework, install the summer CLI and build and run the example app; the component prints them after # .
  2. app/utils/terminal.ts and app/utils/scrollSpy.ts exactly as the interfaces block defines them: pure functions, no Vue or Nuxt imports, erasable TypeScript only, so node --test imports them directly with explicit .ts specifiers.
  3. TerminalCard.vue per "6. Get started → terminal card": title bar (terminal label, Copy button 500 12px Roboto, radius 6px), body padding 22px 20px, Roboto Mono 14px, line-height 1.9, overflow-x: auto, flex column; each line is its own block with white-space: pre; comment lines #8a94a3; the $ prompt #f5c55a in its own span so the command text stays contiguous in the HTML; each group's comment after the first has margin-top: 16px. Copy calls navigator.clipboard?.writeText(copyPayload(groups)) and falls back to a hidden textarea plus document.execCommand('copy') when the Clipboard API is unavailable (Pitfall 5); on success the label shows the en.json Copied text for 1500 ms, then Copy; a failed copy leaves the label unchanged.
  4. StartSection.vue (<section id="start">, full-bleed #1f2b42 band with a top border): two columns as in the Winter section with align-items: center; left column eyebrow GET STARTED, H2 "Build your first app.", the chips row (margin-top: 24px; gap: 8px) with Go 1.27 and PostgreSQL 15+ (D-26, following D-25; plan 11.2-02 re-runs the PG 15 suites and stops if they fail), the note "Alpha 0.1 is for early adopters. APIs may change before 1.0." (margin-top: 20px, 16px/1.65), and the buttons row (margin-top: 28px): "Installation guide" primary → /docs/setup/installation (padding 13px 24px; extension-less per D-47) and "Source" outline → https://git.golem15.com/golem15/summercms (D-38; padding 12px 22px, border 14% white, hover background 8% white and text #fff). Right column the TerminalCard. Insert it in index.vue between Winter and the footer.
  5. Scroll-spy in SiteHeader.vue (D-45): when useAppConfig().landing.scrollSpy is true, onMounted computes the active id from getBoundingClientRect().top of the four sections via activeSection, then on every scroll event with { passive: true }; onBeforeUnmount removes the listener. The active link gets #f5c55a text and rgba(245,197,90,0.1) background. SSR renders no active link (Pitfall 4). With the flag false no listener is attached.
  6. Extend tests/output.test.ts with the last behavior bullet (it reads the six commands and three comment keys from terminal.json and en.json, never from a second hard-coded list) and with the order check top < why < features < winter < start < footer.

Commit the tests and the implementation as feat(site): add the Get started terminal card, copy button and scroll-spy. pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run generate && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app test <fails_when>non-zero exit, a "not ok" line in the TAP output, or the TAP summary missing the terminal and scrollSpy test files</fails_when> node --test ../sm-summercmsio-app/vue-summercmsio-app/tests/terminal.test.ts ../sm-summercmsio-app/vue-summercmsio-app/tests/scrollSpy.test.ts <fails_when>non-zero exit, "# pass 0", or "# fail" with a count above 0</fails_when> Run pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run preview and open the printed URL next to design/SummerCMS Landing.dc.html in a browser. At 1280px, 721px, 720px and 375px wide: layout, colors, type and spacing match; the header stays stuck while scrolling; section links are visible at 721px and hidden at 720px; the active link follows the section in view and none is active above Why; Copy changes to "Copied" for about 1.5s and the clipboard holds six lines without $ or comments; the hero rays and the sun badge match the handoff. <acceptance_criteria> - node -e "const t=require('/media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json'); console.log(t.groups.length, t.groups.flatMap(g=>g.commands).length)" prints 3 6. - grep -c 'PostgreSQL 15+' ../sm-summercmsio-app/vue-summercmsio-app/.output/public/index.html prints at least 1. - grep -c './bin/hello greeter:hello' ../sm-summercmsio-app/vue-summercmsio-app/.output/public/index.html prints at least 1. - grep -n "@media (max-width: 720px)" ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue finds a match. - grep -n 'passive: true' ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue finds a match. - grep -n 'scrollSpy' ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue and grep -n 'showRays' ../sm-summercmsio-app/vue-summercmsio-app/app/components/HeroSection.vue both find a match. </acceptance_criteria> The Get started section shows the D-26 chips, the D-38 links and the D-39 terminal card fed from terminal.json; Copy writes the six commands; scroll-spy follows the handoff rule behind the D-45 flag; unit and output tests pass.

<threat_model>

Trust Boundaries

Boundary Description
npm registry → build machine Third-party packages execute during install and generate
en.json / terminal.json → rendered HTML Repository copy becomes public HTML; authors are trusted, mistakes are expected
page → visitor clipboard The Copy button writes text the visitor will paste into a shell
page → third-party origins Any runtime request to another origin leaks visitor data

STRIDE Threat Register

Threat ID Category Component Severity Disposition Mitigation Plan
T-11.2-01 Tampering (XSS) components rendering en.json medium mitigate Text interpolation only; inline code through <i18n-t> slots; Task 2 criterion greps that no raw-HTML binding exists under app/
T-11.2-02 Information disclosure (privacy) font loading medium mitigate @nuxt/fonts downloads at build and serves from /_fonts/ (D-34); the output test and a criterion assert no Google font host in the generated HTML or CSS
T-11.2-03 Tampering Copy button payload low accept Payload is the bundled terminal.json, identical to the visible lines; the page only writes to the clipboard and never reads it
T-11.2-SC Tampering pnpm installs high mitigate Exact pins equal to vue-fonoteka-app's installed versions, approved in RESEARCH Package Legitimacy Audit; committed pnpm-lock.yaml and --frozen-lockfile; allowBuilds limited to three packages; any other version stops at a blocking-human checkpoint
</threat_model>
- `pnpm -C ../sm-summercmsio-app/vue-summercmsio-app install --frozen-lockfile && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run generate && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app test` green. - `git -C ../sm-summercmsio-app/vue-summercmsio-app status --porcelain` prints nothing after the last commit. - `git -C . status --porcelain` in summercms.go shows no change from this plan.

<success_criteria>

  • SC1: nuxt generate produces the landing page matching the handoff with sticky header and scroll-spy, hero, Why, Features, From WinterCMS, Get started, a working Copy button and the footer; responsive to phone width with section links hidden at 720px or less (visual parts confirmed in UAT).
  • Inputs for SC2 and SC3: the .output/public tree and app/data/terminal.json that plan 11.2-02 embeds, link-checks and runs. </success_criteria>

Artifacts this phase produces

  • Repository vue-summercmsio-app at /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app (branch master, no remote yet; D-43).
  • Config: nuxt.config.ts (modules @nuxt/fonts, @nuxtjs/i18n, @nuxtjs/seo; nitro.prerender.ignore, sitemap.autoI18n: false, ogImage.enabled: false, linkChecker.excludeLinks, i18n.experimental.prerenderMessages), app/app.config.ts keys landing.showRays, landing.scrollSpy, package.json scripts build, dev, generate, preview, prepare, test.
  • Components: SiteHeader, HeroSection, WhySection, FeaturesSection, WinterSection, StartSection, TerminalCard, SiteFooter; page app/pages/index.vue.
  • TypeScript: TerminalGroup, copyPayload (app/utils/terminal.ts); SPY_SECTIONS, SPY_THRESHOLD, activeSection (app/utils/scrollSpy.ts).
  • Data and copy: app/data/terminal.json (groups[].comment, groups[].commands), i18n/locales/en.json (keys meta.*, header.*, hero.*, why.*, features.*, winter.*, start.*, terminal.*, footer.*).
  • Styles: app/assets/css/tokens.css (--navy-900 … --text-4, --border-06 … --border-14, --radius-sm, --radius-card, --radius-pill, --font-sans, --font-mono), app/assets/css/base.css.
  • Images: assets-src/logo.png, public/sun-logo.webp, public/sun-badge.webp, public/favicon.ico, public/apple-touch-icon.png, public/og-image.png; script scripts/derive-images.sh.
  • Tests: tests/output.test.ts, tests/terminal.test.ts, tests/scrollSpy.test.ts.
Create `.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-01-SUMMARY.md` when done