docs: add a Role in the framework section to every module README

Each module README now says where the module sits in the dependency
graph, why it is its own module, and lists use cases for application
developers.
This commit is contained in:
2026-10-11 00:58:17 +02:00
parent 87953f68bf
commit 602e0412f3
24 changed files with 240 additions and 0 deletions

View File

@@ -10,6 +10,16 @@ Per-instance application container that holds the configuration, a typed service
`backpack` plays the role of the Laravel service container that WinterCMS plugins reach through `App::make` and singleton bindings, without any process-global state: every `backpack.App` is independent, so tests and multiple instances in one process do not interfere. The generated `main` of an application loads configuration with [compass](../compass/README.md), creates the container with `backpack.New`, and hands it to [party](../party/README.md), which passes it to every plugin's Register and Boot. `backpack` deliberately does not import `party`, which keeps the dependency graph acyclic.
## Role in the framework
The root of the framework. It depends only on `compass` (configuration) and `festival` (events), and almost every other module (`lagoon`, `conga`, `cabana`, `surf`, `party`, `phrasebook`, `postcard`, `lighthouse`, `flare`, `beachcomber`) receives the application through it. It is the Go counterpart of Laravel's service container: one value handed to every plugin, with typed lookups instead of string keys. It stays its own module, separate from `party`, so the plugin registry can depend on it without an import cycle.
Use cases:
- Reach shared services from a plugin (the database connection, the blob bucket, the translator) without globals.
- Publish a plugin's own service once at Register time so other plugins can look it up by type.
- Read configuration and fire or listen to events from anywhere that holds the application.
## Features
- `backpack.New` wires a container around a loaded `*compass.Config`: `backpack.App.Config`, a fresh service registry in `backpack.App.Services` and a new [festival](../festival/README.md) bus in `backpack.App.Events`.

View File

@@ -16,6 +16,16 @@ The package itself knows no search server. An engine package registers itself fr
`beachcomber.From` builds the app-scoped `beachcomber.Service` on first use and publishes it on the app. It installs the sync GORM callbacks through `lagoon.OnDatabase`, so they reach the production handle even though plugins boot before `serve` publishes the database. The application installs a `beachcomber.Gate`, its kill-switch, with `beachcomber.Service.SetGate`.
## Role in the framework
A leaf service module on top of `lagoon` and `backpack`; nothing in the framework imports it, so an application that has no search pulls none of it in. The engine is behind an interface, with a Typesense driver in the typesense subpackage, so the sync logic does not depend on one search vendor.
Use cases:
- Keep a search index in step with a model such as a blog post: writes after commit, deletes on delete.
- Switch indexing off in development or tests through the application kill-switch.
- Build reindex tooling on the on-demand sync and the drop and ensure index helpers.
## Features
- Engine selection by `search.driver`: `null` (the default) or a registered engine such as `typesense`. An unknown name is a boot error that lists the registered engines. Third-party engines register with `beachcomber.RegisterEngine` and a `beachcomber.EngineFactory`; a duplicate name panics at init.

View File

@@ -10,6 +10,16 @@ HTTP handler that serves the embedded admin SPA build under a configurable path
`boardwalk` embeds the compiled admin SPA (`dist/`, produced by `npm --prefix admin run build`) into the binary and serves it. The build is path-agnostic: `index.html` carries a placeholder token that the handler replaces once, at construction, with the prefix the admin is mounted under, so one build works at any backend URI. [cabana](../cabana/README.md) mounts it when it activates the admin routes. It stands in for the server-rendered backend layouts of WinterCMS, which the Go port replaces with a single-page app.
## Role in the framework
A small leaf with no framework dependencies; `cabana` is its only importer. It exists on its own because it owns the generated admin SPA bundle in dist/, which is rebuilt and drift-checked separately from Go code, and because its handler is easy to test against an in-memory file system. Application code normally does not import it: `cabana` mounts it under the backend prefix.
Use cases:
- Serve the admin SPA from the application binary with no separate frontend deployment.
- Move the admin to another path by changing the backend URI; the bundle is rewritten for the prefix at boot.
- Serve as the pattern to copy when a plugin embeds its own static site (the embedding and cache headers, not the admin-only security headers).
## Features
- Serves the embedded build under any prefix, rewriting relative asset URLs and the admin base meta in `index.html` for that prefix (`boardwalk.RewriteIndex`).

View File

@@ -10,6 +10,16 @@ Declarative console commands for the `summer` tool and application binaries, ada
bonfire is the console layer of SummerCMS. Plugins and framework modules describe commands as plain `bonfire.Command` values (name, flags, arguments and a run function), and `bonfire.NewRoot` turns a slice of them into a Cobra root command. Commands never touch Cobra directly: they read arguments through `bonfire.Input` and write through `bonfire.Output`, which also provides tables, spinners, progress bars and interactive prompts. It is the counterpart of WinterCMS's artisan console commands (`registerConsoleCommand` and Laravel's `Illuminate\Console\Command` output helpers).
## Role in the framework
A dependency-free base layer for console commands. `pact` uses it so plugins can contribute commands, and `lagoon`, `conga`, `cabana`, `lighthouse`, `flare` and `surf` build their own commands (migrate, workers, serve, admin tools) on it. Keeping it separate means every module describes commands the same way without depending on the CLI binary.
Use cases:
- Add a plugin command such as blog:import with typed arguments and options.
- Ask for confirmation or input interactively and print styled tables and progress.
- Ship operational commands (backfills, health checks) inside the same binary as the application.
## Features
- Command values with a description, positional arguments (`bonfire.Arg`) and string flags (`bonfire.Flag`), collected from plugins or the tool itself.

View File

@@ -10,6 +10,16 @@ Authentication for SummerCMS: HS256 JWT minting, verification and refresh, reque
bouncer decides who is making a request. Guards (`bouncer.Guard`, `bouncer.CredentialGuard`) turn an `*http.Request` into a `bouncer.Principal`; a `bouncer.Registry` holds named guards that plugins register and turns each one into HTTP middleware that stores the principal on the request context. The JWT side issues and checks HS256 tokens for two audiences, frontend users (`bouncer.AudienceUser`) and admin users (`bouncer.AudienceBackend`), with a refresh flow and a jti blacklist compatible with tokens issued by the PHP jwt-auth library. It is the counterpart of WinterCMS's Auth and BackendAuth facades and the JWT auth layer used by API plugins.
## Role in the framework
A dependency-free security primitive. `surf` uses it for request guards, `cabana` for admin sign-in, and `conga` and `lighthouse` for authenticated workers and realtime tokens. It is separate so authentication code can be reviewed and tested on its own, without the HTTP or admin layers around it.
Use cases:
- Issue and refresh JWTs for a single-page or mobile frontend.
- Protect API routes with a guard and read the authenticated user downstream.
- Revoke tokens on sign-out and hash and check passwords with bcrypt.
## Features
- Token minting with `bouncer.Mint` (frontend audience) and `bouncer.MintAudience` (any audience), each returning the signed token and its random jti.

View File

@@ -10,6 +10,16 @@ Schema-driven admin backend that compiles WinterCMS-style YAML list, form, filte
`cabana` is the SummerCMS counterpart of WinterCMS's backend: controllers with the List, Form and Relation behaviors, their `config_list.yaml`, `config_form.yaml`, `config_filter.yaml` and `config_relation.yaml` files, the model `columns.yaml` and `fields.yaml`, backend users, roles and permissions, settings models and backend navigation. Plugins declare admin controllers through the [pact](../pact/README.md) capability interfaces and embed their YAML; `cabana.Activate` compiles all of it once at boot, fails fast on any schema error, and returns the admin routes that [surf](../surf/README.md) mounts under the admin prefix (`backend.uri`, default `/backend`). The JSON API lives under `<prefix>/api/v1`, and every other path under the prefix serves the admin SPA from [boardwalk](../boardwalk/README.md).
## Role in the framework
The largest module and the admin backend of the framework. It sits on top of `backpack`, `lagoon`, `bouncer`, `party`, `pact`, `phrasebook`, `towel` and `bonfire`, and it mounts `boardwalk` to serve the admin SPA. `surf` wires its routes in. It mirrors the WinterCMS backend: YAML-driven lists, forms, filters and relations compiled once at boot.
Use cases:
- Give a model such as a blog post a full admin screen from fields.yaml and columns.yaml, with no Vue code.
- Add relation managers, filters, bulk actions and settings pages declaratively.
- Add custom form widgets and controller actions that run behind the admin's CSRF and permission checks.
## Features
- Boot-time schema compilation: `cabana.CompileList` and `cabana.CompileForm` read a controller's YAML from the plugin's embedded tree, check that `modelClass` matches the controller's model name, and cache a locale-neutral schema. Each request gets a translated copy (`cabana.ListSchema.Localize`, `cabana.FormSchema.Localize`, `cabana.RelationSchema.Localize`) through [phrasebook](../phrasebook/README.md), with CLDR plural forms for the SPA's messages.

View File

@@ -10,6 +10,16 @@ Layered YAML configuration with per-environment directories, `SUMMER_` environme
`compass` is the SummerCMS counterpart of WinterCMS's `config/*.php` files, per-environment config directories, `.env` support and `Config::get('app.name')`. It merges several layers into one tree built on koanf, and every key is read with a dot path such as `app.name`. Plugin defaults live under the bare plugin ID (`acme.blog.posts_per_page`) instead of WinterCMS's `acme.blog::posts_per_page`. The generated `main` of an application calls `compass.Load("config")` and hands the result to [backpack](../backpack/README.md); [party](../party/README.md) merges plugin defaults into it during activation.
## Role in the framework
A dependency-free foundation module that `backpack` holds and most service modules read from. It is separate so configuration loading can run before the application exists, and so any package can read settings without depending on the container.
Use cases:
- Keep shared settings in a base file and override them per environment.
- Override any key from the environment, for example in containers and CI.
- Ship plugin defaults embedded in the plugin, so the plugin works with no host configuration.
## Features
- A fixed layer order, lowest to highest precedence:

View File

@@ -14,6 +14,16 @@ Every River client runs on the one `*sql.DB` pool that `lagoon` opens. A worker
The scheduler is the Go form of WinterCMS `registerSchedule`. Plugins declare recurring console commands through `pact.HasSchedule`; every worker turns them into River periodic jobs on wall-clock `conga.Daily` and `conga.Every` schedules in the `app.timezone` location. Each due run is a job on the `scheduled` queue that calls the command in-process through the app's `bonfire.Catalog`.
## Role in the framework
A service module over `lagoon`'s Postgres pool, using `backpack`, `pact`, `party`, `bouncer` and `bonfire`. `lighthouse` depends on it to send broadcasts as jobs, and `surf` runs its workers next to the HTTP server. Jobs are separate from the data layer because not every application runs workers, and the queue has its own lifecycle (dispatch, retry, schedule).
Use cases:
- Send mail, resize images or call slow external APIs outside the request.
- Dispatch a job in the same transaction as the write that caused it, so it never runs for a rolled-back change.
- Run scheduled work such as nightly cleanups, and show progress to users through the job record.
## Features
- River-free job declarations: `conga.Job` turns `func(ctx context.Context, args T) error` into a `pact.Job`; `conga.OnQueue`, `conga.MaxAttempts` and `conga.Timeout` set per-job defaults, which `conga.Describe` reports back for tests. A `pact.Job` not built by `conga.Job` is rejected with `conga.ErrNotCongaJob`.

View File

@@ -10,6 +10,16 @@ Typed, synchronous event bus with listener priorities, payload collection and st
`festival` is the SummerCMS counterpart of WinterCMS's `Event::listen` and `Event::fire`, the mechanism plugins use to extend each other without direct calls. Events are routed by their Go type rather than by a string name, so a listener registered for `*PostPublished` receives exactly that type, and a payload mismatch is a compile error. Each application owns one bus: [backpack](../backpack/README.md) creates it in `backpack.New` and exposes it as `backpack.App.Events`, and plugins register listeners from their Boot step.
## Role in the framework
A dependency-free core primitive owned by `backpack`, so every plugin reaches the bus through the application. It is in-process and synchronous. Delivery to browsers is `lighthouse` and delivery to devices is `flare`; those depend on the database and jobs, so they cannot sit under `backpack` without an import cycle.
Use cases:
- Let one plugin react to another without importing it, for example sending a welcome mail when a user registers.
- Let plugins veto or change an action by priority, the WinterCMS "before" event style.
- Collect contributions from several listeners, such as extra fields for an API response.
## Features
- Listener registration with `festival.Bus.Listen` (priority 0) and `festival.Bus.ListenPriority`. Higher priorities run first; listeners with equal priority run in registration order. Every listener carries the ID of the plugin that owns it.

View File

@@ -12,6 +12,16 @@ Guarded outbound HTTP client and fetcher that blocks private and reserved addres
`fetchguard.Fetch` is a one-shot guarded GET. For calling a service API (any method, JSON or multipart bodies, bearer credentials) build one `fetchguard.Client` per vendor with `fetchguard.NewClient`; it applies the same policy to every request it sends and replaces a plugin's own curl request sender.
## Role in the framework
A small security leaf that depends only on `compass`. `tide` uses it to fetch from the reference backend. It is separate so every outbound call in an application can go through one reviewed, SSRF-safe client.
Use cases:
- Fetch user-supplied URLs (link previews, image imports, webhooks) without reaching internal addresses.
- Call operator-configured third-party APIs in trusted mode, with timeouts and size limits.
- Cap response sizes so a hostile endpoint cannot exhaust memory.
## Features
- HTTPS only in the guarded modes: any other scheme fails with `fetchguard.ReasonScheme`.

View File

@@ -19,6 +19,16 @@ The application owns the subscriptions. When a browser subscribes, the frontend
Endpoints come from browsers, so they are untrusted URLs. The driver only sends to `https` endpoints whose host is in `push.allowed_hosts`, checks this before it opens a connection, and never follows a redirect.
## Role in the framework
A leaf service module on `backpack`, `compass` and `bonfire`; the summer CLI uses it for VAPID key commands. It has no database dependency: the application stores subscriptions and hands them to the pusher. It is separate from `lighthouse` because Web Push reaches devices while the page is closed, while `lighthouse` serves live, subscribed browsers.
Use cases:
- Notify users on their devices when something they follow changes, even with the site closed.
- Generate VAPID keys from the command line.
- Clean up dead subscriptions when the push service reports them gone.
## Features
- `flare.Pusher` with one method, `Send(ctx, sub, payload, opts)`. `flare.SendOptions` sets the `TTL` (default `push.ttl`), `Urgency` and `Topic` headers.

View File

@@ -12,6 +12,16 @@ Postgres data layer: the shared GORM connection, per-plugin migrations, model he
`lagoon` is the WinterCMS models and migrations counterpart: it opens the one `*sql.DB` pool (pgx stdlib driver) that GORM and the rest of the application share, runs each plugin's gormigrate set in its own history table, and ports the Eloquent model conventions that WinterCMS plugins rely on, such as `$fillable`, `$hidden`, `$jsonable`, encrypted casts and Laravel-style validation rules. Its `attach` subpackage ports WinterCMS's `system_files` attachments, storing originals and lazily generated thumbnails in a gocloud.dev blob bucket. The application binary gets the migrate and key commands from `lagoon.RuntimeCommands`.
## Role in the framework
The data layer every stateful module builds on. It uses `backpack`, `compass`, `pact`, `party`, `phrasebook` and `bonfire`, and `cabana`, `conga`, `lighthouse`, `beachcomber` and `surf` depend on it. It is the counterpart of Eloquent and the migrator: one shared GORM connection, migrations owned by each plugin, and after-commit hooks other modules build on.
Use cases:
- Define a plugin's models and ship its migrations with it, with the last one rolled back while developing.
- Attach files and images to models, stored through the blob bucket.
- Run work after a transaction commits, which jobs, broadcasts and search sync all rely on.
## Features
- One shared pool: `lagoon.Open`, `lagoon.Use` and `lagoon.OpenFromApp` return a `*sql.DB` and a `*gorm.DB` built on that same pool; `lagoon.Publish` makes both available on the `backpack.App`.

View File

@@ -22,6 +22,16 @@ Model broadcasts follow the WinterCMS `BroadcastableModel` trait with one change
The `centrifugo` sub-package is the Centrifugo driver. It has a hand-rolled `net/http` client for the Centrifugo HTTP API, a token issuer with the claims of the WinterCMS `JwtTokenGenerator`, the token route handler, and the subscribe proxy handler.
## Role in the framework
A service module high in the stack: it depends on `backpack`, `bouncer`, `compass`, `conga`, `lagoon`, `pact` and `wire`, and only the summer CLI imports it. The publisher interface is transport-neutral, with the Centrifugo driver in the centrifugo subpackage. It is separate from `festival` (in-process) and `flare` (Web Push) because it needs the database and jobs to send broadcasts only for committed writes.
Use cases:
- Push live updates to open browser tabs when a model changes.
- Authorize private and per-user channels when a client subscribes.
- Build chat, presence or progress features on channels without writing WebSocket code.
## Features
- Driver selection by `realtime.driver`. The built-in drivers are `null` (the default; it discards everything), `log` (logs channel names and the event, never the payload) and `memory` (`lighthouse.MemoryDriver` records every `lighthouse.Publication` for tests). An unknown name is a boot error that lists the registered drivers.

View File

@@ -10,6 +10,16 @@ Capability interfaces that compiled plugins implement to contribute routes, conf
`pact` is the contract layer between plugins and the framework. It holds interfaces and plain data types, with no behaviour of its own. A plugin opts into a capability by implementing one of the `Has*` interfaces, and the framework package that owns the capability discovers it with a type assertion ([party](../party/README.md) for config, [surf](../surf/README.md) for routes and middleware, [lagoon](../lagoon/README.md) for migrations, [cabana](../cabana/README.md) for admin controllers, [conga](../conga/README.md) for jobs and schedules). It replaces the `register*()` methods of a WinterCMS PluginBase (`registerPermissions`, `registerNavigation`, `registerSettings` and so on) with small, separately implementable interfaces.
## Role in the framework
The contract layer between the framework and plugins; it depends only on `bonfire`. `party`, `lagoon`, `conga`, `cabana`, `phrasebook`, `postcard`, `lighthouse` and `surf` all read it. It holds interfaces only, so a plugin can depend on the contracts without pulling in the modules that implement them.
Use cases:
- Make a plugin contribute routes, migrations, commands and admin screens by implementing the matching interfaces.
- Offer optional features: a plugin implements only the capabilities it needs.
- Write plugins that are compiled in without a runtime loader, checked by the compiler.
## Features
- Plugin capability interfaces: `pact.HasRoutes`, `pact.HasConfig`, `pact.HasMigrations`, `pact.HasCommands`, `pact.HasModels`, `pact.HasJobs`, `pact.HasSchedule`, `pact.HasLang`, `pact.HasLangOverrides` and `pact.HasMailTemplates`.

View File

@@ -10,6 +10,16 @@ Compiled plugin registry that orders plugins by their dependencies and runs thei
`party` is the WinterCMS PluginBase and PluginManager counterpart for plugins compiled into the binary. Each plugin package calls `party.Register` from its `init` function, and the application's generated `main` calls `party.Activate` with the plugin IDs listed in its manifest. Activation validates the selection, sorts it so every plugin comes after the plugins it requires, merges plugin config, runs every Register before any Boot, and wires translations and mail templates in between. Capabilities beyond the lifecycle are declared through the interfaces in [pact](../pact/README.md).
## Role in the framework
The plugin lifecycle, on top of `backpack`, `pact`, `phrasebook` and `postcard`. `lagoon`, `conga`, `cabana` and `surf` ask it which plugins are active and in what order. It is the counterpart of WinterCMS's plugin manager, without autoloading: plugins are registered at build time.
Use cases:
- Declare that a plugin depends on another one, and have it registered and booted after it.
- Choose which compiled plugins are active through the manifest, with missing requirements and cycles caught at boot.
- Run Register before Boot, so plugins can wire services before they use each other.
## Features
- `party.Plugin`, the descriptor every plugin implements: `party.Plugin.ID`, `party.Plugin.Requires`, `party.Plugin.Register` and `party.Plugin.Boot`.

View File

@@ -10,6 +10,16 @@ Namespaced translation catalogs loaded from plugin YAML, with locale fallback, p
phrasebook owns every translatable string in a SummerCMS application. At boot, `phrasebook.Activate` loads the framework's own strings plus the `lang/` tree of every plugin that implements `pact.HasLang`, applies overrides from plugins that implement `pact.HasLangOverrides`, and publishes a single `phrasebook.Translator` on the `backpack.App`. Keys use the WinterCMS form `namespace::group.dot.path`, and message syntax follows Laravel (`:name` placeholders, `|` plural pipes), so it is the counterpart of WinterCMS's `Lang::get` / `trans_choice` and plugin `lang/` directories.
## Role in the framework
The translation layer, on `backpack`, `pact` and `towel`. `lagoon`, `party` and `cabana` use it for model, plugin and admin messages. It is separate so translation works the same in the API, mail and admin, and reads the request locale from `towel` rather than a global.
Use cases:
- Ship a plugin's messages in YAML for each language next to the plugin.
- Pluralize correctly for languages with several plural forms.
- Fall back to a default locale for keys that are missing in one language.
## Features
- YAML catalogs laid out as `lang/<locale>/<group>.yaml`; nested maps flatten into keys such as `acme.blog::posts.title` (`phrasebook.Catalog.Load`). Duplicate keys, duplicate namespaces, malformed paths and non-string leaves fail at load time.

View File

@@ -10,6 +10,16 @@ Transactional mail from plugin-owned Markdown templates and layouts, delivered t
postcard is the mail layer of SummerCMS. Plugins that implement `pact.HasMailTemplates` ship WinterCMS-shaped mail files under `views/mail/`; at boot, `postcard.Activate` publishes one app-scoped `postcard.Mailer` built from the compass `mail.*` config, and `postcard.BootPlugin` registers each plugin's templates and layouts as the plugin boots. Callers then send a template by its dotted name with a map of variables. It is the counterpart of WinterCMS's `Mail::send` with `views/mail/*.htm` templates and mail layouts.
## Role in the framework
A service module on `backpack`, `compass` and `pact`; `party` registers the templates plugins contribute. It is separate so mail rendering and delivery can be tested and swapped (memory, log, SMTP) without touching the code that sends.
Use cases:
- Send account mail (welcome, password reset) from Markdown templates owned by the plugin.
- Share one branded layout across all of an application's mail.
- Capture sent mail in memory in tests, or log it in development instead of delivering it.
## Features
- Templates named `<plugin>::mail.<name>` and loaded from `views/mail/<name>.htm` (dots become directories). A plugin may only register names in its own namespace; missing files, duplicates and unknown layout aliases fail at boot.

View File

@@ -26,6 +26,16 @@ cd sm-blog-app && curl -fsSL https://summercms.io/update | bash
The script lives inside this package so `//go:embed` can reach it, and a web server serves the bytes of the framework release it was built against. `sunrise.Script` returns them; `sunrise.UpdateScript` returns the same script with update mode preset, for the update URL.
## Role in the framework
A distribution leaf outside the runtime: no module imports it and the application binary does not need it. It lives in a package only so the script is embedded and versioned with the framework release it installs.
Use cases:
- Start a new application with one command on a fresh machine.
- Update an existing application and its framework checkout to the newest release.
- Serve the install script from the project website, pinned to a release.
## Features
- Truncation-safe: everything is a function definition and the last line is `main "$@"` (`main "$@" --update` in `sunrise.UpdateScript`), so a download cut short runs nothing.

View File

@@ -10,6 +10,16 @@ Credential-redacting slog handler that keeps API keys, tokens and passwords out
`sunscreen` wraps any `log/slog` handler and rewrites every record before it is written: values of sensitive attribute keys become `[REDACTED]`, and credential shapes inside messages and string values are scrubbed. It is the SummerCMS counterpart of a Monolog tap that a WinterCMS application wires into its log channels to scrub contexts and messages. Every application `main` that `summer build` generates calls `sunscreen.InstallDefault(os.Stderr)` as its first statement, so the default logger, the standard `log` package and every plugin that falls back to `slog.Default` log through it from the start.
## Role in the framework
A dependency-free leaf the application wires into its logger at startup. It is separate because it wraps the standard slog handler and applies to all log output, whichever module writes it.
Use cases:
- Keep API keys, tokens and passwords out of logs, including logs from third-party code.
- Log request and configuration details while debugging without leaking secrets.
- Meet a "no credentials in logs" audit requirement in one place.
## Features
- Sensitive keys (`sunscreen.RedactedKeys`): `api_key`, `apikey`, `authorization`, `bearer`, `password`, `secret`, `token`, `webhook_secret`, `admin_password`, `openai_api_key`, `anthropic_api_key` and `perplexity_api_key`, compared case-insensitively at any group depth. The value of such an attribute becomes `sunscreen.Redacted`, whatever its kind.

View File

@@ -10,6 +10,16 @@ HTTP routing for SummerCMS: collects plugin routes and named middleware into a `
surf turns the routes that plugins declare through `pact.HasRoutes` into one `http.Handler`. `surf.BuildRouter` registers the built-in and plugin middleware, walks every plugin's route declarations through a Laravel-style group builder (`surf.Router`, implementing `pact.Router`), mounts the [cabana](../cabana/README.md) admin, and checks every route at boot; `surf.Assemble` then compiles the result onto a standard library ServeMux. Configuration mistakes such as duplicate routes, unknown middleware names or malformed throttles fail at boot, not on the first request. It is the counterpart of WinterCMS's plugin `routes.php` files with Laravel's `Route::group`, `->middleware()`, `->where()` and `throttle` middleware.
## Role in the framework
The HTTP entry point, at the top of the stack: it depends on `backpack`, `bonfire`, `bouncer`, `cabana`, `compass`, `conga`, `lagoon`, `pact`, `party`, `towel` and `wire`, and provides the serve command. Plugins contribute routes through `pact`; `surf` collects them into one standard library ServeMux.
Use cases:
- Expose a plugin's API routes with named middleware, such as auth guards.
- Apply rate limits, body limits and CORS per route group.
- List every route in the application from the command line while developing.
## Features
- Laravel-style route groups: `surf.Router.Group` with a path prefix and middleware list (`surf.Use` builds the list), `surf.Router.Get`, `surf.Router.Post`, `surf.Router.Put`, `surf.Router.Patch` and `surf.Router.Delete` (the same methods exist on each `surf.Group`), with Go 1.22+ path patterns such as `/posts/{id}`.

View File

@@ -10,6 +10,16 @@ HTTP parity toolkit that records request and response fixtures from a reference
`tide` is the acceptance-test engine for porting an existing WinterCMS or other PHP backend to SummerCMS: the reference backend's real responses define the contract, and the Go port must reproduce them. It records YAML fixtures (flows of request and response steps) either by driving a spec against a target or by sitting as a loopback reverse proxy in front of the reference backend while a real client uses it, then replays those fixtures against the port and diffs the responses after masking values that legitimately differ, such as IDs and timestamps. It also checks realtime side effects: a fake Centrifugo recorder captures the publications a backend sends while a flow runs, and broadcast golden files hold them normalised for comparison. Upstream sidecars record the calls a backend makes to outside vendors, through a loopback HTTPS recording proxy, and replay them offline through an asserting fake. The `summer` CLI's parity:proxy, parity:record, parity:replay, parity:broadcasts and parity:upstream commands are thin wrappers around this package. It has no WinterCMS counterpart.
## Role in the framework
A tooling module, used by the summer CLI and by an application's parity suite; it depends only on `fetchguard`. It is not part of a running server. It exists for ports: it checks that a rewritten backend answers like the old one.
Use cases:
- Record real responses from a reference backend as fixtures.
- Replay them against the new backend in CI and fail on differences.
- Normalize timestamps and generated IDs so only real differences are reported.
## Features
- Flow fixtures: `tide.Flow` is a versioned, ordered list of `tide.Step` values, loaded strictly (unknown fields rejected) with `tide.LoadFlow` and `tide.ParseFlow`, and written atomically with `tide.SaveFlow` or `tide.SaveFlowExclusive`. Response bodies can live in sidecar files, confined to the fixture directory and checked against an optional SHA-256 digest.

View File

@@ -10,6 +10,16 @@ Request-scoped actor, organization, collection and locale values carried through
`towel` replaces the request-global state that WinterCMS reads through facades (the current locale, the acting user) with explicit values on the request context. Middleware stores a value once, and any code further down the call chain that receives the context reads it back without a global lookup. [surf](../surf/README.md) sets the locale from `Accept-Language` (or the signed-in user's preferred locale) and the organization for every request, [phrasebook](../phrasebook/README.md) reads the locale when it translates, and [cabana](../cabana/README.md) sets it while localizing admin schemas.
## Role in the framework
A dependency-free leaf for request-scoped values. `surf` sets them, and `phrasebook`, `cabana` and plugin code read them. It replaces WinterCMS facades for per-request state, so it has to be importable by any module without cycles.
Use cases:
- Read the acting user or organization in a service without passing them through every call.
- Translate in the request's locale from deep inside a plugin.
- Set a locale explicitly in a job or a test.
## Features
- Four independent string values, each with a setter and a getter: actor (`towel.WithActor`, `towel.Actor`), organization (`towel.WithOrganization`, `towel.Organization`), collection (`towel.WithCollection`, `towel.Collection`) and locale (`towel.WithLocale`, `towel.Locale`).

View File

@@ -10,6 +10,16 @@ JSON response helpers and value types that keep API bodies byte-compatible with
`wire` is the lowest layer of the HTTP stack: it decides how a Go value becomes response bytes. Its helpers reproduce what `json_encode` and Carbon produce in a WinterCMS or Laravel app (unescaped HTML characters, no trailing newline, `+00:00` timestamps, nullable booleans, `[]` rather than `null` for empty lists), so an endpoint ported from PHP returns the same body its existing clients already parse. [surf](../surf/README.md) uses it for the opaque 500 response of its panic recovery, and application handlers use it directly for their JSON responses.
## Role in the framework
A dependency-free leaf for response shapes. `surf` and `lighthouse` use it, and so do plugin controllers. It is separate so every handler produces the same JSON as the PHP backend being replaced, byte for byte.
Use cases:
- Return paginated lists and error envelopes in the shape an existing frontend expects.
- Encode timestamps, nullable booleans and empty lists the way Laravel serializes them.
- Port endpoints one at a time without the frontend noticing the backend changed.
## Features
- `wire.WriteJSON` encodes a value with HTML escaping disabled and without the trailing newline `encoding/json` adds, then sets `Content-Type: application/json` and the status code. If encoding fails, it writes the opaque 500 body instead of a partial response.

View File

@@ -10,6 +10,16 @@ An OAuth authorization server for MCP clients: RFC 8414 metadata, RFC 7591 dynam
wristband implements the protocol side of an OAuth authorization server so an application can let MCP clients (AI assistants and connectors) act on behalf of its users. A `wristband.Server` provides ready-made `net/http` handlers for the metadata, authorize, token and registration endpoints, plus Go methods the application's own consent screen calls. Everything application-specific stays outside the package: persistence arrives through the `wristband.Backend` and `wristband.Tx` interfaces, access tokens are minted by the application's `wristband.AccessTokenIssuer`, and issuer, scopes and lifetimes come from `wristband.Options`. It imports no ORM and no application package. WinterCMS core has no counterpart.
## Role in the framework
A standalone protocol leaf: it imports no framework module and no ORM. The application supplies storage and token minting through interfaces. It is separate because OAuth is security code that should be reviewed on its own, and only applications that serve MCP clients need it.
Use cases:
- Let AI assistants and MCP connectors act on behalf of the application's users.
- Register clients dynamically and complete the authorization-code flow with PKCE.
- Rotate refresh tokens and drive the application's own consent screen.
## Features
- RFC 8414 metadata document (`wristband.Server.Metadata`) advertising the authorize, token and registration endpoints under the issuer, the `authorization_code` and `refresh_token` grants, S256 PKCE and the RFC 9207 `iss` response parameter.