Files
summercms/.planning/phases/10-admin-vue-spa/10-01-SUMMARY.md
2026-09-27 15:37:02 +02:00

20 KiB

phase, plan, subsystem, tags, requires, provides, affects, actuals, plan_head_before, plan_head_after, app_plan_head_before, app_plan_head_after, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status
phase plan subsystem tags requires provides affects actuals plan_head_before plan_head_after app_plan_head_before app_plan_head_after tech-stack key-files key-decisions patterns-established requirements-completed coverage duration completed status
10-admin-vue-spa 01 admin
vue
vite
tailwind
openapi-fetch
openapi-typescript
swag
embed
jwt-cookie
csrf
phase provides
09-backend-admin-authentication-and-schema-pipeline cabana admin API (auth, navigation, list schema and list), backend guard, Phase 9 security matrix and OpenAPI annotations
backend.uri admin prefix (default /backend) for the admin API ({prefix}/api/v1) and the embedded SPA ({prefix})
summer_admin HttpOnly cookie transport with CSRF header check; Bearer transport unchanged
boardwalk package serving the committed boardwalk/dist with one-time index rewrite
framework-owned admin OpenAPI document admin/openapi/admin.json and generated admin/src/api/schema.d.ts
admin/ Vite SPA with login, plugin rail, section panel and read-only list
check-admin-openapi.sh and check-admin-dist.sh drift gates
fonoteka lang catalog, lucide icons, Collections menu item, /plytadmin mount
10-02
10-03
10-04
10-05
tokens tasks commits
43300 3 4
8c3e131111 dafdb18234 feaca6bdb9761051bb2fa1a8cdb7e90f3986e0e2 bb4cd7a17584fe52f43c9884ec555e319431c12f
added patterns
vue 3.5.35, vue-router 5.1.0, reka-ui 2.9.10, @lucide/vue 1.17.0, openapi-fetch 0.17.0, @fontsource/dm-sans 5.3.0, @fontsource/dm-mono 5.3.0
dev
vite 7.3.5, @vitejs/plugin-vue 6.0.8, typescript 5.9.3, vue-tsc 3.3.11, tailwindcss 4.3.0, @tailwindcss/vite 4.3.0, vitest 3.2.7, @vue/test-utils 2.4.11, happy-dom 20.11.6, openapi-typescript 7.13.0
no new Go module requirement (swag runs through go run @v1.16.6)
Path-agnostic SPA build (Vite base ./) plus a boot-time index.html rewrite keyed by a meta token
Typed generic envelopes (Envelope[T], ListEnvelope[T]) as swag doc types, converted to OpenAPI 3 and fed to openapi-typescript
Test helper adminAPI(rel) in each repo instead of prefix literals
Transport selection by X-Requested-With
cookie body without a token, Bearer body unchanged
created modified
cabana/prefix.go
cabana/csrf.go
cabana/admin_paths_test.go
cabana/phase10_auth_test.go
cabana/phase10_csrf_test.go
boardwalk/boardwalk.go
boardwalk/boardwalk_test.go
boardwalk/dist/index.html
internal/tools/swagger2openapi/main.go
scripts/check-admin-openapi.sh
scripts/check-admin-dist.sh
surf/admin_prefix_test.go
admin/package.json
admin/package-lock.json
admin/openapi/admin.json
admin/src/api/schema.d.ts
admin/src/api/client.ts
admin/src/views/ListView.vue
admin/tests/smoke/tracer.smoke.test.ts
../fonoteka.go/config/backend.yaml
../fonoteka.go/plugins/golem15/fonoteka/lang.go
../fonoteka.go/plugins/golem15/fonoteka/lang/pl/lang.yaml
../fonoteka.go/plugins/golem15/fonoteka/lang/en/lang.yaml
../fonoteka.go/plugins/golem15/fonoteka/admin_paths_test.go
../fonoteka.go/plugins/golem15/fonoteka/admin_phase10_tracer_test.go
../fonoteka.go/plugins/golem15/fonoteka/admin_phase10_auth_test.go
cabana/http.go
cabana/auth.go
cabana/admin_openapi.go
cabana/registry.go
bouncer/jwt.go
surf/router.go
go.mod
.gitignore
../fonoteka.go/config/admin.yaml
../fonoteka.go/scripts/check-openapi.sh
../fonoteka.go/docs/openapi.json
../fonoteka.go/plugins/golem15/fonoteka/admin_navigation.go
../fonoteka.go/plugins/golem15/fonoteka/admin_settings.go
../fonoteka.go/plugins/golem15/fonoteka/controllers/genre_controller.go
npm package gate (Task 1) approved by the user: the 17 exact pins were installed with npm install --save-exact; any other package or version needs a new checkpoint
The CSRF check wraps each state-changing handler inside the backend guard, as planned: a cookie-only unsafe request is refused before decoding, controller lookup or handler SQL (the guard's user lookup still runs first)
The rail hides a plugin whose side menu is empty on the SPA side; the server keeps its Phase 9 filtering unchanged
i18n t() returns the key until the backend::lang bundle lands (10-02 serves it, 10-03 loads it); SPA copy uses backend::lang.auth.*, nav.*, list.*, page.* keys
The converter rewrites cabana.jsonScalar and cabana.fieldContext into unions so generated TS types are exact
/auth/me now writes the typed AdminProfile struct (same keys, struct field order) so the document and the wire share one type
Framework admin routes are registered under service.apiBase(); a zero service uses DefaultAdminPrefix
Every SPA call goes through api (openapi-fetch) with X-Requested-With and same-origin credentials; 401 single-flights one refresh and replays once
ADMIN-06
id description requirement verification human_judgment
D1 fonoteka serves the embedded SPA at /plytadmin; a developer admin logs in over the cookie, reads navigation and the Genres list, and a cookie-only bulk delete without the header is refused ADMIN-06
kind ref status
integration ../fonoteka.go/plugins/golem15/fonoteka/admin_phase10_tracer_test.go#TestPhase10TracerSPA pass
false
id description requirement verification human_judgment
D2 Cookie transport: login and refresh bodies carry no token, Bearer bodies unchanged, cookie refresh rotates and needs the header, logout blacklists and expires the cookie ADMIN-06
kind ref status
integration cabana/phase10_auth_test.go#TestPhase10CookieAuth pass
kind ref status
integration ../fonoteka.go/plugins/golem15/fonoteka/admin_phase10_auth_test.go#TestPhase10AdminAuth pass
kind ref status
unit cabana/phase10_csrf_test.go#TestPhase10CSRF pass
false
id description requirement verification human_judgment
D3 backend.uri normalization, validation, custom prefix, reserved vendor segments, cookie_secure production guard and plugin route collisions ADMIN-06
kind ref status
integration cabana/phase10_auth_test.go#TestPhase10Prefix pass
kind ref status
unit surf/admin_prefix_test.go#TestPhase10AdminPrefixCollision pass
false
id description requirement verification human_judgment
D4 boardwalk serving: index rewrite, missing-token error, embedded assets, api/ delegation, extension 404, traversal, no listing, MIME types, cache and security headers, no inline script ADMIN-06
kind ref status
unit go test ./boardwalk pass
false
id description requirement verification human_judgment
D5 Framework admin OpenAPI document and generated TS types are committed and drift-checked; committed dist matches a fresh build ADMIN-06
kind ref status
other scripts/check-admin-openapi.sh --check pass
kind ref status
other scripts/check-admin-dist.sh pass
kind ref status
unit cabana/phase09_contract_test.go#TestPhase09ContractInventory pass
false
id description requirement verification human_judgment
D6 SPA smoke: runtime base from meta, login with CSRF header and no stored token, redirect safety, grouped navigation with empty-plugin omission, list columns and rows, single-flight refresh and proactive refresh ADMIN-06
kind ref status
unit admin/tests/smoke/tracer.smoke.test.ts pass
false
id description requirement verification human_judgment
D7 fonoteka lang catalog resolves every admin key in pl and en; navigation and settings use lucide icons with a Collections item ADMIN-06
kind ref status
unit ../fonoteka.go/plugins/golem15/fonoteka/admin_phase10_auth_test.go#TestPhase10LangCatalog pass
kind ref status
unit ../fonoteka.go/plugins/golem15/fonoteka/admin_metadata_test.go#TestAdminMetadataNavigation pass
false
id description verification human_judgment rationale
D8 Visual fidelity of the login screen, rail, section panel and list against design Direction C v2 in a real browser
true No browser e2e in Phase 10 (D-23); component tests assert structure and roles, not rendered appearance
31min 2026-09-27 complete

Phase 10 Plan 01: Admin SPA tracer Summary

The fonoteka binary serves an embedded Vue 3 admin at /plytadmin: cookie login (HttpOnly summer_admin plus X-Requested-With CSRF check), server-filtered navigation and a schema-driven Genres list, all typed from a framework-owned OpenAPI document.

Performance

  • Duration: 31 min
  • Started: 2026-09-27T13:04:06Z
  • Completed: 2026-09-27T13:34:49Z
  • Tasks: 3 (Task 1 package gate approved before this run; Tasks 2 and 3 executed)
  • Files modified: 86 in summercms.go (41 are generated dist assets, the lockfile and the OpenAPI outputs), 25 in fonoteka.go

Accomplishments

  • Every admin route now lives under backend.uri ({prefix}/api/v1, default /backend, fonoteka /plytadmin); the old /_admin/api/v1 prefix is gone from code and tests and reaches no handler.
  • D-19 cookie transport: an X-Requested-With login sets summer_admin (HttpOnly, Secure, SameSite=Strict, Path=prefix) and returns only token_type and expires_in. Refresh rotates the cookie. Logout blacklists the jti and expires the cookie. A cookie-only POST, PUT or DELETE without the header gets 403 forbidden. Bearer clients keep the Phase 9 bodies.
  • boardwalk embeds boardwalk/dist (all:dist) and rewrites index.html once per prefix. API misses get the JSON not_found envelope, missing files with an extension get a 404, and directories are never listed. Responses carry nosniff, DENY, CSP, Referrer-Policy and noindex.
  • The framework owns admin/openapi/admin.json with prefix-relative paths and typed envelopes for the six tracer routes. schema.d.ts is generated from it and both are drift-checked. fonoteka's parity document no longer lists admin paths.
  • The admin/ SPA covers login, the plugin rail (lucide icons, Winter aliases, neutral fallback, empty plugins hidden), the section panel, the header and a read-only list. It also single-flights refresh on 401, replays the request once and refreshes proactively at 80 percent of expires_in.
  • Boot guards reject an invalid backend.uri, the reserved vendor segments (api, assets, login, settings), non-cabana routes under the prefix, and backend.cookie_secure: false in production.
  • fonoteka: the PHP lang files are ported to lang/{pl,en}/lang.yaml behind LangFS, icons are lucide names, Collections sits after Albums, and blacklist_grace is 30.

Task Commits

  1. Task 1: Verify npm package legitimacy - no commit (blocking-human gate; user replied "approved" for the 17 exact pins)
  2. Task 2: Admin logs in through the embedded SPA and reads the Genres list end to end - 5f93538 (feat, summercms.go), d804ca3 (feat, fonoteka.go)
  3. Task 3: Harden the session transport and prefix, and give fonoteka its admin copy and icons - dafdb18 (feat, summercms.go), bb4cd7a (feat, fonoteka.go)

Plan metadata: recorded in the docs commit that adds this file.

Tracer gate (Task 2): the tracer's automated verify was re-run end to end and passed before Task 3 started (human_verify_mode end-of-phase, no human-check).

Package gate approval (Task 1)

The user approved installing exactly these pins with npm install --save-exact in admin/: vue 3.5.35, vue-router 5.1.0, reka-ui 2.9.10, @lucide/vue 1.17.0, openapi-fetch 0.17.0, @fontsource/dm-sans 5.3.0, @fontsource/dm-mono 5.3.0; dev: vite 7.3.5, @vitejs/plugin-vue 6.0.8, typescript 5.9.3, vue-tsc 3.3.11, tailwindcss 4.3.0, @tailwindcss/vite 4.3.0, vitest 3.2.7, @vue/test-utils 2.4.11, happy-dom 20.11.6, openapi-typescript 7.13.0. All 17 exist, their repositories match, and none declares an install script. Five are not in the vue-fonoteka-app lockfile (openapi-fetch, both @fontsource fonts, vue-tsc, openapi-typescript), and the user accepted that. No other package was installed. npm 12 blocked two transitive install scripts (esbuild and vue-demi postinstall). Neither was needed: vite build, vitest and vue-tsc all run without them.

Files Created/Modified

  • cabana/prefix.go - DefaultAdminPrefix, AdminCookieName, AdminPrefix(app) normalization and validation
  • cabana/csrf.go - requireAjax wrapper on every unsafe admin route except login
  • cabana/http.go - prefix-based mount, SPA routes, boardwalk wiring, cookie_secure, reserved-segment check
  • cabana/auth.go - cookie transport for login, refresh and logout; typed AdminProfile; prefix issuer
  • cabana/admin_openapi.go - swag general info, Envelope[T], ListEnvelope[T], AdminRecord, AdminProfile, prefix-relative @Router
  • cabana/registry.go - checkReservedSegments
  • bouncer/jwt.go - NewBackendJWTGuard(..., cookieNames ...string)
  • surf/router.go - checkAdminPrefix after admin.Mount
  • boardwalk/boardwalk.go - embedded dist handler
  • internal/tools/swagger2openapi/main.go - Swagger 2 to OpenAPI 3 converter with union rewrites
  • scripts/check-admin-openapi.sh, scripts/check-admin-dist.sh - generation and drift gates (executable)
  • admin/ - Vite project, generated types, SPA modules and components, fixtures and smoke tests
  • ../fonoteka.go/config/backend.yaml, config/admin.yaml - /plytadmin, blacklist_grace: 30
  • ../fonoteka.go/plugins/golem15/fonoteka/lang.go, lang/{pl,en}/lang.yaml - plugin translations
  • ../fonoteka.go/plugins/golem15/fonoteka/admin_navigation.go, admin_settings.go - lucide icons, Collections item

Decisions Made

  • The package gate approval is recorded above. Every pin was installed exactly as approved.
  • CSRF check placement follows the plan (a handler wrapper applied in mount). The backend guard, including its user lookup, still runs before the wrapper on guarded routes. No handler, decoder, controller lookup or handler query runs for a refused request.
  • The SPA hides a rail plugin whose side menu is empty. The server's Phase 9 metadata filtering is unchanged.
  • /auth/me now writes the AdminProfile struct. It has the same keys as before, but the JSON key order follows the struct, and the admin API is not a parity surface.
  • The OpenAPI converter also turns cabana.jsonScalar and cabana.fieldContext into unions (research Pattern 4), so the generated TS types match the wire.

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Blocking] Extra internal test file for TestPhase10CSRF

  • Found during: Task 3
  • Issue: The plan puts TestPhase10CookieAuth, TestPhase10CSRF and TestPhase10Prefix in one file. The cookie and prefix tests need surf.Assemble, which only an external cabana_test file can import (an internal cabana test importing surf is an import cycle). The CSRF spy needs the unexported service.mount handlers, so it must be internal.
  • Fix: cabana/phase10_auth_test.go (external) holds CookieAuth and Prefix. cabana/phase10_csrf_test.go (internal) holds TestPhase10CSRF, which walks every mounted handler with a body-read spy.
  • Commit: dafdb18

2. [Rule 3 - Blocking] adminAPI helper duplicated for the external cabana test package

  • Found during: Task 2
  • Issue: cabana/auth_test.go and commands_test.go are package cabana_test and cannot see the internal admin_paths_test.go helper.
  • Fix: Added the same adminAPI(rel) helper to auth_test.go.
  • Commit: 5f93538

3. [CLAUDE.md - green at every commit] TDD RED kept as verified evidence, not as a failing commit

  • Found during: Task 3 (tdd="true")
  • Issue: The TDD flow commits a failing test first, but CLAUDE.md requires go vet and go test ./... to be green at every commit, and CLAUDE.md takes precedence.
  • Fix: All Task 3 tests were written first and run red on the planned assertions: cookie refresh 401, reserved vendor accepted, cookie_secure false accepted in production, prefix collisions accepted, lang keys missing, old icons, and four SPA refresh cases. RED evidence records were verified RED_EVIDENCE_OK by gsd-tools check tdd-red-evidence for TestPhase10CookieAuth, TestPhase10AdminAuth and the single-flight refresh smoke test. Tests and implementation were then committed together per repository as one logical change. TestPhase10CSRF and the boardwalk tests were already green at RED time, because Task 2 had shipped the CSRF wrapper and the handler.
  • Commits: dafdb18, bb4cd7a

4. [Process] Tracer test written with, not before, the Task 2 implementation

  • Found during: Task 2
  • Issue: The plan says to start with a failing TestPhase10TracerSPA. It was written after the Go and SPA code, so no RED run was captured.
  • Fix: None needed for correctness. It asserts every step in action item 7 against real PostgreSQL and passes. Recorded here for the verifier.

5. [Process] Commits land on master

  • Issue: The executor's HEAD assertion treats master as protected, but this project uses branching_strategy: none, and every Phase 1 to 9 plan committed directly to master in both repositories. The orchestrator instructed normal commits on the main working tree.
  • Fix: Committed on master in both repositories, following the established project practice.

Total deviations: 2 auto-fixed (Rule 3), 1 CLAUDE.md-driven, 2 process notes. Impact on plan: None on scope. Test file layout and commit granularity differ slightly from the plan text.

Issues Encountered

  • The fonoteka.go parity package already failed before this plan: TestMigrateSeedsCanonicalGenres and TestSchemaMatchesPHPSnapshot reject the Phase 9 cabana migration history table and the backend tables. The failure reproduces on untouched copies of both repositories at the pre-plan commits. It is logged in deferred-items.md and not fixed here. Every other package in both repositories passes go vet and go test ./....

Known Stubs

  • admin/src/app/i18n.ts - t() returns the key until the backend::lang bundle is loaded, so the login copy, nav aria labels, list footer and empty or loading texts show backend::lang.* keys. This is intentional per the plan: Plan 10-02 serves GET {prefix}/api/v1/lang and adds the keys, and Plan 10-03 loads the bundle at startup. Server-resolved labels (navigation, list titles, column labels) already render real text.
  • admin/src/views/ListView.vue - the list is read-only, with no search, sort, paging controls or row links. It is the tracer scope, and Plan 10-03 builds the full list screens.

User Setup Required

None. For local development, SUMMER_ADMIN_DEV_PREFIX and SUMMER_ADMIN_DEV_TARGET configure the Vite dev proxy, and backend.cookie_secure: false can be set outside production if a browser rejects Secure cookies on plain http.

Next Phase Readiness

  • Plan 10-02 can grow the contract. The prefix, cookie and CSRF transport, typed envelopes and the OpenAPI pipeline are in place, and phase09Routes in cabana/security_coverage_test.go is the inventory to extend.
  • Plan 10-02 must add every backend::lang key the SPA already uses (auth.title, auth.login, auth.password, auth.invalid, auth.submit, nav.plugins, nav.sections, nav.breadcrumbs, nav.empty, page.not_found, list.no_records, list.no_results, list.loading, list.load_failed, list.pagination_range, list.column_switch_true, list.column_switch_false). Its TestPhase10SPAKeysResolve will check them.
  • After any SPA change, run npm --prefix admin run build and commit boardwalk/dist. scripts/check-admin-dist.sh enforces this.

Phase: 10-admin-vue-spa Completed: 2026-09-27

Self-Check: PASSED

All created files listed above exist; commits 5f93538 and dafdb18 (summercms.go) and d804ca3 and bb4cd7a (fonoteka.go) are present.