diff --git a/.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-05-SUMMARY.md b/.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-05-SUMMARY.md new file mode 100644 index 0000000..7a32523 --- /dev/null +++ b/.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-05-SUMMARY.md @@ -0,0 +1,282 @@ +--- +phase: 11.1-summercms-documentation-for-humans-and-ai-agents +plan: 05 +subsystem: docs +tags: [docs, wintercms, walkthrough, scaffolding, examples, lagoon, cabana, bonfire, surf] + +requires: + - phase: 11.1-04 + provides: "Backend, Database and Services pages the walkthrough links to; the src= snippet, command and identifier checkers" + - phase: 11.1-03 + provides: "Coming from WinterCMS concept map, console scaffolding page, requiredPages" +provides: + - "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 , 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/" +affects: [11.1-06] + +estimate_ref: "tokens 110000, tasks 3, confidence low" +actuals: + tokens: 21070 + tasks: 3 + commits: 4 +plan_head_before: 4e83d06025af7993233caf8fd7bf16ea7a2fcc83 +plan_head_after: 4e05450f465c16a6a3c40b252bcdf612e091c390 + +tech-stack: + 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" + +key-files: + created: + - 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 + modified: + - docs/setup/coming-from-wintercms.md + - docs/index.md + - docs/console/scaffolding.md + - cmd/summer/docs_test.go + +key-decisions: + - "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" + +patterns-established: + - "docs/examples/ 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" + +requirements-completed: [DOCS-04, DOCS-07] + +coverage: + - id: D1 + description: "The acme.blog plugin compiles in the root module, activates through party and backpack, and registers GET /api/blog/posts with surf" + requirement: DOCS-07 + verification: + - kind: unit + ref: "docs/examples/blog/blog_test.go#TestPluginActivates" + status: pass + - kind: unit + ref: "docs/examples/blog/blog_test.go#TestRoutesRegistered" + status: pass + human_judgment: false + - id: D2 + description: "Migrations go up on an ICU pl-PL database, the last one rolls back (published_at gone, table kept) and applies again" + requirement: DOCS-07 + verification: + - kind: integration + ref: "docs/examples/blog/postgres_test.go#TestMigrateUpAndRollback" + status: pass + - kind: unit + ref: "docs/examples/blog/updates/updates_test.go#TestMigrationIDs" + status: pass + human_judgment: false + - id: D3 + description: "The route serves published posts newest first with Laravel pagination meta and Carbon timestamps; writes go through the lagoon.Fill allow-list" + requirement: DOCS-07 + verification: + - kind: integration + ref: "docs/examples/blog/postgres_test.go#TestPostsRouteAgainstDatabase" + status: pass + - kind: unit + ref: "docs/examples/blog/models/post_test.go#TestPostTableAndFill" + status: pass + human_judgment: false + - id: D4 + description: "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" + requirement: DOCS-07 + verification: + - kind: unit + ref: "docs/examples/blog/controllers/posts_test.go#TestPostsControllerDeclaration" + status: pass + - kind: unit + ref: "docs/examples/blog/console/publish_test.go#TestPublishCommandShape" + status: pass + - kind: integration + ref: "docs/examples/blog/postgres_test.go#TestPublishCommandAgainstDatabase" + status: pass + - kind: integration + ref: "docs/examples/blog/postgres_test.go#TestPublishCommandOpensDatabase" + status: pass + human_judgment: false + - id: D5 + description: "The walkthrough's file set equals what make:plugin, make:model, make:migration, make:admin-controller and make:command write for acme.blog" + requirement: DOCS-07 + verification: + - kind: unit + ref: "docs/examples/blog/scaffold_layout_test.go#TestScaffoldLayout" + status: pass + human_judgment: false + - id: D6 + description: "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" + requirement: DOCS-04 + verification: + - kind: other + ref: "go run ./cmd/summer docs:build --check (no problems); docs:sync all snippets up to date" + status: pass + - kind: unit + ref: "cmd/summer/docs_test.go#TestDocsRequiredPages" + status: pass + - kind: unit + ref: "cmd/summer/docs_test.go#TestDocsAIOutputsInSync" + status: pass + - kind: other + ref: "scripts/check-phase11.1.sh --docs and --forbidden" + status: pass + human_judgment: false + - id: D7 + description: "A WinterCMS developer can follow the walkthrough from top to bottom and end with the same plugin" + verification: [] + human_judgment: true + rationale: "Backstop truth in the plan: readability for a WinterCMS developer is a reader's judgment" + +duration: 18min +completed: 2026-09-30 +status: 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 ` 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.