docs(11.2): capture phase context

Four repos (sm-summercms-app, vue-summercms-app, sm-summercms-plugin,
sm-newsletter-plugin), Nuxt 4 static site in EN and PL embedded in the
binary, double opt-in signup with honeypot, consent and neutral
responses, and a standalone sending-ready subscribers table.
This commit is contained in:
Jakub Zych
2026-09-29 02:24:10 +02:00
parent 720ee5796b
commit 9d23ac5832
3 changed files with 245 additions and 6 deletions

View File

@@ -529,20 +529,20 @@ Plans:
### Phase 11.2: Ready to share: summercms.io website and newsletter plugin (INSERTED)
**Goal:** summercms.io is ready to share publicly. A fresh Vue website runs on a SummerCMS backend. Visitors subscribe for updates through the initial Go version of the Golem15 Newsletter plugin, which confirms each email by double opt-in. The Phase 11.1 docs are served at `/docs` and linked from the site.
**Goal:** summercms.io is ready to share publicly. A fresh Nuxt 4 website in English and Polish runs on a SummerCMS binary. Visitors subscribe for updates through the initial Go version of the Golem15 Newsletter plugin, which confirms each email by double opt-in. The Phase 11.1 docs are served at `/docs` and linked from the site.
**Requirements**: TBD
**Depends on:** Phase 11.1
**Repos:** three new repos: (1) `sm-newsletter-plugin`, the Go newsletter plugin as a compiled SummerCMS plugin module; (2) `sm-summercms-app`, the root app for summercms.io, which registers the plugin, serves the website's API and serves the docs; (3) `vue-summercms-app`, the fresh Vue website, held inside `sm-summercms-app`. `summercms.go` changes only if the site needs a framework feature that is missing.
**Repos:** four 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; (4) `sm-newsletter-plugin`, the reusable signup plugin. `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`.
**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):
1. The newsletter plugin repo exists and ships an initial stub: a public signup endpoint (validated, deduplicated, rate-limited and spam-guarded), double opt-in confirmation mail with a tokenized confirm link, unsubscribe, and a backend subscriber list in the admin SPA. Composing and sending newsletters is out of scope.
2. The Vue website repo exists and ships a simple summercms.io landing page with a signup widget. The widget calls the plugin's API on the SummerCMS backend and covers the pending, confirmed and error states.
1. The newsletter plugin repo exists and ships an initial stub: a public signup endpoint (validated, honeypot and rate-limited, with required consent), double opt-in confirmation mail with a tokenized confirm link, a welcome mail, unsubscribe, and subscriber add, edit and delete in the admin SPA. Composing and sending newsletters is out of scope.
2. The website repo exists and ships a prerendered English and Polish summercms.io landing page with a signup widget. The widget calls the plugin's API on the same origin and covers the pending, confirmed and error states.
3. The Phase 11.1 docs are served at `summercms.io/docs`, the landing page links to them, and every link resolves.
4. The site deploys with documented run steps: the SummerCMS binary plus the built Vue assets.
4. The site deploys as one binary with the Nuxt build and the docs embedded, with documented build and run steps.
**Open questions:** The Vue build approach (SPA or prerendered). Whether the stub's schema should be designed so newsletter sending can be added later, with confirmed subscribers joining the existing user-based audience.
**Context:** `.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-CONTEXT.md`
**Plans:** 0 plans

View File

@@ -0,0 +1,146 @@
# Phase 11.2: Ready to share: summercms.io website and newsletter plugin - Context
**Gathered:** 2026-09-29
**Status:** Ready for planning
<domain>
## Phase Boundary
summercms.io becomes shareable. A Nuxt 4 landing site in English and Polish runs on a SummerCMS binary. Visitors subscribe for updates through a new `sm-newsletter-plugin`, a stub that collects emails with double opt-in and does not send newsletters. The Phase 11.1 docs are served at `/docs` and linked from the site. The work spans four new repos, laid out like a WinterCMS project (root app, Vue frontend, site plugin and a reusable plugin). The framework (`summercms.go`) changes only if a missing framework feature blocks the site.
</domain>
<decisions>
## Implementation Decisions
### Repos and layout
- **D-01:** Four 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-newsletter-plugin` is the reusable signup plugin, with Go package `newsletter`.
Public static-site serving goes in `sm-summercms-plugin`, not in a new framework module. — **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-newsletter-plugin`. — **Reversibility:** one-way — a published Go module path is baked into every importer.
- **D-03:** The root app wires the framework and both plugins the way `fonoteka.go` does today (a go.work workspace with a local replace during development).
### Website build and hosting
- **D-04:** The site is Nuxt 4 with `nuxt generate` (static output), the same stack as `vue-fonoteka-app`. Every route is real prerendered HTML, so link previews and search engines see content. The signup widget hydrates as normal Vue.
- **D-05:** The generated site and the 11.1 docs output are embedded in the Go binary with `go:embed`. One binary serves `/`, `/docs` and the plugin API. The build order (nuxt generate, then `summer docs:build`, then `go build`) is documented and scripted in the root app.
- **D-06:** The site is in English and Polish (for example `/` and `/pl/`, via `@nuxtjs/i18n` as in `vue-fonoteka-app`). The confirmation and welcome mails are localized to the subscriber's locale through phrasebook/postcard. The 11.1 docs stay English-only.
- **D-07:** The site's served responses must be indexable. The admin-only `boardwalk` behaviour (`noindex, nofollow`, admin CSP, base-path rewrite) must not apply to the public site. The site plugin sets its own cache headers: immutable for hashed assets, no-cache for HTML.
### Landing page content
- **D-08:** The page has these sections: a hero with the email signup, "what it is" (plugins that extend each other, YAML admin forms and lists, scaffolding CLI, headless API, a single compiled binary), "Coming from WinterCMS" linking to the 11.1 concept map, and status and roadmap.
- **D-09:** The "Coming from WinterCMS" section tells the lineage as a seasons story: it started in October (OctoberCMS), went through Winter (WinterCMS), and now it's time for Summer. The seasons theme can carry through the copy and visuals.
- **D-10:** The page is clearly pre-release in tone. It says the project is early and asks people to subscribe for the first release.
- **D-11:** The page has no link to source repos yet. Subscribe is the call to action.
- **D-12:** A "Built by Golem15" footer credit links to golem15.com.
### Signup and double opt-in
- **D-13:** Bot protection is a honeypot field, a minimum time-to-submit check and a per-IP `surf` rate-limit bucket. There is no captcha or third-party script.
- **D-14:** A required consent checkbox ("I agree to receive SummerCMS updates") links to a privacy note. The plugin stores the consent timestamp, IP and locale with the subscriber as GDPR evidence.
- **D-15:** Every signup gets the same neutral "check your inbox" response, and the response never reveals list membership:
- pending: resend the confirmation, throttled
- unsubscribed: new confirmation cycle
- confirmed: no mail
- **D-16:** The confirm link is tokenized. On success it redirects to a localized thank-you page on the site and sends a welcome mail with the unsubscribe link, as `figs.org.pl` does. Invalid or expired tokens redirect to a localized error page.
- **D-17:** Unsubscribe is a tokenized link in every mail. It marks the subscriber unsubscribed and redirects to a localized "unsubscribed" page.
### Plugin shape
- **D-18:** Subscribers are standalone: the plugin has its own subscribers table and no dependency on the user plugin. Merging confirmed subscribers with a user-based audience (as the PHP `AudienceResolver` does) belongs to the later sending phase.
- **D-19:** The stub ships only the subscribers table, but with the fields sending will need: email, status (pending, confirmed, unsubscribed), locale, confirmation and unsubscribe tokens, `confirmed_at`, `unsubscribed_at`, source (form or admin), consent timestamp and IP, and timestamps. The newsletter, template and recipient tables are ported from PHP in the sending phase. — **Reversibility:** costly — the migration shape becomes the base that the sending phase extends.
- **D-20:** The admin backend (cabana YAML list and form) supports list, search, filters (status, locale), view, add, edit and delete. Delete is the GDPR erasure path.
- **D-21:** An admin-added subscriber is created as pending with source=admin, and the normal confirmation mail is sent. Nobody lands on the list without their own confirmation.
- **D-22:** Composing and sending newsletters, templates and campaign CLI are out of scope.
### Claude's Discretion
- How long confirmation tokens stay valid and the resend throttle interval.
- Exact URL scheme for the API endpoints and localized result pages.
- Token format (random opaque token vs signed).
- Visual design within the Direction C v2 brand (dark navy + sunny yellow), copy wording and the exact page structure.
- How `sm-summercms-plugin` mounts the docs output, and whether the Nuxt build is embedded by the plugin or the root app.
</decisions>
<canonical_refs>
## Canonical References
**Downstream agents MUST read these before planning or implementing.**
### Phase scope
- `.planning/ROADMAP.md` § Phase 11.2: goal, repos, naming convention, port source, success criteria.
- `.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-CONTEXT.md`: how the docs are built (`summer docs:build`, static output, `internal/docsite`). 11.2 serves that output at `/docs`.
- `.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-UI-SPEC.md`: the brand token values copied from the admin SPA. The site reuses the same palette.
### Port sources and references
- `/media/nvme/dev/golem15/horoskopia.eu/plugins/golem15/newsletter`: Golem15.Newsletter PHP (`github.com/golem15com/wn-newsletter-plugin`). It is the plugin identity and future sending model (`models/`, `classes/AudienceResolver.php`, `classes/UnsubscribeTokenGenerator.php`, `routes.php`). Do not modify it.
- `/media/nvme/dev/golem15/figs.org.pl/plugins/figs/website`: the site-plugin pattern (D-01). It also has a working subscribe flow: `models/Subscriber.php`, `models/subscriber/{fields,columns}.yaml`, `components/subscribe/`, and hash confirm/resign routes in `routes.php` that redirect to thank-you pages.
- `/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app`: the Nuxt 4 + `@nuxtjs/i18n` setup to mirror for `vue-summercms-app`.
- `admin/src/styles/main.css` (`:root` and `.dark` blocks) and `.planning/phases/10-admin-vue-spa/design/README.md`: the Direction C v2 brand tokens.
### Framework modules used
- `modules/postcard/README.md`: confirmation and welcome mail templates.
- `modules/phrasebook/README.md`: EN and PL strings for mail and API messages.
- `modules/surf/README.md`: routes, named rate-limit buckets and body limits.
- `modules/lagoon/README.md`: per-plugin migrations and models.
- `modules/cabana/README.md`: YAML list and form for the subscribers admin.
- `modules/pact/README.md`: the plugin capability interfaces the two plugins implement.
- `modules/boardwalk/README.md`: the admin SPA handler. It is the reference for embedding and cache headers, and it must not be reused as-is for the public site (D-07).
- `../fonoteka.go`: the reference for app wiring (go.work, local replace, plugin registration).
</canonical_refs>
<code_context>
## Existing Code Insights
### Reusable Assets
- `postcard`: plugin-owned Markdown mail templates with memory, log and SMTP drivers. It covers the confirmation and welcome mails.
- `surf`: named rate buckets and trusted-proxy client IP. It covers the per-IP signup limit and gives the IP to store as consent evidence.
- `cabana` + admin SPA: YAML-driven list, form and filters. The subscriber admin needs no custom Vue.
- `lagoon`: GORM models, per-plugin gormigrate migrations and validation.
- `phrasebook`: namespaced EN and PL catalogs with fallback.
- `boardwalk`: its embed and cache-header handling is a pattern to copy into `sm-summercms-plugin`, but its admin security headers must not be.
### Established Patterns
- Plugins are compiled Go modules registered at build time (no runtime loading).
- Every application repo is a go.work workspace requiring the framework with a local replace, as `fonoteka.go` is.
- Framework READMEs never name a consuming application. The two new plugins are not framework modules, but `sm-newsletter-plugin` is reusable, so its README uses neutral examples too.
- 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 both plugins and serves `/`, `/docs`, `/backend` (admin) and the newsletter API from one mux.
- The Nuxt site calls the newsletter API with a same-origin fetch, so no CORS is needed.
- The 11.1 `summer docs:build` output feeds the embed step, so Phase 11.2 depends on 11.1 being executed.
</code_context>
<specifics>
## Specific Ideas
- The seasons lineage story: October, then Winter, now Summer (D-09).
- The site should feel like the admin and docs brand (Direction C v2: dark navy + sunny yellow), so summercms.io, `/docs` and `/backend` look like one product.
- Follow the figs.org.pl experience for confirmation: thank-you page, welcome mail and unsubscribe page.
</specifics>
<deferred>
## Deferred Ideas
- CSV export of subscribers. It waits for a framework export or toolbar capability; the user noted the framework doesn't have it ready.
- Composing and sending newsletters, templates and the campaign CLI, ported from the PHP plugin in a later phase. That phase also merges confirmed subscribers with the user-based audience.
- Renaming `fonoteka.go` to `sm-fonoteka-app` is a separate task, outside this phase.
- A source repo link on the landing page, once the repos are public.
- Translating the docs into Polish.
### Reviewed Todos (not folded)
- "Nest framework packages under modules/" is already delivered by Phase 10.2, and the match was keyword noise.
- "Backend admin personal API tokens" is unrelated to the public site.
- "Extend fetchguard into a guarded outbound http.Client" is unrelated, because the site makes no outbound fetches.
</deferred>
---
*Phase: 11.2-ready-to-share-summercms-io-website-and-newsletter-plugin*
*Context gathered: 2026-09-29*

View File

@@ -0,0 +1,93 @@
# Phase 11.2: Ready to share: summercms.io website and newsletter plugin - Discussion Log
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
**Date:** 2026-09-29
**Phase:** 11.2-ready-to-share-summercms-io-website-and-newsletter-plugin
**Areas discussed:** Website build & hosting, Landing page content, Signup & opt-in flow, Plugin shape & future
---
## Website build & hosting
| Option | Description | Selected |
|--------|-------------|----------|
| Prerendered (vite-ssg) | Vite + Vue with build-time prerendering | |
| Nuxt 4 (static generate) | Same stack as vue-fonoteka-app, `nuxt generate` | ✓ |
| Plain Vue SPA | Empty HTML shell until JS runs | |
| Option | Description | Selected |
|--------|-------------|----------|
| Embedded in the Go binary | go:embed site + docs, one binary | ✓ |
| Served from disk by the binary | dist/ next to the binary | |
| Static host/CDN + API binary | Separate hosting, needs CORS | |
| Option | Description | Selected |
|--------|-------------|----------|
| English only | Docs are English | |
| English + Polish | vue-i18n + phrasebook for site and mail | ✓ |
| Option | Description | Selected |
|--------|-------------|----------|
| New framework module | Reusable public static handler in summercms.go | |
| In sm-summercms-app only | App-local handler | |
| You decide | Research picks | |
**User's choice:** Nuxt 4 static generate, embedded in the binary, EN + PL. Serving: "we do as we did in wintercms - sm-summercms-plugin that does all the stuff around serving data. this is proven approach, used in figs.org.pl, golem15.com etc".
**Notes:** The user first asked what "serving code" meant. After the explanation they chose a site plugin, which added a fourth repo.
---
## Landing page content
| Option | Description | Selected |
|--------|-------------|----------|
| Hero + signup | Pitch with signup in the hero | ✓ |
| What it is / features | Feature list | ✓ |
| Coming from WinterCMS | Side-by-side, links to the 11.1 concept map | ✓ |
| Status & roadmap | Honest pre-release block | ✓ |
**User's choice:** All four. Notes on the WinterCMS section: "nice if it would have some cool way that it started in October (OctoberCMS) went trough WinterCMS but now it's time for Summer".
**Other answers:** status tone was clear pre-release (the recommended option); no repo link yet (recommended); "Built by Golem15" credit (recommended).
---
## Signup & opt-in flow
| Question | Options | Selected |
|----------|---------|----------|
| Spam guard | Honeypot + rate limit / Cloudflare Turnstile | Honeypot + rate limit |
| Consent | Required checkbox / Notice text only | Required checkbox |
| Re-signup | Same neutral response / Tell the user their status | Same neutral response |
| After confirm | Thank-you page + welcome mail / Thank-you page only | Thank-you page + welcome mail |
---
## Plugin shape & future
| Question | Options | Selected |
|----------|---------|----------|
| Users | Standalone subscribers / Optional link to users | Standalone |
| Schema | Subscribers only, sending-ready / Port all PHP tables now | Subscribers only, sending-ready |
| Admin | List+filter+delete / Also CSV export / Also manual add/edit | Free text: "add/edit/delete are must be, csv export - i don't think we have that ready within framework so it waits." |
| Module path | git.golem15.com / github.com/golem15com | git.golem15.com |
| Admin add status | Pending + confirmation mail / Admin chooses | Pending + confirmation mail |
**Notes:** The first question batch in this area failed in the UI and was asked again.
---
## Claude's Discretion
- How long tokens stay valid, the resend throttle, the token format and the URL scheme.
- Visual design within the Direction C v2 brand, and the copy.
- Whether the plugin or the root app embeds the Nuxt build, and how the docs are mounted.
## Deferred Ideas
- CSV export of subscribers, which needs framework support.
- Sending newsletters and merging audiences, in a later phase.
- Renaming fonoteka.go to sm-fonoteka-app.
- A source repo link on the landing page.
- Polish docs.