Files
summercms/.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-05-SUMMARY.md

17 KiB

phase, plan, subsystem, tags, requires, provides, affects, estimate_ref, actuals, plan_head_before, plan_head_after, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status
phase plan subsystem tags requires provides affects estimate_ref actuals plan_head_before plan_head_after tech-stack key-files key-decisions patterns-established requirements-completed coverage duration completed status
11.1-summercms-documentation-for-humans-and-ai-agents 05 docs
docs
wintercms
walkthrough
scaffolding
examples
lagoon
cabana
bonfire
surf
phase provides
11.1-04 Backend, Database and Services pages the walkthrough links to; the src= snippet, command and identifier checkers
phase provides
11.1-03 Coming from WinterCMS concept map, console scaffolding page, requiredPages
docs/examples/blog: the acme.blog plugin as a package tree of the root module (blog.Plugin, models.Post, models.NewPost, updates.CreatePosts, updates.AddPublishedAt, controllers.PostsController, console.PublishCommand)
Route GET /api/blog/posts (published posts, newest first, lagoon.Paginate), command blog:publish <slug>, permission acme.blog.access_posts
Short tests TestPluginActivates, TestRoutesRegistered, TestPostTableAndFill, TestMigrationIDs, TestPostsControllerDeclaration, TestPublishCommandShape, TestScaffoldLayout
Docker tests TestMigrateUpAndRollback, TestPostsRouteAgainstDatabase, TestPublishCommandAgainstDatabase, TestPublishCommandOpensDatabase on an ICU pl-PL database
docs/setup/porting-a-plugin.md, linked from docs/setup/coming-from-wintercms.md and docs/index.md
Three scaffolder gap todos under .planning/todos/pending/
11.1-06
tokens 110000, tasks 3, confidence low
tokens tasks commits
21070 3 4
4e83d06025 4e05450f46
added patterns
Walkthrough code is real scaffolder output finished by hand, kept in the root module and pinned to the scaffolder by a file-set test
A plugin reaches the database through the app it kept in Boot: Lookup[*gorm.DB] in handlers, withDB (published handle or lagoon.OpenFromApp for the command's duration) in commands
Response DTOs built field by field (postJSON with wire.Time) rather than serialising the model
Docker tests in a docs example package use their own testcontainers TestMain and one ICU pl-PL database per test
created modified
docs/examples/blog/plugin.go
docs/examples/blog/routes.go
docs/examples/blog/registry.gen.go
docs/examples/blog/blog_test.go
docs/examples/blog/postgres_test.go
docs/examples/blog/scaffold_layout_test.go
docs/examples/blog/models/post.go
docs/examples/blog/models/post_test.go
docs/examples/blog/models/posts/fields.yaml
docs/examples/blog/models/posts/columns.yaml
docs/examples/blog/updates/20260101000000_create_acme_blog_posts.go
docs/examples/blog/updates/20260101000100_add_published_at.go
docs/examples/blog/updates/updates_test.go
docs/examples/blog/controllers/posts.go
docs/examples/blog/controllers/posts_test.go
docs/examples/blog/controllers/posts/config_form.yaml
docs/examples/blog/controllers/posts/config_list.yaml
docs/examples/blog/console/publish.go
docs/examples/blog/console/publish_test.go
docs/examples/blog/config/config.yaml
docs/examples/blog/lang/en/lang.yaml
docs/examples/blog/views/mail/welcome.htm
docs/examples/blog/{classes,console,controllers,jobs,middleware,models,updates}/doc.go
docs/setup/porting-a-plugin.md
.planning/todos/pending/scaffold-same-second-migration-order.md
.planning/todos/pending/scaffold-admin-controller-incomplete.md
.planning/todos/pending/scaffold-generated-header-and-command-deps.md
docs/setup/coming-from-wintercms.md
docs/index.md
docs/console/scaffolding.md
cmd/summer/docs_test.go
The permission is acme.blog.access_posts; the controller also implements pact.AdminRecordSource and the plugin pact.AdminAssets, pact.HasPermissions and pact.HasNavigation, because cabana refuses to start without AdminFS
console.PublishCommand takes a withDB parameter: a generated zero-argument command cannot reach the app. The plugin returns it from Commands next to generatedCommands(), and registry.gen.go stays byte-identical to what the scaffolder regenerates for this tree (checked by re-running make: on a copy)
fields.yaml has title, slug and body only: cabana forms have no date picker, so published_at is a datetime list column, is not fillable, and blog:publish sets it
The posts route lists published posts only (published_at IS NOT NULL, newest first) through a postJSON DTO with wire.Time, and clamps page and per_page
blog:publish uses COALESCE(published_at, NOW()) with a bound slug, so publishing twice keeps the first time and an unknown slug is an error
Adding a column is a subsection of Migrations, before Routes, because the route already filters on published_at
The scaffold-layout test calls the make functions in the page's order (plugin, model, migration, admin controller, command) and runs with GOPROXY=off
docs/examples/<plugin> is an in-root package tree; its layout is pinned by TestScaffoldLayout and its commands are collected by the docs command checker from bonfire.Command literals
DOCS-04
DOCS-07
id description requirement verification human_judgment
D1 The acme.blog plugin compiles in the root module, activates through party and backpack, and registers GET /api/blog/posts with surf DOCS-07
kind ref status
unit docs/examples/blog/blog_test.go#TestPluginActivates pass
kind ref status
unit docs/examples/blog/blog_test.go#TestRoutesRegistered pass
false
id description requirement verification human_judgment
D2 Migrations go up on an ICU pl-PL database, the last one rolls back (published_at gone, table kept) and applies again DOCS-07
kind ref status
integration docs/examples/blog/postgres_test.go#TestMigrateUpAndRollback pass
kind ref status
unit docs/examples/blog/updates/updates_test.go#TestMigrationIDs pass
false
id description requirement verification human_judgment
D3 The route serves published posts newest first with Laravel pagination meta and Carbon timestamps; writes go through the lagoon.Fill allow-list DOCS-07
kind ref status
integration docs/examples/blog/postgres_test.go#TestPostsRouteAgainstDatabase pass
kind ref status
unit docs/examples/blog/models/post_test.go#TestPostTableAndFill pass
false
id description requirement verification human_judgment
D4 The admin controller declares acme.blog.access_posts, its YAML compiles through cabana without a database, and blog:publish runs with bonfire against a published and a self-opened database DOCS-07
kind ref status
unit docs/examples/blog/controllers/posts_test.go#TestPostsControllerDeclaration pass
kind ref status
unit docs/examples/blog/console/publish_test.go#TestPublishCommandShape pass
kind ref status
integration docs/examples/blog/postgres_test.go#TestPublishCommandAgainstDatabase pass
kind ref status
integration docs/examples/blog/postgres_test.go#TestPublishCommandOpensDatabase pass
false
id description requirement verification human_judgment
D5 The walkthrough's file set equals what make:plugin, make:model, make:migration, make:admin-controller and make:command write for acme.blog DOCS-07
kind ref status
unit docs/examples/blog/scaffold_layout_test.go#TestScaffoldLayout pass
false
id description requirement verification human_judgment
D6 Every Go and YAML fence on porting-a-plugin.md is a src= copy of docs/examples/blog; the page lists the make commands and is linked from the concept map and index DOCS-04
kind ref status
other go run ./cmd/summer docs:build --check (no problems); docs:sync all snippets up to date pass
kind ref status
unit cmd/summer/docs_test.go#TestDocsRequiredPages pass
kind ref status
unit cmd/summer/docs_test.go#TestDocsAIOutputsInSync pass
kind ref status
other scripts/check-phase11.1.sh --docs and --forbidden pass
false
id description verification human_judgment rationale
D7 A WinterCMS developer can follow the walkthrough from top to bottom and end with the same plugin
true Backstop truth in the plan: readability for a WinterCMS developer is a reader's judgment
18min 2026-09-30 complete

Phase 11.1 Plan 05: acme/blog porting walkthrough Summary

A compiled acme.blog plugin under docs/examples/blog, built from real summer make:* output, with a Post model behind a fill allow-list, two reversible migrations, a published-posts API route, an admin controller behind acme.blog.access_posts and a blog:publish command, all tested against an ICU pl-PL Postgres. docs/setup/porting-a-plugin.md walks a WinterCMS developer through it with PHP next to src= copies, and a scaffold-layout test pins the tree to the scaffolder.

Performance

  • Duration: about 18 min
  • Started: 2026-09-30T21:24:56Z
  • Completed: 2026-09-30T21:42:43Z
  • Tasks: 3
  • Files modified: 37

Accomplishments

  • Tracer: the scaffolder's make:plugin and make:model output, copied into the root module with fixed migration timestamps, became a plugin that activates, keeps the app in Boot, reads acme.blog.per_page from its embedded config and serves GET /api/blog/posts through lagoon.Paginate and wire.WriteJSON.
  • Admin, command, second migration: controllers.PostsController serves models.Post behind acme.blog.access_posts with WinterCMS-style form and list YAML embedded through pact.AdminAssets; blog:publish <slug> publishes a post with a bound parameter; updates.AddPublishedAt adds and drops published_at. The route now lists published posts only.
  • Docker tier: a package TestMain starts testcontainers Postgres (and fails the full run without Docker); each test gets its own TEMPLATE template0 ... ICU_LOCALE 'pl-PL' database. The tests migrate, roll back and re-apply, seed through models.NewPost, call the route through surf.Assemble, and run blog:publish both with the published handle and with one the command opens from database.dsn.
  • Scaffold pin: TestScaffoldLayout runs the five make functions for acme.blog in a copy of examples/hello and diffs the file set against the walkthrough (mutation-checked with a planted file).
  • Page: registration, model, migrations with an added column, routes, admin controller and console command sections, each PHP first; a "Scaffold it yourself" section with the exact commands and the go.mod a real plugin gets; "What the scaffolder leaves to you"; and a closing checklist. 20 src= fences (6 YAML), linked from the concept map and the index.

Task Commits

  1. Task 1: Tracer, the plugin with its model, migration and posts route: 41a3190 (feat)
  2. Task 2: Admin controller, publish command and published_at migration against Postgres: dd82b8a (feat)
  3. Task 3: Scaffold-layout test, make commands on the page, concept map and index links: 63290c6 (feat)
  4. Todos for the scaffolder gaps found while porting: 4e05450 (docs)

Plan metadata: the docs(11.1-05) commit that adds this file

Files Created/Modified

See key-files in the frontmatter.

Decisions Made

See key-decisions in the frontmatter.

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Blocking] The scaffolded plugin cannot boot an admin controller

  • Found during: Task 2
  • Issue: cabana refuses a plugin with admin controllers but no AdminFS, the generated controller has no record source or permission, and a delete toolbar button needs showCheckboxes: true. The model also needs Rules before the admin API saves.
  • Fix: plugin.go embeds controllers/*/*.yaml models/*/*.yaml and implements AdminFS, Permissions and Navigation; the controller implements NewRecord and RequiredPermissions; models.Post has Rules; the list YAML sets showCheckboxes. Logged in scaffold-admin-controller-incomplete.md.
  • Commit: dd82b8a, 4e05450

2. [Rule 3 - Blocking] A generated command cannot reach the database

  • Found during: Task 2
  • Issue: the registry only lists zero-argument command functions, and the generated main opens no database for plugin commands.
  • Fix: console.PublishCommand(withDB) takes the plugin's withDB (published handle, else lagoon.OpenFromApp for the command's duration, as the framework's migrate commands do); Plugin.Commands appends it. registry.gen.go stays what the scaffolder regenerates. Logged in scaffold-generated-header-and-command-deps.md.
  • Commit: dd82b8a, 4e05450

3. [Rule 1 - Accuracy] published_at is not a form field

  • Found during: Task 2
  • Issue: the plan asked for a published_at field in fields.yaml, but cabana forms have no date type (docs/backend/forms.md lists the supported types).
  • Fix: the form has title, slug and body; published_at is a datetime list column and is set by blog:publish. The page explains why.
  • Commit: dd82b8a

4. [Rule 1 - Accuracy] docs/console/scaffolding.md claimed same-second migrations get consecutive timestamps

  • Found during: Task 3 (probe: make:model then make:migration in one second gave both files one timestamp, and registry.gen.go listed the ALTER migration first)
  • Fix: the sentence now says only same-name migrations move to the next second; the porting page tells the developer to check updates/. internal/build is unchanged; the bug is logged in scaffold-same-second-migration-order.md.
  • Files modified: docs/console/scaffolding.md (beyond the plan's file list)
  • Commit: 63290c6, 4e05450

5. [Rule 2 - Safety] The route lists published posts only and answers through a DTO

  • Found during: Task 2
  • Issue: a public list of every post would expose drafts, and serialising the model would leak any column added later.
  • Fix: WHERE published_at IS NOT NULL, a postJSON type with wire.Time, clamped page and per_page; TestPostsRouteAgainstDatabase asserts the draft is absent, created_at is not leaked and per_page is clamped.
  • Commit: dd82b8a

Other additions beyond the plan

  • models.NewPost, the Go form of Post::make($input), gives the walkthrough a write path through lagoon.Fill that the page shows and the Docker tests use to seed.
  • TestPublishCommandOpensDatabase covers the lagoon.OpenFromApp branch of withDB, which is how the command runs from ./bin/acme.
  • The migrations section is ordered before the routes section, and "Adding a column" is its subsection, because the route filters on published_at.

Total deviations: 5 auto-fixed (2 blocking, 2 accuracy, 1 safety). Impact: no framework code changed; three scaffolder gaps are logged as todos.

Issues Encountered

  • A probe command in the scratch directory stalled on an interactive rm alias; it was stopped and the scratch files removed. No repository file was affected.

Verification

  • go vet ./... clean; go test -short ./... green.
  • Full go test ./... -count=1 with Docker: all 35 packages with tests pass, no FAIL lines; every walkthrough Docker test reports PASS, not SKIP.
  • go run ./cmd/summer docs:build --check: no problems; docs:sync: all snippets up to date.
  • scripts/check-phase11.1.sh --docs, --forbidden and --deps, scripts/check-phase10.sh --hygiene and scripts/check-phase11.sh --hygiene pass.
  • Acceptance greps: no docs/examples/blog/go.mod; 20 src=docs/examples/blog/ fences (6 YAML); 16 summer make: mentions; lagoon.Fill in models/post.go and post_test.go; ICU_LOCALE in postgres_test.go; "blog:publish" in console/publish.go; RequiredPermissions in controllers/posts.go; porting-a-plugin.md linked from coming-from-wintercms.md and index.md; no forbidden names under docs; git diff --name-only 9033d81 -- internal/build is empty.

Known Stubs

None. views/mail/welcome.htm is the scaffolder's placeholder template, kept so the layout matches make:plugin; MailTemplates returns nil, so it is never registered or sent.

Threat Flags

None. The example adds a public read-only route and an admin controller behind a permission; both are covered by the plan's threat register (T-11.1-16, T-11.1-17).

User Setup Required

None.

Next Phase Readiness

Plan 11.1-06 (unit tests last) can cover the walkthrough packages as they are; every sub-package with code already has a _test.go. The three scaffolder todos are ready for a code phase that owns internal/build.

Self-Check: PASSED

All listed files exist. Commits 41a3190, dd82b8a, 63290c6 and 4e05450 are in the log.