docs(11.2): rename the site repositories to summercmsio (D-48)

This commit is contained in:
Jakub Zych
2026-10-01 15:58:30 +02:00
parent 5d2184e9e6
commit 130b6ac38f
10 changed files with 314 additions and 313 deletions

View File

@@ -573,13 +573,13 @@ Plans:
**Goal:** summercms.io replaces its "Under construction" page with the Alpha 0.1 landing page from the claude.ai/design handoff. The page is a Nuxt 4 static site, English only and i18n-ready, embedded in and served by a SummerCMS binary that also serves the Phase 11.1 docs at `/docs`. It deploys behind nginx with supervisor using documented steps. This is a quick phase of 2-3 plans; newsletter signup moved to Phase 11.3.
**Requirements**: TBD
**Depends on:** Phase 11.1
**Repos:** three new repos under `git.golem15.com/golem15/`: (1) `sm-summercms-app`, the root app that builds the binary and wires the plugins; (2) `vue-summercms-app`, the Nuxt 4 site, held inside the root app; (3) `sm-summercms-plugin`, the site plugin that serves the embedded site at `/`, the docs at `/docs` and any site-specific data, the WinterCMS site-plugin pattern. `summercms.go` changes only if the site needs a framework feature that is missing.
**Repos:** three new repos under `git.golem15.com/golem15/`: (1) `sm-summercmsio-app`, the root app that builds the binary and wires the plugins; (2) `vue-summercmsio-app`, the Nuxt 4 site, held inside the root app; (3) `sm-summercmsio-plugin`, the site plugin that serves the embedded site at `/`, the docs at `/docs` and any site-specific data, the WinterCMS site-plugin pattern. `summercms.go` changes only if the site needs a framework feature that is missing.
**Naming convention:** the same as OctoberCMS (`oc-`) and WinterCMS (`wn-`), with the `sm-` prefix: root app `sm-<name>-app`, Vue frontend `vue-<name>-app`, plugin `sm-<name>-plugin`, theme `sm-<name>-theme`. The Go package inside a plugin keeps its plain name (`newsletter`). The meta root `summercms.io` and the framework `summercms.go` keep their names. `fonoteka.go` becomes `sm-fonoteka-app`.
**Design source:** `.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/design/` (handoff `README.md`, `SummerCMS Landing.dc.html`, `assets/sun-crop.png`). The design is high fidelity: colors, type, spacing and copy are final. The handoff supersedes 11.2-CONTEXT D-06 (English only for now), D-08, D-10 and D-11 (no signup, "Alpha 0.1 is out", Source links allowed).
**Success Criteria** (what must be TRUE):
1. `nuxt generate` in `vue-summercms-app` produces the landing page matching the handoff: sticky header with scroll-spy, hero, the Why, Features, From WinterCMS and Get started sections, a terminal card with a working Copy button, and a footer. It is responsive down to phone width, and the section links hide at 720px or less.
2. One `sm-summercms-app` binary embeds the Nuxt output and the Phase 11.1 docs build. It serves `/` and `/docs` with indexable responses (no admin `noindex`/CSP) and with cache headers: immutable for hashed assets, no-cache for HTML.
1. `nuxt generate` in `vue-summercmsio-app` produces the landing page matching the handoff: sticky header with scroll-spy, hero, the Why, Features, From WinterCMS and Get started sections, a terminal card with a working Copy button, and a footer. It is responsive down to phone width, and the section links hide at 720px or less.
2. One `sm-summercmsio-app` binary embeds the Nuxt output and the Phase 11.1 docs build. It serves `/` and `/docs` with indexable responses (no admin `noindex`/CSP) and with cache headers: immutable for hashed assets, no-cache for HTML.
3. Every link on the page resolves, including the eight feature-tile `/docs/...` links and the WinterCMS concept-map link. They are verified against the built docs, not assumed.
4. A scripted build (`nuxt generate`, then `summer docs:build`, then `go build`) and a `DEPLOY.md` in the root app cover building, uploading, a supervisor program config, and an nginx site config (TLS, proxy to the binary, gzip). Following it on a clean server brings the site up.
5. The new Go code has unit tests, delivered in the phase's last plan.
@@ -590,10 +590,10 @@ Plans:
Plans:
**Wave 1**
- [x] 11.2-01-PLAN.md — vue-summercms-app: Nuxt 4 landing page matching the handoff (en-only i18n, @nuxt/fonts, @nuxtjs/seo with static OG, sun assets from logo.png, single-source terminal commands, Copy, scroll-spy, app config flags)
- [x] 11.2-01-PLAN.md — vue-summercmsio-app: Nuxt 4 landing page matching the handoff (en-only i18n, @nuxt/fonts, @nuxtjs/seo with static OG, sun assets from logo.png, single-source terminal commands, Copy, scroll-spy, app config flags)
**Wave 2** *(blocked on Wave 1 completion)*
- [ ] 11.2-02-PLAN.md — Framework PG15 verification and site_url/site_label (D-25, D-41, D-46), v0.1.0 tag checkpoint (D-42), sm-summercms-plugin static serving at / and /docs (D-07, D-47), sm-summercms-app wiring, build and smoke scripts, link and terminal checks (SC3, D-40), DEPLOY.md with nginx, supervisor and rollback
- [ ] 11.2-02-PLAN.md — Framework PG15 verification and site_url/site_label (D-25, D-41, D-46), v0.1.0 tag checkpoint (D-42), sm-summercmsio-plugin static serving at / and /docs (D-07, D-47), sm-summercmsio-app wiring, build and smoke scripts, link and terminal checks (SC3, D-40), DEPLOY.md with nginx, supervisor and rollback
**Wave 3** *(blocked on Wave 2 completion)*
- [ ] 11.2-03-PLAN.md — Unit tests last: plugin rule table at 90%+ coverage, site_url/site_label branches, terminal-check helpers, check-phase11.2.sh gate, validated VALIDATION.md
@@ -603,7 +603,7 @@ Plans:
**Goal:** Visitors to summercms.io subscribe for updates through the initial Go version of the Golem15 Newsletter plugin, which confirms each email by double opt-in. The signup widget is added to the Phase 11.2 landing page, and the site gains Polish alongside English.
**Requirements**: TBD
**Depends on:** Phase 11.2
**Repos:** `sm-newsletter-plugin` (new, under `git.golem15.com/golem15/`, Go package `newsletter`), plus changes to the Phase 11.2 repos (`vue-summercms-app` for the widget and Polish locale, `sm-summercms-app` to wire the plugin). `summercms.go` changes only if a framework feature is missing.
**Repos:** `sm-newsletter-plugin` (new, under `git.golem15.com/golem15/`, Go package `newsletter`), plus changes to the Phase 11.2 repos (`vue-summercmsio-app` for the widget and Polish locale, `sm-summercmsio-app` to wire the plugin). `summercms.go` changes only if a framework feature is missing.
**Port source:** Golem15.Newsletter, `github.com/golem15com/wn-newsletter-plugin`, checked out at `/media/nvme/dev/golem15/horoskopia.eu/plugins/golem15/newsletter`. The PHP plugin is work in progress. Its audience comes from registered users (it requires `Golem15.User`), and its only public routes are unsubscribe. It has no public subscriber model or signup widget, so this phase adds those in Go. The PHP original is not changed.
**Success Criteria** (what must be TRUE):

View File

@@ -158,7 +158,7 @@ Progress: [██████░░░░] 60%
- Phase 10.1 inserted after Phase 10: Runtime admin extension point (URGENT)
- Phase 10.2 inserted after Phase 10: Nest framework packages under modules and write run docs (URGENT)
- Phase 11.1 inserted after Phase 11: SummerCMS documentation for humans and AI agents, modelled on wintercms.com/docs; after core framework, before Płytarium API port
- Phase 11.2 inserted after Phase 11.1: Ready to share: summercms.io website (sm-summercms-app + vue-summercms-app), sm-newsletter-plugin stub with double opt-in, 11.1 docs at /docs
- Phase 11.2 inserted after Phase 11.1: Ready to share: summercms.io website (sm-summercmsio-app + vue-summercmsio-app), sm-newsletter-plugin stub with double opt-in, 11.1 docs at /docs
- Phase 11.2 edited: edited fields: title, goal, repos, design source, success_criteria — reshaped into the Alpha 0.1 landing page (Nuxt 4 on SummerCMS, nginx + supervisor deploy); newsletter moved to 11.3
- Phase 11.3 inserted after Phase 11.2: Newsletter plugin and signup on summercms.io (split out of the original 11.2)

View File

@@ -5,41 +5,41 @@ type: execute
wave: 1
depends_on: []
files_modified:
- ../sm-summercms-app/vue-summercms-app/package.json
- ../sm-summercms-app/vue-summercms-app/pnpm-lock.yaml
- ../sm-summercms-app/vue-summercms-app/pnpm-workspace.yaml
- ../sm-summercms-app/vue-summercms-app/.nvmrc
- ../sm-summercms-app/vue-summercms-app/.gitignore
- ../sm-summercms-app/vue-summercms-app/README.md
- ../sm-summercms-app/vue-summercms-app/tsconfig.json
- ../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/app.vue
- ../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/assets-src/logo.png
- ../sm-summercms-app/vue-summercms-app/scripts/derive-images.sh
- ../sm-summercms-app/vue-summercms-app/public/sun-logo.webp
- ../sm-summercms-app/vue-summercms-app/public/sun-badge.webp
- ../sm-summercms-app/vue-summercms-app/public/favicon.ico
- ../sm-summercms-app/vue-summercms-app/public/apple-touch-icon.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
- ../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
autonomous: true
requirements: []
assumption_delta_decision: no-change
@@ -54,7 +54,7 @@ estimate:
must_haves:
truths:
- "Per D-04 and SC1, `pnpm -C ../sm-summercms-app/vue-summercms-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-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."
@@ -70,58 +70,58 @@ must_haves:
- statement: "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."
verification: backstop
artifacts:
- path: "../sm-summercms-app/vue-summercms-app/nuxt.config.ts"
- path: "../sm-summercmsio-app/vue-summercmsio-app/nuxt.config.ts"
provides: "Nuxt 4 SSG config: fonts, i18n, seo, prerender ignore for /docs"
contains: "prerender"
- path: "../sm-summercms-app/vue-summercms-app/app/app.config.ts"
- path: "../sm-summercmsio-app/vue-summercmsio-app/app/app.config.ts"
provides: "D-45 landing flags"
contains: "scrollSpy"
- path: "../sm-summercms-app/vue-summercms-app/i18n/locales/en.json"
- path: "../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json"
provides: "all page copy (D-33)"
contains: "A new dawn in content management"
- path: "../sm-summercms-app/vue-summercms-app/app/data/terminal.json"
- path: "../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json"
provides: "single source of the six terminal commands (D-40)"
contains: "./bin/hello greeter:hello"
- path: "../sm-summercms-app/vue-summercms-app/app/components/TerminalCard.vue"
- path: "../sm-summercmsio-app/vue-summercmsio-app/app/components/TerminalCard.vue"
provides: "terminal card with Copy"
contains: "copyPayload"
- path: "../sm-summercms-app/vue-summercms-app/app/utils/scrollSpy.ts"
- path: "../sm-summercmsio-app/vue-summercmsio-app/app/utils/scrollSpy.ts"
provides: "pure scroll-spy selection"
contains: "export function activeSection"
- path: "../sm-summercms-app/vue-summercms-app/app/utils/terminal.ts"
- path: "../sm-summercmsio-app/vue-summercmsio-app/app/utils/terminal.ts"
provides: "pure copy payload builder"
contains: "export function copyPayload"
- path: "../sm-summercms-app/vue-summercms-app/tests/output.test.ts"
- path: "../sm-summercmsio-app/vue-summercmsio-app/tests/output.test.ts"
provides: "SC1 assertions on the generated output"
contains: "index.html"
- path: "../sm-summercms-app/vue-summercms-app/scripts/derive-images.sh"
- path: "../sm-summercmsio-app/vue-summercmsio-app/scripts/derive-images.sh"
provides: "D-36 image derivation from the original sun"
contains: "magick"
key_links:
- from: "../sm-summercms-app/vue-summercms-app/app/components/TerminalCard.vue"
to: "../sm-summercms-app/vue-summercms-app/app/data/terminal.json"
- from: "../sm-summercmsio-app/vue-summercmsio-app/app/components/TerminalCard.vue"
to: "../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json"
via: "static JSON import; the card renders and copies only these commands"
pattern: "data/terminal\\.json"
- from: "../sm-summercms-app/vue-summercms-app/app/components/TerminalCard.vue"
to: "../sm-summercms-app/vue-summercms-app/app/utils/terminal.ts"
- from: "../sm-summercmsio-app/vue-summercmsio-app/app/components/TerminalCard.vue"
to: "../sm-summercmsio-app/vue-summercmsio-app/app/utils/terminal.ts"
via: "copyPayload(groups) builds the clipboard text"
pattern: "copyPayload\\("
- from: "../sm-summercms-app/vue-summercms-app/app/components/SiteHeader.vue"
to: "../sm-summercms-app/vue-summercms-app/app/utils/scrollSpy.ts"
- from: "../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue"
to: "../sm-summercmsio-app/vue-summercmsio-app/app/utils/scrollSpy.ts"
via: "scroll listener calls activeSection with section tops"
pattern: "activeSection\\("
- from: "../sm-summercms-app/vue-summercms-app/nuxt.config.ts"
- from: "../sm-summercmsio-app/vue-summercmsio-app/nuxt.config.ts"
to: "/docs links on the page"
via: "nitro.prerender.ignore keeps generate from crawling /docs, which the Go binary serves"
pattern: "ignore:.*'/docs'"
---
<objective>
Build `vue-summercms-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).
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-summercms-app/vue-summercms-app` (it becomes a submodule of `sm-summercms-app` in plan 11.2-02), with node:test assertions over the generated output and the two pure utilities.
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.
</objective>
<execution_context>
@@ -143,7 +143,7 @@ Output: a new git repository at `/media/nvme/dev/golem15/summercms.io/summercms/
@/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-summercms-app/vue-summercms-app` (absolute: `/media/nvme/dev/golem15/summercms.io/summercms/sm-summercms-app/vue-summercms-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-summercms-app/` stays a plain directory until plan 11.2-02 makes it a repository.
**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.
@@ -164,7 +164,7 @@ Pure utilities consumed by components and by node:test (use only erasable TypeSc
<task type="tracer">
<name>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</name>
<precondition>`node --version` prints v22.18 or newer, `pnpm --version` prints 11.x, and `magick -version` succeeds with WEBP and ICO delegates (RESEARCH Environment Availability).</precondition>
<files>../sm-summercms-app/vue-summercms-app/package.json, ../sm-summercms-app/vue-summercms-app/pnpm-lock.yaml, ../sm-summercms-app/vue-summercms-app/pnpm-workspace.yaml, ../sm-summercms-app/vue-summercms-app/.nvmrc, ../sm-summercms-app/vue-summercms-app/.gitignore, ../sm-summercms-app/vue-summercms-app/README.md, ../sm-summercms-app/vue-summercms-app/tsconfig.json, ../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/app.vue, ../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/assets/css/tokens.css, ../sm-summercms-app/vue-summercms-app/app/assets/css/base.css, ../sm-summercms-app/vue-summercms-app/i18n/locales/en.json, ../sm-summercms-app/vue-summercms-app/assets-src/logo.png, ../sm-summercms-app/vue-summercms-app/scripts/derive-images.sh, ../sm-summercms-app/vue-summercms-app/public/sun-logo.webp, ../sm-summercms-app/vue-summercms-app/public/sun-badge.webp, ../sm-summercms-app/vue-summercms-app/public/favicon.ico, ../sm-summercms-app/vue-summercms-app/public/apple-touch-icon.png, ../sm-summercms-app/vue-summercms-app/public/og-image.png, ../sm-summercms-app/vue-summercms-app/tests/output.test.ts</files>
<files>../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</files>
<read_first>
- .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)
@@ -176,8 +176,8 @@ Pure utilities consumed by components and by node:test (use only erasable TypeSc
<action>
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-summercms-app/vue-summercms-app`, then `git init -b master` inside `vue-summercms-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-summercms-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.
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).
@@ -190,26 +190,26 @@ Create the repository and the thinnest production-quality slice through every la
Commit as `feat(site): scaffold the Nuxt 4 landing page with header, hero, fonts and SEO`.
</action>
<verify>
<automated>pnpm -C ../sm-summercms-app/vue-summercms-app install --frozen-lockfile && pnpm -C ../sm-summercms-app/vue-summercms-app run generate && pnpm -C ../sm-summercms-app/vue-summercms-app test</automated>
<automated>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</automated>
<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>
</verify>
<acceptance_criteria>
- `node -p "const p=require('/media/nvme/dev/golem15/summercms.io/summercms/sm-summercms-app/vue-summercms-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-summercms-app/vue-summercms-app/.output/public/index.html && test -f ../sm-summercms-app/vue-summercms-app/.output/public/sitemap.xml` succeeds (a regular file, not a directory).
- `ls ../sm-summercms-app/vue-summercms-app/.output/public/_fonts/*.woff2 | wc -l` prints at least 1.
- `grep -c "fonts.googleapis.com\|fonts.gstatic.com" ../sm-summercms-app/vue-summercms-app/.output/public/index.html` prints 0.
- `grep -n "ignore: \['/docs'\]" ../sm-summercms-app/vue-summercms-app/nuxt.config.ts` finds a match.
- `magick identify ../sm-summercms-app/vue-summercms-app/public/og-image.png` reports `1200x630`, and `magick identify ../sm-summercms-app/vue-summercms-app/assets-src/logo.png` reports `746x744`.
- `git -C ../sm-summercms-app/vue-summercms-app log --format='%(trailers:key=Co-authored-by,valueonly)' master` prints only empty lines.
- `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>
<done>A new vue-summercms-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.</done>
<done>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.</done>
</task>
<task type="auto">
<name>Task 2: Visitors read the Why, Features and From WinterCMS sections and the footer, with every docs, source and credit link from the handoff</name>
<files>../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/SiteFooter.vue, ../sm-summercms-app/vue-summercms-app/app/pages/index.vue, ../sm-summercms-app/vue-summercms-app/app/assets/css/base.css, ../sm-summercms-app/vue-summercms-app/i18n/locales/en.json, ../sm-summercms-app/vue-summercms-app/tests/output.test.ts</files>
<files>../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</files>
<read_first>
- ../sm-summercms-app/vue-summercms-app/i18n/locales/en.json, app/pages/index.vue, app/assets/css/base.css and tests/output.test.ts (as written in Task 1)
- ../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
@@ -227,24 +227,24 @@ Add the three content sections and the footer as components, all copy in `en.jso
Commit as `feat(site): add the Why, Features and From WinterCMS sections and the footer`.
</action>
<verify>
<automated>pnpm -C ../sm-summercms-app/vue-summercms-app run generate && pnpm -C ../sm-summercms-app/vue-summercms-app test</automated>
<automated>pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run generate && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app test</automated>
<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>
</verify>
<acceptance_criteria>
- `grep -o 'href="/docs/[a-z/-]*"' ../sm-summercms-app/vue-summercms-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-summercms-app/vue-summercms-app/.output/public/index.html` prints at least 1.
- `grep -c 'caller’s transaction' ../sm-summercms-app/vue-summercms-app/i18n/locales/en.json` prints 1.
- `grep -rn 'v-html' ../sm-summercms-app/vue-summercms-app/app` prints nothing.
- `grep -c '<i18n-t' ../sm-summercms-app/vue-summercms-app/app/components/WhySection.vue` prints at least 1.
- `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>
<done>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.</done>
</task>
<task type="auto" tdd="true">
<name>Task 3: Visitors copy six working commands from the Get started terminal card, and the header highlights the section they are reading</name>
<files>../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/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/app/components/SiteHeader.vue, ../sm-summercms-app/vue-summercms-app/app/pages/index.vue, ../sm-summercms-app/vue-summercms-app/i18n/locales/en.json, ../sm-summercms-app/vue-summercms-app/tests/terminal.test.ts, ../sm-summercms-app/vue-summercms-app/tests/scrollSpy.test.ts, ../sm-summercms-app/vue-summercms-app/tests/output.test.ts</files>
<files>../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</files>
<read_first>
- ../sm-summercms-app/vue-summercms-app/app/components/SiteHeader.vue, app/pages/index.vue, i18n/locales/en.json and tests/output.test.ts (as written in Tasks 1-2)
- ../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
@@ -270,19 +270,19 @@ Write the tests from the behavior block first (they fail), then implement until
Commit the tests and the implementation as `feat(site): add the Get started terminal card, copy button and scroll-spy`.
</action>
<verify>
<automated>pnpm -C ../sm-summercms-app/vue-summercms-app run generate && pnpm -C ../sm-summercms-app/vue-summercms-app test</automated>
<automated>pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run generate && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app test</automated>
<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>
<automated>node --test ../sm-summercms-app/vue-summercms-app/tests/terminal.test.ts ../sm-summercms-app/vue-summercms-app/tests/scrollSpy.test.ts</automated>
<automated>node --test ../sm-summercmsio-app/vue-summercmsio-app/tests/terminal.test.ts ../sm-summercmsio-app/vue-summercmsio-app/tests/scrollSpy.test.ts</automated>
<fails_when>non-zero exit, "# pass 0", or "# fail" with a count above 0</fails_when>
<human-check>Run `pnpm -C ../sm-summercms-app/vue-summercms-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.</human-check>
<human-check>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.</human-check>
</verify>
<acceptance_criteria>
- `node -e "const t=require('/media/nvme/dev/golem15/summercms.io/summercms/sm-summercms-app/vue-summercms-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-summercms-app/vue-summercms-app/.output/public/index.html` prints at least 1.
- `grep -c './bin/hello greeter:hello' ../sm-summercms-app/vue-summercms-app/.output/public/index.html` prints at least 1.
- `grep -n "@media (max-width: 720px)" ../sm-summercms-app/vue-summercms-app/app/components/SiteHeader.vue` finds a match.
- `grep -n 'passive: true' ../sm-summercms-app/vue-summercms-app/app/components/SiteHeader.vue` finds a match.
- `grep -n 'scrollSpy' ../sm-summercms-app/vue-summercms-app/app/components/SiteHeader.vue` and `grep -n 'showRays' ../sm-summercms-app/vue-summercms-app/app/components/HeroSection.vue` both find a match.
- `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>
<done>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.</done>
</task>
@@ -310,8 +310,8 @@ Commit the tests and the implementation as `feat(site): add the Get started term
</threat_model>
<verification>
- `pnpm -C ../sm-summercms-app/vue-summercms-app install --frozen-lockfile && pnpm -C ../sm-summercms-app/vue-summercms-app run generate && pnpm -C ../sm-summercms-app/vue-summercms-app test` green.
- `git -C ../sm-summercms-app/vue-summercms-app status --porcelain` prints nothing after the last commit.
- `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.
</verification>
@@ -322,7 +322,7 @@ Commit the tests and the implementation as `feat(site): add the Get started term
## Artifacts this phase produces
- Repository `vue-summercms-app` at `/media/nvme/dev/golem15/summercms.io/summercms/sm-summercms-app/vue-summercms-app` (branch `master`, no remote yet; D-43).
- 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`).

View File

@@ -8,7 +8,7 @@ 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"
- "vue-summercmsio-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)"
@@ -17,10 +17,10 @@ affects: [11.2-02 site plugin embed and link check, 11.2-02 D-40 terminal comman
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
commits: 4 # MEASURED in vue-summercmsio-app: git rev-list --count master (repository created by this plan)
plan_head_before: null # vue-summercmsio-app did not exist before this plan; summercms.go HEAD was 88f21af923e0f4efff6a72437977f636e48923c7 and gained no code commits
plan_head_after: 17e9f923559f6c2a9f176bdb5ae3aa78af934d9f # vue-summercmsio-app master
commits_repo: /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-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)"]
@@ -33,29 +33,29 @@ tech-stack:
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
- ../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/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/scripts/derive-images.sh
- ../sm-summercmsio-app/vue-summercmsio-app/assets-src/logo.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
modified: []
key-decisions:
@@ -158,7 +158,7 @@ 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`.**
**Nuxt 4 static landing page in the new `vue-summercmsio-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
@@ -166,11 +166,11 @@ status: complete
- **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)
- **Files created:** 35 tracked files in vue-summercmsio-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`.
- New repository `/media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-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.
@@ -181,7 +181,7 @@ status: complete
## Task Commits
All in `vue-summercms-app`:
All in `vue-summercmsio-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)
@@ -245,7 +245,7 @@ See `key-decisions` in the frontmatter. In short: the full page title with a `%s
## 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.
None. Creating the `vue-summercmsio-app` remote on git.golem15.com is a D-43 launch item listed for plan 11.2-02.
## Next Phase Readiness
@@ -256,8 +256,8 @@ None. Creating the `vue-summercms-app` remote on git.golem15.com is a D-43 launc
## 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`.
- Files: all 35 tracked files are present in `vue-summercmsio-app` (`git ls-files`), including every `key-files.created` entry.
- Commits: `c41cdac`, `a29b2d2`, `d2ffb9b` and `17e9f92` are in `vue-summercmsio-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.
---

View File

@@ -17,39 +17,39 @@ files_modified:
- cmd/summer/docs.go
- cmd/summer/docs_test.go
- docs/console/utilities.md
- ../sm-summercms-app/plugins/golem15/summercms/go.mod
- ../sm-summercms-app/plugins/golem15/summercms/go.sum
- ../sm-summercms-app/plugins/golem15/summercms/plugin.go
- ../sm-summercms-app/plugins/golem15/summercms/static.go
- ../sm-summercms-app/plugins/golem15/summercms/smoke_test.go
- ../sm-summercms-app/plugins/golem15/summercms/links_test.go
- ../sm-summercms-app/plugins/golem15/summercms/public/README.md
- ../sm-summercms-app/plugins/golem15/summercms/.gitignore
- ../sm-summercms-app/plugins/golem15/summercms/README.md
- ../sm-summercms-app/go.mod
- ../sm-summercms-app/go.sum
- ../sm-summercms-app/go.work
- ../sm-summercms-app/summer.yaml
- ../sm-summercms-app/main.go
- ../sm-summercms-app/plugins.gen.go
- ../sm-summercms-app/config/app.yaml
- ../sm-summercms-app/config/http.yaml
- ../sm-summercms-app/config/storage.yaml
- ../sm-summercms-app/config/queue.yaml
- ../sm-summercms-app/config/database.yaml
- ../sm-summercms-app/.gitignore
- ../sm-summercms-app/.gitmodules
- ../sm-summercms-app/scripts/build.sh
- ../sm-summercms-app/scripts/smoke.sh
- ../sm-summercms-app/scripts/check-deploy.sh
- ../sm-summercms-app/terminal_check_test.go
- ../sm-summercms-app/README.md
- ../sm-summercms-app/DEPLOY.md
- ../sm-summercms-app/deploy/nginx/summercms.io.conf
- ../sm-summercms-app/deploy/supervisor/summercms-io.conf
- ../sm-summercms-app/deploy/env.example
- ../sm-summercms-app/deploy/rollback/under-construction/index.html
- ../sm-summercms-app/deploy/rollback/under-construction/logo.png
- ../sm-summercmsio-app/plugins/golem15/summercms/go.mod
- ../sm-summercmsio-app/plugins/golem15/summercms/go.sum
- ../sm-summercmsio-app/plugins/golem15/summercms/plugin.go
- ../sm-summercmsio-app/plugins/golem15/summercms/static.go
- ../sm-summercmsio-app/plugins/golem15/summercms/smoke_test.go
- ../sm-summercmsio-app/plugins/golem15/summercms/links_test.go
- ../sm-summercmsio-app/plugins/golem15/summercms/public/README.md
- ../sm-summercmsio-app/plugins/golem15/summercms/.gitignore
- ../sm-summercmsio-app/plugins/golem15/summercms/README.md
- ../sm-summercmsio-app/go.mod
- ../sm-summercmsio-app/go.sum
- ../sm-summercmsio-app/go.work
- ../sm-summercmsio-app/summer.yaml
- ../sm-summercmsio-app/main.go
- ../sm-summercmsio-app/plugins.gen.go
- ../sm-summercmsio-app/config/app.yaml
- ../sm-summercmsio-app/config/http.yaml
- ../sm-summercmsio-app/config/storage.yaml
- ../sm-summercmsio-app/config/queue.yaml
- ../sm-summercmsio-app/config/database.yaml
- ../sm-summercmsio-app/.gitignore
- ../sm-summercmsio-app/.gitmodules
- ../sm-summercmsio-app/scripts/build.sh
- ../sm-summercmsio-app/scripts/smoke.sh
- ../sm-summercmsio-app/scripts/check-deploy.sh
- ../sm-summercmsio-app/terminal_check_test.go
- ../sm-summercmsio-app/README.md
- ../sm-summercmsio-app/DEPLOY.md
- ../sm-summercmsio-app/deploy/nginx/summercms.io.conf
- ../sm-summercmsio-app/deploy/supervisor/summercms-io.conf
- ../sm-summercmsio-app/deploy/env.example
- ../sm-summercmsio-app/deploy/rollback/under-construction/index.html
- ../sm-summercmsio-app/deploy/rollback/under-construction/logo.png
autonomous: false
requirements: []
assumption_delta_decision: no-change
@@ -64,7 +64,7 @@ estimate:
must_haves:
truths:
- "Per D-01, D-02 and D-43, `sm-summercms-app` (module `git.golem15.com/golem15/sm-summercms-app`) is a git repository next to summercms.go, holding `vue-summercms-app` and `plugins/golem15/summercms` (module `git.golem15.com/golem15/sm-summercms-plugin`, plugin ID `golem15.summercms`) as submodules whose URLs are `git@git.golem15.com:golem15/<repo>.git`; creating those remotes is a listed user step."
- "Per D-01, D-02 and D-43, `sm-summercmsio-app` (module `git.golem15.com/golem15/sm-summercmsio-app`) is a git repository next to summercms.go, holding `vue-summercmsio-app` and `plugins/golem15/summercms` (module `git.golem15.com/golem15/sm-summercmsio-plugin`, plugin ID `golem15.summercms`) as submodules whose URLs are `git@git.golem15.com:golem15/<repo>.git`; creating those remotes is a listed user step."
- "Per D-03, the app is a go.work workspace (`use . ./plugins/golem15/summercms`) that requires the framework with `replace git.golem15.com/golem15/summercms => ../summercms.go`, the plugin requires it with `=> ../../../../summercms.go`, and `go list ./...` in the app lists only the app's own package."
- "Per D-05 and SC2, one `summercms-io` binary embeds the Nuxt output and the docs build through `//go:embed all:public` in the site plugin, and `scripts/build.sh` runs nuxt generate, then `summer docs:build --base-url /docs --site-url / --site-label summercms.io`, then the plugin tests, then `summer build` and a linux/amd64 `go build`."
- "Per D-07 and SC2, responses for `/` and `/docs/` carry no robots-blocking header and no Content-Security-Policy; `/_nuxt/*` (except `/_nuxt/builds/*`) and `/_fonts/*` get `public, max-age=31536000, immutable`; every other file, including `/docs/assets/*`, gets `no-cache` with a strong ETag, and a matching If-None-Match returns 304."
@@ -74,36 +74,36 @@ must_haves:
- "Per D-41 and D-46, `docs/site.yaml` accepts optional `site_url` and `site_label`, `summer docs:build` and `summer docs:serve` accept `--site-url` and `--site-label` overrides, the docs header shows a link back to the main site only when a site URL is set (label: explicit, else the URL host, else `Home`), the framework's own docs output is byte-identical when nothing is set, and `docs/console/utilities.md` documents both keys and flags with a neutral example."
- "Per D-42, the framework is tagged `v0.1.0` only after the user confirms at a blocking-human checkpoint, at a commit where the D-25 and D-41/D-46 changes are green; the app and the plugin require `git.golem15.com/golem15/summercms v0.1.0`, and the release build takes the docs and the compiled framework from the tag export."
- "Per SC3 and D-44, `TestLandingLinks` resolves every href and src in the built index.html through the real assembled handler (one 301 followed, final 200), checks every in-page anchor, and requires all ten handoff `/docs/...` targets plus `/docs`; external links are checked anonymously by `TestExternalLinks` at cutover."
- "Per D-40, `TestTerminalCommands` runs the six commands from `vue-summercms-app/app/data/terminal.json` in a temp directory with a temp GOBIN first on PATH and no SUMMER_* or GOWORK variables, and sees `handled=true`; only the clone URL may be overridden (SUMMERCMS_CLONE_URL) until D-38 makes the repository public, and a drift test proves every command and comment appears in the built page."
- "Per D-40, `TestTerminalCommands` runs the six commands from `vue-summercmsio-app/app/data/terminal.json` in a temp directory with a temp GOBIN first on PATH and no SUMMER_* or GOWORK variables, and sees `handled=true`; only the clone URL may be overridden (SUMMERCMS_CLONE_URL) until D-38 makes the repository public, and a drift test proves every command and comment appears in the built page."
- "Per SC4 and D-27 to D-32, DEPLOY.md and the committed nginx and supervisor configs cover the launch checklist, one-time server setup (system user, Postgres role `summercms` owning `summercms_io`, a 0600 `.env`), local build, rsync upload, migrate before restart, nginx with certbot TLS, HTTP to HTTPS and www to apex redirects, gzip, proxy headers and denied admin paths, the supervisor program on 127.0.0.1:8095, verification and rollback; `scripts/check-deploy.sh` passes `nginx -t`."
- statement: "Following DEPLOY.md on rome brings the site up at https://summercms.io (manual, at cutover)."
verification: backstop
artifacts:
- path: "../sm-summercms-app/plugins/golem15/summercms/plugin.go"
- path: "../sm-summercmsio-app/plugins/golem15/summercms/plugin.go"
provides: "site plugin: ID, embed, routes for /, /docs and /docs/{path...}"
contains: "go:embed all:public"
- path: "../sm-summercms-app/plugins/golem15/summercms/static.go"
- path: "../sm-summercmsio-app/plugins/golem15/summercms/static.go"
provides: "embedded static handler: resolution, MIME table, cache rules, ETag, 301s, 404s"
contains: "http.ServeContent"
- path: "../sm-summercms-app/plugins/golem15/summercms/links_test.go"
- path: "../sm-summercmsio-app/plugins/golem15/summercms/links_test.go"
provides: "SC3 link check, D-40 drift guard, D-46 header check, external link check"
contains: "TestLandingLinks"
- path: "../sm-summercms-app/scripts/build.sh"
- path: "../sm-summercmsio-app/scripts/build.sh"
provides: "D-05/D-28 scripted build, release from the tag and dev"
contains: "docs:build --base-url /docs --site-url / --site-label summercms.io"
- path: "../sm-summercms-app/scripts/smoke.sh"
- path: "../sm-summercmsio-app/scripts/smoke.sh"
provides: "boot on postgres:15 and HTTP assertions"
contains: "postgres:15"
- path: "../sm-summercms-app/terminal_check_test.go"
- path: "../sm-summercmsio-app/terminal_check_test.go"
provides: "D-40 verbatim terminal command check"
contains: "TestTerminalCommands"
- path: "../sm-summercms-app/DEPLOY.md"
- path: "../sm-summercmsio-app/DEPLOY.md"
provides: "SC4 deploy, launch checklist and rollback"
contains: "supervisorctl restart summercms-io"
- path: "../sm-summercms-app/deploy/nginx/summercms.io.conf"
- path: "../sm-summercmsio-app/deploy/nginx/summercms.io.conf"
provides: "nginx server blocks"
contains: "proxy_pass http://127.0.0.1:8095"
- path: "../sm-summercms-app/deploy/supervisor/summercms-io.conf"
- path: "../sm-summercmsio-app/deploy/supervisor/summercms-io.conf"
provides: "supervisor program"
contains: "user=summercms"
- path: "internal/docsite/load.go"
@@ -116,19 +116,19 @@ must_haves:
provides: "--site-url and --site-label on docs:build and docs:serve"
contains: "site-label"
key_links:
- from: "../sm-summercms-app/plugins.gen.go"
to: "../sm-summercms-app/plugins/golem15/summercms/plugin.go"
- from: "../sm-summercmsio-app/plugins.gen.go"
to: "../sm-summercmsio-app/plugins/golem15/summercms/plugin.go"
via: "generated blank import registers the plugin through its init"
pattern: "sm-summercms-plugin"
- from: "../sm-summercms-app/plugins/golem15/summercms/plugin.go"
pattern: "sm-summercmsio-plugin"
- from: "../sm-summercmsio-app/plugins/golem15/summercms/plugin.go"
to: "modules/surf router"
via: "Routes registers a raw group with GET /docs, GET /docs/{path...} and GET /"
pattern: "GroupRaw\\("
- from: "../sm-summercms-app/scripts/build.sh"
to: "../sm-summercms-app/plugins/golem15/summercms/public"
- from: "../sm-summercmsio-app/scripts/build.sh"
to: "../sm-summercmsio-app/plugins/golem15/summercms/public"
via: "rsync of .output/public into public/site and docs:build --out public/docs before go build"
pattern: "public/(site|docs)"
- from: "../sm-summercms-app/scripts/build.sh"
- from: "../sm-summercmsio-app/scripts/build.sh"
to: "summercms.go tag v0.1.0"
via: "git archive of the required tag feeds docs:build and a temporary go.work replace"
pattern: "git -C \"\\$FW\" archive"
@@ -136,18 +136,18 @@ must_haves:
to: "internal/docsite/docsite.go"
via: "docsOptions copies --site-url and --site-label into Options"
pattern: "SiteURL"
- from: "../sm-summercms-app/terminal_check_test.go"
to: "../sm-summercms-app/vue-summercms-app/app/data/terminal.json"
- from: "../sm-summercmsio-app/terminal_check_test.go"
to: "../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json"
via: "reads the single command list the page renders"
pattern: "app/data/terminal\\.json"
---
<objective>
Make summercms.io shippable: the small framework changes the site needs (D-25 PostgreSQL 15, D-41/D-46 docs link back to the site), the `v0.1.0` tag behind a user confirmation (D-42), the `sm-summercms-plugin` site plugin that serves the embedded Nuxt build at `/` and the docs at `/docs` with indexable responses and correct cache headers (D-01, D-05, D-07, D-47), the `sm-summercms-app` root app with its workspace, config, build and smoke scripts (D-02, D-03, D-24, D-28), the link and terminal-command checks (SC3, D-40, D-44), and DEPLOY.md with nginx, supervisor and rollback (SC4, D-27 to D-32, D-38, D-43).
Make summercms.io shippable: the small framework changes the site needs (D-25 PostgreSQL 15, D-41/D-46 docs link back to the site), the `v0.1.0` tag behind a user confirmation (D-42), the `sm-summercmsio-plugin` site plugin that serves the embedded Nuxt build at `/` and the docs at `/docs` with indexable responses and correct cache headers (D-01, D-05, D-07, D-47), the `sm-summercmsio-app` root app with its workspace, config, build and smoke scripts (D-02, D-03, D-24, D-28), the link and terminal-command checks (SC3, D-40, D-44), and DEPLOY.md with nginx, supervisor and rollback (SC4, D-27 to D-32, D-38, D-43).
Purpose: SC2, SC3 and SC4. Plan 11.2-03 adds the full unit-test coverage on top of the interfaces fixed here.
Output: framework commits in summercms.go (two, then the tag), and two new repositories, `sm-summercms-app` and its submodule `sm-summercms-plugin` (the plan 11.2-01 repository becomes the second submodule).
Output: framework commits in summercms.go (two, then the tag), and two new repositories, `sm-summercmsio-app` and its submodule `sm-summercmsio-plugin` (the plan 11.2-01 repository becomes the second submodule).
Task count: five tasks including the tag checkpoint, above the usual three, because the user fixed this phase at three plans (CLAUDE.md lean rule) and placed all of this scope in plan 02.
</objective>
@@ -176,14 +176,14 @@ Task count: five tasks including the tag checkpoint, above the usual three, beca
@../fonoteka.go/go.work
@../fonoteka.go/summer.yaml
**Paths.** Commands run from the summercms.go root (`FW`). `APP` = `../sm-summercms-app` (absolute `/media/nvme/dev/golem15/summercms.io/summercms/sm-summercms-app`), `PLUG` = `APP/plugins/golem15/summercms`, `SITE` = `APP/vue-summercms-app` (created by plan 11.2-01; this plan only reads it). Each task names the repository it commits to. Never `git add` in the meta repository `summercms/`.
**Paths.** Commands run from the summercms.go root (`FW`). `APP` = `../sm-summercmsio-app` (absolute `/media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app`), `PLUG` = `APP/plugins/golem15/summercms`, `SITE` = `APP/vue-summercmsio-app` (created by plan 11.2-01; this plan only reads it). Each task names the repository it commits to. Never `git add` in the meta repository `summercms/`.
**Commits.** One logical change per commit, conventional messages, never a co-author tag. In summercms.go, stage only the files a task lists (never `git add -A`); planning docs are committed separately by the orchestrator.
**Tools.** A `summer` binary for the app is installed into a temporary GOBIN, for example `GOBIN=$(mktemp -d) go -C <framework tree> install ./cmd/summer`; never rely on a `summer` already on PATH.
<interfaces>
Site plugin, package `summercms`, module `git.golem15.com/golem15/sm-summercms-plugin` (plan 11.2-03 tests these names; keep them stable):
Site plugin, package `summercms`, module `git.golem15.com/golem15/sm-summercmsio-plugin` (plan 11.2-03 tests these names; keep them stable):
- `//go:embed all:public` into `var publicFS embed.FS` (the `all:` prefix is required: Nuxt writes `_nuxt/`, `_fonts/`, `_i18n/` and `_payload.json`).
- `type Plugin struct { fsys fs.FS }`; a nil `fsys` means `fs.Sub(publicFS, "public")`. Methods `ID() string` (returns the literal `"golem15.summercms"`), `Requires() []string` (nil), `Register(*backpack.App) error`, `Boot(*backpack.App) error`, `Routes(r pact.Router) error`. Compile-time assertion `var _ pact.HasRoutes = (*Plugin)(nil)`. `func init() { party.Register(&Plugin{}) }`.
@@ -205,7 +205,7 @@ Framework, package `internal/docsite` and `cmd/summer`:
- Unexported `site` fields `siteURL`, `siteLabel`; `pageView.SiteURL`, `pageView.SiteLabel` filled in `baseView` (so the 404 page has the link too), never passed through `s.url()`.
- CLI flags `site-url` and `site-label` on `docs:build` and `docs:serve`, read in `docsOptions`.
App, package `main` in `sm-summercms-app` (test file only, never shipped in the binary):
App, package `main` in `sm-summercmsio-app` (test file only, never shipped in the binary):
- `type terminalGroup struct { Comment string \`json:"comment"\`; Commands []string \`json:"commands"\` }`, `func loadTerminal(path string) ([]terminalGroup, error)`, `const terminalCloneURL = "https://git.golem15.com/golem15/summercms"`, `func terminalScript(groups []terminalGroup, cloneURL string) string` (commands joined by `\n`; a non-empty cloneURL replaces only `terminalCloneURL` inside the `git clone` command), `func terminalEnv(base []string, gobin string) []string` (drops every `SUMMER_*` and `GOWORK` entry, sets `GOBIN=gobin`, prefixes `PATH` with gobin).
</interfaces>
@@ -219,9 +219,9 @@ Detector signal: pluralization ("a SummerCMS binary that also serves the Phase 1
<task type="tracer">
<name>Task 1: Tracer: one `summercms-io` binary, booted on PostgreSQL 15, serves the Nuxt site at / and the docs at /docs</name>
<precondition>`docker info` exits 0 and `docker image inspect postgres:15` succeeds; `test -f ../sm-summercms-app/vue-summercms-app/app/data/terminal.json` succeeds (plan 11.2-01 is complete).</precondition>
<precondition>`docker info` exits 0 and `docker image inspect postgres:15` succeeds; `test -f ../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json` succeeds (plan 11.2-01 is complete).</precondition>
<reversibility rating="costly">D-02/D-01 module paths and repo names are referenced by go.mod, go.work, summer.yaml, the generated imports and the submodule URLs; nothing is published in this task (remotes are created at cutover), so a rename now touches many files but needs no migration. They become one-way when pushed at cutover, a step the user already chose in CONTEXT.</reversibility>
<files>../sm-summercms-app/plugins/golem15/summercms/go.mod, ../sm-summercms-app/plugins/golem15/summercms/go.sum, ../sm-summercms-app/plugins/golem15/summercms/plugin.go, ../sm-summercms-app/plugins/golem15/summercms/static.go, ../sm-summercms-app/plugins/golem15/summercms/smoke_test.go, ../sm-summercms-app/plugins/golem15/summercms/public/README.md, ../sm-summercms-app/plugins/golem15/summercms/.gitignore, ../sm-summercms-app/go.mod, ../sm-summercms-app/go.sum, ../sm-summercms-app/go.work, ../sm-summercms-app/summer.yaml, ../sm-summercms-app/main.go, ../sm-summercms-app/plugins.gen.go, ../sm-summercms-app/config/app.yaml, ../sm-summercms-app/config/http.yaml, ../sm-summercms-app/config/storage.yaml, ../sm-summercms-app/config/queue.yaml, ../sm-summercms-app/config/database.yaml, ../sm-summercms-app/.gitignore, ../sm-summercms-app/.gitmodules, ../sm-summercms-app/scripts/build.sh, ../sm-summercms-app/scripts/smoke.sh</files>
<files>../sm-summercmsio-app/plugins/golem15/summercms/go.mod, ../sm-summercmsio-app/plugins/golem15/summercms/go.sum, ../sm-summercmsio-app/plugins/golem15/summercms/plugin.go, ../sm-summercmsio-app/plugins/golem15/summercms/static.go, ../sm-summercmsio-app/plugins/golem15/summercms/smoke_test.go, ../sm-summercmsio-app/plugins/golem15/summercms/public/README.md, ../sm-summercmsio-app/plugins/golem15/summercms/.gitignore, ../sm-summercmsio-app/go.mod, ../sm-summercmsio-app/go.sum, ../sm-summercmsio-app/go.work, ../sm-summercmsio-app/summer.yaml, ../sm-summercmsio-app/main.go, ../sm-summercmsio-app/plugins.gen.go, ../sm-summercmsio-app/config/app.yaml, ../sm-summercmsio-app/config/http.yaml, ../sm-summercmsio-app/config/storage.yaml, ../sm-summercmsio-app/config/queue.yaml, ../sm-summercmsio-app/config/database.yaml, ../sm-summercmsio-app/.gitignore, ../sm-summercmsio-app/.gitmodules, ../sm-summercmsio-app/scripts/build.sh, ../sm-summercmsio-app/scripts/smoke.sh</files>
<read_first>
- modules/boardwalk/boardwalk.go lines 24-48 and 132-173 (embed, fs.Sub, content-type table, cache by prefix; the security headers and SPA fallback there are NOT to be copied, D-07)
- internal/docsite/serve.go lines 236-290 (public static semantics: dot-segment refusal, dir index.html, 404.html with status 404)
@@ -236,31 +236,31 @@ Detector signal: pluralization ("a SummerCMS binary that also serves the Phase 1
<action>
Wire one request path through every layer: embedded files → static handler → surf raw group → generated app binary → stock `serve` on Postgres 15 → HTTP.
1. Plugin repository (writes to sm-summercms-plugin): `mkdir -p` PLUG and `git init -b master` inside it. `go.mod`: `module git.golem15.com/golem15/sm-summercms-plugin`, `go 1.27.0`, `require git.golem15.com/golem15/summercms v0.1.0` (D-42; the directory replace resolves it before the tag exists), `replace git.golem15.com/golem15/summercms => ../../../../summercms.go` (same depth as fonoteka's plugins). `.gitignore`: `/public/site/` and `/public/docs/`. `public/README.md`: build.sh fills `site/` (Nuxt output) and `docs/` (docs build); nothing else may be placed in `public/docs`, because docs:build refuses to clean a directory without its marker file; the file exists so the embed pattern always matches.
1. Plugin repository (writes to sm-summercmsio-plugin): `mkdir -p` PLUG and `git init -b master` inside it. `go.mod`: `module git.golem15.com/golem15/sm-summercmsio-plugin`, `go 1.27.0`, `require git.golem15.com/golem15/summercms v0.1.0` (D-42; the directory replace resolves it before the tag exists), `replace git.golem15.com/golem15/summercms => ../../../../summercms.go` (same depth as fonoteka's plugins). `.gitignore`: `/public/site/` and `/public/docs/`. `public/README.md`: build.sh fills `site/` (Nuxt output) and `docs/` (docs build); nothing else may be placed in `public/docs`, because docs:build refuses to clean a directory without its marker file; the file exists so the embed pattern always matches.
2. `plugin.go` exactly as the interfaces block defines it. The plugin implements only the party lifecycle and `pact.HasRoutes`: it must not implement the pact admin-controllers capability or any other capability, so cabana never activates and no `/backend` route exists (D-29, T-11.2-07). `Routes` builds both handlers via `newHandlers` (returning its error, so `serve` fails closed with `summercms: public/site/index.html missing; run scripts/build.sh` on an unbuilt tree) and registers, inside `r.GroupRaw("", nil, …)`: `GET /docs` → `redirectTo("/docs/")` (a permanent 301 instead of ServeMux's 307), `GET /docs/{path...}` → docs tree, `GET /` → site tree (the least specific pattern, verified conflict-free alongside cabana's patterns).
3. `static.go` exactly as the interfaces block defines it (D-07, D-47; Pitfalls 8-11). Copy the ideas of the admin SPA embed handler (fs.FS, own content-type table, cache by path, ServeContent) and of the docsite preview handler (dot refusal, dir index, 404 page), but add no robots-blocking header, no CSP, no frame-deny header, no index-token rewrite and no SPA fallback: the public site wants indexable pages and real 404s. Build the 301 Location only from `t.mount` plus the cleaned name plus `.html`, and only when that file is in the map (T-11.2-05). Stdlib only.
4. `smoke_test.go` (smoke level; coverage is plan 11.2-03): `TestStaticSmoke` builds handlers from a `fstest.MapFS` with `site/index.html`, `site/404.html`, `docs/index.html`, `docs/404.html`, `docs/setup/installation.html` and asserts `GET /` 200 `text/html; charset=utf-8`, docs `GET /docs/setup/installation` 301 to `/docs/setup/installation.html`, and `GET /nope` 404 with the 404 body. Run `go -C PLUG mod tidy`, then commit in PLUG as `feat: serve the embedded site at / and the docs at /docs`.
5. App repository (writes to sm-summercms-app): `git init -b master` in APP. Register the two existing repositories as submodules without cloning (git reports "Adding existing repo"): `git submodule add git@git.golem15.com:golem15/vue-summercms-app.git vue-summercms-app` and `git submodule add git@git.golem15.com:golem15/sm-summercms-plugin.git plugins/golem15/summercms` (D-43; the remotes are created by the user later, see DEPLOY.md). `.gitignore`: `/bin/`, `/tmp/`, `*.exe`, `go.work.sum`, `.env`, `/storage/`.
6. `go.mod`: `module git.golem15.com/golem15/sm-summercms-app`, `go 1.27.0`, `toolchain go1.27.0`, `replace git.golem15.com/golem15/summercms => ../summercms.go`, `require git.golem15.com/golem15/summercms v0.1.0`. `summer.yaml`: `module: git.golem15.com/golem15/sm-summercms-app`, `binary: summercms-io`, `plugins:` with `id: golem15.summercms`, `module: git.golem15.com/golem15/sm-summercms-plugin`. Then, with a summer binary installed from summercms.go into a temp GOBIN, run `summer plugin:add plugins/golem15/summercms` in APP (it adds the plugin require and `replace … => ./plugins/golem15/summercms`, and writes `go.work` with `use ( . ./plugins/golem15/summercms )`; confirm `go 1.27.0` and `toolchain go1.27.0` as in fonoteka.go/go.work, and that go.work does not `use` the framework), then `go -C APP mod tidy`.
5. App repository (writes to sm-summercmsio-app): `git init -b master` in APP. Register the two existing repositories as submodules without cloning (git reports "Adding existing repo"): `git submodule add git@git.golem15.com:golem15/vue-summercmsio-app.git vue-summercmsio-app` and `git submodule add git@git.golem15.com:golem15/sm-summercmsio-plugin.git plugins/golem15/summercms` (D-43; the remotes are created by the user later, see DEPLOY.md). `.gitignore`: `/bin/`, `/tmp/`, `*.exe`, `go.work.sum`, `.env`, `/storage/`.
6. `go.mod`: `module git.golem15.com/golem15/sm-summercmsio-app`, `go 1.27.0`, `toolchain go1.27.0`, `replace git.golem15.com/golem15/summercms => ../summercms.go`, `require git.golem15.com/golem15/summercms v0.1.0`. `summer.yaml`: `module: git.golem15.com/golem15/sm-summercmsio-app`, `binary: summercms-io`, `plugins:` with `id: golem15.summercms`, `module: git.golem15.com/golem15/sm-summercmsio-plugin`. Then, with a summer binary installed from summercms.go into a temp GOBIN, run `summer plugin:add plugins/golem15/summercms` in APP (it adds the plugin require and `replace … => ./plugins/golem15/summercms`, and writes `go.work` with `use ( . ./plugins/golem15/summercms )`; confirm `go 1.27.0` and `toolchain go1.27.0` as in fonoteka.go/go.work, and that go.work does not `use` the framework), then `go -C APP mod tidy`.
7. `config/` (no secrets; Pitfall 12, D-24, D-27): `app.yaml` (`name: summercms-io`, `debug: false`, `locale: en`, `fallback_locale: en`, `key: ""` with the comment "Set SUMMER_APP__KEY to a 32-byte base64 value (key:generate)"); `http.yaml` (`body_limits.default_bytes: 1048576`, `body_limits.upload_bytes: 1048576`, `trusted_proxies` holding `127.0.0.1/32` because nginx proxies on loopback, using the CIDR list format clientip.go reads); `storage.yaml` (`uploads.bucket_url: "file://./storage/app/uploads"`, `uploads.public_path_prefix: "/storage/uploads"`); `queue.yaml` (`work_in_serve: false` with a comment that the site has no jobs, plus fonoteka's `max_attempts`, `job_timeout` and `queues.default: 1`); `database.yaml` (`dsn: ""` with the comment "Set SUMMER_DATABASE__DSN").
8. `scripts/build.sh` (bash, `set -euo pipefail`, `ROOT` from `BASH_SOURCE` as in fonoteka.go/scripts/check-openapi.sh, explicit guards that print `build: …` to stderr and exit 1). This task implements the `dev` mode; Task 4 adds `release`, which needs the tag, so until then any argument other than `dev` exits 2 with a usage line. Steps: check `pnpm`, `go`, `rsync`, `git`, `tar` exist; `FW="${SUMMERCMS_FRAMEWORK:-$ROOT/../summercms.go}"`; (a) in SITE `pnpm install --frozen-lockfile` and `pnpm run generate`, require `.output/public/index.html`, then `rsync -a --delete "$SITE/.output/public/" "$PLUG/public/site/"` (never the `dist` symlink, which embed refuses); (b) `git -C "$FW" archive HEAD | tar -x -C "$TMP/fw"` (TMP from mktemp with an EXIT trap), install summer from the export into `$TMP/bin`, run `docs:build --base-url /docs --out "$PLUG/public/docs"` inside the export, require `public/docs/index.html` and `public/docs/.summer-docs`; (c) `SUMMERCMS_REQUIRE_BUILD=1 go -C "$PLUG" test ./...`; (d) in APP run `"$TMP/bin/summer" build` (writes main.go and plugins.gen.go), then `CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go -C "$ROOT" build -trimpath -o bin/summercms-io .`; print `build: bin/summercms-io (dev) ready`.
9. `scripts/smoke.sh` (bash, `set -euo pipefail`, trap cleanup of the container, the serve process and the temp dir; binary path argument defaulting to `bin/summercms-io`): start `postgres:15` with `docker run -d --rm` (user `summercms`, password `smoke`, database `summercms_io`, port `127.0.0.1::5432`, read back with `docker port`), wait for `pg_isready`; copy `config/` into a temp work dir and write a `.env` there with `SUMMER_DATABASE__DSN` and `SUMMER_APP__KEY` (32 random bytes, base64); from the work dir run the binary's `migrate`, then `serve --addr 127.0.0.1:${SMOKE_PORT:-18095}` in the background and wait for it. Assert with curl: `/` 200, `Content-Type` starting `text/html`, `Cache-Control: no-cache`, an `ETag`, and neither an `X-Robots-Tag` nor a `Content-Security-Policy` header; the same `/` with `If-None-Match: <etag>` is 304; `/docs` 301 with `Location: /docs/`; `/docs/` 200 and no `X-Robots-Tag`; `/docs/setup/installation` 301 to `/docs/setup/installation.html`, which is 200; the first `/_nuxt/*.js` referenced by index.html has `immutable` and `text/javascript`; the first file under `public/site/_fonts/` is `font/woff2` and `immutable`; `/missing-page` 404; `/backend` 404; `/docs/.summer-docs` 404; `POST /` 405. Print `smoke: ok`.
10. Run `scripts/build.sh dev` and `scripts/smoke.sh`, then commit in APP (including `.gitmodules` and both gitlinks) as `feat: wire the summercms.io app with build and smoke scripts`.
</action>
<verify>
<automated>../sm-summercms-app/scripts/build.sh dev && ../sm-summercms-app/scripts/smoke.sh</automated>
<automated>../sm-summercmsio-app/scripts/build.sh dev && ../sm-summercmsio-app/scripts/smoke.sh</automated>
<fails_when>non-zero exit, a line starting "build:" on stderr, or no "smoke: ok" line</fails_when>
<automated>go -C ../sm-summercms-app vet ./... && go -C ../sm-summercms-app/plugins/golem15/summercms vet ./... && go -C ../sm-summercms-app/plugins/golem15/summercms test ./... -run '^TestStaticSmoke$' -count=1 -v</automated>
<automated>go -C ../sm-summercmsio-app vet ./... && go -C ../sm-summercmsio-app/plugins/golem15/summercms vet ./... && go -C ../sm-summercmsio-app/plugins/golem15/summercms test ./... -run '^TestStaticSmoke$' -count=1 -v</automated>
<fails_when>non-zero exit, a "--- FAIL" line, or no "--- PASS: TestStaticSmoke" line</fails_when>
</verify>
<acceptance_criteria>
- `go -C ../sm-summercms-app list ./...` prints exactly `git.golem15.com/golem15/sm-summercms-app` (pnpm's dot-directory layout keeps node_modules out of the module).
- `git -C ../sm-summercms-app submodule status` lists `plugins/golem15/summercms` and `vue-summercms-app`, and `git -C ../sm-summercms-app config -f .gitmodules --get-regexp url` prints `git@git.golem15.com:golem15/sm-summercms-plugin.git` and `git@git.golem15.com:golem15/vue-summercms-app.git`.
- `grep -n 'go:embed all:public' ../sm-summercms-app/plugins/golem15/summercms/plugin.go` and `grep -n 'return "golem15.summercms"' ../sm-summercms-app/plugins/golem15/summercms/plugin.go` both find a match.
- `grep -c 'HasAdminControllers' ../sm-summercms-app/plugins/golem15/summercms/plugin.go` prints 0.
- Every import path printed by `go -C ../sm-summercms-app/plugins/golem15/summercms list -f '{{join .Imports "\n"}}' .` is either a standard-library path or starts with `git.golem15.com/golem15/summercms/modules/` (no new third-party dependency).
- `grep -n 'work_in_serve: false' ../sm-summercms-app/config/queue.yaml` finds a match, and `grep -n 'key: ""' ../sm-summercms-app/config/app.yaml` finds a match.
- `file ../sm-summercms-app/bin/summercms-io` reports an ELF 64-bit x86-64 executable.
- `go -C ../sm-summercmsio-app list ./...` prints exactly `git.golem15.com/golem15/sm-summercmsio-app` (pnpm's dot-directory layout keeps node_modules out of the module).
- `git -C ../sm-summercmsio-app submodule status` lists `plugins/golem15/summercms` and `vue-summercmsio-app`, and `git -C ../sm-summercmsio-app config -f .gitmodules --get-regexp url` prints `git@git.golem15.com:golem15/sm-summercmsio-plugin.git` and `git@git.golem15.com:golem15/vue-summercmsio-app.git`.
- `grep -n 'go:embed all:public' ../sm-summercmsio-app/plugins/golem15/summercms/plugin.go` and `grep -n 'return "golem15.summercms"' ../sm-summercmsio-app/plugins/golem15/summercms/plugin.go` both find a match.
- `grep -c 'HasAdminControllers' ../sm-summercmsio-app/plugins/golem15/summercms/plugin.go` prints 0.
- Every import path printed by `go -C ../sm-summercmsio-app/plugins/golem15/summercms list -f '{{join .Imports "\n"}}' .` is either a standard-library path or starts with `git.golem15.com/golem15/summercms/modules/` (no new third-party dependency).
- `grep -n 'work_in_serve: false' ../sm-summercmsio-app/config/queue.yaml` finds a match, and `grep -n 'key: ""' ../sm-summercmsio-app/config/app.yaml` finds a match.
- `file ../sm-summercmsio-app/bin/summercms-io` reports an ELF 64-bit x86-64 executable.
</acceptance_criteria>
<done>The app and plugin repositories exist with the submodule layout, the binary embeds both trees and boots on postgres:15 through the stock serve command, and the smoke script proves status codes, content types, cache headers, 301s, 404s and the absence of robots-blocking and CSP headers.</done>
</task>
@@ -338,11 +338,11 @@ Commit as `feat(docsite): optional site_url and site_label link back to the main
<name>Task 4: Release build from the v0.1.0 tag, with every landing link and every terminal command proven against the built site</name>
<precondition>Task 3 returned "tag-now" or "defer-tag". For "tag-now": `git ls-remote origin` in summercms.go succeeds (SSH access to git.golem15.com). For "defer-tag": `SUMMERCMS_FRAMEWORK` is exported to the scratch clone (step 1) before any verify command below runs, so the same commands exercise the release path.</precondition>
<reversibility rating="one-way">Pushing v0.1.0 publishes a module version that proxies cache forever (D-42); done only after the Task 3 confirmation.</reversibility>
<files>../sm-summercms-app/scripts/build.sh, ../sm-summercms-app/scripts/smoke.sh, ../sm-summercms-app/terminal_check_test.go, ../sm-summercms-app/plugins/golem15/summercms/links_test.go</files>
<files>../sm-summercmsio-app/scripts/build.sh, ../sm-summercmsio-app/scripts/smoke.sh, ../sm-summercmsio-app/terminal_check_test.go, ../sm-summercmsio-app/plugins/golem15/summercms/links_test.go</files>
<read_first>
- ../sm-summercms-app/scripts/build.sh and scripts/smoke.sh (as written in Task 1)
- ../sm-summercms-app/plugins/golem15/summercms/plugin.go and static.go (as written in Task 1)
- ../sm-summercms-app/vue-summercms-app/app/data/terminal.json and i18n/locales/en.json (the command list and comment texts)
- ../sm-summercmsio-app/scripts/build.sh and scripts/smoke.sh (as written in Task 1)
- ../sm-summercmsio-app/plugins/golem15/summercms/plugin.go and static.go (as written in Task 1)
- ../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json and i18n/locales/en.json (the command list and comment texts)
- modules/surf/example_test.go lines 114-140 (surf.Assemble with backpack.New(nil))
- modules/bonfire/prompts.go lines 25-35 (non-interactive Confirm returns the default)
- .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-RESEARCH.md "Pattern 3", "Every link on the page", "D-40: does ./bin/hello greeter:hello need a database? No.", "Build script outline" (the temporary go.work note)
@@ -353,39 +353,39 @@ Commit as `feat(docsite): optional site_url and site_label link back to the main
3. `scripts/smoke.sh`: add the D-46 assertion that `/docs/` contains `class="site-link" href="/"` and the text `summercms.io`.
4. `links_test.go` in PLUG (package `summercms`, stdlib only) with the helpers from the interfaces block:
- `TestLandingLinks` (`requireBuild`): assemble the real plugin with `surf.Assemble(backpack.New(nil), []party.Plugin{&Plugin{}})`; collect every `href` and `src` value from `public/site/index.html`, plus the `og:image` content; for `#id` require `id="id"` in the page; for a root-relative path, or an absolute `https://summercms.io/` URL mapped to its path, request it through the handler, follow at most one 301 whose Location must start with `/`, and require a final 200; collect other `http(s)` URLs for the external test. Require that the hrefs include `/docs` and the ten D-44 targets: `/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`, `/docs/setup/coming-from-wintercms`, `/docs/setup/installation`.
- `TestTerminalCommandsInPage` (`requireBuild`; the D-40 drift guard): read `../../../vue-summercms-app/app/data/terminal.json` and `../../../vue-summercms-app/i18n/locales/en.json` (overridable with `SUMMERCMS_SITE_DIR`) and require every command and every resolved comment text in the built index.html.
- `TestTerminalCommandsInPage` (`requireBuild`; the D-40 drift guard): read `../../../vue-summercmsio-app/app/data/terminal.json` and `../../../vue-summercmsio-app/i18n/locales/en.json` (overridable with `SUMMERCMS_SITE_DIR`) and require every command and every resolved comment text in the built index.html.
- `TestDocsHeaderSiteLink` (`requireBuild`): `public/docs/index.html` contains `class="site-link" href="/"` and `summercms.io`.
- `TestExternalLinks` (skips unless `SUMMERCMS_CHECK_EXTERNAL=1`): GET each external URL with a fresh client (no cookies, 15 s timeout, redirects followed) and require 200; log each URL with its status. Before D-38 the Source link is expected to fail (private repository); this test runs at cutover.
Commit in PLUG as `test: verify the landing links and terminal commands against the built site`.
5. `terminal_check_test.go` in APP (package `main`) with the helpers from the interfaces block. `TestTerminalCommands` skips unless `SUMMERCMS_TERMINAL_CHECK=1`; it loads `vue-summercms-app/app/data/terminal.json`, creates a temp work dir and a temp GOBIN, runs `bash -euo pipefail -c <terminalScript(groups, os.Getenv("SUMMERCMS_CLONE_URL"))>` in the work dir with stdin from /dev/null, `terminalEnv(os.Environ(), gobin)` and a 10-minute context, then requires exit 0 and `handled=true` in the combined output (logged on failure). With `SUMMERCMS_CLONE_URL` unset the commands run verbatim (cutover, after D-38). If a command needs setup that the check cannot provide without changing the page copy, stop and ask the user (D-40) instead of editing terminal.json.
6. Run the release build (for "defer-tag", with `SUMMERCMS_FRAMEWORK` pointing at the scratch clone), then `scripts/smoke.sh`, then `SUMMERCMS_TERMINAL_CHECK=1 SUMMERCMS_CLONE_URL=/media/nvme/dev/golem15/summercms.io/summercms/summercms.go go -C ../sm-summercms-app test -run '^TestTerminalCommands$' -count=1 -v .`. Commit in APP (including the updated plugin gitlink) as `feat: release builds from the v0.1.0 tag and the terminal command check`.
5. `terminal_check_test.go` in APP (package `main`) with the helpers from the interfaces block. `TestTerminalCommands` skips unless `SUMMERCMS_TERMINAL_CHECK=1`; it loads `vue-summercmsio-app/app/data/terminal.json`, creates a temp work dir and a temp GOBIN, runs `bash -euo pipefail -c <terminalScript(groups, os.Getenv("SUMMERCMS_CLONE_URL"))>` in the work dir with stdin from /dev/null, `terminalEnv(os.Environ(), gobin)` and a 10-minute context, then requires exit 0 and `handled=true` in the combined output (logged on failure). With `SUMMERCMS_CLONE_URL` unset the commands run verbatim (cutover, after D-38). If a command needs setup that the check cannot provide without changing the page copy, stop and ask the user (D-40) instead of editing terminal.json.
6. Run the release build (for "defer-tag", with `SUMMERCMS_FRAMEWORK` pointing at the scratch clone), then `scripts/smoke.sh`, then `SUMMERCMS_TERMINAL_CHECK=1 SUMMERCMS_CLONE_URL=/media/nvme/dev/golem15/summercms.io/summercms/summercms.go go -C ../sm-summercmsio-app test -run '^TestTerminalCommands$' -count=1 -v .`. Commit in APP (including the updated plugin gitlink) as `feat: release builds from the v0.1.0 tag and the terminal command check`.
</action>
<verify>
<automated>../sm-summercms-app/scripts/build.sh && ../sm-summercms-app/scripts/smoke.sh</automated>
<automated>../sm-summercmsio-app/scripts/build.sh && ../sm-summercmsio-app/scripts/smoke.sh</automated>
<fails_when>non-zero exit, a line starting "build:" on stderr, no "(release v0.1.0) ready" line, or no "smoke: ok" line</fails_when>
<automated>SUMMERCMS_REQUIRE_BUILD=1 go -C ../sm-summercms-app/plugins/golem15/summercms test ./... -run '^(TestLandingLinks|TestTerminalCommandsInPage|TestDocsHeaderSiteLink)$' -count=1 -v</automated>
<automated>SUMMERCMS_REQUIRE_BUILD=1 go -C ../sm-summercmsio-app/plugins/golem15/summercms test ./... -run '^(TestLandingLinks|TestTerminalCommandsInPage|TestDocsHeaderSiteLink)$' -count=1 -v</automated>
<fails_when>non-zero exit, a "--- FAIL" or "--- SKIP" line, or fewer than three "--- PASS" lines</fails_when>
<automated>SUMMERCMS_TERMINAL_CHECK=1 SUMMERCMS_CLONE_URL=/media/nvme/dev/golem15/summercms.io/summercms/summercms.go go -C ../sm-summercms-app test -run '^TestTerminalCommands$' -count=1 -v .</automated>
<automated>SUMMERCMS_TERMINAL_CHECK=1 SUMMERCMS_CLONE_URL=/media/nvme/dev/golem15/summercms.io/summercms/summercms.go go -C ../sm-summercmsio-app test -run '^TestTerminalCommands$' -count=1 -v .</automated>
<fails_when>non-zero exit, a "--- SKIP" line, or no "--- PASS: TestTerminalCommands" line</fails_when>
</verify>
<acceptance_criteria>
- For "tag-now": `git ls-remote --tags origin v0.1.0` in summercms.go prints one line ending in `refs/tags/v0.1.0`. For "defer-tag": `git tag -l v0.1.0` in summercms.go prints nothing and the SUMMARY names the scratch clone used.
- `go version -m ../sm-summercms-app/bin/summercms-io | grep 'git.golem15.com/golem15/summercms'` shows version `v0.1.0`.
- `grep -n 'docs:build --base-url /docs --site-url / --site-label summercms.io' ../sm-summercms-app/scripts/build.sh` finds a match.
- `grep -c 'class="site-link" href="/"' ../sm-summercms-app/plugins/golem15/summercms/public/docs/index.html` prints 1.
- `go -C ../sm-summercms-app test ./... -count=1` passes with the gated tests skipped (no env vars set), and `go -C ../sm-summercms-app vet ./...` is clean.
- `go version -m ../sm-summercmsio-app/bin/summercms-io | grep 'git.golem15.com/golem15/summercms'` shows version `v0.1.0`.
- `grep -n 'docs:build --base-url /docs --site-url / --site-label summercms.io' ../sm-summercmsio-app/scripts/build.sh` finds a match.
- `grep -c 'class="site-link" href="/"' ../sm-summercmsio-app/plugins/golem15/summercms/public/docs/index.html` prints 1.
- `go -C ../sm-summercmsio-app test ./... -count=1` passes with the gated tests skipped (no env vars set), and `go -C ../sm-summercmsio-app vet ./...` is clean.
</acceptance_criteria>
<done>The release binary is built from the confirmed v0.1.0 tag (or a scratch tag when deferred), every landing link resolves against the built site with redirects followed, the page and the terminal check share one command list, and the six commands run from a fresh shell to handled=true.</done>
</task>
<task type="auto">
<name>Task 5: An operator can deploy, verify and roll back summercms.io on rome by following DEPLOY.md</name>
<files>../sm-summercms-app/DEPLOY.md, ../sm-summercms-app/README.md, ../sm-summercms-app/deploy/nginx/summercms.io.conf, ../sm-summercms-app/deploy/supervisor/summercms-io.conf, ../sm-summercms-app/deploy/env.example, ../sm-summercms-app/deploy/rollback/under-construction/index.html, ../sm-summercms-app/deploy/rollback/under-construction/logo.png, ../sm-summercms-app/scripts/check-deploy.sh, ../sm-summercms-app/plugins/golem15/summercms/README.md</files>
<files>../sm-summercmsio-app/DEPLOY.md, ../sm-summercmsio-app/README.md, ../sm-summercmsio-app/deploy/nginx/summercms.io.conf, ../sm-summercmsio-app/deploy/supervisor/summercms-io.conf, ../sm-summercmsio-app/deploy/env.example, ../sm-summercmsio-app/deploy/rollback/under-construction/index.html, ../sm-summercmsio-app/deploy/rollback/under-construction/logo.png, ../sm-summercmsio-app/scripts/check-deploy.sh, ../sm-summercmsio-app/plugins/golem15/summercms/README.md</files>
<read_first>
- .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-RESEARCH.md section "Deploy (question 6)" items 1-9, "Live site observations", Assumptions A1-A3, "Security Domain"
- .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-CONTEXT.md D-27 to D-32, D-38, D-42, D-43
- modules/surf/serve.go lines 20-80 (`serve --addr`, 10 s shutdown on SIGTERM), modules/lagoon/keygen.go (key:generate prints a key), modules/compass/README.md lines 85-100 (config dir and the dotenv file next to it)
- ../sm-summercms-app/scripts/build.sh and scripts/smoke.sh (as written in Tasks 1 and 4)
- ../sm-summercmsio-app/scripts/build.sh and scripts/smoke.sh (as written in Tasks 1 and 4)
</read_first>
<action>
Write the deploy contract for rome (D-28 to D-32) and the user's launch steps (D-38, D-42, D-43). Values marked [ASSUMED] in RESEARCH are written as defaults with a "check on rome" note.
@@ -396,7 +396,7 @@ Write the deploy contract for rome (D-28 to D-32) and the user's launch steps (D
4. Rollback copy of today's site (D-31): download `https://summercms.io/` and `https://summercms.io/logo.png` with `curl -fsSL` into `deploy/rollback/under-construction/index.html` and `logo.png` (the live "Under construction" docroot is one HTML file plus the logo).
5. `DEPLOY.md` with these sections:
- Overview: what runs where (nginx :443 → 127.0.0.1:8095 summercms-io → Postgres 15 `summercms_io`), the user, port and paths (`summercms`, `127.0.0.1:8095`, `/srv/summercms-io/{bin,config,storage,.env}`), marked "check on rome" where assumed.
- Launch checklist (once, before cutover, done by the user): create the three repositories `golem15/sm-summercms-app`, `golem15/vue-summercms-app` and `golem15/sm-summercms-plugin` on git.golem15.com, then in each local repository `git remote add origin git@git.golem15.com:golem15/<name>.git` and `git push -u origin master`, submodules first (`ssu` can push submodules that are ahead) (D-43); make `golem15/summercms` public (D-38); confirm `git ls-remote --tags origin v0.1.0` in summercms.go, or create and push the tag now if it was deferred (D-42); run `SUMMERCMS_CHECK_EXTERNAL=1 go -C plugins/golem15/summercms test -run TestExternalLinks -count=1 -v ./...` and `SUMMERCMS_TERMINAL_CHECK=1 go test -run TestTerminalCommands -count=1 -v .` with no clone override; on rome check that port 8095 is free with `ss -ltnp`.
- Launch checklist (once, before cutover, done by the user): create the three repositories `golem15/sm-summercmsio-app`, `golem15/vue-summercmsio-app` and `golem15/sm-summercmsio-plugin` on git.golem15.com, then in each local repository `git remote add origin git@git.golem15.com:golem15/<name>.git` and `git push -u origin master`, submodules first (`ssu` can push submodules that are ahead) (D-43); make `golem15/summercms` public (D-38); confirm `git ls-remote --tags origin v0.1.0` in summercms.go, or create and push the tag now if it was deferred (D-42); run `SUMMERCMS_CHECK_EXTERNAL=1 go -C plugins/golem15/summercms test -run TestExternalLinks -count=1 -v ./...` and `SUMMERCMS_TERMINAL_CHECK=1 go test -run TestTerminalCommands -count=1 -v .` with no clone override; on rome check that port 8095 is free with `ss -ltnp`.
- One-time server setup: `adduser --system --group --home /srv/summercms-io --shell /usr/sbin/nologin summercms`; `sudo -u postgres createuser --pwprompt summercms`; `sudo -u postgres createdb -O summercms -E UTF8 summercms_io` (D-27); create the directories; write `/srv/summercms-io/.env` from `deploy/env.example` with the DSN and a key printed by `./bin/summercms-io key:generate`, `chown summercms:summercms` and `chmod 0600`; install the supervisor program and run `supervisorctl reread && supervisorctl update`.
- Build (local, D-28): `scripts/build.sh` (release from v0.1.0; `scripts/build.sh dev` builds from the framework working tree for previews); the server needs neither Go nor Node.
- Upload: `rsync -av --chmod=F644,D755 config/ rome:/srv/summercms-io/config/` and `rsync -av bin/summercms-io rome:/srv/summercms-io/bin/summercms-io.new`; `.env` is never rsynced.
@@ -409,16 +409,16 @@ Write the deploy contract for rome (D-28 to D-32) and the user's launch steps (D
Commit in PLUG as `docs: describe the site plugin`, then in APP (with the updated plugin gitlink) as `docs: add DEPLOY.md with nginx, supervisor and rollback configs`.
</action>
<verify>
<automated>../sm-summercms-app/scripts/check-deploy.sh</automated>
<automated>../sm-summercmsio-app/scripts/check-deploy.sh</automated>
<fails_when>non-zero exit, "test failed" or "[emerg]" in the nginx output, or no "check-deploy: ok" line</fails_when>
<human-check>At cutover, follow DEPLOY.md on rome from the launch checklist to "Verify after cutover": the site answers at https://summercms.io with the landing page, /docs serves the docs with the "summercms.io" header link, /backend is 404, and https://www.summercms.io redirects to the apex.</human-check>
</verify>
<acceptance_criteria>
- `grep -n 'location ^~ /backend' ../sm-summercms-app/deploy/nginx/summercms.io.conf` and `grep -n 'limit_except GET HEAD' ../sm-summercms-app/deploy/nginx/summercms.io.conf` both find a match.
- `grep -n 'user=summercms' ../sm-summercms-app/deploy/supervisor/summercms-io.conf` and `grep -n '127.0.0.1:8095' ../sm-summercms-app/deploy/supervisor/summercms-io.conf` both find a match.
- `grep -c 'migrate' ../sm-summercms-app/DEPLOY.md` prints at least 1, and `grep -n 'createdb -O summercms' ../sm-summercms-app/DEPLOY.md`, `grep -n 'chmod 0600' ../sm-summercms-app/DEPLOY.md` and `grep -n 'git remote add origin' ../sm-summercms-app/DEPLOY.md` each find a match.
- `grep -n 'Rollback' ../sm-summercms-app/DEPLOY.md` finds a match and `test -s ../sm-summercms-app/deploy/rollback/under-construction/index.html` succeeds.
- `git -C ../sm-summercms-app ls-files -- .env deploy/.env` prints nothing (secrets are never committed).
- `grep -n 'location ^~ /backend' ../sm-summercmsio-app/deploy/nginx/summercms.io.conf` and `grep -n 'limit_except GET HEAD' ../sm-summercmsio-app/deploy/nginx/summercms.io.conf` both find a match.
- `grep -n 'user=summercms' ../sm-summercmsio-app/deploy/supervisor/summercms-io.conf` and `grep -n '127.0.0.1:8095' ../sm-summercmsio-app/deploy/supervisor/summercms-io.conf` both find a match.
- `grep -c 'migrate' ../sm-summercmsio-app/DEPLOY.md` prints at least 1, and `grep -n 'createdb -O summercms' ../sm-summercmsio-app/DEPLOY.md`, `grep -n 'chmod 0600' ../sm-summercmsio-app/DEPLOY.md` and `grep -n 'git remote add origin' ../sm-summercmsio-app/DEPLOY.md` each find a match.
- `grep -n 'Rollback' ../sm-summercmsio-app/DEPLOY.md` finds a match and `test -s ../sm-summercmsio-app/deploy/rollback/under-construction/index.html` succeeds.
- `git -C ../sm-summercmsio-app ls-files -- .env deploy/.env` prints nothing (secrets are never committed).
</acceptance_criteria>
<done>DEPLOY.md, the nginx and supervisor configs, the env template and the rollback copy cover build, upload, release, cutover, verification and rollback on rome; the nginx config passes `nginx -t`; the user's launch steps (remotes, public repository, tag, external checks) are listed.</done>
</task>
@@ -457,8 +457,8 @@ Commit in PLUG as `docs: describe the site plugin`, then in APP (with the update
<verification>
- summercms.go: `go vet ./... && go test ./internal/docsite ./cmd/summer -count=1` green; `scripts/check-phase11.1.sh --docs && scripts/check-phase11.1.sh --forbidden` green.
- APP: `scripts/build.sh` (release) or the scratch-clone equivalent, `scripts/smoke.sh`, `scripts/check-deploy.sh` green; `go -C ../sm-summercms-app vet ./... && go -C ../sm-summercms-app test ./... -count=1` green.
- PLUG: `SUMMERCMS_REQUIRE_BUILD=1 go -C ../sm-summercms-app/plugins/golem15/summercms test ./... -count=1` green.
- APP: `scripts/build.sh` (release) or the scratch-clone equivalent, `scripts/smoke.sh`, `scripts/check-deploy.sh` green; `go -C ../sm-summercmsio-app vet ./... && go -C ../sm-summercmsio-app test ./... -count=1` green.
- PLUG: `SUMMERCMS_REQUIRE_BUILD=1 go -C ../sm-summercmsio-app/plugins/golem15/summercms test ./... -count=1` green.
- D-40: `TestTerminalCommands` green with the local clone override.
</verification>
@@ -475,7 +475,7 @@ Creating the three remotes and pushing, making `golem15/summercms` public, pushi
## Artifacts this phase produces
- Repositories: `sm-summercms-app` (module `git.golem15.com/golem15/sm-summercms-app`, binary `summercms-io`) and `sm-summercms-plugin` (module `git.golem15.com/golem15/sm-summercms-plugin`, package `summercms`, plugin ID `golem15.summercms`) at `/media/nvme/dev/golem15/summercms.io/summercms/sm-summercms-app` and its `plugins/golem15/summercms`; submodule URLs `git@git.golem15.com:golem15/vue-summercms-app.git` and `git@git.golem15.com:golem15/sm-summercms-plugin.git`.
- Repositories: `sm-summercmsio-app` (module `git.golem15.com/golem15/sm-summercmsio-app`, binary `summercms-io`) and `sm-summercmsio-plugin` (module `git.golem15.com/golem15/sm-summercmsio-plugin`, package `summercms`, plugin ID `golem15.summercms`) at `/media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app` and its `plugins/golem15/summercms`; submodule URLs `git@git.golem15.com:golem15/vue-summercmsio-app.git` and `git@git.golem15.com:golem15/sm-summercmsio-plugin.git`.
- Plugin symbols: `Plugin` (field `fsys`; methods `ID`, `Requires`, `Register`, `Boot`, `Routes`), `publicFS`, `newHandlers`, `tree` (fields `files`, `mount`, `immutable`, `htmlRedirect`; method `ServeHTTP`), `file` (fields `body`, `etag`, `ctype`), `newTree`, `errMissingIndex`, `siteImmutable`, `contentTypes`, `contentType`, `redirectTo`, `cacheImmutable`, `cacheNoCache`. Routes `GET /docs`, `GET /docs/{path...}`, `GET /`.
- Plugin tests: `TestStaticSmoke`, `TestLandingLinks`, `TestTerminalCommandsInPage`, `TestDocsHeaderSiteLink`, `TestExternalLinks`; helpers `requireBuild`, `pageLinks`, `resolve`.
- App test symbols: `terminalGroup`, `loadTerminal`, `terminalCloneURL`, `terminalScript`, `terminalEnv`, `TestTerminalCommands`.

View File

@@ -10,12 +10,12 @@ files_modified:
- internal/docsite/theme_test.go
- cmd/summer/docs_test.go
- .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-VALIDATION.md
- ../sm-summercms-app/plugins/golem15/summercms/static_test.go
- ../sm-summercms-app/plugins/golem15/summercms/routes_test.go
- ../sm-summercms-app/plugins/golem15/summercms/links_test.go
- ../sm-summercms-app/terminal_check_test.go
- ../sm-summercms-app/vue-summercms-app/tests/terminal.test.ts
- ../sm-summercms-app/vue-summercms-app/tests/scrollSpy.test.ts
- ../sm-summercmsio-app/plugins/golem15/summercms/static_test.go
- ../sm-summercmsio-app/plugins/golem15/summercms/routes_test.go
- ../sm-summercmsio-app/plugins/golem15/summercms/links_test.go
- ../sm-summercmsio-app/terminal_check_test.go
- ../sm-summercmsio-app/vue-summercmsio-app/tests/terminal.test.ts
- ../sm-summercmsio-app/vue-summercmsio-app/tests/scrollSpy.test.ts
autonomous: true
requirements: []
assumption_delta_decision: no-change
@@ -30,7 +30,7 @@ estimate:
must_haves:
truths:
- "Per SC5 and the CLAUDE.md rule that unit tests are the last plan, the site plugin package `git.golem15.com/golem15/sm-summercms-plugin` reaches at least 90.0% statement coverage from tests that need no build output (fstest fixtures), and `internal/docsite` stays at or above 85.0%."
- "Per SC5 and the CLAUDE.md rule that unit tests are the last plan, the site plugin package `git.golem15.com/golem15/sm-summercmsio-plugin` reaches at least 90.0% statement coverage from tests that need no build output (fstest fixtures), and `internal/docsite` stays at or above 85.0%."
- "Every serving rule from D-07 and D-47 has a test that fails when the rule breaks: content types from the own table, immutable cache only for `_nuxt/` (not `_nuxt/builds/`) and `_fonts/`, no-cache with a strong ETag elsewhere including `/docs/assets/`, 304 on a matching If-None-Match, HEAD and Range handled, dot-segment and traversal paths 404, extension-less docs paths 301 to `.html`, `/docs` 301 to `/docs/`, tree 404 pages with status 404, and no robots-blocking, CSP or frame-deny header on any response."
- "No redirect the plugin emits has a Location outside `/docs/`, and no Location starts with `//` (T-11.2-05)."
- "The plugin assembles alone through `surf.Assemble` with GET-only routes (POST 405, `/backend` falls to the site 404), fails closed with `public/site/index.html missing` on an empty tree, registers itself through init under `golem15.summercms`, declares no admin controllers, and its three patterns coexist with cabana's admin patterns on one ServeMux (ready for Phase 11.3)."
@@ -42,16 +42,16 @@ must_haves:
- statement: "External link reachability and the verbatim terminal clone are re-run at cutover after the repository is made public (D-38)."
verification: backstop
artifacts:
- path: "../sm-summercms-app/plugins/golem15/summercms/static_test.go"
- path: "../sm-summercmsio-app/plugins/golem15/summercms/static_test.go"
provides: "table-driven tests of every static serving rule"
contains: "fstest.MapFS"
- path: "../sm-summercms-app/plugins/golem15/summercms/routes_test.go"
- path: "../sm-summercmsio-app/plugins/golem15/summercms/routes_test.go"
provides: "assembly, fail-closed, coexistence and identity tests"
contains: "surf.Assemble"
- path: "scripts/check-phase11.2.sh"
provides: "phase gate across the four repositories"
contains: "phase11.2 all passed"
- path: "../sm-summercms-app/terminal_check_test.go"
- path: "../sm-summercmsio-app/terminal_check_test.go"
provides: "ungated tests of the D-40 helpers"
contains: "TestTerminalScript"
- path: "internal/docsite/load_test.go"
@@ -62,15 +62,15 @@ must_haves:
contains: "nyquist_compliant: true"
key_links:
- from: "scripts/check-phase11.2.sh"
to: "../sm-summercms-app/plugins/golem15/summercms"
to: "../sm-summercmsio-app/plugins/golem15/summercms"
via: "go test -json with a detector that requires each named test to PASS"
pattern: "go -C .*summercms test"
- from: "../sm-summercms-app/plugins/golem15/summercms/routes_test.go"
- from: "../sm-summercmsio-app/plugins/golem15/summercms/routes_test.go"
to: "modules/surf router"
via: "surf.Assemble(backpack.New(nil), []party.Plugin{...})"
pattern: "surf\\.Assemble\\("
- from: "scripts/check-phase11.2.sh"
to: "../sm-summercms-app/scripts/smoke.sh"
to: "../sm-summercmsio-app/scripts/smoke.sh"
via: "--smoke stage boots the binary on postgres:15"
pattern: "smoke\\.sh"
---
@@ -80,7 +80,7 @@ Bring full unit test coverage to the phase's new code and close the phase with a
Purpose: plans 11.2-01 and 11.2-02 shipped smoke tests only; this plan pins every rule so a regression fails `go test`.
Output: test files in sm-summercms-plugin, sm-summercms-app, vue-summercms-app and summercms.go, the phase gate script in summercms.go, and a validated 11.2-VALIDATION.md.
Output: test files in sm-summercmsio-plugin, sm-summercmsio-app, vue-summercmsio-app and summercms.go, the phase gate script in summercms.go, and a validated 11.2-VALIDATION.md.
</objective>
<execution_context>
@@ -103,7 +103,7 @@ Output: test files in sm-summercms-plugin, sm-summercms-app, vue-summercms-app a
@modules/boardwalk/boardwalk_test.go
@modules/surf/example_test.go
**Paths.** Commands run from the summercms.go root. `APP` = `../sm-summercms-app`, `PLUG` = `APP/plugins/golem15/summercms`, `SITE` = `APP/vue-summercms-app`. Each task names the repositories it commits to.
**Paths.** Commands run from the summercms.go root. `APP` = `../sm-summercmsio-app`, `PLUG` = `APP/plugins/golem15/summercms`, `SITE` = `APP/vue-summercmsio-app`. Each task names the repositories it commits to.
**Interfaces under test** are the ones plan 11.2-02 fixed in its `<interfaces>` block (`Plugin`, `newHandlers`, `tree`, `newTree`, `errMissingIndex`, `siteImmutable`, `contentTypes`, `contentType`, `redirectTo`, `cacheImmutable`, `cacheNoCache`; `checkSiteURL`, `siteLabel`, `Options.SiteURL`, `Options.SiteLabel`; `terminalGroup`, `loadTerminal`, `terminalScript`, `terminalEnv`, `terminalCloneURL`) and plan 11.2-01's TypeScript utilities (`copyPayload`, `activeSection`, `SPY_THRESHOLD`). Read the 11.2-02 SUMMARY for any name that changed during execution and test the shipped name.
@@ -114,9 +114,9 @@ Output: test files in sm-summercms-plugin, sm-summercms-app, vue-summercms-app a
<task type="tracer">
<name>Task 1: Tracer: the phase gate runs the site plugin's full rule table, and the plugin package reaches 90% coverage</name>
<files>../sm-summercms-app/plugins/golem15/summercms/static_test.go, ../sm-summercms-app/plugins/golem15/summercms/routes_test.go, ../sm-summercms-app/plugins/golem15/summercms/links_test.go, scripts/check-phase11.2.sh</files>
<files>../sm-summercmsio-app/plugins/golem15/summercms/static_test.go, ../sm-summercmsio-app/plugins/golem15/summercms/routes_test.go, ../sm-summercmsio-app/plugins/golem15/summercms/links_test.go, scripts/check-phase11.2.sh</files>
<read_first>
- ../sm-summercms-app/plugins/golem15/summercms/plugin.go, static.go, smoke_test.go and links_test.go (as shipped by plan 11.2-02)
- ../sm-summercmsio-app/plugins/golem15/summercms/plugin.go, static.go, smoke_test.go and links_test.go (as shipped by plan 11.2-02)
- modules/boardwalk/boardwalk_test.go lines 1-44 (fstest fixture and `get` helper)
- modules/surf/example_test.go lines 114-140 (surf.Assemble with backpack.New(nil))
- modules/party/registry.go lines 29-60 (Register, Activate)
@@ -140,18 +140,18 @@ Output: test files in sm-summercms-plugin, sm-summercms-app, vue-summercms-app a
2. `routes_test.go` in PLUG: `TestRoutesAssemble`, `TestRoutesFailClosed`, `TestRoutesCoexistWithAdminPatterns` (mirror cabana's patterns as literal strings, register with a `recover` guard), `TestPluginIdentity` and `TestPluginEmbeddedTree` per the behavior block.
3. `links_test.go` in PLUG: add the ungated `TestPageLinks` for the `pageLinks` helper; leave the build-gated tests as shipped.
4. Coverage: `go -C PLUG test -count=1 -coverprofile=<tmp> ./...` then `go tool cover -func` total at least 90.0%. Add cases until it is, or record in the SUMMARY any line that is unreachable without a build and why.
5. `scripts/check-phase11.2.sh` in summercms.go (bash, `set -euo pipefail`, structure and `go test -json` detector modelled on `scripts/check-phase11.1.sh` `detect_json`: fail on a build failure, any FAIL, any SKIP of a named test, zero matched tests or "no tests to run"). This task implements `--plugin`: `go -C "$PLUG" vet ./...`, the named tests from steps 1-3 run with `-json` through the detector, and the coverage threshold; it prints `phase11.2 plugin passed`. Paths resolve from the script location (`APP="$ROOT/../sm-summercms-app"`). Unknown modes print usage and exit 2. Later tasks add the other modes.
5. `scripts/check-phase11.2.sh` in summercms.go (bash, `set -euo pipefail`, structure and `go test -json` detector modelled on `scripts/check-phase11.1.sh` `detect_json`: fail on a build failure, any FAIL, any SKIP of a named test, zero matched tests or "no tests to run"). This task implements `--plugin`: `go -C "$PLUG" vet ./...`, the named tests from steps 1-3 run with `-json` through the detector, and the coverage threshold; it prints `phase11.2 plugin passed`. Paths resolve from the script location (`APP="$ROOT/../sm-summercmsio-app"`). Unknown modes print usage and exit 2. Later tasks add the other modes.
Commit in PLUG as `test: cover every static serving rule and the plugin routes`, and in summercms.go as `test(11.2): add the phase gate with the plugin stage`.
</action>
<verify>
<automated>go -C ../sm-summercms-app/plugins/golem15/summercms vet ./... && go -C ../sm-summercms-app/plugins/golem15/summercms test ./... -run '^(TestStatic|TestNewHandlersMissingIndex|TestContentType|TestSiteImmutable|TestRoutes|TestPlugin|TestPageLinks)' -count=1 -v</automated>
<automated>go -C ../sm-summercmsio-app/plugins/golem15/summercms vet ./... && go -C ../sm-summercmsio-app/plugins/golem15/summercms test ./... -run '^(TestStatic|TestNewHandlersMissingIndex|TestContentType|TestSiteImmutable|TestRoutes|TestPlugin|TestPageLinks)' -count=1 -v</automated>
<fails_when>non-zero exit, a "--- FAIL" line, or "no tests to run"</fails_when>
<automated>scripts/check-phase11.2.sh --plugin</automated>
<fails_when>non-zero exit, a coverage line below 90.0%, or no "phase11.2 plugin passed" line</fails_when>
</verify>
<acceptance_criteria>
- `go -C ../sm-summercms-app/plugins/golem15/summercms test -cover ./... -count=1` prints a `coverage:` value of at least 90.0%.
- `go -C ../sm-summercms-app/plugins/golem15/summercms test ./... -run '^(TestStatic.*|TestRoutes.*|TestPlugin.*)$' -count=1 -v` prints at least 10 `--- PASS` lines (subtests included).
- `go -C ../sm-summercmsio-app/plugins/golem15/summercms test -cover ./... -count=1` prints a `coverage:` value of at least 90.0%.
- `go -C ../sm-summercmsio-app/plugins/golem15/summercms test ./... -run '^(TestStatic.*|TestRoutes.*|TestPlugin.*)$' -count=1 -v` prints at least 10 `--- PASS` lines (subtests included).
- Changing `siteImmutable` to also return true for `_nuxt/builds/` paths in a scratch copy makes `TestStaticSite` fail (checked once by hand during execution and recorded in the SUMMARY), proving the cache test fails when broken.
- `bash -n scripts/check-phase11.2.sh` exits 0 and `scripts/check-phase11.2.sh --bogus` exits 2.
</acceptance_criteria>
@@ -160,12 +160,12 @@ Commit in PLUG as `test: cover every static serving rule and the plugin routes`,
<task type="auto">
<name>Task 2: Every new framework and app branch is pinned: site_url and site_label rules, override precedence, header rendering, CLI flags, terminal-check helpers and the TypeScript edge cases</name>
<files>internal/docsite/load_test.go, internal/docsite/theme_test.go, cmd/summer/docs_test.go, ../sm-summercms-app/terminal_check_test.go, ../sm-summercms-app/vue-summercms-app/tests/terminal.test.ts, ../sm-summercms-app/vue-summercms-app/tests/scrollSpy.test.ts, scripts/check-phase11.2.sh</files>
<files>internal/docsite/load_test.go, internal/docsite/theme_test.go, cmd/summer/docs_test.go, ../sm-summercmsio-app/terminal_check_test.go, ../sm-summercmsio-app/vue-summercmsio-app/tests/terminal.test.ts, ../sm-summercmsio-app/vue-summercmsio-app/tests/scrollSpy.test.ts, scripts/check-phase11.2.sh</files>
<read_first>
- internal/docsite/load.go, internal/docsite/docsite.go, internal/docsite/emit.go and internal/docsite/theme/templates/header.html (as shipped by plan 11.2-02: checkSiteURL, siteLabel, the override block in load)
- internal/docsite/load_test.go (TestParseSite table), internal/docsite/theme_test.go (themeTree, buildTheme, TestSiteLink), cmd/summer/docs_test.go (TestDocsBuildSiteFlags, TestToolCommandNames helpWants)
- ../sm-summercms-app/terminal_check_test.go (helpers as shipped)
- ../sm-summercms-app/vue-summercms-app/tests/terminal.test.ts, tests/scrollSpy.test.ts, app/utils/terminal.ts, app/utils/scrollSpy.ts (as shipped by plan 11.2-01)
- ../sm-summercmsio-app/terminal_check_test.go (helpers as shipped)
- ../sm-summercmsio-app/vue-summercmsio-app/tests/terminal.test.ts, tests/scrollSpy.test.ts, app/utils/terminal.ts, app/utils/scrollSpy.ts (as shipped by plan 11.2-01)
</read_first>
<behavior>
- checkSiteURL accepts `https://acme.example`, `https://acme.example/`, `http://acme.example:8080/x`, `/` and `/home`; rejects the empty string, `javascript:alert(1)`, `JavaScript:alert(1)`, `data:text/html,x`, `//acme.example`, `acme.example`, `docs/x`, `https://`, `https://user:pw@acme.example`, `ftp://acme.example`, `/ x`, a value with a newline and a value with a leading tab.
@@ -174,7 +174,7 @@ Commit in PLUG as `test: cover every static serving rule and the plugin routes`,
- Load precedence: a yaml `site_url` plus an `Options.SiteURL` uses the option; a yaml `site_label` plus `Options.SiteLabel` uses the option; `Options.SiteLabel` with no URL anywhere fails with the `--site-label needs` message; an invalid `Options.SiteURL` fails with the `--site-url:` message; a yaml URL with no label derives the label.
- Rendering: a label `<b>&` appears as `&lt;b&gt;&amp;` in the header; the link is on `index.html`, a section page and `404.html`; with nothing set, the header contains the wordmark's closing tag immediately followed by a newline and `<div class="header-spacer">`.
- CLI: `summer help docs:build` and `summer help docs:serve` (or the command's flag listing the existing help test uses) list `--site-url` and `--site-label`; `docs:build --site-label x` with no site URL returns an error.
- loadTerminal reads the real `vue-summercms-app/app/data/terminal.json` as 3 groups and 6 commands, and returns an error for a missing file and for malformed JSON.
- loadTerminal reads the real `vue-summercmsio-app/app/data/terminal.json` as 3 groups and 6 commands, and returns an error for a missing file and for malformed JSON.
- terminalScript with no override returns the six commands joined by `\n`; with an override `/tmp/fw` the first command becomes `git clone /tmp/fw` and the other five are unchanged.
- terminalEnv drops `SUMMER_ENV`, `SUMMER_DATABASE__DSN` and `GOWORK`, keeps `HOME`, sets `GOBIN`, makes `PATH` start with the gobin directory followed by `:`, and sets `PATH` to the gobin directory when the base has no `PATH`.
- copyPayload: three groups give the six-line payload; an empty list gives `''`; a group with no commands adds nothing; activeSection: the exact-140 boundary is inactive, a custom threshold is honoured, and negative tops (sections scrolled past) still count.
@@ -189,7 +189,7 @@ Commit per repository: summercms.go `test(11.2): cover site_url and site_label a
<verify>
<automated>go vet ./... && go test ./internal/docsite ./cmd/summer -run '^(TestParseSite|TestCheckSiteURL|TestSiteLabel|TestSiteURLPrecedence|TestSiteLink|TestDocsBuildSiteFlags|TestDocsSiteFlagsInHelp|TestDocsTree)$' -count=1 -v</automated>
<fails_when>non-zero exit, a "--- FAIL" line, "no tests to run", or fewer than eight top-level "--- PASS" lines</fails_when>
<automated>go -C ../sm-summercms-app test -run '^(TestLoadTerminal|TestTerminalScript|TestTerminalEnv)$' -count=1 -v .</automated>
<automated>go -C ../sm-summercmsio-app test -run '^(TestLoadTerminal|TestTerminalScript|TestTerminalEnv)$' -count=1 -v .</automated>
<fails_when>non-zero exit, a "--- FAIL" or "--- SKIP" line, or fewer than three "--- PASS" lines</fails_when>
<automated>scripts/check-phase11.2.sh --framework && scripts/check-phase11.2.sh --app && scripts/check-phase11.2.sh --site</automated>
<fails_when>non-zero exit, a docsite coverage value below 85.0%, or a missing "phase11.2 framework passed", "phase11.2 app passed" or "phase11.2 site passed" line</fails_when>
@@ -198,7 +198,7 @@ Commit per repository: summercms.go `test(11.2): cover site_url and site_label a
- `go test -cover ./internal/docsite -count=1` prints a `coverage:` value of at least 85.0%.
- `go test ./internal/docsite -run '^TestCheckSiteURL$' -count=1 -v` prints at least 18 `--- PASS: TestCheckSiteURL/` subtest lines (five accepted, thirteen rejected values).
- Making `checkSiteURL` accept `//host` URLs in a scratch copy makes `TestCheckSiteURL` fail (checked once by hand and recorded in the SUMMARY).
- `pnpm -C ../sm-summercms-app/vue-summercms-app test` prints `# fail 0`.
- `pnpm -C ../sm-summercmsio-app/vue-summercmsio-app test` prints `# fail 0`.
</acceptance_criteria>
<done>Every new framework, app-helper and TypeScript branch has a test, docsite coverage holds at 85% or more, and the gate's framework, app and site stages pass fail-closed.</done>
</task>
@@ -208,7 +208,7 @@ Commit per repository: summercms.go `test(11.2): cover site_url and site_label a
<files>scripts/check-phase11.2.sh, .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-VALIDATION.md</files>
<read_first>
- scripts/check-phase11.2.sh (as written in Tasks 1-2)
- ../sm-summercms-app/scripts/build.sh, scripts/smoke.sh, scripts/check-deploy.sh (as shipped by plan 11.2-02)
- ../sm-summercmsio-app/scripts/build.sh, scripts/smoke.sh, scripts/check-deploy.sh (as shipped by plan 11.2-02)
- .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-VALIDATION.md (draft map and manual-only rows)
- .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-0{1,2}-PLAN.md verify blocks (the commands to list per task)
- .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-VALIDATION.md (a validated example of the format)
@@ -253,8 +253,8 @@ Commit the gate in summercms.go as `test(11.2): complete the phase gate`, then V
<verification>
- `scripts/check-phase11.2.sh --all` prints `phase11.2 all passed`.
- `go -C ../sm-summercms-app/plugins/golem15/summercms test -cover ./...` at least 90.0%; `go test -cover ./internal/docsite` at least 85.0%.
- `git status --porcelain` is empty in summercms.go, sm-summercms-app, sm-summercms-plugin and vue-summercms-app after the last commit.
- `go -C ../sm-summercmsio-app/plugins/golem15/summercms test -cover ./...` at least 90.0%; `go test -cover ./internal/docsite` at least 85.0%.
- `git status --porcelain` is empty in summercms.go, sm-summercmsio-app, sm-summercmsio-plugin and vue-summercmsio-app after the last commit.
</verification>
<success_criteria>

View File

@@ -11,7 +11,7 @@
summercms.io replaces its "Under construction" page with the Alpha 0.1 landing page from the claude.ai/design handoff in `design/`. The page is a Nuxt 4 static site, English only and i18n-ready. It is embedded in one SummerCMS binary that also serves the Phase 11.1 docs at `/docs`. It deploys to the `rome` server behind nginx and supervisor, following a documented and scripted procedure.
Three new repos: `sm-summercms-app`, `vue-summercms-app` and `sm-summercms-plugin`. The framework (`summercms.go`) gets three small, scoped changes: Postgres 15 support verified and documented (D-25), an optional docs-to-site link (D-41) and a `v0.1.0` tag (D-42).
Three new repos: `sm-summercmsio-app`, `vue-summercmsio-app` and `sm-summercmsio-plugin`. The framework (`summercms.go`) gets three small, scoped changes: Postgres 15 support verified and documented (D-25), an optional docs-to-site link (D-41) and a `v0.1.0` tag (D-42).
Out of scope: newsletter signup, the Polish locale and the subscriber admin. These are Phase 11.3.
@@ -22,12 +22,12 @@ Out of scope: newsletter signup, the Polish locale and the subscriber admin. The
### Repos and layout (2026-09-29, still in force)
- **D-01:** Three new repos follow the `sm-` naming convention (`oc-`/`wn-` in October/Winter):
- `sm-summercms-app` is the root app. It builds the binary, holds the other pieces and wires the plugins.
- `vue-summercms-app` is the Nuxt 4 site, held inside the root app as `vue-fonoteka-app` is in the fonoteka project.
- `sm-summercms-plugin` is the site plugin. It holds everything specific to summercms.io: serving the embedded Nuxt build at `/`, mounting the 11.1 docs output at `/docs`, and any site-specific data or routes. This is the proven WinterCMS pattern from `figs.org.pl` (`figs/website`) and golem15.com.
- `sm-summercmsio-app` is the root app. It builds the binary, holds the other pieces and wires the plugins.
- `vue-summercmsio-app` is the Nuxt 4 site, held inside the root app as `vue-fonoteka-app` is in the fonoteka project.
- `sm-summercmsio-plugin` is the site plugin. It holds everything specific to summercms.io: serving the embedded Nuxt build at `/`, mounting the 11.1 docs output at `/docs`, and any site-specific data or routes. This is the proven WinterCMS pattern from `figs.org.pl` (`figs/website`) and golem15.com.
Public static-site serving goes in `sm-summercms-plugin`, not in a new framework module. (The fourth repo, `sm-newsletter-plugin`, moved to 11.3.) — **Reversibility:** costly — repo names and module paths are referenced by the go.work/replace wiring and every import.
- **D-02:** Go module paths live under `git.golem15.com/golem15/`, like the framework (`git.golem15.com/golem15/summercms`): for example `git.golem15.com/golem15/sm-summercms-plugin`. — **Reversibility:** one-way — a published Go module path is baked into every importer.
Public static-site serving goes in `sm-summercmsio-plugin`, not in a new framework module. (The fourth repo, `sm-newsletter-plugin`, moved to 11.3.) — **Reversibility:** costly — repo names and module paths are referenced by the go.work/replace wiring and every import.
- **D-02:** Go module paths live under `git.golem15.com/golem15/`, like the framework (`git.golem15.com/golem15/summercms`): for example `git.golem15.com/golem15/sm-summercmsio-plugin`. — **Reversibility:** one-way — a published Go module path is baked into every importer.
- **D-03:** The root app wires the framework and the site plugin the way `fonoteka.go` does today (a go.work workspace with a local replace during development).
### Website build and hosting (2026-09-29, still in force)
@@ -53,7 +53,7 @@ Out of scope: newsletter signup, the Polish locale and the subscriber admin. The
- **D-27:** The app uses a dedicated Postgres role (`summercms`) that owns a dedicated database (`summercms_io`). The password lives in the app's config or env on the server, never in git. DEPLOY.md documents `createuser`/`createdb`, and every deploy runs the app's `migrate` command before restarting.
### Server and deploy flow
- **D-28:** The binary is built locally for linux/amd64 by a script in `sm-summercms-app` (nuxt generate, then `summer docs:build --base-url /docs`, then `go build`), then rsynced to rome with its config. The server needs neither Go nor Node.
- **D-28:** The binary is built locally for linux/amd64 by a script in `sm-summercmsio-app` (nuxt generate, then `summer docs:build --base-url /docs`, then `go build`), then rsynced to rome with its config. The server needs neither Go nor Node.
- **D-29:** `/backend` and the admin API are not exposed publicly. nginx proxies only `/` and `/docs` to the binary and denies the admin paths. Nothing needs administering until 11.3.
- **D-30:** TLS uses rome's existing certbot / Let's Encrypt setup. DEPLOY.md shows the nginx server block with the certbot-managed certificate paths, HTTP to HTTPS redirect, gzip and proxy headers.
- **D-31:** Cutover replaces the existing summercms.io nginx server block in place. The current "Under construction" block is saved in DEPLOY.md (or alongside it) as the rollback.
@@ -69,7 +69,8 @@ Out of scope: newsletter signup, the Polish locale and the subscriber admin. The
### Planning-session decisions (2026-10-01, after research)
- **D-46:** D-41 is extended. `docs/site.yaml` gets an optional `site_url` and an optional `site_label`. `summer docs:build` and `summer docs:serve` accept `--site-url` and `--site-label` flags that override them, the same way `--base-url` overrides `base_url`. The framework's own `site.yaml` sets neither, so its docs output is unchanged. If `site_label` is unset, the label is the URL's host, or "Home" for a relative URL. The summercms.io build passes `--site-url /` and `--site-label summercms.io`, so the header reads "← summercms.io".
- **D-47:** The landing page keeps the handoff's extension-less `/docs/...` hrefs exactly as written (for example `/docs/backend/admin-spa`). `sm-summercms-plugin` answers an extension-less `/docs/x` with a 301 to `/docs/x.html` when that page exists. The SC3 link check follows the redirect and requires the final 200.
- **D-47:** The landing page keeps the handoff's extension-less `/docs/...` hrefs exactly as written (for example `/docs/backend/admin-spa`). `sm-summercmsio-plugin` answers an extension-less `/docs/x` with a 301 to `/docs/x.html` when that page exists. The SC3 link check follows the redirect and requires the final 200.
- **D-48:** (2026-10-01, after plan 11.2-01) The site's repositories are named `summercmsio`, not `summercms`, to tell the website apart from the framework on Gitea. The user created the remotes `git@git.golem15.com:golem15/vue-summercmsio-app.git`, `git@git.golem15.com:golem15/sm-summercmsio-app.git` and `git@git.golem15.com:golem15/sm-summercmsio-plugin.git`. Local directories, Go module paths (`git.golem15.com/golem15/sm-summercmsio-app`, `git.golem15.com/golem15/sm-summercmsio-plugin`), submodule URLs and the npm package name follow. Unchanged: the framework module `git.golem15.com/golem15/summercms`, the Go package `summercms`, the plugin ID `golem15.summercms`, the plugin directory `plugins/golem15/summercms` and the binary `summercms-io`. Each new repository gets `origin` set to its remote at creation; nothing is pushed during the phase, and DEPLOY.md's step changes from creating the remotes to pushing them. Supersedes the repository names in D-01, D-02 and D-43. — **Reversibility:** one-way once pushed (module paths).
### Get started accuracy
- **D-38:** Both Source links point to `https://git.golem15.com/golem15/summercms`. The repo currently requires login, so making it public is a launch checklist item in DEPLOY.md, done manually by the user before cutover. The link check (roadmap criterion 3) verifies anonymous access at cutover time.
@@ -78,13 +79,13 @@ Out of scope: newsletter signup, the Polish locale and the subscriber admin. The
### Docs and release
- **D-41:** Framework change: an optional `site_url` key in `docs/site.yaml` adds a link back to the main site in the docs header (`internal/docsite/theme/templates/header.html`), for example "← summercms.io". When the key is unset, the output is unchanged. The change also updates the docs page that describes `site.yaml`, and `summer docs:build --check` and `TestDocsTree` stay green. summercms.io sets it to `/`.
- **D-42:** The framework is tagged `v0.1.0` (Alpha 0.1). `sm-summercms-app` requires `git.golem15.com/golem15/summercms v0.1.0` (with the local replace during development), and the embedded docs are built from that tag, so the page, the docs and the code agree. The user confirms before the tag is created and pushed. — **Reversibility:** one-way — a pushed Go module version tag is cached by proxies and cannot be reused.
- **D-43:** The new repos live as siblings of `summercms.go` in the meta repo directory (`summercms/sm-summercms-app/`, like `fonoteka.go`). `vue-summercms-app` and `sm-summercms-plugin` are submodules inside `sm-summercms-app`. Creating the remotes on git.golem15.com is a manual step the plan lists for the user.
- **D-42:** The framework is tagged `v0.1.0` (Alpha 0.1). `sm-summercmsio-app` requires `git.golem15.com/golem15/summercms v0.1.0` (with the local replace during development), and the embedded docs are built from that tag, so the page, the docs and the code agree. The user confirms before the tag is created and pushed. — **Reversibility:** one-way — a pushed Go module version tag is cached by proxies and cannot be reused.
- **D-43:** The new repos live as siblings of `summercms.go` in the meta repo directory (`summercms/sm-summercmsio-app/`, like `fonoteka.go`). `vue-summercmsio-app` and `sm-summercmsio-plugin` are submodules inside `sm-summercmsio-app`. Creating the remotes on git.golem15.com is a manual step the plan lists for the user.
- **D-44:** Every link on the page is verified against the built docs output (roadmap criterion 3), not assumed. As of 2026-10-01 all ten `/docs/...` targets in the handoff exist as pages in `docs/`.
### Claude's Discretion
- The system user, port and filesystem paths on rome (D-32).
- Whether the Nuxt build and the docs are embedded by `sm-summercms-plugin` or by the root app, and how the plugin mounts `/docs`.
- Whether the Nuxt build and the docs are embedded by `sm-summercmsio-plugin` or by the root app, and how the plugin mounts `/docs`.
- The 404 handling for unknown paths (for example Nuxt's generated `404.html` with a 404 status).
- Image formats and sizes derived from the sun artwork, and the OG image layout within the brand.
- Where the D-40 command check lives (root app script or test) and how it shares the command list with the page.
@@ -126,7 +127,7 @@ Out of scope: newsletter signup, the Polish locale and the subscriber admin. The
## Existing Code Insights
### Reusable Assets
- `boardwalk`: its `go:embed` handling and its hashed-asset versus HTML cache headers are a pattern to copy into `sm-summercms-plugin`. Its `noindex`, admin CSP and base-path rewrite are not.
- `boardwalk`: its `go:embed` handling and its hashed-asset versus HTML cache headers are a pattern to copy into `sm-summercmsio-plugin`. Its `noindex`, admin CSP and base-path rewrite are not.
- `summer docs:build --base-url /docs`: already emits docs with the right link prefix for mounting under `/docs`.
- `summer build`: generates the app's `main.go` and `plugins.gen.go` from `summer.yaml`, as in `fonoteka.go`.
- The 11.1 docs checker (`TestDocsTree`, `docs:build --check`): it already verifies internal links and commands across `docs/`, so the D-41 change must keep it green.
@@ -138,7 +139,7 @@ Out of scope: newsletter signup, the Polish locale and the subscriber admin. The
- Unit tests are the last plan of the phase. `go vet` and `go test ./...` stay green in every repo.
### Integration Points
- `sm-summercms-app` registers `sm-summercms-plugin`, and the plugin's routes serve `/` (Nuxt output) and `/docs` (docs output) from the surf mux.
- `sm-summercmsio-app` registers `sm-summercmsio-plugin`, and the plugin's routes serve `/` (Nuxt output) and `/docs` (docs output) from the surf mux.
- nginx on rome proxies `/` and `/docs` to the binary on localhost and denies the admin paths (D-29).
- Phase 11.2 depends on 11.1 being executed, because the docs build output feeds the embed step.

View File

@@ -4,7 +4,7 @@
**Files analyzed:** 27 (new or modified, across 4 repos)
**Analogs found:** 22 / 27
Paths: `FW` = `summercms/summercms.go` (framework), `APP` = `summercms/sm-summercms-app` (new), `PLUG` = `APP/plugins/golem15/summercms` (new submodule, module `git.golem15.com/golem15/sm-summercms-plugin`), `SITE` = `APP/vue-summercms-app` (new submodule), `FON` = `summercms/fonoteka.go`, `VFON` = `/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app`. All framework analog paths below are git-tracked source (checked with `git ls-files`); none are mirrors.
Paths: `FW` = `summercms/summercms.go` (framework), `APP` = `summercms/sm-summercmsio-app` (new), `PLUG` = `APP/plugins/golem15/summercms` (new submodule, module `git.golem15.com/golem15/sm-summercmsio-plugin`), `SITE` = `APP/vue-summercmsio-app` (new submodule), `FON` = `summercms/fonoteka.go`, `VFON` = `/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app`. All framework analog paths below are git-tracked source (checked with `git ls-files`); none are mirrors.
## File Classification
@@ -321,7 +321,7 @@ require (
...
replace git.golem15.com/golem15/fonoteka/plugins/golem15/user => ./plugins/golem15/user
```
→ module `git.golem15.com/golem15/sm-summercms-app`, require `summercms v0.1.0` (D-42) and `sm-summercms-plugin v0.0.0` with `replace ... => ./plugins/golem15/summercms`. Prefer generating these with `summer plugin:add plugins/golem15/summercms`.
→ module `git.golem15.com/golem15/sm-summercmsio-app`, require `summercms v0.1.0` (D-42) and `sm-summercmsio-plugin v0.0.0` with `replace ... => ./plugins/golem15/summercms`. Prefer generating these with `summer plugin:add plugins/golem15/summercms`.
`FON/summer.yaml`:
```yaml
@@ -331,7 +331,7 @@ plugins:
- id: golem15.user
module: git.golem15.com/golem15/fonoteka/plugins/golem15/user
```
→ `binary: summercms-io`, one plugin `golem15.summercms` / `git.golem15.com/golem15/sm-summercms-plugin`.
→ `binary: summercms-io`, one plugin `golem15.summercms` / `git.golem15.com/golem15/sm-summercmsio-plugin`.
`FON/.gitignore`: `/bin/`, `/tmp/`, `*.exe`, `go.work.sum` → add `.env`, `/storage/`.
@@ -361,7 +361,7 @@ plus explicit `if [[ ! -f ... ]]; then echo "build: ... " >&2; exit 1; fi` guard
### `APP/terminal_check_test.go` (e2e, gated `SUMMERCMS_TERMINAL_CHECK=1`)
Partial analog `FW/examples/hello/hello_test.go` (exercises the hello binary). Use `os/exec` with `bash -euo pipefail -c`, temp `GOBIN` first on PATH, `GOWORK` unset, stdin `/dev/null`, optional `SUMMERCMS_CLONE_URL` substitution; read commands from `vue-summercms-app/app/data/terminal.json` (RESEARCH Pattern 3).
Partial analog `FW/examples/hello/hello_test.go` (exercises the hello binary). Use `os/exec` with `bash -euo pipefail -c`, temp `GOBIN` first on PATH, `GOWORK` unset, stdin `/dev/null`, optional `SUMMERCMS_CLONE_URL` substitution; read commands from `vue-summercmsio-app/app/data/terminal.json` (RESEARCH Pattern 3).
---

View File

@@ -13,12 +13,12 @@ The 11.2 planner applies D-01 to D-05, D-07, D-09, D-12 and D-23 to D-45. D-06,
**Repos and layout (2026-09-29, still in force)**
- **D-01:** Three new repos follow the `sm-` naming convention (`oc-`/`wn-` in October/Winter):
- `sm-summercms-app` is the root app. It builds the binary, holds the other pieces and wires the plugins.
- `vue-summercms-app` is the Nuxt 4 site, held inside the root app as `vue-fonoteka-app` is in the fonoteka project.
- `sm-summercms-plugin` is the site plugin. It holds everything specific to summercms.io: serving the embedded Nuxt build at `/`, mounting the 11.1 docs output at `/docs`, and any site-specific data or routes. This is the proven WinterCMS pattern from `figs.org.pl` (`figs/website`) and golem15.com.
- `sm-summercmsio-app` is the root app. It builds the binary, holds the other pieces and wires the plugins.
- `vue-summercmsio-app` is the Nuxt 4 site, held inside the root app as `vue-fonoteka-app` is in the fonoteka project.
- `sm-summercmsio-plugin` is the site plugin. It holds everything specific to summercms.io: serving the embedded Nuxt build at `/`, mounting the 11.1 docs output at `/docs`, and any site-specific data or routes. This is the proven WinterCMS pattern from `figs.org.pl` (`figs/website`) and golem15.com.
Public static-site serving goes in `sm-summercms-plugin`, not in a new framework module. (The fourth repo, `sm-newsletter-plugin`, moved to 11.3.) — **Reversibility:** costly — repo names and module paths are referenced by the go.work/replace wiring and every import.
- **D-02:** Go module paths live under `git.golem15.com/golem15/`, like the framework (`git.golem15.com/golem15/summercms`): for example `git.golem15.com/golem15/sm-summercms-plugin`. — **Reversibility:** one-way — a published Go module path is baked into every importer.
Public static-site serving goes in `sm-summercmsio-plugin`, not in a new framework module. (The fourth repo, `sm-newsletter-plugin`, moved to 11.3.) — **Reversibility:** costly — repo names and module paths are referenced by the go.work/replace wiring and every import.
- **D-02:** Go module paths live under `git.golem15.com/golem15/`, like the framework (`git.golem15.com/golem15/summercms`): for example `git.golem15.com/golem15/sm-summercmsio-plugin`. — **Reversibility:** one-way — a published Go module path is baked into every importer.
- **D-03:** The root app wires the framework and the site plugin the way `fonoteka.go` does today (a go.work workspace with a local replace during development).
**Website build and hosting (2026-09-29, still in force)**
@@ -40,7 +40,7 @@ The 11.2 planner applies D-01 to D-05, D-07, D-09, D-12 and D-23 to D-45. D-06,
- **D-27:** The app uses a dedicated Postgres role (`summercms`) that owns a dedicated database (`summercms_io`). The password lives in the app's config or env on the server, never in git. DEPLOY.md documents `createuser`/`createdb`, and every deploy runs the app's `migrate` command before restarting.
**Server and deploy flow**
- **D-28:** The binary is built locally for linux/amd64 by a script in `sm-summercms-app` (nuxt generate, then `summer docs:build --base-url /docs`, then `go build`), then rsynced to rome with its config. The server needs neither Go nor Node.
- **D-28:** The binary is built locally for linux/amd64 by a script in `sm-summercmsio-app` (nuxt generate, then `summer docs:build --base-url /docs`, then `go build`), then rsynced to rome with its config. The server needs neither Go nor Node.
- **D-29:** `/backend` and the admin API are not exposed publicly. nginx proxies only `/` and `/docs` to the binary and denies the admin paths. Nothing needs administering until 11.3.
- **D-30:** TLS uses rome's existing certbot / Let's Encrypt setup. DEPLOY.md shows the nginx server block with the certbot-managed certificate paths, HTTP to HTTPS redirect, gzip and proxy headers.
- **D-31:** Cutover replaces the existing summercms.io nginx server block in place. The current "Under construction" block is saved in DEPLOY.md (or alongside it) as the rollback.
@@ -61,13 +61,13 @@ The 11.2 planner applies D-01 to D-05, D-07, D-09, D-12 and D-23 to D-45. D-06,
**Docs and release**
- **D-41:** Framework change: an optional `site_url` key in `docs/site.yaml` adds a link back to the main site in the docs header (`internal/docsite/theme/templates/header.html`), for example "← summercms.io". When the key is unset, the output is unchanged. The change also updates the docs page that describes `site.yaml`, and `summer docs:build --check` and `TestDocsTree` stay green. summercms.io sets it to `/`.
- **D-42:** The framework is tagged `v0.1.0` (Alpha 0.1). `sm-summercms-app` requires `git.golem15.com/golem15/summercms v0.1.0` (with the local replace during development), and the embedded docs are built from that tag, so the page, the docs and the code agree. The user confirms before the tag is created and pushed. — **Reversibility:** one-way — a pushed Go module version tag is cached by proxies and cannot be reused.
- **D-43:** The new repos live as siblings of `summercms.go` in the meta repo directory (`summercms/sm-summercms-app/`, like `fonoteka.go`). `vue-summercms-app` and `sm-summercms-plugin` are submodules inside `sm-summercms-app`. Creating the remotes on git.golem15.com is a manual step the plan lists for the user.
- **D-42:** The framework is tagged `v0.1.0` (Alpha 0.1). `sm-summercmsio-app` requires `git.golem15.com/golem15/summercms v0.1.0` (with the local replace during development), and the embedded docs are built from that tag, so the page, the docs and the code agree. The user confirms before the tag is created and pushed. — **Reversibility:** one-way — a pushed Go module version tag is cached by proxies and cannot be reused.
- **D-43:** The new repos live as siblings of `summercms.go` in the meta repo directory (`summercms/sm-summercmsio-app/`, like `fonoteka.go`). `vue-summercmsio-app` and `sm-summercmsio-plugin` are submodules inside `sm-summercmsio-app`. Creating the remotes on git.golem15.com is a manual step the plan lists for the user.
- **D-44:** Every link on the page is verified against the built docs output (roadmap criterion 3), not assumed. As of 2026-10-01 all ten `/docs/...` targets in the handoff exist as pages in `docs/`.
### Claude's Discretion
- The system user, port and filesystem paths on rome (D-32).
- Whether the Nuxt build and the docs are embedded by `sm-summercms-plugin` or by the root app, and how the plugin mounts `/docs`.
- Whether the Nuxt build and the docs are embedded by `sm-summercmsio-plugin` or by the root app, and how the plugin mounts `/docs`.
- The 404 handling for unknown paths (for example Nuxt's generated `404.html` with a 404 status).
- Image formats and sizes derived from the sun artwork, and the OG image layout within the brand.
- Where the D-40 command check lives (root app script or test) and how it shares the command list with the page.
@@ -88,8 +88,8 @@ The 11.2 planner applies D-01 to D-05, D-07, D-09, D-12 and D-23 to D-45. D-06,
## Choices made in this planning session (orchestrator-provided, binding for the planner)
- **Plan count is fixed at 3 plans:**
- **01**: the Nuxt site (`vue-summercms-app`).
- **02**: framework tweaks (PG15 verification and docs, the optional `site_url`, the v0.1.0 tag checkpoint), then `sm-summercms-plugin`, `sm-summercms-app`, the build script, the D-40 command check, the link check, and DEPLOY.md with the nginx and supervisor configs.
- **01**: the Nuxt site (`vue-summercmsio-app`).
- **02**: framework tweaks (PG15 verification and docs, the optional `site_url`, the v0.1.0 tag checkpoint), then `sm-summercmsio-plugin`, `sm-summercmsio-app`, the build script, the D-40 command check, the link check, and DEPLOY.md with the nginx and supervisor configs.
- **03**: unit tests.
- **No UI-SPEC.md.** The `design/` handoff (`README.md` and `SummerCMS Landing.dc.html`) is the design contract (D-23).
</session_choices>
@@ -101,8 +101,8 @@ No requirement IDs are mapped (ROADMAP says "Requirements: TBD"). The five ROADM
| ID | Success criterion (ROADMAP § Phase 11.2) | Research support |
|----|------------------------------------------|------------------|
| SC1 | `nuxt generate` in `vue-summercms-app` produces the landing page matching the handoff, with a sticky header and scroll-spy, a hero, the Why, Features, From WinterCMS and Get started sections, a terminal card with a working Copy button, and a footer. It is responsive down to phone width, and the section links hide at 720px or less. | §Design handoff inventory; Nuxt pattern 1; spike-verified `nuxt.config.ts` (Pitfalls 1–6) |
| SC2 | One `sm-summercms-app` binary embeds the Nuxt output and the 11.1 docs build, and serves `/` and `/docs` with indexable responses (no admin `noindex`/CSP). Cache headers are immutable for hashed assets and no-cache for HTML. | §Framework surface; Pattern 2 (site handler); cache table; Pitfalls 7–11 |
| SC1 | `nuxt generate` in `vue-summercmsio-app` produces the landing page matching the handoff, with a sticky header and scroll-spy, a hero, the Why, Features, From WinterCMS and Get started sections, a terminal card with a working Copy button, and a footer. It is responsive down to phone width, and the section links hide at 720px or less. | §Design handoff inventory; Nuxt pattern 1; spike-verified `nuxt.config.ts` (Pitfalls 1–6) |
| SC2 | One `sm-summercmsio-app` binary embeds the Nuxt output and the 11.1 docs build, and serves `/` and `/docs` with indexable responses (no admin `noindex`/CSP). Cache headers are immutable for hashed assets and no-cache for HTML. | §Framework surface; Pattern 2 (site handler); cache table; Pitfalls 7–11 |
| SC3 | Every link on the page resolves, including the eight feature-tile `/docs/...` links and the WinterCMS concept-map link, verified against the built docs. | All ten targets verified against a real `docs:build --base-url /docs`; the `.html` URL finding; link-check design |
| SC4 | A scripted build (`nuxt generate`, `summer docs:build`, `go build`) and a `DEPLOY.md` cover building, uploading, a supervisor program config and an nginx site config (TLS, proxy, gzip). Following them on a clean server brings the site up. | §Build script; §DEPLOY.md content requirements; boot config requirements (Pitfall 12) |
| SC5 | The new Go code has unit tests, delivered in the last plan. | §Validation Architecture |
@@ -136,7 +136,7 @@ Five findings change the plan:
The D-40 terminal commands were run verbatim in a fresh local clone with a temp `GOBIN`. `./bin/hello greeter:hello` needs no database and printed `name=hello-app ... handled=true`, exit 0. The only blocker for running the commands exactly as written is the `git clone` URL. `https://git.golem15.com/golem15/summercms` returns 404 to anonymous users and `git ls-remote` asks for credentials (verified). So the check needs a clone-source override until the repo is public, and it runs verbatim at cutover.
**Primary recommendation:**
- Embed `all:public` in `sm-summercms-plugin`. The build script fills `public/site` (Nuxt) and `public/docs` (docs from a `git archive v0.1.0` export), and a placeholder `public/README.md` is committed.
- Embed `all:public` in `sm-summercmsio-plugin`. The build script fills `public/site` (Nuxt) and `public/docs` (docs from a `git archive v0.1.0` export), and a placeholder `public/README.md` is committed.
- Serve both trees with one stdlib handler that has its own MIME table, extension-less `.html` resolution, 404 pages with a 404 status, and per-path cache rules.
- Keep the Nuxt config exactly as spike-verified below, with versions pinned to `vue-fonoteka-app`'s lockfile.
@@ -148,14 +148,14 @@ The D-40 terminal commands were run verbatim in a fresh local clone with a temp
| SEO metadata, sitemap, robots, schema.org | Build time (`@nuxtjs/seo` during prerender) | — | Emitted as static files and tags; nothing renders at runtime (D-37). |
| Fonts | Build time (`@nuxt/fonts` downloads to `/_fonts`) | CDN/Static (served by the binary) | No Google request at runtime (D-34). |
| Docs site | Build time (`summer docs:build` from the v0.1.0 tag) | Static (served by the binary) | Deterministic output: two builds were byte-identical. |
| Serving `/` and `/docs`, cache headers, MIME types, 404s | API / Backend (Go binary, `sm-summercms-plugin` handler on the surf mux) | — | One binary (D-05); the plugin owns site specifics (D-01). |
| Serving `/` and `/docs`, cache headers, MIME types, 404s | API / Backend (Go binary, `sm-summercmsio-plugin` handler on the surf mux) | — | One binary (D-05); the plugin owns site specifics (D-01). |
| TLS, gzip, HTTP→HTTPS, www→apex, denying `/backend`, method restriction | Reverse proxy (nginx on rome) | — | D-29/D-30; the binary listens on loopback only. |
| Process lifecycle | OS (supervisor) | — | D-32. |
| Persistence | Database (Postgres 15 `summercms_io`) | — | Required by `serve` (D-24); only framework tables (system_files, backend admin, queue) are migrated. |
## Standard Stack
### Core (frontend: `vue-summercms-app`)
### Core (frontend: `vue-summercmsio-app`)
Versions are pinned to what `vue-fonoteka-app` runs today. Read from its `node_modules` this session, and the same versions were installed and built in the spike.
@@ -189,7 +189,7 @@ Sub-modules resolved inside `@nuxtjs/seo` 5.2.1 in the spike: `@nuxtjs/sitemap`
| `node --test` for TS logic | vitest | vitest is another dependency (latest is 5.x and fonoteka is on 3.x). Built-in `node:test` is enough for two pure functions. |
| ImageMagick one-off | sharp in devDependencies | sharp is flagged SUS (too-new, 0.35.5 published 2026-09-27) and has native binaries. It is not needed when the outputs are committed. |
**Installation (vue-summercms-app):**
**Installation (vue-summercmsio-app):**
```bash
pnpm add nuxt@4.4.8 @nuxt/fonts@0.14.0 @nuxtjs/i18n@10.4.0 @nuxtjs/seo@5.2.1 vue@3.5.35
pnpm add -D typescript@~5.9.0
@@ -261,7 +261,7 @@ Do **not** copy:
- `summer build` writes `main.go` and `plugins.gen.go` (only when changed), checks the models leaf, then runs `go build -o bin/<binary> .` with `GOWORK` set to the nearest `go.work` (build.go:19-77; scaffold.go:519-541). It inherits the environment, so `GOOS`/`GOARCH` pass through. Rome and the local host are both linux/amd64.
- The generated `main.go` registers `lagoon.RuntimeCommands` (`migrate`, `migrate:rollback`, `migrate:status`, `key:generate`), `conga.RuntimeCommands`, `surf.ServeCommand`, `surf.RouteListCommand`, `cabana.RuntimeCommands` and plugin `HasCommands` [VERIFIED: build.go:114-123; lagoon/commands.go:16-75]. **D-27's migrate command is `./bin/summercms-io migrate`** (`summer migrate` delegates to the same binary, cmd/summer/runtime.go:16-26).
- `compass.Load("config")` is relative to the **working directory** (build.go:105). Supervisor's `directory=` must therefore be the app dir. `SUMMER_ENV` defaults to `production`. A `.env` next to `config/` supplies `KEY=VALUE` lines not already in the environment [VERIFIED: modules/compass/README.md:90-97].
- `summer plugin:add <dir>` adds the plugin to `summer.yaml`, runs `go mod edit -require=<mod>@v0.0.0 -replace=<mod>=./<rel>` and `go work use <dir>` (scaffold.go:106-170, 318-370; docs/console/scaffolding.md:34). It reads the plugin ID from the plugin's `ID()` return literal (scaffold.go:284-316). Use it after creating the plugin by hand, because `make:plugin` would set the module path to `<app module>/plugins/<name>` (scaffold.go:75), not `git.golem15.com/golem15/sm-summercms-plugin`.
- `summer plugin:add <dir>` adds the plugin to `summer.yaml`, runs `go mod edit -require=<mod>@v0.0.0 -replace=<mod>=./<rel>` and `go work use <dir>` (scaffold.go:106-170, 318-370; docs/console/scaffolding.md:34). It reads the plugin ID from the plugin's `ID()` return literal (scaffold.go:284-316). Use it after creating the plugin by hand, because `make:plugin` would set the module path to `<app module>/plugins/<name>` (scaffold.go:75), not `git.golem15.com/golem15/sm-summercmsio-plugin`.
### `fonoteka.go` reference wiring (D-03)
@@ -271,7 +271,7 @@ Do **not** copy:
- Config: `config/{app,http,storage,database,queue,...}.yaml`. `database.yaml` has `dsn: ""` (env-provided). `app.yaml` has `key: ""` with the comment "Set SUMMER_APP__KEY". `http.yaml` has `body_limits`. `storage.yaml` has `uploads.bucket_url: "file://./storage/app/uploads"`. `queue.yaml` has `work_in_serve: true`.
- `.gitignore`: `/bin/`, `/tmp/`, `go.work.sum`.
For `sm-summercms-app` with the plugin submodule at `plugins/golem15/summercms`, the plugin's framework replace is `../../../../summercms.go`. That is the same depth as fonoteka, and it resolves to the meta-repo sibling `summercms/summercms.go`.
For `sm-summercmsio-app` with the plugin submodule at `plugins/golem15/summercms`, the plugin's framework replace is `../../../../summercms.go`. That is the same depth as fonoteka, and it resolves to the meta-repo sibling `summercms/summercms.go`.
### Postgres-at-boot config the app must ship (D-24)
@@ -531,9 +531,9 @@ Copy payload: `git clone https://git.golem15.com/golem15/summercms\ncd summercms
### System Architecture Diagram
```
BUILD (local, scripts/build.sh in sm-summercms-app)
BUILD (local, scripts/build.sh in sm-summercmsio-app)
┌──────────────────────────┐ pnpm install --frozen-lockfile + nuxt generate
│ vue-summercms-app (sub) │──────────────► .output/public/ ──rsync -a --delete──┐
│ vue-summercmsio-app (sub) │──────────────► .output/public/ ──rsync -a --delete──┐
└──────────────────────────┘ ▼
┌──────────────────────────┐ git archive v0.1.0 → tmp; go run ./cmd/summer plugins/golem15/summercms/public/site/
│ ../summercms.go @ v0.1.0 │ docs:build --base-url /docs --site-url / ────────► plugins/golem15/summercms/public/docs/
@@ -560,7 +560,7 @@ Copy payload: `git clone https://git.golem15.com/golem15/summercms\ncd summercms
```
summercms/ # meta repo dir (siblings)
├── summercms.go/ # framework (D-25, D-41, D-42 changes only)
└── sm-summercms-app/ # root app, module git.golem15.com/golem15/sm-summercms-app
└── sm-summercmsio-app/ # root app, module git.golem15.com/golem15/sm-summercmsio-app
├── go.mod go.work summer.yaml # replace summercms => ../summercms.go; use . ./plugins/golem15/summercms
├── main.go plugins.gen.go # generated by summer build (committed, like fonoteka)
├── config/{app,http,storage,database,queue}.yaml # no secrets; work_in_serve: false
@@ -568,7 +568,7 @@ summercms/ # meta repo dir (siblings)
├── deploy/{nginx-summercms.io.conf, supervisor-summercms-io.conf, rollback/} # referenced by DEPLOY.md
├── DEPLOY.md README.md .gitignore (/bin/ /tmp/ go.work.sum .env)
├── terminal_check_test.go # D-40 (gated by SUMMERCMS_TERMINAL_CHECK=1)
├── vue-summercms-app/ # submodule (Nuxt 4)
├── vue-summercmsio-app/ # submodule (Nuxt 4)
│ ├── nuxt.config.ts app/app.config.ts app/app.vue app/pages/index.vue
│ ├── app/components/{SiteHeader,HeroSection,WhySection,FeaturesSection,WinterSection,StartSection,TerminalCard,SiteFooter}.vue
│ ├── app/assets/css/{tokens.css,base.css}
@@ -578,7 +578,7 @@ summercms/ # meta repo dir (siblings)
│ ├── public/{og-image.png,favicon.ico,favicon-32x32.png,apple-touch-icon.png,sun-*.png|webp}
│ ├── scripts/derive-images.sh # ImageMagick one-off from logo.png
│ └── tests/*.test.ts # node --test
└── plugins/golem15/summercms/ # submodule sm-summercms-plugin, module git.golem15.com/golem15/sm-summercms-plugin
└── plugins/golem15/summercms/ # submodule sm-summercmsio-plugin, module git.golem15.com/golem15/sm-summercmsio-plugin
├── go.mod (replace summercms => ../../../../summercms.go)
├── plugin.go (ID golem15.summercms, Routes, embed all:public)
├── static.go (handler: resolve, MIME, cache, ETag, 404)
@@ -679,7 +679,7 @@ Handler rules:
- `.js`/`.mjs` → `text/javascript; charset=utf-8`
### Pattern 3: Single-source terminal commands (D-40)
`vue-summercms-app/app/data/terminal.json` (imported by `TerminalCard.vue`; comments are i18n keys):
`vue-summercmsio-app/app/data/terminal.json` (imported by `TerminalCard.vue`; comments are i18n keys):
```json
{ "groups": [
{ "comment": "terminal.getFramework", "commands": ["git clone https://git.golem15.com/golem15/summercms", "cd summercms"] },
@@ -687,7 +687,7 @@ Handler rules:
{ "comment": "terminal.buildExample", "commands": ["cd examples/hello", "summer build", "./bin/hello greeter:hello"] }
] }
```
- **Check (Go test in sm-summercms-app, `TestTerminalCommands`):**
- **Check (Go test in sm-summercmsio-app, `TestTerminalCommands`):**
- Skip unless `SUMMERCMS_TERMINAL_CHECK=1`.
- Read the JSON and make a temp dir and a temp `GOBIN`, with `PATH=$GOBIN:$PATH`, `GOWORK` unset and no `SUMMER_*` env.
- Run `bash -euo pipefail -c "<commands joined by \n>"` in the temp dir with stdin `/dev/null`.
@@ -769,7 +769,7 @@ It also claims `main.go` drifts, which was not reproduced. Irrelevant to the pag
## Code Examples
### Build script outline (`sm-summercms-app/scripts/build.sh`)
### Build script outline (`sm-summercmsio-app/scripts/build.sh`)
```bash
#!/usr/bin/env bash
# Source: composed from verified steps in this session. [ASSUMED script; each command VERIFIED individually]
@@ -778,8 +778,8 @@ APP="$(cd "$(dirname "$0")/.." && pwd)"; FW="$APP/../summercms.go"; PLUG="$APP/p
TAG="$(cd "$APP" && go list -m -f '{{.Version}}' git.golem15.com/golem15/summercms)" # v0.1.0 from require
MODE="${1:-release}" # release: docs from $TAG export; dev: docs from working tree (HEAD export)
# 1) Nuxt
(cd "$APP/vue-summercms-app" && pnpm install --frozen-lockfile && pnpm generate)
rsync -a --delete "$APP/vue-summercms-app/.output/public/" "$PLUG/public/site/"
(cd "$APP/vue-summercmsio-app" && pnpm install --frozen-lockfile && pnpm generate)
rsync -a --delete "$APP/vue-summercmsio-app/.output/public/" "$PLUG/public/site/"
# 2) Docs from the tag (D-42)
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
REF="$TAG"; [ "$MODE" = dev ] && REF=HEAD
@@ -860,7 +860,7 @@ In release mode, to make "code agrees with tag" true for the binary as well, eit
| Property | Value |
|----------|-------|
| Framework | Go `testing` (stdlib, `net/http/httptest`, `testing/fstest`), plus `node:test` (built-in, runs `.ts` natively on Node 22.23) |
| Config file | none. Go tests per repo. `vue-summercms-app/package.json` script `"test": "node --test tests/*.test.ts"` |
| Config file | none. Go tests per repo. `vue-summercmsio-app/package.json` script `"test": "node --test tests/*.test.ts"` |
| Quick run command | plugin: `go test ./...` (in `plugins/golem15/summercms`); framework: `go test ./internal/docsite/ ./cmd/summer -run 'TestParseSite\|TestDocsTree\|TestBuildSiteMarkers'` |
| Full suite command | framework: `go vet ./... && go test ./...`; app: `scripts/build.sh dev` (runs the plugin tests with `SUMMERCMS_REQUIRE_BUILD=1`) then `go vet ./... && go test ./...`; site: `pnpm generate && pnpm test` |
@@ -889,9 +889,9 @@ In release mode, to make "code agrees with tag" true for the binary as well, eit
- **Phase gate:** all of the above, plus `SUMMERCMS_TERMINAL_CHECK=1` with a local clone override, `scripts/smoke.sh` on postgres:15, `nginx -t` of the shipped config, and the manual browser UAT. External links and verbatim clone are checked at cutover after D-38.
### Wave 0 Gaps
- [ ] `vue-summercms-app/tests/{output,scrollSpy,terminal}.test.ts` and the `"test"` script
- [ ] `vue-summercmsio-app/tests/{output,scrollSpy,terminal}.test.ts` and the `"test"` script
- [ ] `plugins/golem15/summercms/{static_test.go,routes_test.go,links_test.go}`, with fixtures via `fstest.MapFS`
- [ ] `sm-summercms-app/terminal_check_test.go`, `scripts/smoke.sh`, `scripts/check-nginx.sh` (optional)
- [ ] `sm-summercmsio-app/terminal_check_test.go`, `scripts/smoke.sh`, `scripts/check-nginx.sh` (optional)
- [ ] framework: extend `internal/docsite/load_test.go` (`site_url` cases) and `theme_test.go` (header link present/absent)
## Security Domain

View File

@@ -19,8 +19,8 @@ created: "2026-10-01"
| Property | Value |
|----------|-------|
| **Framework** | Go `testing` (stdlib, `httptest`, `testing/fstest`) in summercms.go, sm-summercms-plugin and sm-summercms-app; `node:test` in vue-summercms-app |
| **Config file** | none; `vue-summercms-app/package.json` script `"test": "node --test tests/*.test.ts"` (Wave 0) |
| **Framework** | Go `testing` (stdlib, `httptest`, `testing/fstest`) in summercms.go, sm-summercmsio-plugin and sm-summercmsio-app; `node:test` in vue-summercmsio-app |
| **Config file** | none; `vue-summercmsio-app/package.json` script `"test": "node --test tests/*.test.ts"` (Wave 0) |
| **Quick run command** | touched repo: `go vet ./... && go test ./...`; site: `pnpm generate && pnpm test` |
| **Full suite command** | framework `go vet ./... && go test ./...`; app `scripts/build.sh dev` then `go vet ./... && go test ./...` (plugin with `SUMMERCMS_REQUIRE_BUILD=1`); site `pnpm generate && pnpm test` |
| **Estimated runtime** | ~180 seconds (framework DB suites dominate) |
@@ -62,9 +62,9 @@ Filled from the PLAN.md files once written. Requirement → check map:
## Wave 0 Requirements
- [ ] `vue-summercms-app/tests/{output,scrollSpy,terminal}.test.ts` and the `"test"` script
- [ ] sm-summercms-plugin `static_test.go`, `routes_test.go`, `links_test.go` with `fstest.MapFS` fixtures
- [ ] sm-summercms-app `terminal_check_test.go`, `scripts/smoke.sh`, optional `scripts/check-nginx.sh`
- [ ] `vue-summercmsio-app/tests/{output,scrollSpy,terminal}.test.ts` and the `"test"` script
- [ ] sm-summercmsio-plugin `static_test.go`, `routes_test.go`, `links_test.go` with `fstest.MapFS` fixtures
- [ ] sm-summercmsio-app `terminal_check_test.go`, `scripts/smoke.sh`, optional `scripts/check-nginx.sh`
- [ ] framework: extend `internal/docsite/load_test.go` and `theme_test.go` for `site_url`
---