docs(10.1-03): complete Albums extension proof plan

This commit is contained in:
Jakub Zych
2026-09-29 02:28:40 +02:00
parent c3efbc3428
commit 29336a58ce
2 changed files with 246 additions and 3 deletions

View File

@@ -0,0 +1,215 @@
---
phase: 10.1-runtime-admin-extension-point
plan: 03
subsystem: admin
tags: [go, fonoteka, cabana, custom-elements, html-template, lagoon, admin, discogs-stub]
requires:
- phase: 10.1-runtime-admin-extension-point
provides: "10.1-01 pact AdminClientAssets/HasAdminActions/AdminPartialData, widget/toolbar/partial routes, plugin asset route; 10.1-02 SPA widget host, header partial host, toolbar actions, partial style kit"
provides:
- Albums statistics strip (headerPartial stats, controllers/albums/_stats.htm) scoped by scopeAlbums
- albumsAdminController.PartialData, AdminJS, AdminCSS, AdminActions with albumsStatsView/albumsStatItem view models
- Albums form year field and discogs widget field (golem15-fonoteka-discogs-lookup, action discogsLookup, fill [year, format])
- discogsLookup and discogsSync stub actions gated by golem15.fonoteka.access_albums
- plain-JS custom element assets/js/discogs-lookup.js and assets/css/albums.css embedded in the plugin AdminFS
- en and pl copy for item.year, stats.*, discogs.lookup_*, discogs.sync_button, discogs.stub_*
- TestPhase101AlbumsSmoke (stats, widget, assets, toolbar)
- lagoon.Fill converts json.Number into integer, unsigned and float fields
affects: [10.1-04 unit tests and gate, 14 Discogs client]
actuals:
tokens: 15900
tasks: 3
commits: 4
plan_head_before: 720ee5796b617a2ae48460ca85a796baf868b5a7
plan_head_after: c3efbc3428654e05ce13719e3ea4b308bfdc94fa
fonoteka_head_before: be3fbf4d23315e10abe37ef405400e7907ae33fa
fonoteka_head_after: 9868a0a26957d4657e7084755655dd3a6f6c8f11
tech-stack:
added: []
patterns:
- "A header partial's view model is a curated struct of labels and integers; each count query is built fresh from db.WithContext(ctx).Model(...) and passed through the controller's scope"
- "Application widget elements are plain JS custom elements with one light-DOM button; text comes only from the SPA's label and busy-label attributes and the element only dispatches summer-action"
- "Discogs actions reuse the existing Winter permission instead of adding permission codes"
key-files:
created:
- ../fonoteka.go/plugins/golem15/fonoteka/controllers/albums/_stats.htm
- ../fonoteka.go/plugins/golem15/fonoteka/assets/js/discogs-lookup.js
- ../fonoteka.go/plugins/golem15/fonoteka/assets/css/albums.css
- ../fonoteka.go/plugins/golem15/fonoteka/admin_phase101_smoke_test.go
modified:
- ../fonoteka.go/plugins/golem15/fonoteka/controllers/albums_admin_controller.go
- ../fonoteka.go/plugins/golem15/fonoteka/controllers/albums/config_list.yaml
- ../fonoteka.go/plugins/golem15/fonoteka/models/album/fields.yaml
- ../fonoteka.go/plugins/golem15/fonoteka/admin.go
- ../fonoteka.go/plugins/golem15/fonoteka/lang/en/lang.yaml
- ../fonoteka.go/plugins/golem15/fonoteka/lang/pl/lang.yaml
- ../fonoteka.go/plugins/golem15/fonoteka/admin_albums_test.go
- ../fonoteka.go/plugins/golem15/fonoteka/admin_phase10_controllers_test.go
- ../fonoteka.go/plugins/golem15/fonoteka/admin_phase10_copy_test.go
- modules/lagoon/fill.go
- modules/lagoon/fill_test.go
- modules/lagoon/README.md
key-decisions:
- "lagoon.Fill converts json.Number into numeric fields; a fraction or an overflow into an integer field is an error, not a silent truncation"
- "The stats template trims whitespace between items with {{- -}}, so the node tree carries no whitespace-only text nodes between stats"
- "The discogs widget sends only its fill keys; the stub ignores them and the record, so the create form (record-id empty) gets the same answer"
patterns-established:
- "Application extension surfaces get a smoke test through the assembled router at the admin prefix with a real cookie admin and a collection binding"
requirements-completed: [ADMIN-07]
coverage:
- id: D1
description: "Albums list header partial: a statistics strip with the total, per-format counts in getFormatOptions order and No shelf, counted only inside the admin's collection"
requirement: ADMIN-07
verification:
- kind: integration
ref: "../fonoteka.go/plugins/golem15/fonoteka/admin_phase101_smoke_test.go#TestPhase101AlbumsSmoke/stats"
status: pass
human_judgment: false
- id: D2
description: "Albums form year field and Load from Discogs widget whose stub action fills year 1977 and format LP without saving; a save persists both"
requirement: ADMIN-07
verification:
- kind: integration
ref: "../fonoteka.go/plugins/golem15/fonoteka/admin_phase101_smoke_test.go#TestPhase101AlbumsSmoke/widget"
status: pass
- kind: unit
ref: "modules/lagoon/fill_test.go#TestFillJSONNumber"
status: pass
human_judgment: false
- id: D3
description: "Controller JS and CSS served from the embedded AdminFS with JS/CSS content types, nosniff and an ETag; undeclared plugin files are 404"
requirement: ADMIN-07
verification:
- kind: integration
ref: "../fonoteka.go/plugins/golem15/fonoteka/admin_phase101_smoke_test.go#TestPhase101AlbumsSmoke/assets"
status: pass
human_judgment: false
- id: D4
description: "Sync with Discogs toolbar action: localized label, stub message with an empty fill, CSRF header required, Genres-only admin gets 403 on toolbar, widget and partial"
requirement: ADMIN-07
verification:
- kind: integration
ref: "../fonoteka.go/plugins/golem15/fonoteka/admin_phase101_smoke_test.go#TestPhase101AlbumsSmoke/toolbar"
status: pass
- kind: integration
ref: "../fonoteka.go/plugins/golem15/fonoteka/admin_phase10_copy_test.go#TestPhase10ControllerCopy"
status: pass
human_judgment: false
- id: D5
description: "In a real browser: plugin module script loads under CSP, the element upgrades, the widget turns busy and fills the form, the strip and buttons wrap at 768px with pl labels, and the Albums stylesheet is off on other controllers"
verification: []
human_judgment: true
rationale: "CSP enforcement, module loading, custom-element upgrade and the 768px layout backstops need a real browser (plan Task 3 human-check, UI-SPEC backstop rows); collected at /gsd-verify-work"
duration: 11min
completed: 2026-09-29
status: complete
---
# Phase 10.1 Plan 03: Albums extension proof Summary
**Płytarium Albums now uses all three extension surfaces. A server-rendered statistics strip above the list is counted through scopeAlbums. A plain-JS "Load from Discogs" form widget has a stub action that fills Release year 1977 and Format LP into the unsaved form. A "Sync with Discogs" toolbar stub shows a test-mode toast. Both Discogs actions require golem15.fonoteka.access_albums, and there is pl and en copy for everything.**
## Performance
- **Duration:** 11 min
- **Started:** 2026-09-29T00:17:03Z
- **Completed:** 2026-09-29T00:27:41Z
- **Tasks:** 3
- **Files modified:** 13 in fonoteka.go, 3 in summercms.go
## Accomplishments
- `albumsAdminController.PartialData("stats")` returns an `albumsStatsView` built from three separate queries, each scoped by `scopeAlbums`: the total, a grouped per-format count and a no-shelf count. `_stats.htm` renders it with the `.summer-stats` kit classes only. With 0 albums it shows "All albums 0". The smoke test proves that five albums in a second collection are never counted.
- `fields.yaml` gains `year` (number, span right, next to shelf) and a last, full-width `discogs` widget field. `AdminJS`/`AdminCSS` declare the element and stylesheet, which are embedded in AdminFS and served under `/plytadmin/assets/golem15/fonoteka/`.
- `discogs-lookup.js` defines one light-DOM button and handles `busy`/`busy-label`/`aria-busy`. It dispatches `summer-action` and contains no import, no string, no network call and no cookie or storage access. `albums.css` follows the S3 contract and uses only `--c-*` variables.
- `toolbar.buttons: [create, delete, discogsSync]` with the `discogsSync` stub. The smoke test checks the localized `toolbarActions` label in en and pl, the stub answer with `fill: {}`, 403 when the CSRF header is missing, and 403 for a Genres-only admin on the toolbar, widget and partial routes.
- The Phase 10 pins moved in the same commits: Albums field count 7, `widget` in `phase10BuiltinFieldTypes`, and the Albums toolbar compared per controller.
## Task Commits
fonoteka.go:
1. **Task 1: statistics strip** - `e9b1373` (feat)
2. **Task 2: Load from Discogs widget** - `fbc5692` (feat)
3. **Task 3: Sync with Discogs toolbar action** - `9868a0a` (feat)
summercms.go:
- `c3efbc3` fix(10.1-03): fill numeric model fields from JSON numbers (deviation 1, found in Task 2)
Commit count: 4 plan commits (1 in summercms.go, 3 in fonoteka.go). In summercms.go, `git rev-list --count 720ee57..c3efbc3` is 2 because a concurrent session committed `9d23ac5 docs(11.2): capture phase context` in that range. That commit does not belong to this plan.
## Files Created/Modified
- `controllers/albums_admin_controller.go`: stats view model, `PartialData`, `AdminJS`, `AdminCSS`, `AdminActions` (two stubs), compile-time pact assertions
- `controllers/albums/_stats.htm`: the strip template
- `controllers/albums/config_list.yaml`: `headerPartial: stats`, the `discogsSync` toolbar button
- `models/album/fields.yaml`: `year` and the `discogs` widget
- `assets/js/discogs-lookup.js`, `assets/css/albums.css`: element and styles
- `admin.go`: embed list
- `lang/{en,pl}/lang.yaml`: `item.year`, the `stats` group and the `discogs.*` additions
- `admin_phase101_smoke_test.go`: `TestPhase101AlbumsSmoke`
- `admin_albums_test.go`, `admin_phase10_controllers_test.go`, `admin_phase10_copy_test.go`: shape pins
- `modules/lagoon/fill.go`, `fill_test.go`, `README.md` (summercms.go): json.Number conversion
## Decisions Made
- `lagoon.Fill` parses `json.Number` for integer, unsigned and float fields. A fraction or an overflow into an integer field is an error. String fields keep the number's text through the existing conversion.
- The stats template uses `{{- -}}` trimming so no whitespace-only text nodes appear between items. Only the file's trailing newline stays as a root text node, and the smoke test ignores it.
- The Discogs actions use the literal `golem15.fonoteka.access_albums`, the same code the controller requires, and no permission is added to the catalog.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] Saving a `type: number` field into an integer model column was a 500**
- **Found during:** Task 2 (smoke `widget` subtest, PUT with `year: 1977`)
- **Issue:** cabana decodes save bodies with `UseNumber`, so `year` arrives as `json.Number`, a string kind. `lagoon.Fill` could not convert it to `*int`, so `CapabilityError` produced a 500. No earlier form had a number field backed by an integer column, so the SPA's Save of Release year would have failed.
- **Fix:** `convertNumber` in `modules/lagoon/fill.go`, with `TestFillJSONNumber` and one README sentence (CLAUDE.md module README rule).
- **Files modified:** modules/lagoon/fill.go, modules/lagoon/fill_test.go, modules/lagoon/README.md (summercms.go)
- **Verification:** `go vet ./... && go test ./...` in summercms.go is green, and TestPhase101AlbumsSmoke/widget persists year 1977 and format LP.
- **Committed in:** c3efbc3 (summercms.go)
---
**Total deviations:** 1 auto-fixed (1 bug)
**Impact on plan:** This was needed for the plan's "a PUT carrying year 1977 and format LP persists both" and for the human check's save-and-reload step. It is a framework change outside the plan's fonoteka-only file list, but it contains nothing application-specific, and the hygiene gate passes.
## Issues Encountered
None beyond the deviation.
## Known Stubs
| File | Line | Stub | Resolved by |
|------|------|------|-------------|
| ../fonoteka.go/plugins/golem15/fonoteka/controllers/albums_admin_controller.go | 71 | `discogsLookup` answers the fixed fill `{year: 1977, format: "LP"}` and makes no Discogs call (D-02, intentional) | Phase 14 |
| ../fonoteka.go/plugins/golem15/fonoteka/controllers/albums_admin_controller.go | 82 | `discogsSync` answers `stub_not_implemented` and changes nothing (D-02, intentional) | Phase 14 |
Both are recorded in `.planning/WINDOWS.md` as entries 6 and 7.
## User Setup Required
None. No external service configuration required.
## Next Phase Readiness
- Plan 10.1-04 can add the named unit tests and the phase gate. Its two-collection acceptance test can build on `phase101Album` and `phase101Stats` in the smoke test.
- The browser check from Task 3 is still outstanding (D5 above): `summer serve` in fonoteka.go, Albumy in pl at full width and at 768px, the widget on create and update, the toolbar toast, and Gatunki without the Albums stylesheet.
## Self-Check: PASSED
- FOUND: controllers/albums/_stats.htm, assets/js/discogs-lookup.js, assets/css/albums.css, admin_phase101_smoke_test.go
- FOUND commits: e9b1373, fbc5692, 9868a0a (fonoteka.go); c3efbc3 (summercms.go)
- Plan verification: `go vet ./... ./plugins/golem15/fonoteka/...` and `go test ./...` in fonoteka.go, `go vet ./... && go test ./...` in summercms.go, `scripts/check-phase10.sh --hygiene` ("phase10 hygiene passed"), and every task's acceptance greps: all pass.
---
*Phase: 10.1-runtime-admin-extension-point*
*Completed: 2026-09-29*