diff --git a/.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-02-SUMMARY.md b/.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-02-SUMMARY.md new file mode 100644 index 0000000..24c35ca --- /dev/null +++ b/.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-02-SUMMARY.md @@ -0,0 +1,337 @@ +--- +phase: 11.2-ready-to-share-summercms-io-website-and-newsletter-plugin +plan: 02 +subsystem: infra +tags: [go-embed, static-site, nuxt, docsite, nginx, supervisor, postgres15, release] + +requires: + - phase: 11.2-01 + provides: vue-summercmsio-app (Nuxt landing page, terminal.json single command source, en.json copy) + - phase: 11.1 + provides: summer docs:build static docs site and the docs checker +provides: + - sm-summercmsio-plugin (golem15.summercms) serving the embedded Nuxt build at / and the docs at /docs + - sm-summercmsio-app with go.work workspace, config, build (release/dev), smoke and check-deploy scripts + - framework docs say PostgreSQL 15 or newer (verified on postgres:15) + - docsite site_url/site_label keys and --site-url/--site-label flags (header link back to the main site) + - link, terminal-command, docs-header and external-link checks against the built site + - DEPLOY.md with nginx, supervisor, env template and rollback copy for rome +affects: [11.2-03, cutover, v0.1.0 tag] + +actuals: + tokens: 24561 + tasks: 5 + commits: 2 + commits_all_repos: 8 +plan_head_before: 130b6ac38f9ae8feaf5e42c107a47febb804ff48 +plan_head_after: a494375db74c27683c0a652e1f96556568893710 + +tech-stack: + added: [] + patterns: + - "Site plugin embeds build output with //go:embed all:public and serves in-memory trees with strong ETags and per-path Cache-Control" + - "Release builds compile the framework from a tag export through a temporary go.work replace" + - "Env-gated build-dependent tests (SUMMERCMS_REQUIRE_BUILD, SUMMERCMS_TERMINAL_CHECK, SUMMERCMS_CHECK_EXTERNAL)" + +key-files: + created: + - ../sm-summercmsio-app/plugins/golem15/summercms/plugin.go + - ../sm-summercmsio-app/plugins/golem15/summercms/static.go + - ../sm-summercmsio-app/plugins/golem15/summercms/links_test.go + - ../sm-summercmsio-app/plugins/golem15/summercms/README.md + - ../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/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 + modified: + - README.md + - docs/setup/installation.md + - internal/docsite/load.go + - internal/docsite/docsite.go + - internal/docsite/emit.go + - internal/docsite/theme/templates/header.html + - internal/docsite/theme/assets/site.css + - cmd/summer/docs.go + - docs/console/utilities.md + +key-decisions: + - "D-42 resolved as defer-tag: no v0.1.0 tag created or pushed in summercms.go; the release path was proven against a scratch clone with a local tag, and creating + pushing v0.1.0 (and pushing master) is a DEPLOY.md launch step" + - "Terminal check clone override appends the page's clone directory (git clone summercms) so the page's own cd summercms works for a local path override" + - "check-deploy.sh moves listen ports to unprivileged loopback ports in its temp copy because nginx -t binds the listeners" + - "DEPLOY.md installs the supervisor program on the first release (after the binary exists) and uploads with --rsync-path=\"sudo rsync\" into root-owned bin/ and config/" + +patterns-established: + - "Build-dependent plugin tests skip unless SUMMERCMS_REQUIRE_BUILD=1, and fail (not skip) on a missing build when it is set" + - "Landing links are resolved through the real surf-assembled handler with at most one root-relative 301" + +requirements-completed: [] + +coverage: + - id: D1 + description: "One summercms-io binary embeds the Nuxt site and the docs and serves / and /docs with indexable responses, cache rules, ETag/304, 301s, real 404s, no /backend and 405 on POST, booted on postgres:15" + verification: + - kind: e2e + ref: "scripts/smoke.sh (release binary, postgres:15) -> smoke: ok" + status: pass + - kind: unit + ref: "plugins/golem15/summercms/smoke_test.go#TestStaticSmoke" + status: pass + human_judgment: false + - id: D2 + description: "Framework database suites pass on postgres:15 and README/installation docs say PostgreSQL 15 or newer (D-25)" + verification: + - kind: integration + ref: "HEAD export with postgres:15: go test -count=1 -p 4 lagoon, lagoon/attach, cabana, beachcomber, lighthouse, bouncer, conga, docs/examples/blog" + status: pass + human_judgment: false + - id: D3 + description: "docsite site_url/site_label keys and --site-url/--site-label flags add a validated header link back to the main site; unset output unchanged apart from additive CSS (D-41/D-46)" + verification: + - kind: unit + ref: "go test ./internal/docsite ./cmd/summer -run '^(TestDocsTree|TestDocsBuildRealTree|TestToolCommandNames|TestParseSite|TestSiteLink|TestDocsBuildSiteFlags)$'" + status: pass + - kind: other + ref: "scripts/check-phase11.1.sh --docs && --forbidden" + status: pass + human_judgment: false + - id: D4 + description: "Release build takes docs, summer CLI and compiled framework from the v0.1.0 tag (proven on a scratch clone with a local tag; real tag deferred)" + verification: + - kind: e2e + ref: "SUMMERCMS_FRAMEWORK= scripts/build.sh -> build: bin/summercms-io (release v0.1.0) ready; go version -m shows summercms v0.1.0 => /fw" + status: pass + human_judgment: false + - id: D5 + description: "Every landing link resolves against the built site, the page shows every terminal command and comment, and the docs header links back to / (SC3, D-40, D-44, D-46)" + verification: + - kind: integration + ref: "SUMMERCMS_REQUIRE_BUILD=1 plugins/golem15/summercms#TestLandingLinks,TestTerminalCommandsInPage,TestDocsHeaderSiteLink" + status: pass + - kind: e2e + ref: "SUMMERCMS_TERMINAL_CHECK=1 SUMMERCMS_CLONE_URL=../summercms.go go test -run TestTerminalCommands . (handled=true)" + status: pass + human_judgment: false + - id: D6 + description: "DEPLOY.md, nginx and supervisor configs, env template and rollback copy cover launch, setup, build, upload, release, cutover, verification and rollback on rome (SC4)" + verification: + - kind: other + ref: "scripts/check-deploy.sh -> check-deploy: ok (nginx -t + supervisor parse)" + status: pass + human_judgment: true + rationale: "Bringing the site up on rome by following DEPLOY.md (and the external link / verbatim terminal checks, which need the public repository) is the cutover UAT item; it cannot run before the user pushes the repos, makes golem15/summercms public and creates the tag." + +duration: 22min +completed: 2026-10-01 +status: complete +--- + +# Phase 11.2 Plan 02: summercms.io app, site plugin, release path and deploy Summary + +**One `summercms-io` binary that embeds the Nuxt landing page and the SummerCMS docs. It serves them on PostgreSQL 15 with indexable, cache-correct responses. The framework gained PG15 docs and a docs header link back to the site. The release build is proven from a (scratch) v0.1.0 tag, every landing link and terminal command is checked against the built site, and DEPLOY.md covers rome.** + +## Performance + +- **Duration:** 22 min (two executor sessions: tasks 1-2, then the tag checkpoint, then tasks 4-5) +- **Started:** 2026-10-01T14:00:22Z +- **Completed:** 2026-10-01T14:22:03Z +- **Tasks:** 5 (4 auto/tracer, 1 decision checkpoint) +- **Files modified:** 10 in summercms.go, 26 in sm-summercmsio-app plus plugin (excluding generated build output) + +## Accomplishments + +- **Site plugin** `golem15.summercms` (module `git.golem15.com/golem15/sm-summercmsio-plugin`). It embeds `public/` with `all:` and serves `/` and `/docs` from in-memory trees with these behaviours: + - strong ETags with 304 revalidation; + - `immutable` caching for `_nuxt/` (except `builds/`) and `_fonts/`, and `no-cache` for everything else; + - a 301 from extension-less docs URLs to `.html`; + - the tree's own 404 page, and dot-segment paths refused; + - no robots-blocking header, no CSP and no admin. +- **App** `sm-summercmsio-app` is a go.work workspace with the plugin and site as submodules, config without secrets, `summer build` generated sources, and these scripts: + - `scripts/build.sh`, with release (default, from the tag) and dev modes; + - `scripts/smoke.sh`, which runs postgres:15, migrate, serve and HTTP assertions, including the D-46 docs header link; + - `scripts/check-deploy.sh`. +- **Framework (summercms.go):** + - The PG15 suites pass, and README.md and docs/setup/installation.md say "PostgreSQL 15 or newer". + - `docs/site.yaml` accepts the `site_url` and `site_label` keys, and `docs:build` and `docs:serve` take `--site-url` and `--site-label`. Both are validated: `javascript:` and `//host` are rejected. +- **Checks:** + - `TestLandingLinks` checks 30 distinct links through the real `surf.Assemble` handler, including all ten D-44 docs targets and `/docs`. + - `TestTerminalCommandsInPage` is the D-40 drift guard. + - `TestDocsHeaderSiteLink` checks the docs header link back to `/`. + - `TestExternalLinks` runs at cutover. + - `TestTerminalCommands` runs the six page commands from a fresh shell to `handled=true`. +- **DEPLOY.md** covers the launch checklist, server setup, build, upload, release, cutover, verification and rollback. It comes with the nginx config (HTTP→HTTPS, a 443 www→apex block, gzip, `/backend` denied, GET/HEAD only, proxy to 127.0.0.1:8095), the supervisor program, an env template and a copy of the live "Under construction" page. + +## Task Commits + +| Task | Commit | Repo | Message | +|------|--------|------|---------| +| 1 (tracer) | `0ab96ed` | sm-summercmsio-plugin | feat: serve the embedded site at / and the docs at /docs | +| 1 (tracer) | `e0b9b7c` | sm-summercmsio-app | feat: wire the summercms.io app with build and smoke scripts | +| 2A | `7936234` | summercms.go | docs: require PostgreSQL 15 or newer (verified on postgres:15) | +| 2B | `a494375` | summercms.go | feat(docsite): optional site_url and site_label link back to the main site | +| 3 | — | — | decision checkpoint, resolved **defer-tag** (no commit) | +| 4 | `7774763` | sm-summercmsio-plugin | test: verify the landing links and terminal commands against the built site | +| 4 | `699785a` | sm-summercmsio-app | feat: release builds from the v0.1.0 tag and the terminal command check | +| 5 | `fe77fa7` | sm-summercmsio-plugin | docs: describe the site plugin | +| 5 | `77eef5d` | sm-summercmsio-app | docs: add DEPLOY.md with nginx, supervisor and rollback configs | + +Each app commit carries the updated plugin gitlink. Nothing was pushed anywhere, and no commit has a co-author trailer. + +## D-42 tag decision: defer-tag + +- **What the user chose:** "defer-tag" at the blocking-human checkpoint (Task 3). +- **summercms.go is untouched:** `git tag -l v0.1.0` in summercms.go prints nothing, and nothing was pushed. +- **How the release path was proven:** against the scratch clone `/tmp/claude-1000/-media-nvme-dev-golem15-summercms-io-summercms-summercms-go/b0d2a3f5-592c-4ca7-8d0c-8f89dbb2cfac/scratchpad/fw-release`. That clone is a `git clone` of the local summercms.go with a local annotated `v0.1.0` tag at `a494375` (the Task 2 docsite commit). The build ran as `SUMMERCMS_FRAMEWORK= scripts/build.sh`. +- **Release build output:** `build: bin/summercms-io (release v0.1.0) ready`. `go version -m bin/summercms-io` shows `dep git.golem15.com/golem15/summercms v0.1.0 => /tmp/…/fw (devel)`, so the compiled framework is the tag export. +- **Without the tag:** plain `scripts/build.sh` against the real checkout stops with `build: tag v0.1.0 not found in …/summercms.go; run 'scripts/build.sh dev' or create the tag (DEPLOY.md)`. +- **Launch checklist step 3 in DEPLOY.md:** + 1. Review the commit. + 2. `git tag -a v0.1.0 -m "SummerCMS Alpha 0.1" `. + 3. `git push origin master`. + 4. `git push origin v0.1.0`. + 5. Confirm with `git ls-remote --tags origin v0.1.0`. + +## Verification Evidence + +**D-25, PostgreSQL 15 (previous session).** +- Setup: HEAD export, with `postgres:16-alpine` replaced by `postgres:15` in 9 test files, run as `go test -count=1 -p 4 -v`. +- Result: EXIT=0, and all 11 containers were postgres:15 (log: `scratchpad/pg15.log`). +- Packages, every line `ok`, no `FAIL`: + - modules: `lagoon` 53.2s, `lagoon/attach` 66.9s, `cabana` 65.6s, `beachcomber` 44.4s, `beachcomber/typesense` 0.2s, `lighthouse` 22.2s, `lighthouse/centrifugo` 0.1s, `bouncer` 8.0s, `conga` 19.7s; + - docs/examples: `blog` 5.5s, `blog/console`, `blog/controllers`, `blog/models`, `blog/updates`. + +**Task 1 tracer gate (previous session).** `scripts/build.sh dev` and `scripts/smoke.sh` ("smoke: ok") passed, `go vet` was clean in the app and the plugin, and `TestStaticSmoke` passed. All Task 1 acceptance criteria passed. + +**Task 2.** +- `go vet ./...` is clean. +- The six named tests pass: TestDocsTree, TestDocsBuildRealTree, TestToolCommandNames, TestParseSite, TestSiteLink and TestDocsBuildSiteFlags. +- `scripts/check-phase11.1.sh --docs` and `--forbidden` pass. +- Re-run at the end of this session: `go vet ./... && go test ./internal/docsite ./cmd/summer -count=1` ok, and both phase11.1 checks passed. + +**Task 4.** +- Release build: see the D-42 section above. +- `scripts/smoke.sh` → `smoke: ok`, with the new D-46 `/docs/` site-link assertions. +- `SUMMERCMS_REQUIRE_BUILD=1 go test ./...` in the plugin: PASS for TestLandingLinks, TestTerminalCommandsInPage, TestDocsHeaderSiteLink and TestStaticSmoke; TestExternalLinks SKIP (it is gated). +- `SUMMERCMS_TERMINAL_CHECK=1 SUMMERCMS_CLONE_URL=…/summercms.go go test -run TestTerminalCommands`: PASS. The output included `built hello in 1.93s`, `golem15.greeter active` and `… handled=true`. +- `grep -c 'class="site-link" href="/"' public/docs/index.html` = 1. +- `go vet ./...` and `go test ./... -count=1` pass in the app (gated tests skip). +- `file bin/summercms-io`: ELF 64-bit x86-64, statically linked. + +**Task 5.** +- `scripts/check-deploy.sh` → `check-deploy: ok`. It runs `nginx -t` on nginx 1.30.4 with a temp self-signed certificate, temp DH parameters and loopback ports. The only output is deprecation warnings for `listen … http2`, which is kept for rome's assumed nginx 1.22, per A3. +- All acceptance greps match. `git ls-files` lists no `.env`. + +## Files Created/Modified + +**summercms.go (Task 2, previous session)** +- `README.md` and `docs/setup/installation.md`: PostgreSQL 15 or newer. +- `internal/docsite/{load,docsite,emit}.go`, `theme/templates/header.html`, `theme/assets/site.css`: the `site_url`/`site_label` link. +- `cmd/summer/docs.go`: the flags. `docs/console/utilities.md`: the docs, with a neutral `acme.example` example. +- Tests in `load_test.go`, `theme_test.go` and `cmd/summer/docs_test.go`. + +**sm-summercmsio-plugin** +- `plugin.go`, `static.go`, `smoke_test.go`, `go.mod`/`go.sum`, `.gitignore`, `public/README.md` (Task 1). +- `links_test.go` (Task 4) and `README.md` (Task 5). + +**sm-summercmsio-app** +- Workspace, config, generated sources and submodules (Task 1). +- `scripts/build.sh`: release mode. `scripts/smoke.sh`: D-46 assertion. `terminal_check_test.go` (Task 4). +- `DEPLOY.md`, `README.md`, `deploy/**`, `scripts/check-deploy.sh` (Task 5). + +## Decisions Made + +- **D-42: defer-tag (user decision).** See the section above. +- **Clone override in `terminalScript`.** A non-empty `SUMMERCMS_CLONE_URL` becomes `git clone summercms`, with the directory taken from `path.Base` of the page URL. The page's next command, `cd summercms`, then works for a local override such as `…/summercms.go`. With no override, the commands run verbatim. +- **`check-deploy.sh` test copy.** It rewrites the listen ports to `127.0.0.1:18480` / `[::1]:18480` and `127.0.0.1:18443` / `[::1]:18443` (overridable), because `nginx -t` binds the listeners and a non-root check cannot bind 80 or 443. The committed config keeps 80 and 443. +- **DEPLOY.md choices:** + - `bin/` and `config/` are root-owned, so the service cannot rewrite its own binary; `storage/` is owned by summercms. + - Uploads use `--rsync-path="sudo rsync"`. + - The supervisor program is installed on the first release, because autostart before the binary exists would put the program into FATAL. + - The key comes from `./bin/summercms-io key:generate`, which prints `✓ `. +- **`TestLandingLinks` scope.** It also checks `src` links, `_payload.json?` and the og:image (`https://summercms.io/og-image.png` mapped to `/og-image.png`), not only hrefs. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] The terminal check's clone override changed the clone directory** +- **Found during:** Task 4. +- **Issue:** `git clone /…/summercms.go` clones into `summercms.go/`, so the page's next command, `cd summercms`, failed (`cd: summercms: No such file or directory`). +- **Fix:** with an override, `terminalScript` passes the page URL's directory name explicitly (`git clone summercms`). The verbatim run (no override) is unchanged. +- **Files modified:** `sm-summercmsio-app/terminal_check_test.go`. +- **Verification:** TestTerminalCommands PASS with handled=true. +- **Committed in:** `699785a`. + +**2. [Rule 3 - Blocking] `nginx -t` binds the listen ports** +- **Found during:** Task 5. +- **Issue:** As a normal user, `nginx -t` failed with `bind() to 0.0.0.0:80 failed (13: Permission denied)`. +- **Fix:** `check-deploy.sh` rewrites the listen directives in its temp copy to unprivileged loopback ports, and fails if any 80/443 listen remains unreplaced. The DH parameters are generated with `openssl dhparam -dsaparam` instead of dropping the `ssl_dhparam` line. +- **Files modified:** `sm-summercmsio-app/scripts/check-deploy.sh`. +- **Verification:** `check-deploy: ok`. Earlier, a failing run showed the script surfaces nginx errors. +- **Committed in:** `77eef5d`. + +**3. [Rule 1 - Bug, previous session] `checkSiteURL` also rejects backslashes** +- **Issue:** browsers treat `/\host` as `//host`, which would make a protocol-relative link to another host. +- **Fix:** backslashes are rejected, and `--site-label` is validated as one non-empty line. The override logic lives in `load()` in `load.go`. +- **Committed in:** `a494375`. + +**4. [Rule 3 - Blocking, previous session] `summer plugin:add` wrote an absolute path into the `go.work` `use` list** +- **Fix:** rewritten to `./plugins/golem15/summercms` with `go work edit`. `go mod tidy` before the first `summer build` dropped the requires: they were restored, the build ran, the module was tidied, and `toolchain go1.27.0` was re-added. +- **Follow-up:** the framework quirks are logged in `deferred-items.md`. +- **Committed in:** `e0b9b7c`. + +**5. [Acceptance-check note, previous session] Task 2 byte-identical check** +- **Issue:** a literal `diff -r pre post` cannot be empty, because the content of `utilities.md` changed and the `.site-link` rules live in the shared `assets/site.css`. +- **Isolated check:** the pre-change docs source was built with the new binary. Only `assets/site.css` differs, by 17 additive lines. All 72 HTML pages, the `.md` copies, `llms*.txt` and `search-index.json` are byte-identical. An unset build has 0 `site-link` in `index.html` and `404.html`. + +**6. [Acceptance-check note] The `location ^~ /backend` grep** +- **Issue:** the plan's literal `grep -n 'location ^~ /backend'` cannot match, because GNU BRE treats the mid-pattern `^` as an anchor. +- **Check:** `grep -nF 'location ^~ /backend'` matches line 50 of `deploy/nginx/summercms.io.conf`. + +--- + +**Total deviations:** 4 auto-fixed (2 bugs, 2 blocking) and 2 acceptance-check notes. +**Impact on plan:** All fixes were needed for the checks to run as specified. There was no scope creep, and no new Go module or npm dependency. + +## Issues Encountered + +- **HEAD on `master`.** The executor's protected-branch assertion reports `master` as protected. This project commits directly on master (branching_strategy `none`, use_worktrees `false`), and the sequential orchestrator dispatch said to use normal commits on the main working tree, as the previous session did for `7936234` and `a494375`. Commits were made on master, and none were pushed. +- **Generated build output.** `bin/summercms-io` and `plugins/golem15/summercms/public/{site,docs}` now hold a release build from the scratch tag. These paths are gitignored. + +## Known Stubs + +None. The gated tests (`TestExternalLinks`, and `TestTerminalCommands` without an override) are designed to run at cutover, not stubs. + +## Threat Flags + +None beyond the plan's threat model. T-11.2-04/05/06/07/08/09/11/12/14 are mitigated as planned. T-11.2-13 (the tag) is deferred to the user at cutover. + +## User Setup Required + +These are the cutover steps from the DEPLOY.md launch checklist, all for the user: +1. Push `sm-summercmsio-plugin`, `vue-summercmsio-app`, then `sm-summercmsio-app`. The origins are already set. +2. Make `golem15/summercms` public (D-38). +3. Create `v0.1.0` of `git.golem15.com/golem15/summercms` at the reviewed commit, push it, and push summercms.go `master` (D-42, deferred). +4. Run `TestExternalLinks` and the verbatim `TestTerminalCommands`. +5. On rome: check that port 8095 is free, do the one-time setup, save the existing summercms.io server block into `deploy/rollback/nginx-under-construction.conf`, then deploy and cut over. + +## Next Phase Readiness + +- Plan 11.2-03 (unit test coverage) can build on the stable interfaces: `Plugin`, `newHandlers`, `tree`, `newTree`, `contentType`, `siteImmutable`, `redirectTo`, `pageLinks`, `resolve`, `requireBuild`, `terminalScript`, `terminalEnv`, `loadTerminal`, `checkSiteURL` and `siteLabel`. +- The cutover is blocked only on the user steps above. + +## Self-Check: PASSED + +- Files exist: plugin.go, static.go, links_test.go and README.md in the plugin; build.sh, smoke.sh, check-deploy.sh, terminal_check_test.go, DEPLOY.md, README.md, the deploy/nginx and deploy/supervisor configs, deploy/env.example and deploy/rollback/under-construction/{index.html,logo.png} in the app. +- Commits found: `0ab96ed`, `7774763` and `fe77fa7` (plugin); `e0b9b7c`, `699785a` and `77eef5d` (app); `7936234` and `a494375` (summercms.go). +- No `v0.1.0` tag in summercms.go, and nothing pushed. + +--- +*Phase: 11.2-ready-to-share-summercms-io-website-and-newsletter-plugin* +*Completed: 2026-10-01* diff --git a/.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/deferred-items.md b/.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/deferred-items.md new file mode 100644 index 0000000..abaca74 --- /dev/null +++ b/.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/deferred-items.md @@ -0,0 +1,13 @@ +## Deferred Items + +- `summer plugin:add` writes an absolute path into the app's `go.work` `use` list + status: open + **Found during:** 11.2-02 Task 1 (wiring sm-summercmsio-app). + **What:** `ensureGoWork` in `internal/build/scaffold.go` adds the plugin directory to `go.work` as an absolute path instead of the relative `./plugins//`, so a committed `go.work` only works on the machine that ran the command. The app's `go.work` was corrected by hand with `go work edit` to `./plugins/golem15/summercms`. + **Why deferred:** framework behaviour outside this plan's scope (the plan changes only docsite, the docs CLI flags and two docs pages in summercms.go). + +- `go mod tidy` in an app whose generated sources do not exist yet drops the plugin and framework requires + status: open + **Found during:** 11.2-02 Task 1. + **What:** running `go mod tidy` before the first `summer build` (no `main.go` / `plugins.gen.go` yet, so no Go sources import the plugin) removes the `require` lines that `summer plugin:add` added, and the `toolchain` line. Workaround used: restore the requires, run `summer build`, then tidy and re-add `toolchain go1.27.0`. The scaffolding docs or `plugin:add` could warn about the order. + **Why deferred:** framework/CLI ergonomics, out of scope for the site.