docs(quick-261015-w7f): docs since v0.1.14 and the Upgrading to 0.2 notes

- new setup/upgrading.md: version rule, the two 0.2 boot refusals with
  their fixes, behaviour changes, and what 0.2 adds since 0.1
- architecture module table lists all 28 modules
- WinterCMS map: cache and reorder are provided; rows for field types,
  sanitizing, singletons, API tokens, CORS, Turnstile, menu item types
- configuration keys for CORS profiles, api_docs, auth.cookie, turnstile
- index, setup intro and frontend page link the new topics
This commit is contained in:
Jakub Zych
2026-10-15 23:24:56 +02:00
parent 76d788821a
commit f3e8a984b0
7 changed files with 107 additions and 15 deletions

View File

@@ -24,12 +24,12 @@ Each framework module is one Go package under `modules/`, documented by its READ
| Concern | Modules |
|---------|---------|
| Plugins and the container | [party](../../modules/party/README.md), [pact](../../modules/pact/README.md), [backpack](../../modules/backpack/README.md), [festival](../../modules/festival/README.md), [compass](../../modules/compass/README.md) |
| Plugins and the container | [party](../../modules/party/README.md), [pact](../../modules/pact/README.md), [backpack](../../modules/backpack/README.md), [festival](../../modules/festival/README.md), [compass](../../modules/compass/README.md), [signpost](../../modules/signpost/README.md) |
| HTTP | [surf](../../modules/surf/README.md), [towel](../../modules/towel/README.md), [wire](../../modules/wire/README.md), [bouncer](../../modules/bouncer/README.md), [wristband](../../modules/wristband/README.md), [fetchguard](../../modules/fetchguard/README.md) |
| Data | [lagoon](../../modules/lagoon/README.md), [beachcomber](../../modules/beachcomber/README.md) |
| Admin | [cabana](../../modules/cabana/README.md), [boardwalk](../../modules/boardwalk/README.md) |
| Services | [phrasebook](../../modules/phrasebook/README.md), [postcard](../../modules/postcard/README.md), [conga](../../modules/conga/README.md), [lighthouse](../../modules/lighthouse/README.md), [flare](../../modules/flare/README.md), [sunscreen](../../modules/sunscreen/README.md) |
| Console and tooling | [bonfire](../../modules/bonfire/README.md), [tide](../../modules/tide/README.md) |
| Admin | [cabana](../../modules/cabana/README.md), [boardwalk](../../modules/boardwalk/README.md), [seaglass](../../modules/seaglass/README.md) |
| Services | [cooler](../../modules/cooler/README.md), [phrasebook](../../modules/phrasebook/README.md), [postcard](../../modules/postcard/README.md), [conga](../../modules/conga/README.md), [lighthouse](../../modules/lighthouse/README.md), [flare](../../modules/flare/README.md), [sunscreen](../../modules/sunscreen/README.md) |
| Console and tooling | [bonfire](../../modules/bonfire/README.md), [tide](../../modules/tide/README.md), [sunrise](../../modules/sunrise/README.md) |
| Utilities | [sieve](../../modules/sieve/README.md) |
## What runs where

View File

@@ -12,10 +12,10 @@ Start with [Installation](setup/installation.md) to set up the toolchain and the
## Where to start
- [Setup](setup/introduction.md): what SummerCMS is, installation, configuration, the move from WinterCMS and a [plugin porting walkthrough](setup/porting-a-plugin.md).
- [Setup](setup/introduction.md): what SummerCMS is, installation, configuration, the move from WinterCMS, a [plugin porting walkthrough](setup/porting-a-plugin.md) and [Upgrading](setup/upgrading.md) to a newer release, with what to check before 0.2.
- [Architecture](architecture/introduction.md): the single binary, Go modules, the application lifecycle, the request lifecycle and [performance and scaling](architecture/performance-and-scaling.md) compared with PHP-FPM.
- [Plugins](plugins/registration.md): registering a plugin, scheduling, extending other plugins and testing.
- [Backend](backend/admin-controllers.md): admin controllers, forms, lists, relations, users, settings and the admin SPA.
- [Plugins](plugins/registration.md): registering a plugin, scheduling, extending other plugins, [menu item types](plugins/menu-item-types.md) and testing.
- [Backend](backend/admin-controllers.md): admin controllers, forms, lists, relations, [plugin field types](backend/field-types.md), users and [API tokens](backend/users-and-permissions.md#api-tokens), settings and the admin SPA.
- [Database](database/models.md): models, migrations, queries, relations, casts, attachments and transactions.
- [Services](services/configuration.md): configuration, events, routing, authentication, mail, jobs, realtime, search and the other services, plus what SummerCMS does not provide for the frontend.
- [Services](services/configuration.md): configuration, events, routing, authentication, [caching](services/caching.md), mail, jobs, realtime, search and the other services, plus what SummerCMS does not provide for the frontend.
- [Console](console/introduction.md): the `summer` tool, the application binary's commands and writing your own.

View File

@@ -27,9 +27,12 @@ A WinterCMS plugin that shipped components and AJAX handlers is ported as routes
Build the frontend with any framework that can call a JSON API, as its own project with its own build and deployment:
- **Data:** call the plugins' JSON routes. [Routing](routing.md) shows how routes, auth groups and JSON responses are declared, and [Queries and pagination](../database/queries-and-pagination.md) the list envelope.
- **Signing in:** the user plugin issues JWTs; send them as a bearer token or in the cookie the guard reads. See [Authentication](authentication.md).
- **Signing in:** the user plugin issues JWTs; send them as a bearer token or in the cookie the guard reads. A browser frontend can keep the token in an httpOnly cookie that the API sets, with `bouncer.CookieRefreshHandler` and `bouncer.CookieLogoutHandler` as its refresh and logout endpoints. See [Authentication](authentication.md) and [Cookie transport](authentication.md#cookie-transport).
- **Live updates:** instead of polling an AJAX handler, subscribe to realtime channels. The frontend connects to Centrifugo with a token from the token route and receives model broadcasts and explicit events. See [Realtime](realtime.md).
- **Cross-origin calls:** when the frontend runs on another origin, allow it in `http.cors`, as [Routing](routing.md) describes.
- **Cross-origin calls:** when the frontend runs on another origin, allow it in `http.cors`, as [Routing](routing.md#cors) describes. A credentialed single-page app that needs its own policy on some routes names a profile with the `cors:<profile>` middleware; see [CORS profiles](routing.md#cors-profiles).
- **Bot protection:** guard public forms such as sign-up or guest entry with Cloudflare Turnstile: the frontend widget's token goes with the request, and the `turnstile:open` or `turnstile:closed` middleware checks it. See [Turnstile](authentication.md#turnstile).
- **Page cache:** a server-rendered frontend that caches its pages can have them purged after every admin save, so an edit shows on the next page view. See [Site cache purge](caching.md#site-cache-purge).
- **API documentation:** publish a browsable Swagger UI page of the public API for frontend developers with `http.api_docs.enabled`. See [API documentation page](routing.md#api-documentation-page).
- **Push notifications:** see [Web Push](push.md).
The admin is the one frontend SummerCMS ships. It is a single-page app built the same way, against the admin API; see [Admin SPA](../backend/admin-spa.md).

View File

@@ -26,6 +26,9 @@ Each row names the WinterCMS concept, the SummerCMS identifiers that replace it,
| Laravel collections: `map`, `filter`, `reduce`, `keyBy`, `groupBy`, `unique`, `partition`, `first` and `pluck` | Generic functions on plain slices and maps: `sieve.Map`, `sieve.TryMap`, `sieve.Filter`, `sieve.Reduce`, `sieve.KeyBy`, `sieve.GroupBy`, `sieve.Uniq`, `sieve.Partition` and `sieve.Find`. Pluck is `sieve.Map` with a field accessor, unique by key is `sieve.UniqBy`, `chunk` is the standard `slices.Chunk`, and sets are built with `sieve.SetOf` | [sieve](../../modules/sieve/README.md) |
| `fields.yaml` and `columns.yaml` | The same YAML, embedded in the plugin through `pact.AdminAssets` and compiled at boot into a `cabana.CompiledController` | [Forms](../backend/forms.md), [Lists and filters](../backend/lists-and-filters.md), [cabana](../../modules/cabana/README.md) |
| Backend controllers with the Form, List and Relation behaviours | A `pact.AdminController` returned from `pact.HasAdminControllers`; the generic admin API replaces the behaviours, and hooks such as `pact.FormBeforeCreate` and `pact.ListExtendQuery` replace behaviour overrides | [Admin controllers](../backend/admin-controllers.md), [cabana](../../modules/cabana/README.md) |
| The Reorder behaviour and `config_reorder.yaml` | A `config_reorder.yaml` next to `config_list.yaml` and the `reorder` toolbar button: a drag-and-drop tree, or a flat list for records without a parent column; `pact.ReorderAfterSave` runs after a reorder save | [Lists and filters](../backend/lists-and-filters.md#tree-reorder), [cabana](../../modules/cabana/README.md) |
| Form widgets and `registerFormWidgets` | A plugin field type: `pact.HasFormFieldTypes` returning `pact.FormFieldType` values, usable as `type:` in any controller's `fields.yaml`; a one-off element with a server action is a `type: widget` field | [Plugin field types](../backend/field-types.md), [Partials and widgets](../backend/partials-and-widgets.md#form-widgets) |
| HTML cleaning of rich editor content | Every `richeditor` value is sanitized on save by `seaglass.Sanitize`, an allowlist policy; plugin code calls `seaglass.SanitizeReport` to learn what was removed | [Rich text](../backend/forms.md#rich-text), [seaglass](../../modules/seaglass/README.md) |
| `routes.php` | `pact.HasRoutes`, declaring routes on a `pact.Router` with groups, middleware names and `pact.Router.Where` constraints | [Routing](../services/routing.md), [surf](../../modules/surf/README.md) |
| Route middleware | Named middleware of type `pact.Middleware`, registered through `pact.HasMiddleware` | [Routing](../services/routing.md), [surf](../../modules/surf/README.md) |
| `config/*.php`, `.env` and `Config::get` | `compass.Config` with per-environment directories and `SUMMER_` overrides; plugin defaults through `pact.HasConfig` | [Configuration](../services/configuration.md), [compass](../../modules/compass/README.md) |
@@ -37,6 +40,12 @@ Each row names the WinterCMS concept, the SummerCMS identifiers that replace it,
| `registerSchedule` and the scheduler | `pact.HasSchedule` returning `pact.ScheduledCommand` entries with a `pact.Cadence` | [Task scheduling](../plugins/scheduling.md), [pact](../../modules/pact/README.md) |
| Mail templates in `views/mail` | `pact.HasMailTemplates`, sent through `postcard.Mailer` | [Mail](../services/mail.md), [postcard](../../modules/postcard/README.md) |
| Settings models and `registerSettings` | `pact.HasSettings` returning `pact.SettingsItem` entries; an entry with `'url' => Backend::url(...)` sets the `Controller` field to the admin controller ID | [Settings](../backend/settings.md), [cabana](../../modules/cabana/README.md) |
| A backend controller that only ever edits one record (its index redirects to the update screen) | A singleton controller: `pact.AdminSingleton` returns the one record, and a settings entry's `Controller` links to it | [Admin controllers](../backend/admin-controllers.md#singleton-controllers), [Settings](../backend/settings.md) |
| Personal access tokens for API clients | Administrator API tokens with scopes and expiry, sent as `Authorization: Bearer g15_…` and managed in My account or with `admin:token:create` and the other `admin:token:*` commands | [Users and permissions](../backend/users-and-permissions.md#api-tokens), [cabana](../../modules/cabana/README.md) |
| The Laravel `Cache` facade and `Cache::remember` | `cooler.Cache` with `cooler.Cache.Get`, `cooler.Cache.Set`, `cooler.Cache.Remember` and `cooler.Cache.Forget`, kept in memory or in Redis (`cache.driver`) | [Caching](../services/caching.md), [cooler](../../modules/cooler/README.md) |
| The Laravel CORS config (`config/cors.php`) | The same keys under `http.cors`, plus named profiles under `http.cors_profiles` applied with the `cors:<profile>` middleware | [Routing](../services/routing.md#cors), [surf](../../modules/surf/README.md) |
| A captcha on public forms | Cloudflare Turnstile as the `turnstile:open` and `turnstile:closed` middleware, with `turnstile.secret` | [Authentication](../services/authentication.md#turnstile), [bouncer](../../modules/bouncer/README.md) |
| The Static Pages menu item events `pages.menuitem.listTypes`, `pages.menuitem.getTypeInfo` and `pages.menuitem.resolveItem` | Typed menu item types: `pact.HasMenuItemTypes` returning `pact.MenuItemType` values, collected in the `signpost.Registry` that menu and sitemap plugins read | [Menu item types](../plugins/menu-item-types.md), [signpost](../../modules/signpost/README.md) |
| Laravel broadcasting | `lighthouse.Publisher` drivers and models that implement `lighthouse.Broadcastable` | [Realtime](../services/realtime.md), [lighthouse](../../modules/lighthouse/README.md) |
| Laravel Scout search | Models that implement `beachcomber.Searchable`, synced after commit | [Search](../services/search.md), [beachcomber](../../modules/beachcomber/README.md) |
| The Laravel HTTP client | `fetchguard.Fetch` with a `fetchguard.Policy` that blocks private addresses and limits size and time | [Outbound HTTP](../services/outbound-http.md), [fetchguard](../../modules/fetchguard/README.md) |
@@ -53,10 +62,8 @@ SummerCMS does not port the WinterCMS frontend or the PHP helpers that Go alread
| The AJAX framework and Snowboard | Not provided. The frontend calls the JSON API and subscribes to realtime channels; see [Frontend and AJAX](../services/frontend-and-ajax.md). |
| The media manager | Not provided. Store uploads as model attachments; see [Attachments](../database/attachments.md). |
| Import and export in backend lists | Not provided. |
| Record sorting (the Reorder behaviour) | Not provided. |
| The fluent collection object (the Collection class and method chaining) | Not provided. Call the [sieve](../../modules/sieve/README.md) functions and the standard `slices` and `maps` packages on plain slices and maps. |
| Behaviours and dynamic class extension | Not provided. Use Go interfaces and composition. |
| Cache | Not provided. Use the Go standard library or a service another plugin publishes. |
| Session | Not provided. The API is stateless and authenticates with tokens; see [Frontend and AJAX](../services/frontend-and-ajax.md). |
## Plugin.php in Go

View File

@@ -52,7 +52,9 @@ These are the keys most applications set. Each module's reference page lists all
| `app.timezone` | Timezone of scheduled commands. Defaults to UTC. | [conga](../../modules/conga/README.md) |
| `app.locale`, `app.fallback_locale` | Default and fallback translation locales. | [phrasebook](../../modules/phrasebook/README.md) |
| `http.body_limits.default_bytes`, `http.body_limits.upload_bytes` | Request body limits. Required by `serve` and `route:list`. | [surf](../../modules/surf/README.md) |
| `http.cors.*`, `http.trusted_proxies` | CORS and the proxies whose forwarded client IP is trusted. | [surf](../../modules/surf/README.md) |
| `http.cors.*`, `http.trusted_proxies` | CORS and the proxies whose forwarded client IP is trusted. With `supports_credentials: true`, a wildcard host other than the whole leftmost label and an origin pattern not anchored with `^` and `$` fail the start-up. | [surf](../../modules/surf/README.md) |
| `http.cors_profiles.*` | Named CORS profiles with the `http.cors` keys except `paths`, applied to a route or group with the `cors:<profile>` middleware; checked like `http.cors`. | [surf](../../modules/surf/README.md) |
| `http.api_docs.enabled`, `http.api_docs.path`, `http.api_docs.title` | The opt-in Swagger UI page for the public API (off by default; path `/api-docs`, title `API documentation`). | [surf](../../modules/surf/README.md) |
| `http.rate_limit.store` | Where `throttle` counters live: `memory` (per instance, the default) or `redis` (shared by every instance). | [cooler](../../modules/cooler/README.md) |
| `cache.driver`, `cache.prefix`, `cache.memory.max_entries` | The cache driver (`memory`, the default, or `redis`), the key prefix (default from `app.name`) and the memory driver's size bound (default 10000). | [cooler](../../modules/cooler/README.md) |
| `site_cache.purge_url`, `site_cache.purge_token` | The host site's page-cache purge route and its bearer token (at least 32 characters). Set both or neither; http only to a loopback host. Keep the token in `SUMMER_SITE_CACHE__PURGE_TOKEN`. | [cooler](../../modules/cooler/README.md) |
@@ -61,6 +63,8 @@ These are the keys most applications set. Each module's reference page lists all
| `storage.uploads.bucket_url` | Uploads bucket, `file://`, `mem://` or `s3://` (the last only in a binary built with the `summercms_s3` tag from `build.tags` in `summer.yaml`; credentials from the AWS environment). Required by `serve`. | [lagoon](../../modules/lagoon/README.md) |
| `storage.uploads.public_url` | Absolute `http` or `https` base of a public bucket or CDN; file and thumbnail URLs become that base plus the partition path. Unset keeps `storage.uploads.public_path_prefix`. Only for a bucket holding public files alone. | [lagoon](../../modules/lagoon/README.md) |
| `storage.uploads.serve` | Mounts `attach.StaticHandlerPublic` under `storage.uploads.public_path_prefix` with `X-Content-Type-Options: nosniff`, a sandboxing CSP and `Content-Disposition: attachment` for anything but a raster image. Defaults to `false`. | [surf](../../modules/surf/README.md) |
| `auth.cookie.name`, `auth.cookie.domain`, `auth.cookie.path`, `auth.cookie.secure`, `auth.cookie.same_site` | The httpOnly token cookie for a browser frontend (defaults `token`, host-only, `/`, Secure when `app.url` is https, `lax`). In production a cookie that is not Secure fails the start-up. | [bouncer](../../modules/bouncer/README.md) |
| `turnstile.secret`, `turnstile.field` | The Cloudflare Turnstile secret behind the `turnstile:open` and `turnstile:closed` middleware (keep it in `SUMMER_TURNSTILE__SECRET`) and the request field carrying the token (default `turnstile_token`). | [surf](../../modules/surf/README.md) |
| `admin.jwt.secret` | Secret for admin tokens. Required as soon as a plugin registers an admin controller. | [cabana](../../modules/cabana/README.md) |
| `backend.uri` | Path the admin is served under. Defaults to `/backend`. | [cabana](../../modules/cabana/README.md) |
| `mail.driver`, `mail.from`, `mail.smtp.*` | Mail delivery: `memory`, `log` or `smtp`. | [postcard](../../modules/postcard/README.md) |
@@ -69,6 +73,8 @@ These are the keys most applications set. Each module's reference page lists all
| `search.driver`, `search.typesense.*` | The search engine and its Typesense settings. | [beachcomber](../../modules/beachcomber/README.md) |
| `push.enabled`, `push.public_key`, `push.private_key` | Web Push with VAPID keys. | [flare](../../modules/flare/README.md) |
Some keys are checked more strictly from 0.2 on, so a value that started an older release can stop the start-up; [Upgrading](upgrading.md#upgrading-to-02) lists them and their fixes.
> [!NOTE]
> The documentation checks every identifier, link and command name it shows against the code, but not configuration keys. The keys on this page are reviewed by hand; the module reference pages are the authority.

View File

@@ -30,9 +30,12 @@ The data layer supports PostgreSQL only, and SummerCMS targets Go 1.27.
## How these docs are organised
- **Setup** covers installation, configuration and the move from WinterCMS.
- **Setup** covers installation, configuration, the move from WinterCMS and [Upgrading](upgrading.md) to a newer framework release.
- **Architecture** explains the single binary, Go modules, the application lifecycle and how a request reaches your code.
- **Plugins** covers registering a plugin, scheduling, extending other plugins and testing.
- **Plugins** covers registering a plugin, scheduling, extending other plugins, menu item types and testing.
- **Backend** covers the admin: controllers, forms and field types, lists and filters, relations, users and API tokens, settings and the admin SPA.
- **Database** covers models, migrations, queries and pagination, relations, casts and validation, attachments and transactions.
- **Services** covers configuration, events, routing, authentication, caching, rate limiting, storage, logging, mail, jobs, realtime, search, push and the other runtime services.
- **Console** lists the `summer` tool's commands and the commands of every application binary, and shows how to write your own.
- **API reference** has one page per framework module.

73
docs/setup/upgrading.md Normal file
View File

@@ -0,0 +1,73 @@
---
title: Upgrading
description: Move an application to a newer framework release, what the version numbers promise, and the config to check before you take 0.2.
section: setup
order: 60
---
# Upgrading
An application created by `summer new` builds against a framework checkout next to it; the update script moves that checkout to the newest release tag, reinstalls the `summer` tool and rebuilds the application (see [Updating an application](installation.md#updating-an-application)). An application or plugin that requires a tagged framework version in its `go.mod` moves with the go command instead:
```sh
go get git.golem15.com/golem15/summercms@v0.2.0
go mod tidy
```
Then rebuild, run `migrate` with the new binary and restart it.
## Version numbers
SummerCMS is in the 0.x series, and the version tells you whether to read before you upgrade:
- The minor version (0.1 to 0.2) goes up when an upgrade can refuse a configuration that the previous release accepted, or changes a documented behaviour. Read the section for that version below before you deploy it.
- A patch version (0.2.0 to 0.2.1) is safe to take without reading: it fixes bugs and adds features, and an application that started on the previous patch starts on it too.
Applications pin exact versions, so a new release never reaches an application until it moves its `go.mod` or its checkout.
## Upgrading to 0.2
0.2 tightens two configuration checks so that settings which leaked credentials stop the start-up instead. Most applications are not affected: the first concerns applications that load an `auth.cookie` block, the second applications that send credentialed CORS answers. Start the new binary once in each environment before you switch traffic to it; a refused configuration fails `serve` at start-up with an error that names the key.
### Boot refusals
**A token cookie that is not Secure in production.** In the production environment, `bouncer.LoadCookieConfig` refuses a token cookie whose `Secure` attribute resolves to false. That covers `auth.cookie.secure: false` and, since 0.2, an unset `auth.cookie.secure` with an http or empty `app.url`, from which the cookie inherited `Secure=false` before. The error names `auth.cookie.secure` and `app.url`.
It affects applications whose plugins load an `auth.cookie` block for a browser frontend (the cookie transport). To fix it, set `app.url` to the application's public https URL, or set `auth.cookie.secure: true` explicitly. See [Cookie transport](../services/authentication.md#cookie-transport).
**Credentialed CORS with a wildcard host or an unanchored pattern.** With `supports_credentials: true`, globally under `http.cors` or in any profile under `http.cors_profiles.<name>`, these entries now fail the start-up, and the error names the key with its index, such as `http.cors.allowed_origins[1]` or `http.cors_profiles.spa.allowed_origins_patterns[0]`:
- an `allowed_origins` entry with a `*` that is not the whole leftmost host label after a literal scheme, such as `https://*`, `*://*`, `https://*example.com` or `http*://www.example.com` (`https://*.example.com` stays valid);
- an `allowed_origins_patterns` entry not anchored with `^` and `$`, in every alternative of the expression;
- any origin rule that matches an origin outside the configured domains, such as `https://*.com` or `^https?://.*$`.
It affects applications that send credentialed CORS answers, to a browser frontend on another origin that calls the API with cookies. To fix it, list the frontend's exact origins, or write anchored patterns such as `^https://[a-z]+\.example\.com$`. Without credentials these entries load as before. See [CORS](../services/routing.md#cors) and [CORS profiles](../services/routing.md#cors-profiles).
### Behaviour changes
These need no configuration change, but a test or an operator may notice them:
- **Site-cache purges are queued.** After an admin save commits, the purge request to the site is queued and sent by a background worker, with at most one request in flight per purger; saves committed meanwhile collapse into one follow-up request. The save no longer waits for the site. A test that asserts a purge calls `cooler.SiteCachePurger.Wait` first, and `serve` drains the queue on shutdown. See [Site cache purge](../services/caching.md#site-cache-purge).
- **Protected uploads need a private bucket.** While `storage.uploads.public_url` is set, `attach.Store` refuses a protected (`is_public` false) upload with `attach.ErrProtectedNeedsPrivateBucket` before it writes anything, and `serve` logs one warning at start-up that the bucket must hold public files only. Keep protected relations on a bucket without `public_url`, or make the relation public. See [Public URL base](../services/storage.md#public-url-base).
- **The Journal plugin's purge keys are deprecated.** `golem15.journal.site_cache.purge_url` and `golem15.journal.site_cache.purge_token` still work as aliases of the framework's `site_cache.purge_url` and `site_cache.purge_token`, which win when both are set; the plugin logs a deprecation warning. Move the values to the `site_cache` keys.
- **Cookie defaults.** A `bouncer.CookieConfig` built by hand with every field zero writes `SameSite=Lax` and `Secure`; a config with any field set keeps its own `Secure` value.
- **The API documentation page** at `/api-docs` runs only scripts that carry the page's per-request nonce. See [API documentation page](../services/routing.md#api-documentation-page).
- **Turnstile without a secret.** When `turnstile.secret` is empty and routes name `turnstile:open` or `turnstile:closed`, the start-up logs one warning with the number of such routes. See [Turnstile](../services/authentication.md#turnstile).
- **CORS profile headers on every answer.** A route that names `cors:<profile>` carries the profile's headers on every response, including refusals from guards, throttle, Turnstile and the body limit, and recovered panics.
## What 0.2 adds since 0.1
The 0.1 series grew through many patch releases. These are the larger features that arrived after its first public releases, with the page that documents each:
- Caching with memory and Redis drivers, and a Redis store that shares `throttle` counters between instances: [Caching](../services/caching.md), [Rate limiting](../services/rate-limiting.md).
- The site-cache purge after admin saves, including reorder saves through `pact.ReorderAfterSave`: [Site cache purge](../services/caching.md#site-cache-purge).
- Token cookies with ready-made refresh and logout handlers, and Turnstile bot protection: [Cookie transport](../services/authentication.md#cookie-transport), [Turnstile](../services/authentication.md#turnstile).
- Credentialed CORS, per-route CORS profiles (`cors:<profile>`) and the opt-in API documentation page: [CORS](../services/routing.md#cors), [CORS profiles](../services/routing.md#cors-profiles), [API documentation page](../services/routing.md#api-documentation-page).
- Go build tags in `summer.yaml` (`build.tags`), S3 uploads buckets behind the `summercms_s3` tag and a public URL base for a bucket or CDN: [The summer.yaml manifest](../architecture/go-modules-and-workspaces.md#the-summeryaml-manifest), [S3 and S3-compatible buckets](../services/storage.md#s3-and-s3-compatible-buckets), [Public URL base](../services/storage.md#public-url-base).
- Serving uploads from the application and a per-request access log: [Serving files](../services/storage.md#serving-files), [Request log](../services/logging.md#request-log).
- Plugin-provided admin field types: [Plugin field types](../backend/field-types.md).
- Rich text, repeater, money, password and taglist fields, and dependent dropdowns: [Rich text](../backend/forms.md#rich-text), [Repeater](../backend/forms.md#repeater), [Money](../backend/forms.md#money), [Form-only fields](../backend/forms.md#form-only-fields), [Taglist](../backend/forms.md#taglist), [Dependent dropdowns](../backend/forms.md#dependent-dropdowns).
- Record reordering as a tree or a flat list: [Tree reorder](../backend/lists-and-filters.md#tree-reorder), [Flat reorder](../backend/lists-and-filters.md#flat-reorder).
- Singleton controllers, record actions and write-only settings secrets: [Singleton controllers](../backend/admin-controllers.md#singleton-controllers), [Record actions](../backend/admin-controllers.md#record-actions), [Write-only secrets](../backend/settings.md#write-only-secrets).
- Personal API tokens for administrators and the `admin:token:*` commands: [API tokens](../backend/users-and-permissions.md#api-tokens), [Admin accounts](../console/setup-and-maintenance.md#admin-accounts).
- Menu item types that content plugins register for menus and sitemaps: [Menu item types](../plugins/menu-item-types.md).
- New framework modules: [cooler](../../modules/cooler/README.md) (caches, shared counters and the site-cache purge), [seaglass](../../modules/seaglass/README.md) (the rich text sanitizer), [sieve](../../modules/sieve/README.md) (collection helpers) and [signpost](../../modules/signpost/README.md) (the menu item type registry).