commit 8ce164bb0c01da19909a8ef4761cce7d799bbcf8 Author: Jakub Zych Date: Wed Sep 16 02:07:04 2026 +0200 Initial commit: SummerCMS Go planning docs and research Records the move from Scala to Go, the compiled-plugin decision, the Płytarium port as the v1 target, and the Go ecosystem research that backs the choice. No code yet. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3437085 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Go +/bin/ +/dist/ +*.test +*.out +coverage.* + +# Env and local config +.env +.env.* +!.env.example +*.local.* + +# Editors +.idea/ +.vscode/ +*.swp + +# OS +.DS_Store diff --git a/.planning/notes/v1-target-plytarium.md b/.planning/notes/v1-target-plytarium.md new file mode 100644 index 0000000..63d76e6 --- /dev/null +++ b/.planning/notes/v1-target-plytarium.md @@ -0,0 +1,73 @@ +--- +title: v1 target — Płytarium port +date: 2026-09-16 +context: Chosen during the opening exploration session over the smaller `inventory` project. Defines v1 scope and the acceptance rule. +--- + +# v1 target: Płytarium port + +**Definition of done for v1:** `vue-fonoteka-app` (Nuxt 4) runs unchanged against the SummerCMS Go backend, and `fonoteka-mcp` talks to it through the same OAuth2 flow it uses today. + +Płytarium is the `fonoteka` project at `/media/nvme/dev/golem15/fonoteka`. It is a headless WinterCMS backend for a household record collection, forked from `wn-inventory-plugin`, on the Golem15 starter v1.1.8 LTS. + +## Why this one + +- Headless. No theme engine needed in v1. +- Actively developed and actually used, so "done" is testable by daily use. +- It forces every ecosystem claim at once: OAuth2/OIDC provider, background jobs, realtime, search, file uploads, external APIs, org-scoped permissions. +- Roughly three times the size of `inventory`, which was the alternative. Large enough to be real, small enough to finish. + +## What must be ported + +### Domain plugin `golem15.fonoteka` + +| Aspect | Count | +|---|---| +| Models | 25 (Album, Artist, Collection, Genre, Style, AlbumRating, AlbumReservation, ApiToken, CollectionEditor, CollectionInvitation, CsvImport, CsvImportRow, Notification, OAuthAuthCode, OAuthClient, OAuthRefreshToken, OrgAiCredential, OrgDiscogsCredential, PendingInvitationRegistration, Settings, UserAiCredential, UserCollectionContext, UserDiscogsCredential, WishlistDigestQueue, WishlistSubscription) | +| Admin controllers | 5 (Albums, Artists, Collections, Genres, Styles) with YAML-driven forms and lists | +| API routes | 160 (collections, albums, wishlist, sharing, notifications, realtime channels, CSV export, locale, context) | +| Migrations | 27 | +| Console commands | 3 (IssueOAuthClient, PruneNotifications, ReindexAlbums) | +| Jobs | CSV import, wishlist digest | +| Test files | 143 | +| PHP lines | ~57k | + +### Stack plugins in play + +| Plugin | Role in Płytarium | +|---|---| +| user | Accounts, JWT, organizations, the base of the OAuth2 provider | +| backend | Admin panel improvements | +| apparatus | DI, scenarios, backend tooling | +| golem | AI integration (cover recognition via Anthropic / OpenAI) | +| translate | pl / en | +| websockets | Realtime via Centrifugo | +| userfriends | Friends and groups for collection sharing (optional) | +| journal | Blog / notes (optional) | +| feedback | Feedback submissions | +| sitemap | Sitemap | + +### Hard parts, and the Go answer from research + +| Hard part | Go pick | +|---|---| +| OAuth2 provider used by ChatGPT connector and MCP | `zitadel/oidc` | +| JWT for the SPA | `golang-jwt/jwt` | +| Queued jobs (CSV import, digests) | `riverqueue/river` (Postgres-backed) | +| Realtime notifications | `coder/websocket`, or keep Centrifugo and port only the publisher | +| Search (Typesense via Scout today) | Typesense Go client | +| File uploads, cover images | `gocloud.dev/blob` | +| Discogs, Anthropic, OpenAI HTTP clients | stdlib `net/http`, official Anthropic Go SDK | +| Validation of YAML `rules:` | `go-playground/validator` | + +## Out of scope for v1 + +- Server-rendered themes and components. Second target: keios.eu (see `../seeds/keios-port-themes-payments.md`). +- Payments (paymentgateway, pgstripe). Same second target. +- Chat, forum, video. wavepath.org territory, later. +- WASM extension API. See `../seeds/wasm-extension-api.md`. + +## Reference material + +- `wavepath.org/plugins/golem15/oauthserver` (11k lines, has tests and `INTEGRATING.md`) is the fuller OAuth2 implementation in the stack. Use it as the behavioral reference for the user plugin's OIDC port even though Płytarium's own OAuth models are smaller. +- `fonoteka/.planning/` holds the GSD history of the PHP project, including `STARTER-LINEAGE.md`. diff --git a/.planning/notes/why-go-not-scala.md b/.planning/notes/why-go-not-scala.md new file mode 100644 index 0000000..7557296 --- /dev/null +++ b/.planning/notes/why-go-not-scala.md @@ -0,0 +1,45 @@ +--- +title: Why Go, not Scala +date: 2026-09-16 +context: Exploration session that opened the summercms.go repository. Records the language decision so it is not relitigated. +--- + +# Why Go, not Scala + +## Background + +SummerCMS started in February 2026 as a Scala 3 rewrite of WinterCMS. The stack was direct-style Scala on JDK 21 virtual threads (Tapir, Ox, Magnum, Jig). Three infrastructure modules shipped as independent sbt projects: `summer-compass` (config), `summer-phrasebook` (i18n), `summer-bonfire` (console). They live in the meta repo under `modules/`. + +The effort stalled before any application ran. It was building Illuminate-shaped infrastructure bottom-up with no real app pulling requirements. + +## The deciding test + +We gave both ecosystems the same task: an OAuth2 server with full OIDC support. + +- **PHP / WinterCMS.** The agent took `league/oauth2-server` as a model, read the existing Golem15 user plugin forks, reused the JWT primitives already in the stack, and produced a plugin. The Płytarium project today runs its own OAuth2 provider on this basis. +- **Scala.** The agent searched and found nothing reusable. It proposed a from-scratch implementation. The proposal was rejected as too large to fund. + +The Scala language was never the problem. The ecosystem was. A CMS is mostly glue over solved problems, and glue needs the problems to be solved already. + +## Why Go passes the same test + +The Go ecosystem research (see `../research/go-ecosystem.md`) found maintained, widely used libraries for every concern in the Illuminate module map, including the one that failed in Scala: `zitadel/oidc` is an OpenID-certified provider and client library. Go is closer to PHP's situation than Scala is: many small, boring, well-tested libraries, and several Laravel-shaped frameworks and CMSes to read for patterns (Goravel, PocketBase, GoFrame). + +## What Go changes + +Go is statically compiled, so "drop a plugin folder and it autoloads" does not exist. The decision: + +- **Primary: compiled plugins.** Caddy/xcaddy model. `plugins/` is a workspace of Go modules. A generated import list registers them. `summer make:plugin` scaffolds, `summer build` rebuilds, and a watch-rebuild loop in dev makes it feel like WinterCMS. Full type safety, plugins extend each other through ordinary Go interfaces, `go test` works. +- **Later: WASM extension API.** A narrow, sandboxed surface for untrusted third-party extensions. Only after the core plugin API is stable. See `../seeds/wasm-extension-api.md`. +- **Rejected:** stdlib `plugin` (Linux-only, toolchain lockstep, no unload), yaegi (lags Go releases, partial generics), RPC plugins (interface too wide to marshal for model extension). + +## What carries over from the Scala work + +- The module naming scheme in `IDEA_LIB_NAMES.md` (backpack, compass, phrasebook, bonfire, ...) can be reused as Go package names. +- The design of compass (config layering and plugin namespaces), phrasebook (CLDR plurals, namespaced keys), and bonfire (command registry, rich output) is language-neutral and worth porting. +- The open ORM question from `STACK.md` (immutable models vs Eloquent mutation) largely disappears in Go: structs are mutable, and both `ent` and `GORM` support Eloquent-like flows. + +## Non-goals + +- Not chasing JVM performance. Go's baseline is enough and deploys as one binary. +- Not porting Illuminate module by module. The v1 target app pulls what is needed. See `v1-target-plytarium.md`. diff --git a/.planning/research/go-ecosystem.md b/.planning/research/go-ecosystem.md new file mode 100644 index 0000000..08b38de --- /dev/null +++ b/.planning/research/go-ecosystem.md @@ -0,0 +1,69 @@ +--- +title: Go ecosystem viability for SummerCMS +date: 2026-09-16 +source: Research pass during the opening exploration session. Stars and last-push dates verified via GitHub API on 2026-09-16 unless tagged [ASSUMED]. +--- + +# Go ecosystem viability report (WinterCMS -> Go) + +Verdict: the "Go is closer to PHP's richness" bet holds for every concern except the plugin model, and that has a proven answer (section 2). + +## 1. Ecosystem coverage + +| Concern | Pick (stars, last push) | Runner-up | Maturity note | +|---|---|---|---| +| HTTP router | net/http ServeMux (stdlib; method and wildcard patterns since 1.22) | chi 22.8k (09-2026) | stdlib mux is enough for a CMS; chi adds middleware groups and stays net/http-compatible. gin 89k, echo 32.7k, fiber 40k are alive but use non-stdlib handler types; fiber (fasthttp) breaks stdlib middleware. Avoid. | +| ORM / query | ent 17.2k (09-2026) or GORM 40k (09-2026) | bun 5k, sqlc 18.3k | ent = schema-as-code + codegen, best fit for YAML-driven form generation and migrations. GORM = closest to Eloquent DX, weaker typing. sqlx 17.7k last push 2024-08, stagnant. | +| Migrations | goose 11.5k (09-2026) | golang-migrate 18.9k, atlas 8.7k | goose supports Go-code migrations (needed for plugin migrations). atlas pairs with ent for declarative diff. | +| OAuth2 / OIDC server | zitadel/oidc 1.9k (09-2026), OP+RP, OpenID-certified | go-oauth2/oauth2 3.6k (09-2026, RFC 6749 only, no OIDC) | This is the Scala gap, closed. ory/fosite 2.6k last push 2025-11; Ory pushes users to Hydra. Reference only. | +| JWT | golang-jwt/jwt 9.2k (09-2026) | | De facto standard. | +| Sessions | alexedwards/scs 2.6k (11-2025) | gorilla/sessions 3.2k (08-2024) | scs is server-side with pluggable stores (pg, redis, sqlite). gorilla is cookie-signed and dormant. | +| Validation | go-playground/validator 20.2k (09-2026) | | Tag-based, i18n error messages, maps well to YAML `rules:`. | +| Config | koanf 4.2k (09-2026) | viper 30.5k (01-2026) | koanf is lighter with fewer deps. Either fine. | +| CLI | cobra 44.6k (07-2026) | urfave/cli 24.2k (09-2026) | cobra for `summer make:plugin`-style scaffolding. | +| Templates | html/template (stdlib) + templ 10.5k (09-2026) for typed components | pongo2 3.1k (05-2026) for Twig syntax | pongo2 gives theme authors Twig-like syntax (WinterCMS parity) but is single-maintainer. Use it only for themes, not admin. | +| i18n | go-i18n 3.5k (09-2026) | spreak 94 stars | go-i18n is CLDR-plural, standard. | +| Queue / jobs | river 5.7k (09-2026, Postgres/SQLite, transactional) | asynq 13.7k (06-2026, Redis) | river avoids a Redis dependency. | +| Cache | otter 2.7k (06-2026) in-memory; go-redis 22.2k (09-2026) | ristretto 7k (09-2026) | otter v2 is W-TinyLFU, faster than ristretto per maintainers' benchmarks [ASSUMED]. | +| Mail | wneessen/go-mail 1.5k (09-2026) | | Active, SMTP auth variants, no deps. | +| Events / pubsub | In-process: hand-rolled typed bus with generics | watermill ~8k [ASSUMED] for cross-process | No dominant in-process event lib. It is ~200 LOC. | +| WebSockets | coder/websocket 5.5k (06-2026) | gorilla/websocket 24.9k (03-2025) | coder is context-aware and wasm-capable. gorilla maintained but quiet. | +| File storage | gocloud.dev/blob 9.9k (09-2026) | afero 6.7k (09-2026) | blob = S3/GCS/Azure/local behind one API (Flysystem equivalent). afero for FS mocking. | + +## 2. Plugin architecture (key risk) + +| Approach | Real project | DX | Verdict | +|---|---|---|---| +| (a) Compile-time registration + custom build | Caddy + xcaddy 1.5k (08-2026); Grafana core; Hugo | Plugin = Go module with `init()` registering into a registry. `summer build --with github.com/x/plugin` rebuilds the binary. | Recommended. Full type safety, zero runtime overhead, plugins extend each other via normal Go interfaces, `go test` works. Pain: rebuild required (seconds), Go toolchain at deploy or in CI. | +| (b) stdlib `plugin` | almost nobody in prod | Linux/macOS only, host and plugin must share exact toolchain and dep versions, no unload, panic kills host | Reject. Docs still say "known to have a number of issues". | +| (c) RPC subprocess | hashicorp/go-plugin 6.1k (09-2026); Grafana backend plugins; Terraform | Each plugin a separate binary over gRPC, 30-50us per call | Good for isolation, wrong for a CMS where plugins register models, form fields, components, and extend each other. Surface too wide to marshal. | +| (d) WASM | extism 5.8k (09-2026) on wazero (~5k [ASSUMED]) | Sandboxed, polyglot, hot-reloadable | Same interface-width problem as (c), plus no direct DB/ORM access from guest. Suitable only for untrusted marketplace "widgets". | +| (e) Interpreter | traefik/yaegi 8.4k, last push 02-2026, README targets Go 1.21/1.22 | Drop source in folder, runs at startup. Traefik vendors deps because yaegi has no module support. | Lagging Go releases badly, partial generics, no cgo, slower. Traefik plugins are tiny middlewares, not CMS plugins. | + +Recommendation: (a) as the primary mechanism, mirroring xcaddy: `plugins/` dir of Go modules, a generated `plugins.go` import list, `summer plugin:add` / `make:plugin` commands that scaffold, rewrite go.mod (replace directive for local dev) and rebuild. Add an `air`/reflex-style watch-rebuild for dev to feel like WinterCMS's drop-in loop. Keep an optional (d) WASM slot later for untrusted extensions. This is what Hugo, Caddy, Grafana core, Mattermost (partly), and Gitea converged on. + +## 3. Existing Go frameworks and CMSes + +| Project | Status | Steal | Base or reference | +|---|---|---|---| +| Goravel (framework 524 stars, skeleton 4.8k, 09-2026) | Alive, active issues 2026 | Facade layer, artisan-style CLI, Laravel-shaped config/queue/mail abstractions | Reference only. Small core team, own ORM wrapper, coupled to its facades. | +| GoFrame 13.3k (09-2026) | Alive, Chinese-led | `gf gen` codegen (dao/model/ctrl), gi18n, config, ORM | Reference. Monolithic, non-idiomatic. | +| Beego 32.4k (09-2026) | Alive but legacy | Little; MVC only | Reference. | +| Buffalo 8.4k | Archived Feb 2024; API shows unarchived, push 03-2026 (unclear) | `pop` ORM migrations, plush templates, generators | Dead for basing. | +| PocketBase 61k (09-2026) | Very alive; "standalone app or Go framework"; SQLite; hooks + JS VM (goja) plugin | Hook/event system, collection-schema-driven admin UI, Go-framework-mode API | Best reference for schema-driven admin forms. Not a base (SQLite-only, no plugin packaging, single maintainer). | +| Fastschema 571 (07-2026) | Beta | ent-backed schema-to-CRUD, plugin system | Reference. Too small. | +| Ponzu 5.8k | Abandoned (README says so) | Nothing | Dead. | + +## 4. Go 1.27 (Aug 2026) notes + +- Generic methods landed (methods may declare their own type params, not on interfaces). Cleans up repository and query-builder APIs. +- `encoding/json` now backed by json/v2: faster, stricter (rejects duplicate keys and invalid UTF-8). Relevant to YAML/JSON form definitions. +- Iterators (`range over func`, `iter` pkg) stable since 1.23. Use for paginated model queries. +- net/http ServeMux method and wildcard patterns stable since 1.22. 1.27 adds RFC 9218 HTTP/2 priority. +- No change to `plugin` package limitations. + +## Flags + +- wazero star count unverified. +- Buffalo archived-status contradiction (web says archived 2024, API says not). +- otter-vs-ristretto performance claim is from maintainer benchmarks, not independently verified. diff --git a/.planning/seeds/keios-port-themes-payments.md b/.planning/seeds/keios-port-themes-payments.md new file mode 100644 index 0000000..59226ef --- /dev/null +++ b/.planning/seeds/keios-port-themes-payments.md @@ -0,0 +1,25 @@ +--- +title: keios.eu port — server-rendered themes and payments +trigger_condition: Płytarium v1 is running in production on SummerCMS Go (vue-fonoteka-app unchanged). +planted_date: 2026-09-16 +--- + +# keios.eu port: themes and payments + +Second port target after Płytarium. keios.eu is the smallest Golem15 project that has both a Twig theme and a payment flow. + +| Aspect | keios.eu | +|---|---| +| Golem15 plugins | apparatus, dualformfield, faq, golem, journal, knobwidget, paymentgateway, pgstripe, quote, sitemap, translate, user, websockets | +| Theme | `wn-keioseu-theme`, 14 template files | +| Plugin PHP lines | ~74k | + +## What it adds to SummerCMS + +- Theme engine with Twig-like syntax (`pongo2` per research, used only for themes) and components dropped into templates. +- `paymentgateway` and `pgstripe` port. Stripe Go SDK is official. +- `journal`, `faq`, `quote` as classic content plugins with admin forms. + +## Related + +- `../notes/v1-target-plytarium.md` diff --git a/.planning/seeds/wasm-extension-api.md b/.planning/seeds/wasm-extension-api.md new file mode 100644 index 0000000..eb23005 --- /dev/null +++ b/.planning/seeds/wasm-extension-api.md @@ -0,0 +1,23 @@ +--- +title: WASM extension API for untrusted third-party extensions +trigger_condition: Core plugin API (models, form fields, components, events) has been stable for one full milestone and at least two compiled plugins extend each other through it. +planted_date: 2026-09-16 +--- + +# WASM extension API + +Decided during the opening exploration: primary plugins compile in (Caddy/xcaddy model), and a second, narrow extension surface runs sandboxed at runtime for third parties who cannot or should not rebuild the binary. + +## Shape + +- Runtime: `extism` on `wazero` (pure Go, no cgo). +- Surface: deliberately narrow. Hooks on events, custom form field renderers, HTTP handlers under a namespaced prefix, read-only model queries through a host function. No direct DB access, no model extension. +- Packaging: one `.wasm` per extension plus a manifest. Installable from the admin without rebuild. + +## Why not now + +Both RPC and WASM plugins fail the same way for a CMS core: the plugin surface (register models, extend other plugins' models, add form widgets, add components) is too wide to marshal across a boundary. Only once the compiled API settles do we know which narrow slice is worth exposing. + +## Related + +- `../notes/why-go-not-scala.md` (plugin decision) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..da5a904 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,25 @@ +# CLAUDE.md + +Guidance for Claude Code when working in this repository. + +## What this is + +SummerCMS in Go: a rewrite of the WinterCMS/OctoberCMS content management framework for the Golem15 stack. Read `README.md` first, then `.planning/notes/` for decisions and `.planning/research/` for ecosystem findings. + +The reference implementation is WinterCMS. The Golem15 starter lives at `../examples/golem15-wintercms-starter`, and the v1 port target (Płytarium) at `/media/nvme/dev/golem15/fonoteka`. + +## GSD workflow rules (lean mode) + +These rules apply to every GSD phase in this project and override defaults: + +1. **Lean planning.** Prefer fewer, larger plans per phase. Skip optional agents unless a phase touches security or the plugin API. +2. **Checkpoint on plan count.** Before writing plans for a phase, present the suggested number of plans with a one-line scope for each and wait for confirmation. The user adjusts the number; only then write PLAN.md files. +3. **Unit tests are always the last plan of a phase.** Every phase ends with a dedicated plan that brings full unit test coverage for that phase's code. Earlier plans in the phase may include smoke tests but must not be blocked on coverage. +4. **Go conventions.** Standard library first (net/http ServeMux, html/template, encoding/json). Add a dependency only when the research doc or a phase decision names it. Keep `go vet` and `go test ./...` green at every commit. +5. **Compiled plugins.** Plugins are Go modules registered at build time. Do not introduce runtime plugin loading (stdlib `plugin`, yaegi) without a decision note. +6. **API parity is the acceptance test.** When porting Płytarium endpoints, the existing Nuxt app's requests define the contract. Do not "improve" response shapes during the port. + +## Commit rules + +- Never add co-author tags to commit messages. +- One logical change per commit. Planning docs and code in separate commits. diff --git a/README.md b/README.md new file mode 100644 index 0000000..374bfe3 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# SummerCMS (Go) + +A Go rewrite of the WinterCMS/OctoberCMS content management framework, built for the Golem15 stack. + +SummerCMS keeps what makes WinterCMS productive — plugins that extend each other, YAML-driven admin forms, models/controllers/components, scaffolding commands — and drops the parts that do not survive a compiled language. + +## Status + +Pre-alpha. Planning and research. Nothing runs yet. + +## Why Go + +The first SummerCMS attempt was Scala 3. Three infrastructure modules were built (config, i18n, console) before the effort stalled on ecosystem depth: proven, reusable libraries for things like an OAuth2/OIDC server did not exist, and building them from scratch was out of budget. Go's ecosystem covers every concern in the Illuminate module map with maintained, widely used libraries. See `.planning/notes/why-go-not-scala.md` and `.planning/research/go-ecosystem.md`. + +## v1 target + +Port **Płytarium** (the `fonoteka` project): a headless WinterCMS backend with a Nuxt 4 frontend, 160 API routes, its own OAuth2 provider, Discogs and AI integrations, queued jobs, realtime notifications, and organization-scoped collections. + +Definition of done for v1: `vue-fonoteka-app` runs unchanged against the Go backend. + +See `.planning/notes/v1-target-plytarium.md`. + +## Architecture decisions so far + +- Compiled plugins, Caddy/xcaddy style: a `plugins/` workspace of Go modules, a generated import list, scaffold and rebuild commands, watch-rebuild in dev. +- A sandboxed WASM extension API for untrusted third-party extensions comes later, behind a stable core plugin API. +- Headless first. Admin is a schema-driven SPA. Server-rendered themes come with the second port target (keios.eu). + +## Layout + +``` +.planning/ GSD planning artifacts (notes, research, seeds, roadmap) +``` + +Everything else will be created by the GSD roadmap phases. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ea329a2 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.golem15.com/golem15/summercms + +go 1.27.0