diff --git a/.planning/quick/261029-1r3-derive-the-summercms-io-landing-version-/261029-1r3-PLAN.md b/.planning/quick/261029-1r3-derive-the-summercms-io-landing-version-/261029-1r3-PLAN.md
new file mode 100644
index 00000000..b3efbc3d
--- /dev/null
+++ b/.planning/quick/261029-1r3-derive-the-summercms-io-landing-version-/261029-1r3-PLAN.md
@@ -0,0 +1,249 @@
+---
+phase: quick-261029-1r3
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app/app/utils/version.ts
+ - /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app/app/composables/useSummercmsVersion.ts
+ - /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app/nuxt.config.ts
+ - /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json
+ - /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue
+ - /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app/app/components/HeroSection.vue
+ - /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app/app/components/StartSection.vue
+ - /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app/tests/version.test.ts
+ - /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app/changelog/testing/expect.txt
+ - /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app/README.md
+ - /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/plugins/golem15/summercms/version_test.go
+ - /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/plugins/golem15/summercms/README.md
+ - /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/scripts/build.sh
+ - /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/README.md
+ - /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/CLAUDE.md
+ - /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app (submodule pointer)
+ - /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/plugins/golem15/summercms (submodule pointer)
+autonomous: true
+requirements:
+ - quick-261029-1r3
+
+estimate:
+ tokens: 90000
+ raw_tokens: 90000
+ tasks: 3
+ confidence: low
+
+must_haves:
+ truths:
+ - "The header pill, the hero button and the Get started note of the built landing show the major.minor of the git.golem15.com/golem15/summercms version the app's go.mod requires (v0.5.0 shows 0.5); the patch is never shown (user decision 2026-10-29)"
+ - "en.json holds no hard-coded version in header.pill, hero.ctaAlpha or start.note: each carries a {version} placeholder rendered with t(key, { version })"
+ - "scripts/build.sh (release and dev) exports NUXT_PUBLIC_SUMMERCMS_VERSION, read from go.mod with go list -m, to pnpm generate and pnpm build:changelog; without the variable nuxt.config.ts reads ../go.mod; with neither, the Nuxt build stops with an error instead of rendering an empty version"
+ - "The changelog SSR app (extends '..') renders the same pill in its SiteHeader from the inherited runtimeConfig.public.summercmsVersion"
+ - "With SUMMERCMS_REQUIRE_BUILD=1, the plugin test TestLandingVersion fails when the built page shows a version other than go.mod's major.minor, so a stale site fails scripts/build.sh"
+ artifacts:
+ - path: /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app/app/utils/version.ts
+ provides: "frameworkVersionFromGoMod(goMod) and majorMinor(version), pure erasable TypeScript"
+ - path: /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app/app/composables/useSummercmsVersion.ts
+ provides: "useSummercmsVersion(): major.minor from runtimeConfig.public.summercmsVersion"
+ - path: /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/plugins/golem15/summercms/version_test.go
+ provides: "TestLandingVersion (gated) plus ungated helper table tests"
+ key_links:
+ - from: "sm-summercmsio-app/go.mod (require git.golem15.com/golem15/summercms vX.Y.Z)"
+ to: "scripts/build.sh NUXT_PUBLIC_SUMMERCMS_VERSION, or nuxt.config.ts reading ../go.mod"
+ via: "go list -m -f '{{.Version}}' / frameworkVersionFromGoMod"
+ - from: "nuxt.config.ts runtimeConfig.public.summercmsVersion"
+ to: "SiteHeader.vue, HeroSection.vue, StartSection.vue"
+ via: "useSummercmsVersion() -> t(key, { version })"
+ - from: "plugins/golem15/summercms/public/site/index.html"
+ to: "go.mod (read at SUMMERCMS_SITE_DIR/../go.mod)"
+ via: "TestLandingVersion under SUMMERCMS_REQUIRE_BUILD=1 (scripts/build.sh step 3)"
+---
+
+
+Make the summercms.io landing show the framework version from the app's go.mod instead of three strings bumped by hand. Today en.json hard-codes the version in the header pill, the hero button and the Get started note, so the site drifts from the release. After this task, go.mod's `require git.golem15.com/golem15/summercms vX.Y.Z` is the single source: the build passes it to Nuxt, the page renders it as major.minor only (user decision 2026-10-29: `0.5`, never the patch), and the plugin's build-gated test fails when the built page and go.mod disagree.
+
+Purpose: every framework bump already goes through go.mod and a rebuild, so the landing (and the changelog's header) follow the release without a separate copy edit.
+
+Output: commits in vue-summercmsio-app and sm-summercmsio-plugin (inner repos first), then two commits in sm-summercmsio-app (build script + docs, then the submodule pointer bump). No push, tag or deploy.
+
+
+
+@~/.claude/msd-core/workflows/execute-plan.md
+@~/.claude/msd-core/templates/summary.md
+
+
+
+@/media/nvme/dev/golem15/summercms.io/summercms/summercms.go/.planning/todos/pending/2026-10-28-derive-summercms-io-landing-version-from-go-mod.md
+@/media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/CLAUDE.md
+
+Where the work happens: NOT in summercms.go. Three git repos, all on `master`:
+- APP = /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app (go.mod, go.work, scripts/build.sh, README.md, CLAUDE.md)
+- SITE = APP/vue-summercmsio-app (submodule, repo golem15/vue-summercmsio-app: Nuxt 4 landing plus the `changelog/` SSR app that extends it with `extends: ['..', 'vue-journal-layer']`)
+- PLUG = APP/plugins/golem15/summercms (submodule, repo golem15/sm-summercmsio-plugin: the embedding plugin and its build-gated tests)
+
+Facts found while planning (do not re-derive):
+- APP/go.mod requires `git.golem15.com/golem15/summercms v0.5.0` inside a `require (` block and has `replace git.golem15.com/golem15/summercms => ../summercms.go`. The latest framework tag is v0.5.3; both show 0.5.
+- `go -C APP list -m -f '{{.Version}}' git.golem15.com/golem15/summercms` prints the required version (v0.5.0) even with the local replace; build.sh already uses this in release mode only.
+- SITE/i18n/locales/en.json is the only landing locale (no pl.json). The version sits in `header.pill` (line 11), `hero.ctaAlpha` (line 31) and `start.note` (line 174). SITE/changelog/i18n/locales/en.json has no version strings.
+- Usage: SITE/app/components/SiteHeader.vue line 77 `t('header.pill')`, HeroSection.vue line 19 `t('hero.ctaAlpha')`, StartSection.vue line 17 `t('start.note')`. SiteHeader is also rendered by the changelog app; components there use relative imports because in a layer `~` resolves against the consuming project (SiteHeader already imports `../utils/scrollSpy`). The changelog composable pattern is SITE/changelog/app/composables/useChangelogStream.ts (imports `../utils/streams.ts`).
+- Pure helpers live in SITE/app/utils/*.ts with the header "No Vue or Nuxt imports, and only erasable TypeScript, so `node --test` runs this file directly" (see scrollSpy.ts, terminal.ts); tests in SITE/tests/*.test.ts import them with the `.ts` extension. `pnpm test` runs all of tests/*.test.ts, including output.test.ts, which needs a prior `pnpm generate`.
+- SITE/tests/output.test.ts `assertLeavesRendered` already checks a leaf with a `{placeholder}` by its literal parts, so `{version}` strings pass it unchanged.
+- The built pill today is `...` (scoped-style attribute), so match it with `]*>([^<]*)`.
+- PLUG/links_test.go has `requireBuild(t)`, `siteDir()` (SUMMERCMS_SITE_DIR, default `../../../vue-summercmsio-app`) and `message(tree, dottedKey)`; reuse them, do not duplicate. `siteDir()/..` is APP, so the test reads go.mod from `filepath.Join(siteDir(), "..", "go.mod")`: the same file nuxt.config.ts reads (`../go.mod` from SITE), with no new env var.
+- PLUG/public/site and public/docs exist from an earlier build (gitignored); requireBuild needs both index.html files.
+- APP has a pre-existing untracked `scripts/__pycache__/` that is not this task's: leave it, never stage it. Because of it a release build refuses the tree; use `scripts/build.sh dev` for verification.
+- Several Claude sessions work in these repos: run `git status --short` in APP, SITE and PLUG before editing, leave any unrelated change alone, stage explicit paths only.
+
+Out of scope (report in SUMMARY, do not change): DEPLOY.md's hard-coded `v0.4.0` tag-check steps; PLUG/go.mod requiring summercms v0.4.0; the changelog post version pills (release.ts, a different feature).
+
+
+
+
+
+ Task 1 (tracer): go.mod version end-to-end into the header pill, checked by the plugin build test
+
+ SITE/app/utils/version.ts (new),
+ SITE/app/composables/useSummercmsVersion.ts (new),
+ SITE/nuxt.config.ts,
+ SITE/i18n/locales/en.json,
+ SITE/app/components/SiteHeader.vue,
+ APP/scripts/build.sh,
+ PLUG/version_test.go (new)
+
+
+ SITE/nuxt.config.ts, SITE/app/utils/scrollSpy.ts, SITE/changelog/app/composables/useChangelogStream.ts, SITE/app/components/SiteHeader.vue (script block and line 77), APP/scripts/build.sh, PLUG/links_test.go (requireBuild, siteDir, message, TestTerminalCommandsInPage)
+
+
+Wire one path, go.mod to the header pill to the plugin check, through every layer this task touches. Major.minor only, per the user decision of 2026-10-29.
+
+1. SITE/app/utils/version.ts (new). Use the same header comment as scrollSpy.ts (no Vue or Nuxt imports, erasable TypeScript only). Export two functions:
+ - `frameworkVersionFromGoMod(goMod: string): string | null` returns the version on the `git.golem15.com/golem15/summercms` require line, for example `v0.5.0`. Accept both forms: a single `require ` line, and a ` ` line inside a `require (` block. Allow a trailing `// comment` such as `// indirect`. Use one multiline regex anchored at line start (optional leading whitespace, optional `require ` keyword, the exact module path, whitespace, `v` + digits.digits.digits + any non-space suffix, optional whitespace and `//` comment, end of line). Because the version must end the line, `replace` directives (`replace => ...`, or ` v0.5.0 => ...` inside a replace block) and sibling paths such as `.../summercms-extra` or `.../summercms/sub` never match. Return null when nothing matches.
+ - `majorMinor(version: string): string | null` turns `v0.5.0`, `0.5.3`, `v0.6.0-rc.1` and `0.5` into `0.5`, `0.5`, `0.6` and `0.5`. It returns null for empty, `latest`, `v1` and other non-semver input. Use a regex: optional `v`, major, minor, optional `.patch`, optional `-`/`+` suffix.
+2. SITE/nuxt.config.ts. Import `readFileSync` from `node:fs`, and both helpers from `./app/utils/version.ts` (a relative import; it resolves the same way when the changelog loads this file as its `..` layer). Add a top-level function `frameworkVersion()`. It returns `process.env.NUXT_PUBLIC_SUMMERCMS_VERSION` when that is non-empty. Otherwise it reads `new URL('../go.mod', import.meta.url)` (the app's go.mod, since this repo is mounted at APP/vue-summercmsio-app) and applies `frameworkVersionFromGoMod`. Throw an Error that names both sources ("set NUXT_PUBLIC_SUMMERCMS_VERSION or build inside sm-summercmsio-app so ../go.mod exists") when neither yields a value, or when `majorMinor` of the value is null. Do not fall back to a default; fail closed like the plugin's Routes. Add `runtimeConfig: { public: { summercmsVersion: frameworkVersion() } }` with a comment. Store the raw tag (`v0.5.0`), not major.minor: Nuxt lets the same NUXT_PUBLIC_SUMMERCMS_VERSION override the value at runtime (during the prerender and in the changelog Node process), so the render normalizes it in useSummercmsVersion. The changelog app inherits the value through `extends: ['..']`. Do not add runtimeConfig to SITE/changelog/nuxt.config.ts.
+3. SITE/app/composables/useSummercmsVersion.ts (new). Export `useSummercmsVersion(): string`. It reads `useRuntimeConfig().public.summercmsVersion`, returns `majorMinor` of it (imported from `../utils/version.ts`, a relative import for the layer), and throws an Error when that is null. A build without a version then fails the prerender or the changelog SSR check instead of showing an empty pill. Add a short header comment that explains why it normalizes at render.
+4. SITE/i18n/locales/en.json: set `header.pill` to `ALPHA {version}`. Hero and note follow in Task 2.
+5. SITE/app/components/SiteHeader.vue: import `useSummercmsVersion` from `../composables/useSummercmsVersion`, the same explicit relative import style as `../utils/scrollSpy`. Add `const version = useSummercmsVersion()` in setup and render `t('header.pill', { version })`. Change nothing else in the header (the changelog menu from quick 261028-wqu stays as is).
+6. APP/scripts/build.sh:
+ - Move the framework version lookup (`go -C "$ROOT" list -m -f '{{.Version}}' git.golem15.com/golem15/summercms`, with both existing `die` checks) out of the release-only branch into a variable `FW_VERSION`, resolved in both modes. In release mode set `TAG="$FW_VERSION"`; the tag-exists check and `REF` stay as they are.
+ - Before step 1 (pnpm install/generate), `export NUXT_PUBLIC_SUMMERCMS_VERSION="$FW_VERSION"` with a comment. The comment says that the landing pill, hero button and Get started note, and the changelog header, show its major.minor, and that the plugin's TestLandingVersion compares the built page with go.mod. Exporting once covers `pnpm generate`, `pnpm build:changelog` and `test:changelog`, and overrides any value already in the caller's shell, because go.mod is the single source.
+ - In the header comment, replace the hard-coded framework version in parentheses (after "the version go.mod requires") with nothing, so the comment names no version. Add one line saying both modes pass go.mod's framework version to the site as NUXT_PUBLIC_SUMMERCMS_VERSION.
+7. PLUG/version_test.go (new, package summercms):
+ - Go equivalents of the two helpers as unexported test helpers, `frameworkVersionFromGoMod(goMod []byte) (string, bool)` and `majorMinor(v string) (string, bool)`, with the same regex semantics as the TypeScript (`(?m)` anchored line, end-of-line anchor that excludes replace forms). Standard library only (regexp); no golang.org/x/mod.
+ - `TestLandingVersion`: call `requireBuild(t)` first. Read `filepath.Join(siteDir(), "..", "go.mod")` and derive `want` with both helpers; t.Fatal when either fails, naming the path. Read `public/site/index.html` and `siteDir()/i18n/locales/en.json`. Resolve `header.pill` with `message()`. Require that it contains `{version}`, then build the expected text with `strings.ReplaceAll(msg, "{version}", want)`. Extract the pill with the regex `]*>([^<]*)`, apply `html.UnescapeString` and `strings.TrimSpace`, and compare. The failure message says what go.mod requires (full tag and major.minor), what the page shows, and "rebuild with scripts/build.sh". Add a doc comment that names this the landing version drift guard (the go.mod source, the major.minor rule).
+
+
+ APP=/media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app; git -C "$APP" status --porcelain -- main.go plugins.gen.go; (cd "$APP" && scripts/build.sh dev) && grep -o 'class="pill"[^>]*>[^<]*<' "$APP/plugins/golem15/summercms/public/site/index.html" | grep -q '>ALPHA 0\.5<' && SUMMERCMS_REQUIRE_BUILD=1 go -C "$APP/plugins/golem15/summercms" test -count=1 -v -run TestLandingVersion ./... | grep -q -- '--- PASS: TestLandingVersion' && echo TRACER-OK
+
+
+ `scripts/build.sh dev` exits 0: nuxt generate, build:changelog, test:changelog and the gated plugin tests including TestLandingVersion all pass. The built pill in PLUG/public/site/index.html reads `ALPHA 0.5`, derived from go.mod's v0.5.0. TestLandingVersion reports PASS and is not skipped. Before and after the build, compare `git status --porcelain -- main.go plugins.gen.go` in APP: if they were clean before and `summer build` (dev, framework HEAD) changed them, restore them with `git -C APP checkout -- main.go plugins.gen.go`, because they are generated and not part of this task. If they were dirty before, leave them alone.
+
+
+
+
+ Task 2: Hero button and Get started note follow the same version; helper tests and a proven failure path
+
+ SITE/i18n/locales/en.json,
+ SITE/app/components/HeroSection.vue,
+ SITE/app/components/StartSection.vue,
+ SITE/tests/version.test.ts (new),
+ SITE/changelog/testing/expect.txt,
+ PLUG/version_test.go
+
+
+ - frameworkVersionFromGoMod: block require, single-line require, `// indirect` suffix -> the version; `replace git.golem15.com/golem15/summercms => ../summercms.go` alone -> null; a replace-block line `git.golem15.com/golem15/summercms v0.5.0 => ../x` alone -> null; only `git.golem15.com/golem15/summercms-extra v1.0.0` -> null; the real APP/go.mod text -> v0.5.0
+ - majorMinor: v0.5.0 -> 0.5, 0.5.3 -> 0.5, v0.6.0-rc.1 -> 0.6, v1.12.4+meta -> 1.12, 0.5 -> 0.5; '' / latest / v1 -> null
+ - Built page: header.pill, hero.ctaAlpha and start.note each render with `{version}` replaced by go.mod's major.minor; the literal `{version}` never appears in the page
+ - A go.mod requiring v0.9.0 next to the current build makes TestLandingVersion fail
+
+
+Expand from the proven slice to the other two strings, and pin the helpers with tests on both sides.
+
+1. SITE/i18n/locales/en.json: set `hero.ctaAlpha` to `Alpha {version} is out` and `start.note` to `Alpha {version} is for early adopters. APIs may change before 1.0.`. After this, none of the three strings contains a digit version.
+2. SITE/app/components/HeroSection.vue and StartSection.vue: import `useSummercmsVersion` from `../composables/useSummercmsVersion` the same way as SiteHeader, add `const version = useSummercmsVersion()`, and render `t('hero.ctaAlpha', { version })` and `t('start.note', { version })`. Change no markup or style.
+3. SITE/tests/version.test.ts (new, node:test plus node:assert/strict, importing `../app/utils/version.ts`): table tests covering every case in for both helpers. For the real-file case, read `../../go.mod` relative to the test file (APP/go.mod) and skip that one test with `t.skip` when the file is absent, so a standalone SITE checkout still runs the others.
+4. SITE/changelog/testing/expect.txt: under "Landing chrome and the Changelog menu", add `200 /changelog notcontains={version}`. Together with useSummercmsVersion throwing on a missing value, this guards the inherited runtimeConfig in the changelog build.
+5. PLUG/version_test.go:
+ - Add ungated table tests `TestFrameworkVersionFromGoMod` and `TestMajorMinor` with the same cases as (the real-file case reads `filepath.Join(siteDir(), "..", "go.mod")` and skips when it is absent).
+ - Extend `TestLandingVersion` from the pill to all three keys (`header.pill`, `hero.ctaAlpha`, `start.note`). Each message must contain `{version}`, and the page must contain `html.EscapeString` of the message with the placeholder replaced. Keep the exact pill comparison and add a check that the page does not contain the literal `{version}`. Report every miss before failing (t.Errorf per key), as TestTerminalCommandsInPage does.
+6. Prove the paths (the commands are in ):
+ - NUXT_PUBLIC_SUMMERCMS_VERSION=v0.7.2 makes the generated page show 0.7 in the pill and the hero (env path plus render-time normalization).
+ - A plain generate with the variable unset falls back to ../go.mod and shows 0.5. Then rsync `.output/public/` to PLUG/public/site/ the way build.sh step 1 does, and run the gated plugin tests.
+ - A scratch SUMMERCMS_SITE_DIR whose parent go.mod requires v0.9.0 makes TestLandingVersion fail and name 0.9.
+
+
+ APP=/media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app; SITE="$APP/vue-summercmsio-app"; PLUG="$APP/plugins/golem15/summercms"; set -e; test -z "$(grep -nE '(ALPHA|Alpha) [0-9]' "$SITE/i18n/locales/en.json")"; (cd "$SITE" && NUXT_PUBLIC_SUMMERCMS_VERSION=v0.7.2 pnpm generate >/dev/null && grep -q '>ALPHA 0\.7<' .output/public/index.html && grep -q 'Alpha 0\.7 is out' .output/public/index.html); (cd "$SITE" && env -u NUXT_PUBLIC_SUMMERCMS_VERSION pnpm generate >/dev/null && pnpm test && pnpm build:changelog >/dev/null && pnpm test:changelog); rsync -a --delete "$SITE/.output/public/" "$PLUG/public/site/"; go -C "$PLUG" vet ./...; go -C "$PLUG" test -count=1 ./...; SUMMERCMS_REQUIRE_BUILD=1 go -C "$PLUG" test -count=1 ./...; T=$(mktemp -d); mkdir -p "$T/site/i18n/locales"; cp "$SITE/i18n/locales/en.json" "$T/site/i18n/locales/"; printf 'module x\n\ngo 1.27.0\n\nrequire git.golem15.com/golem15/summercms v0.9.0\n' > "$T/go.mod"; if SUMMERCMS_SITE_DIR="$T/site" SUMMERCMS_REQUIRE_BUILD=1 go -C "$PLUG" test -count=1 -run TestLandingVersion ./... > "$T/out.txt" 2>&1; then echo "drift not detected"; exit 1; fi; grep -q '0\.9' "$T/out.txt"; echo EXPANSION-OK
+
+
+ en.json's three strings carry `{version}` and no digit version. With NUXT_PUBLIC_SUMMERCMS_VERSION=v0.7.2 the page shows 0.7. Without the variable it shows 0.5 from ../go.mod. `pnpm test` passes, including tests/version.test.ts and the unchanged output.test.ts. `pnpm test:changelog` passes with the new notcontains line. `go vet` passes in PLUG, and `go test` passes there both ungated and with SUMMERCMS_REQUIRE_BUILD=1. TestLandingVersion fails, naming 0.9, when go.mod requires v0.9.0. PLUG/public/site holds the 0.5 build at the end, not the 0.7 one.
+
+
+
+
+ Task 3: Document the version contract and commit inner repos first, then the app and its pointer bump
+
+ SITE/README.md,
+ PLUG/README.md,
+ APP/README.md,
+ APP/CLAUDE.md,
+ APP/vue-summercmsio-app (gitlink),
+ APP/plugins/golem15/summercms (gitlink)
+
+
+ SITE/README.md (Layout section), PLUG/README.md (Testing section), APP/README.md (line 5 and the gated-variable table), APP/CLAUDE.md (Commands block line 25, Gated test variables, Cross-repo contracts)
+
+
+Document the contract in the files that describe the touched behavior, then commit. The rules: no co-author tags, explicit paths only, never `git add -A`, never stage APP/scripts/__pycache__/, no push, tag or deploy.
+
+1. Docs:
+ - SITE/README.md, Layout. In the `nuxt.config.ts` bullet, add that `runtimeConfig.public.summercmsVersion` (the framework tag) comes from NUXT_PUBLIC_SUMMERCMS_VERSION, which scripts/build.sh sets from the app's go.mod, or else from `../go.mod`, and that the build fails when neither exists. In the `i18n/locales/en.json` bullet, add that `header.pill`, `hero.ctaAlpha` and `start.note` take `{version}` (major.minor, from `useSummercmsVersion()`) and must not hard-code a version. In the `app/utils/` bullet, add `frameworkVersionFromGoMod` and `majorMinor` to the helper list.
+ - PLUG/README.md, Testing: the REQUIRE_BUILD line lists the version check too ("link, terminal, version and docs header checks"). Add one sentence: TestLandingVersion compares the built pill, hero button and Get started note with the major.minor that `/../go.mod` requires.
+ - APP/README.md: in line 5, replace the hard-coded framework version with wording that names none ("the framework version its go.mod requires"). In the `SUMMERCMS_REQUIRE_BUILD=1` row, add "the header pill, hero button and Get started note show go.mod's framework version (major.minor)".
+ - APP/CLAUDE.md: in the Commands block, drop the parenthesized framework version after "framework from the go.mod tag" on the build.sh line. Under Cross-repo contracts, add one bullet: the landing version comes from go.mod. build.sh exports NUXT_PUBLIC_SUMMERCMS_VERSION from `go list -m` in both modes, nuxt.config.ts falls back to `../go.mod`, the three en.json strings take `{version}` shown as major.minor (the changelog header inherits it), and TestLandingVersion fails a build whose page disagrees with go.mod.
+2. Commits, in this order, each preceded by `git status --short` in that repo to confirm only this task's files are staged:
+ a. SITE, message `feat(landing): show the framework version from go.mod`, with a short body (go.mod single source, major.minor only, {version} placeholders, useSummercmsVersion, fails closed without a version). Stage exactly: i18n/locales/en.json, nuxt.config.ts, app/utils/version.ts, app/composables/useSummercmsVersion.ts, app/components/SiteHeader.vue, app/components/HeroSection.vue, app/components/StartSection.vue, tests/version.test.ts, changelog/testing/expect.txt, README.md.
+ b. PLUG, message `test: the landing version matches go.mod`. Stage exactly: version_test.go, README.md.
+ c. APP, message `feat(build): pass the go.mod framework version to the site build`. Stage exactly: scripts/build.sh, README.md, CLAUDE.md.
+ d. APP, message `chore: bump vue-summercmsio-app and the site plugin for the go.mod landing version`, with a body naming both inner commits. Stage exactly the gitlinks vue-summercmsio-app and plugins/golem15/summercms.
+
+
+ APP=/media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app; set -e; git -C "$APP/vue-summercmsio-app" log -1 --format=%s | grep -q 'framework version from go.mod'; git -C "$APP/plugins/golem15/summercms" log -1 --format=%s | grep -q 'landing version matches go.mod'; git -C "$APP" log -2 --format=%s | grep -q 'bump vue-summercmsio-app'; git -C "$APP" log -2 --format=%s | grep -q 'go.mod framework version'; test -z "$(git -C "$APP" log -2 --format=%B | grep -i 'co-authored-by')"; test -z "$(git -C "$APP/vue-summercmsio-app" log -1 --format=%B | grep -i 'co-authored-by')"; test -z "$(git -C "$APP/plugins/golem15/summercms" log -1 --format=%B | grep -i 'co-authored-by')"; test -z "$(git -C "$APP" submodule status vue-summercmsio-app plugins/golem15/summercms | grep '^+')"; test -z "$(git -C "$APP/vue-summercmsio-app" status --porcelain)"; test -z "$(git -C "$APP/plugins/golem15/summercms" status --porcelain)"; test -z "$(grep -n 'v0\.4\.0' "$APP/CLAUDE.md" "$APP/README.md" "$APP/scripts/build.sh")"; echo COMMITS-OK
+
+
+ Four commits exist, in this order: SITE, PLUG, then APP build+docs, then APP pointer bump. None carries a co-author tag. Both gitlinks match the checked-out submodule commits, and SITE and PLUG are clean. APP shows no change of this task's (the pre-existing scripts/__pycache__/ may remain untracked). CLAUDE.md, README.md and build.sh no longer hard-code a framework version. Nothing was pushed or tagged.
+
+
+
+
+
+
+## Trust Boundaries
+
+| Boundary | Description |
+|----------|-------------|
+| build environment -> prerendered HTML | NUXT_PUBLIC_SUMMERCMS_VERSION (set by build.sh from go.mod, or by the caller) becomes page text and the public runtime payload |
+| changelog Node process env -> SSR header | Nuxt allows the same variable to override the public runtime value at request time |
+
+## STRIDE Threat Register
+
+| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
+|-----------|----------|-----------|----------|-------------|-----------------|
+| T-q1r3-01 | Tampering | nuxt.config.ts frameworkVersion(), useSummercmsVersion() | low | mitigate | The value must pass majorMinor (digits.digits) at config time and at render, or the build or render fails. The text goes through vue-i18n into `{{ }}` mustache output, which Vue escapes, so no markup can be injected. |
+| T-q1r3-02 | Information disclosure | runtimeConfig.public.summercmsVersion in the page payload | low | accept | The framework tag is already public (changelog posts, the public repository); exposing it to the client is intended. |
+| T-q1r3-03 | Repudiation / integrity of release | stale landing version after a framework bump | low | mitigate | TestLandingVersion runs in scripts/build.sh step 3 with SUMMERCMS_REQUIRE_BUILD=1 and fails the build when the page disagrees with go.mod. |
+| T-q1r3-SC | Tampering | npm/pip/cargo installs | low | accept | No packages are added. Go helpers use the standard library only (no golang.org/x/mod); pnpm install stays --frozen-lockfile. |
+
+
+
+- Task 1: `scripts/build.sh dev` passes end to end, and the built pill reads ALPHA 0.5, derived from go.mod v0.5.0.
+- Task 2: env override (v0.7.2 shows 0.7), go.mod fallback (0.5), `pnpm test`, `pnpm test:changelog`, PLUG `go vet` and `go test` (ungated and with SUMMERCMS_REQUIRE_BUILD=1), and the drift failure with a v0.9.0 go.mod.
+- Task 3: commit order and contents, no co-author tags, gitlinks in sync, no hard-coded framework version left in APP CLAUDE.md, README.md or build.sh.
+
+
+
+- The landing shows go.mod's framework major.minor in the header pill, the hero button and the Get started note, and the changelog header shows the same. No hand bump is needed on the next framework release.
+- A build whose page disagrees with go.mod fails in scripts/build.sh (TestLandingVersion).
+- Docs in SITE, PLUG and APP describe the contract; commits land inner repos first, then the app pointer bump; nothing is pushed, tagged or deployed.
+
+
+
diff --git a/.planning/quick/261029-1r3-derive-the-summercms-io-landing-version-/261029-1r3-SUMMARY.md b/.planning/quick/261029-1r3-derive-the-summercms-io-landing-version-/261029-1r3-SUMMARY.md
new file mode 100644
index 00000000..a58c7a07
--- /dev/null
+++ b/.planning/quick/261029-1r3-derive-the-summercms-io-landing-version-/261029-1r3-SUMMARY.md
@@ -0,0 +1,131 @@
+---
+phase: quick-261029-1r3
+plan: 01
+subsystem: sm-summercmsio-app (landing, build, site plugin)
+tags: [summercms-io, landing, nuxt, build, go-mod, versioning]
+status: complete
+requires: []
+provides:
+ - "go.mod as the single source of the landing's framework version (major.minor)"
+ - "TestLandingVersion drift guard in scripts/build.sh step 3"
+affects:
+ - sm-summercmsio-app/vue-summercmsio-app (landing + changelog header)
+ - sm-summercmsio-app/plugins/golem15/summercms (gated tests)
+ - sm-summercmsio-app/scripts/build.sh
+tech-stack:
+ added: []
+ patterns:
+ - "runtimeConfig.public value derived at config time, normalized at render (fails closed both places)"
+ - "TS helper in app/utils mirrored by a Go test helper with the same regex semantics"
+key-files:
+ created:
+ - sm-summercmsio-app/vue-summercmsio-app/app/utils/version.ts
+ - sm-summercmsio-app/vue-summercmsio-app/app/composables/useSummercmsVersion.ts
+ - sm-summercmsio-app/vue-summercmsio-app/tests/version.test.ts
+ - sm-summercmsio-app/plugins/golem15/summercms/version_test.go
+ modified:
+ - sm-summercmsio-app/vue-summercmsio-app/nuxt.config.ts
+ - sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json
+ - 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/StartSection.vue
+ - sm-summercmsio-app/vue-summercmsio-app/changelog/testing/expect.txt
+ - sm-summercmsio-app/vue-summercmsio-app/README.md
+ - sm-summercmsio-app/plugins/golem15/summercms/README.md
+ - sm-summercmsio-app/scripts/build.sh
+ - sm-summercmsio-app/README.md
+ - sm-summercmsio-app/CLAUDE.md
+decisions:
+ - "The landing shows go.mod's framework version as major.minor only (v0.5.0 -> 0.5); runtimeConfig stores the raw tag and useSummercmsVersion() normalizes at render"
+ - "No default version: nuxt.config.ts throws when neither NUXT_PUBLIC_SUMMERCMS_VERSION nor ../go.mod yields a semver value"
+metrics:
+ duration: 7min
+ completed: 2026-10-29
+actuals:
+ tokens: 8000
+ tasks: 3
+ commits: 4
+plan_head_before: "multi-repo: sm-summercmsio-app 29dd1b3f4e72, vue-summercmsio-app f1cd5871b409, sm-summercmsio-plugin 65cc37224585"
+plan_head_after: "multi-repo: sm-summercmsio-app b688a80e6b4f, vue-summercmsio-app b3bfc5da80d0, sm-summercmsio-plugin ca5941540c45"
+---
+
+# Phase quick-261029-1r3 Plan 01: Derive the summercms.io landing version from go.mod Summary
+
+The landing's header pill, hero button and Get started note (and the changelog's inherited header) now render `{version}` from the framework tag the app's go.mod requires, shown as major.minor (`ALPHA 0.5`). scripts/build.sh exports it as `NUXT_PUBLIC_SUMMERCMS_VERSION`, nuxt.config.ts falls back to `../go.mod`, and the plugin's gated `TestLandingVersion` fails the build when the page and go.mod disagree.
+
+## Commits
+
+Nothing in summercms.go changed apart from this SUMMARY. Commits are in the sibling repos, in the plan's order (inner repos first, then the app's pointer bump). None has a co-author tag. Nothing was pushed or tagged.
+
+| # | Repo | Hash | Message |
+|---|------|------|---------|
+| 1 | vue-summercmsio-app (`sm-summercmsio-app/vue-summercmsio-app`) | b3bfc5d | feat(landing): show the framework version from go.mod |
+| 2 | sm-summercmsio-plugin (`sm-summercmsio-app/plugins/golem15/summercms`) | ca59415 | test: the landing version matches go.mod |
+| 3 | sm-summercmsio-app | afe3f35 | feat(build): pass the go.mod framework version to the site build |
+| 4 | sm-summercmsio-app | b688a80 | chore: bump vue-summercmsio-app and the site plugin for the go.mod landing version |
+
+The plan's tasks did not each get their own commit. The plan puts all commits in Task 3, one per repo, so each repo got one logical commit and the pointer bump followed.
+
+## Tasks
+
+1. **Tracer.** Added `app/utils/version.ts` (`frameworkVersionFromGoMod`, `majorMinor`), `useSummercmsVersion()`, `runtimeConfig.public.summercmsVersion` in nuxt.config.ts, `header.pill` = `ALPHA {version}`, the SiteHeader wiring, the `FW_VERSION` lookup and export in build.sh for both modes, and a pill-only `TestLandingVersion`.
+2. **Expansion.** `hero.ctaAlpha` and `start.note` now take `{version}`, wired in HeroSection and StartSection. Added `tests/version.test.ts`, the line `200 /changelog notcontains={version}` in expect.txt, the ungated Go tests `TestFrameworkVersionFromGoMod` and `TestMajorMinor`, and extended `TestLandingVersion` to all three keys plus a check that no literal `{version}` is left on the page.
+3. **Docs and commits.** Updated SITE/PLUG/APP READMEs and APP CLAUDE.md (Commands line, gated-test comment, new Cross-repo contracts bullet). The four commits are listed above.
+
+## Verification
+
+- **Task 1 (tracer gate):** `scripts/build.sh dev` exit 0 (nuxt generate, build:changelog, `smoke-ssr: ok (76 checks)`, docs:build 89 pages, plugin tests `ok`, `Built bin/summercms-io`). Built pill: `class="pill" data-v-3407a8c8>ALPHA 0.5<`. `--- PASS: TestLandingVersion`. TRACER-OK.
+- **main.go / plugins.gen.go:** `git status --porcelain -- main.go plugins.gen.go` was empty before and after the dev build, so nothing had to be restored.
+- **Task 2:** EXPANSION-OK.
+ - No `ALPHA|Alpha ` left in en.json.
+ - With `NUXT_PUBLIC_SUMMERCMS_VERSION=v0.7.2` the page shows `>ALPHA 0.7<`, `Alpha 0.7 is out` and `Alpha 0.7 is for early...`.
+ - Plain generate with the variable unset: `ALPHA 0.5` from `../go.mod`.
+ - `pnpm test`: `# tests 70 / # pass 70 / # fail 0 / # skipped 0`.
+ - `pnpm test:changelog`: `smoke-ssr: ok (77 checks)` (the new notcontains line is included).
+ - PLUG `go vet` passes. PLUG `go test` passes both ungated and with `SUMMERCMS_REQUIRE_BUILD=1`.
+ - Drift test with a scratch go.mod requiring v0.9.0 fails as intended. Verbatim output:
+ ```
+ --- FAIL: TestLandingVersion (0.00s)
+ version_test.go:89: go.mod requires v0.9.0 (shown as 0.9), the page does not show header.pill "ALPHA 0.9"; rebuild with scripts/build.sh
+ version_test.go:98: go.mod requires v0.9.0 (shown as 0.9), the page's pill shows "ALPHA 0.5", want "ALPHA 0.9"; rebuild with scripts/build.sh
+ version_test.go:89: go.mod requires v0.9.0 (shown as 0.9), the page does not show hero.ctaAlpha "Alpha 0.9 is out"; rebuild with scripts/build.sh
+ version_test.go:89: go.mod requires v0.9.0 (shown as 0.9), the page does not show start.note "Alpha 0.9 is for early adopters. APIs may change before 1.0."; rebuild with scripts/build.sh
+ FAIL
+ ```
+ - PLUG/public/site holds the 0.5 build at the end.
+- **Extra (fail-closed config):** `NUXT_PUBLIC_SUMMERCMS_VERSION=latest pnpm generate` exits 1 with `ERROR no framework version (got "latest"): set NUXT_PUBLIC_SUMMERCMS_VERSION or build inside sm-summercmsio-app so ../go.mod exists`.
+- **Task 3:** COMMITS-OK.
+ - Commit order is right and there are no co-author tags.
+ - The gitlinks match the checkouts; SITE and PLUG are clean.
+ - No `v0.4.0` left in APP CLAUDE.md, README.md or build.sh.
+ - APP `go vet ./...` and `go test -short ./...` pass.
+ - None of the commits deletes a file.
+ - APP status shows only the pre-existing untracked `scripts/__pycache__/`, which was not staged.
+
+## Deviations from Plan
+
+**Judgment call (not a rule fix):** The real-file case of `frameworkVersionFromGoMod`, in both `tests/version.test.ts` and `TestFrameworkVersionFromGoMod`, checks that the app's go.mod yields a valid version. It does not pin the literal `v0.5.0`. A pinned value would break on the next framework bump, which is the hand edit this task removes. Both tests also gained a CRLF case and an empty-go.mod case.
+
+Otherwise the plan was executed as written.
+
+## Left out on purpose (follow-ups)
+
+- **DEPLOY.md** still hard-codes `v0.4.0`:
+ - line 58, step 3 "Check the framework tag `v0.4.0`"
+ - line 61, `ls-remote --tags origin v0.4.0`
+ - line 64, the `build: tag v0.4.0 not found` text
+ - line 187, the `scripts/build.sh` release comment
+ - line 193, `go version -m` shows `v0.4.0`
+
+ This was out of scope. go.mod now requires v0.5.0, so these steps are stale.
+- **PLUG/go.mod** still has `require git.golem15.com/golem15/summercms v0.4.0`, with a local replace to `../../../../summercms.go`. This was out of scope. The landing version comes from the app's go.mod, not the plugin's.
+- **Changelog post version pills** (`changelog/app/utils/release.ts`) are a different feature and were not touched.
+
+## Known Stubs
+
+None.
+
+## Self-Check: PASSED
+
+- FOUND: vue-summercmsio-app/app/utils/version.ts, app/composables/useSummercmsVersion.ts, tests/version.test.ts; plugins/golem15/summercms/version_test.go
+- FOUND commits: b3bfc5d (vue-summercmsio-app), ca59415 (sm-summercmsio-plugin), afe3f35 and b688a80 (sm-summercmsio-app)
diff --git a/.planning/todos/pending/2026-10-28-derive-summercms-io-landing-version-from-go-mod.md b/.planning/todos/completed/2026-10-28-derive-summercms-io-landing-version-from-go-mod.md
similarity index 100%
rename from .planning/todos/pending/2026-10-28-derive-summercms-io-landing-version-from-go-mod.md
rename to .planning/todos/completed/2026-10-28-derive-summercms-io-landing-version-from-go-mod.md