Files
summercms/.planning/phases/10-admin-vue-spa/10-05-SUMMARY.md

19 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 05 admin
vitest
vue-test-utils
go-test
testcontainers
gate
security-review
validation
phase plan provides
10-admin-vue-spa 04 relation manager, picker, collapsible shell, user menu, breadcrumbs, dark mode
Vitest unit and component suites for every admin/src module (48 files, 441 tests)
typed fixture helper binding every JSON fixture to its generated OpenAPI schema type
branch-level Go tests for every Phase 10 change in bouncer, boardwalk, cabana, phrasebook, surf and swagger2openapi
TestPhase10AssembledAcceptance proving SC-1 to SC-4 at /plytadmin on PostgreSQL
scripts/check-phase10.sh fail-closed phase gate with self-test
10-SECURITY-REVIEW.md with mutation-checked evidence for every high threat
finalized 10-VALIDATION.md (nyquist_compliant true)
phase-10-verification
11
tokens tasks commits
72900 3 6
8259a456bb 074fc52 cb6727f3c284265d6254c42e8c00db3f7ab4655e 3359a83
added patterns
Fixtures typed through tests/fixtures/typed.ts, so an admin API change the fixtures no longer match fails vue-tsc
Component tests mount one component with the real admin router on memory history (helpers routerAt, signIn, resetState)
Field controls import ./control, never the registry (no registry -> control -> registry import cycle)
go test -json detector with required-test and exact package:test allow-lists; an allow-listed failure that starts passing refuses the gate
Hygiene self-test plants each violation with a scratch test import, so the refusal must come from the rule under test
High threats proven by mutation
change the control, run the named test, require a failure, restore
created modified
admin/tests/fixtures/typed.ts
admin/tests/app/{i18n,winterUrl,listQuery,runtime,icons,client,router,controllerRoutes,theme}.test.ts
admin/tests/state/{useAuth,useNavigation,useSidebar,useToasts,useSettings,useBreadcrumbs}.test.ts
admin/tests/shell/{AppShell,PluginRail,SectionPanel,SectionFlyout,UserMenu,Breadcrumbs}.test.ts
admin/tests/list/{DataTable,ListToolbar,FilterBar,Pagination,CellValue,ListView}.test.ts
admin/tests/form/{registry,formState,FormGrid,FormField,FormTabs,fields,RelationField,FormView,Settings}.test.ts
admin/tests/relation/{RelationManager,RelationPickerModal}.test.ts
admin/tests/views/{LoginView,App}.test.ts
admin/tests/ui/ui.test.ts
admin/src/components/form/control.ts
bouncer/cookie_guard_test.go
cabana/phase10_coverage_test.go
internal/tools/swagger2openapi/main_test.go
scripts/check-phase10.sh
.planning/phases/10-admin-vue-spa/10-SECURITY-REVIEW.md
../fonoteka.go/plugins/golem15/fonoteka/admin_phase10_e2e_test.go
admin/tests/helpers.ts
admin/src/app/icons.ts
admin/src/components/form/registry.ts
admin/src/components/form/fields/*.vue
admin/src/components/relation/RelationManager.vue
admin/src/components/shell/Breadcrumbs.vue
admin/src/views/ListView.vue
admin/src/styles/main.css
boardwalk/dist/**
boardwalk/boardwalk_test.go
phrasebook/phase10_test.go
phrasebook/translator_test.go
surf/admin_prefix_test.go
bouncer/registry_test.go, cabana/form_schema_test.go, cabana/list_schema_test.go, surf/{cors,cors_coverage,middleware,router}_test.go
.planning/phases/10-admin-vue-spa/10-VALIDATION.md
.planning/WINDOWS.md
The gate allow-lists exactly the two pre-existing fonoteka parity failures by package and test name, prints each on every run, and refuses once either passes. Nothing else may fail.
Field controls share props and helpers through components/form/control.ts; registry.ts re-exports them. The old layout was an import cycle that left CheckboxField unregistered when a control module loaded first.
Tailwind no longer scans admin/tests: test words such as a 'blur' event had added a .blur utility to the shipped CSS
Framework tests name no application: the pre-existing fonoteka, album and vinyl strings in bouncer, cabana, phrasebook and surf tests became neutral acme and file names, as the hygiene stage requires
T-10-10 has two layers (parse skip and write skip). The review names TestPhase10Coverage, which catches the loss of one layer, and TestPhase10CollectionOwnerReadOnly, which catches the loss of both.
A new admin/src module needs a test that imports it; --hygiene fails otherwise
A new unsafe admin route must be added to TestPhase10CSRF's walk; TestPhase10Coverage fails on any change to the unsafe route set
ADMIN-06
id description requirement verification human_judgment
D1 Every SPA module, composable and component has behaviour and accessibility tests that run offline (48 files, 441 tests), with typed neutral fixtures ADMIN-06
kind ref status
unit npm --prefix admin run typecheck && npm --prefix admin test -- tests/app tests/state tests/shell tests/list tests/form tests/relation tests/views tests/smoke pass
kind ref status
other scripts/check-phase10.sh --hygiene (every admin/src module imported by a test) pass
false
id description requirement verification human_judgment
D2 Every Phase 10 Go change has named branch-level tests; both repositories pass go vet and go test apart from the two deferred parity tests ADMIN-06
kind ref status
unit bouncer/cookie_guard_test.go#TestPhase10CookieGuard pass
kind ref status
integration cabana/phase10_coverage_test.go#TestPhase10Coverage pass
kind ref status
unit boardwalk/boardwalk_test.go#TestPhase10BoardwalkServing pass
kind ref status
unit phrasebook/phase10_test.go#TestPhase10LangPrecedence, TestPhase10BundleMerge, TestPhase10FormsShapes pass
kind ref status
unit surf/admin_prefix_test.go#TestPhase10AdminPrefixCollision pass
kind ref status
unit internal/tools/swagger2openapi/main_test.go#TestUnionRewrite pass
kind ref status
other scripts/check-phase10.sh --go pass
false
id description requirement verification human_judgment
D3 SC-1 to SC-4 proven through the assembled router at /plytadmin over the cookie on PostgreSQL ADMIN-06
kind ref status
integration ../fonoteka.go/plugins/golem15/fonoteka/admin_phase10_e2e_test.go#TestPhase10AssembledAcceptance pass
false
id description requirement verification human_judgment
D4 One fail-closed phase gate with a self-test, and threat and validation evidence ADMIN-06
kind ref status
other scripts/check-phase10.sh --self-test && scripts/check-phase10.sh --all (phase10 all passed) pass
false
id description verification human_judgment rationale
D5 Visual fidelity and the full browser flow (manual-only rows of 10-VALIDATION.md)
true D-23 excludes browser e2e; the human-checks of Plans 10-03 and 10-04 are collected at /gsd-verify-work
53min 2026-09-27 complete

Phase 10 Plan 05: Unit tests, assembled acceptance and phase gate Summary

Phase 10 is closed by one command, scripts/check-phase10.sh --all. It runs 441 Vitest tests across every admin/src module, branch-level Go tests for every Phase 10 change, a PostgreSQL acceptance test proving SC-1 to SC-4 at /plytadmin, and drift, hygiene and evidence checks. Each check fails closed, and a threat review backs every high threat with a mutation check.

Performance

  • Duration: 53 min
  • Started: 2026-09-27T15:29:00Z
  • Completed: 2026-09-27T16:22:15Z
  • Tasks: 3
  • Files modified: 75 in summercms.go (excluding rebuilt dist assets), 1 in fonoteka.go

Accomplishments

  • SPA under test (D-23). There are 41 new suites under admin/tests/{app,state,shell,list,form,relation,views,ui} next to the 7 smoke files: 48 files and 441 tests, with no network and no browser.
    • They assert behaviour and a11y attributes: aria-sort, aria-current, aria-invalid, aria-describedby, aria-required, role=switch, tablist/tab, dialog with aria-modal, listbox with aria-multiselectable, menu, status and alert.
    • tests/fixtures/typed.ts types every fixture as its generated OpenAPI schema.
    • The hygiene stage confirms that a test imports every admin/src module.
  • Go coverage.
    • TestPhase10CookieGuard covers the cookie versus Bearer rules, an empty cookie, a frontend audience and a blacklisted jti.
    • TestPhase10BoardwalkServing covers HEAD, query strings, encoded traversal, index.html by name, a nested prefix, the MIME fallback and constructor errors.
    • TestPhase10Coverage covers:
      • the unsafe route set against the CSRF walk
      • option edges: empty search, a page beyond the last, the per_page cap
      • filter option edges
      • read-only labels, including a dangling key
      • relation message defaults
      • a form-less toolbar
      • the bundle's configured fallback locale
      • a cookie refresh of an expired token inside the refresh window
    • phrasebook covers override precedence, a new locale, the Bundle merge order and every Forms shape.
    • surf covers deeper paths and the default /backend prefix.
    • swagger2openapi covers TestUnionRewrite and the converter branches.
  • Acceptance (SC-1 to SC-4). TestPhase10AssembledAcceptance runs against the real router and PostgreSQL:
    • SC-1: a limited admin sees only Genres and gets 403 on Albums. A developer sees all five fonoteka items.
    • SC-2: each of the five controllers serves its list schema, list and form schema, then a create, an update and a show.
    • SC-3: the Collections editors relation searches candidates (the owner is never offered), links, lists and unlinks.
    • It also round-trips the settings, reads /lang, and checks that logout makes the old cookie 401.
    • SC-4: every path template it called exists in admin/openapi/admin.json.
  • Gate. scripts/check-phase10.sh has these stages: --self-test, --go, --security, --postgres, --spa, --openapi, --dist, --hygiene, --evidence and --all.
    • phase10_detect refuses failed, skipped, zero-test, non-JSON and build-failed runs, and any named test that did not pass.
    • The final --all run ended with "phase10 all passed", with no refuse: line.
  • Evidence. 10-SECURITY-REVIEW.md covers T-10-01 to T-10-25 and T-10-SC. 10-VALIDATION.md maps every executed task to its command and gate stage, with nyquist_compliant: true.

Task Commits

  1. Task 1: every SPA module under Vitest. 1c2a66d (test, summercms.go) also contains the four source fixes the tests exposed and the rebuilt dist.
  2. Task 2: every Phase 10 Go change and SC-1 to SC-4. ef448da (test, summercms.go), 3359a83 (test, fonoteka.go).
  3. Task 3: gate and evidence. 07edc6c and fbef773 (test, summercms.go: script, then the storage rule and precise self-test plants). 074fc52 (docs, summercms.go: security review and validation).

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

TDD evidence (tasks carry tdd="true")

As in 10-01 to 10-04, tests and fixes were committed together so every commit stays green (CLAUDE.md), and no separate RED commits exist. The RED runs that drove the source changes were observed before each fix:

  • tests/app/icons.test.ts: iconFor('constructor') returned Object.
  • tests/form/registry.test.ts: rendererFor('checkbox') returned UnsupportedField.
  • tests/form/fields.test.ts: the dropdown showed "— none —" for an unknown stored value.
  • tests/list/ListView.test.ts: a failed schema load was not announced.

Decisions Made

See key-decisions in the frontmatter. The one with the widest effect is the gate's exact allow-list for the two known parity failures, which keeps the gate honest without hiding them.

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] iconFor returned inherited object members

  • Found during: Task 1 (icons suite).
  • Issue: A server icon name such as constructor, toString or __proto__ resolved to Object.prototype members and would have been rendered as a component.
  • Fix: Lookups use own properties only (Object.hasOwn).
  • Files: admin/src/app/icons.ts.
  • Commit: 1c2a66d.

2. [Rule 1 - Bug] Import cycle left a field renderer unregistered

  • Found during: Task 1 (registry suite).
  • Issue: The field controls imported registry.ts, which imports every control. Whichever module loaded first decided whether renderers captured an undefined component. rendererFor('checkbox') returned UnsupportedField when CheckboxField loaded first.
  • Fix: The props and helpers moved to components/form/control.ts, which the controls import. registry.ts re-exports them, so no import site changed. RelationManager imports its prop type from control.ts.
  • Commit: 1c2a66d.

3. [Rule 1 - Bug] Dropdown showed the empty option for an unknown stored value

  • Found during: Task 1 (fields suite).
  • Issue: With an emptyOption, a stored value matching no option selected the non-existent placeholder, so the browser showed "— none —".
  • Fix: The disabled placeholder is rendered whenever no option matches.
  • Files: DropdownField.vue.
  • Commit: 1c2a66d.

4. [Rule 1 - Bug] List hid a failed schema load

  • Found during: Task 1 (ListView suite).
  • Issue: The schema and the rows load in parallel. A rows response arriving after a failed schema reset the shared failed flag, which left eternal skeleton rows.
  • Fix: Separate schemaFailed and listFailed flags, with the alert shown if either is set.
  • Files: ListView.vue.
  • Commit: 1c2a66d.

5. [Rule 3 - Blocking] Tests changed the shipped CSS

  • Issue: Tailwind scanned admin/tests, and the word "blur" added a .blur utility to boardwalk/dist.
  • Fix: @source not "../../tests" in main.css. The CSS is byte-identical to before, and only the JS bundle changed (fixes 1 to 4).
  • Commit: 1c2a66d.

6. [Rule 3 - Blocking] Framework tests named the application

  • Issue: The Task 2 acceptance grep and the hygiene stage require no app names in summercms.go tests. Pre-existing tests used them:
    • golem15.fonoteka in bouncer, cabana and surf tests
    • albumy in the phrasebook plural test
    • Winyl and Płyta in the cabana form test
  • Fix: They became neutral golem15.acme/acme strings and file words. Behaviour is unchanged and all packages pass.
  • Commit: ef448da.

7. [Rule 2 - Correctness] Gate extras

  • --hygiene also confines browser storage to useSidebar.ts (T-10-22), which the plan had checked only by grep.
  • --self-test imports each planted violation from a scratch test, so a plant cannot pass the check by tripping the untested-module rule instead of its own.
  • --security and --postgres also run the cabana PostgreSQL suites (TestPhase10RelationOptions, RelationSave, Coverage) and TestPhase10AdminAuth.
  • Commits: 07edc6c, fbef773.

8. [Scope note] Suites beyond the planned list

  • The plan says to add a suite for any module added after planning. These cover controllerRoutes.ts, theme.ts, useBreadcrumbs.ts, formState.ts, control.ts, App.vue, NotFoundView.vue and the ui/ components: tests/app/controllerRoutes.test.ts, tests/app/theme.test.ts, tests/state/useBreadcrumbs.test.ts, tests/form/formState.test.ts, tests/views/App.test.ts and tests/ui/ui.test.ts.
  • FieldRenderer and FormErrorBanner are covered in tests/form/FormField.test.ts.
  • SettingsIndexView and SettingsFormView are covered in tests/form/Settings.test.ts.

9. [Process] The gate accepts two named, pre-existing failures

  • Issue: The plan requires --go to pass go test ./... in both repositories. In fonoteka.go that fails parity TestMigrateSeedsCanonicalGenres and TestSchemaMatchesPHPSnapshot. These predate Phase 10 (deferred-items.md) and are not this plan's to fix.
  • Handling:
    • --go accepts exactly those two tests in exactly that package and prints each as "known pre-existing failure".
    • It refuses any other failure, a build failure, or either test starting to pass (self-test cases allowed, allowed-other, allowed-wrong-package and allowed-now-passes).
    • The allow-list is recorded as an open deviation in .planning/WINDOWS.md (entry 5).

10. [Process] Commits on master, no co-author trailer

  • As in 10-01 to 10-04 (branching_strategy: none), commits land on master in both repositories. Per the user's rule, no co-author trailer was added.

Total deviations: 7 auto-fixed (4 bugs, 2 blocking, 1 correctness), 3 notes. Impact on plan: No scope change. Four real SPA defects were fixed along the way.

Issues Encountered

  • A single-layer mutation for T-10-10 (dropping only the parse-time read-only skip) did not fail TestPhase10CollectionOwnerReadOnly, because assignBelongsTo refuses protected keys on its own. TestPhase10Coverage does fail on it, and removing both layers fails the fonoteka test. The review records both.
  • npm ci warns that npm 12 blocked the esbuild and vue-demi postinstall scripts. Neither is needed, as in 10-01.

Known Stubs

None.

Threat Flags

None. The plan adds tests, a gate script and docs. The source fixes reduce surface: own-property icon lookup and no inherited component rendering.

User Setup Required

None.

Next Phase Readiness

  • Phase 10 is complete and ready for /gsd-verify-work, where the manual-only rows are collected: visual fidelity in light and dark mode at desktop and tablet widths, the five controllers in a browser, editors link and unlink, and the limited admin versus the superuser.
  • Before /gsd-ship: WINDOWS entry 5 (the parity allow-list) stays open until a Phase 9 follow-up adds the cabana history table and the backend tables to the parity allow-lists.

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

Self-Check: PASSED

All created key files exist. Commits 1c2a66d, ef448da, 07edc6c, fbef773 and 074fc52 (summercms.go) and 3359a83 (fonoteka.go) are present. Acceptance re-run:

  • grep -rniE 'pl[yý]tarium|fonoteka|albumy|kolekcj' admin/tests and grep -rn playwright admin/package.json print nothing.
  • The Task 2 app-name grep over boardwalk, bouncer, cabana, phrasebook, surf and internal/tools *_test.go prints nothing.
  • grep -c 'T-10-' in the review is 38. nyquist_compliant: true appears once in 10-VALIDATION.md, and no table row there says pending.
  • scripts/check-phase10.sh --all exits 0 with "phase10 all passed".