Files
summercms/.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-CONTEXT.md
Jakub Zych 9d23ac5832 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.
2026-09-29 02:24:10 +02:00

12 KiB

Phase 11.2: Ready to share: summercms.io website and newsletter plugin - Context

Gathered: 2026-09-29 Status: Ready for planning

## 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.

## 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.

<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>

## 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.
## 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.

Phase: 11.2-ready-to-share-summercms-io-website-and-newsletter-plugin Context gathered: 2026-09-29