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

19 KiB

phase, plan, subsystem, tags, requires, provides, affects, 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 actuals plan_head_before plan_head_after tech-stack key-files key-decisions patterns-established requirements-completed coverage duration completed status
10-admin-vue-spa 03 admin
vue
spa
forms
lists
filters
settings
i18n
openapi-fetch
reka-ui
phase plan provides
10-admin-vue-spa 02 relation options and saves with labels, backend::lang bundle, CLDR messages, declarative toolbar, filter choices, typed admin OpenAPI
backend::lang bundle loading at boot, t/tc/message with Intl.PluralRules and phrasebook-compatible interpolate (D-20, D-24)
mapWinterUrl for recordUrl and config_form redirects onto the D-10 list, create and record routes
schema-driven FormView with tabs, 422 mapping, toasts, footer actions, dirty guard and redirect mapping
D-05 field registry with text, textarea, number, dropdown, switch, checkbox and relation renderers plus the UnsupportedField fallback
schema-driven ListView with URL state, sort, search, selection, bulk delete, filter bar and pagination
settings index and settings form through the same renderer (D-21)
write request bodies (AdminRecord, AdminIDsRequest) and the deepObject filter query in the admin OpenAPI document
10-04
10-05
tokens tasks commits
43500 3 3
453b8ae786 8f32416f4f
added patterns
Every API payload type is an alias of a generated schema type; list query and write bodies are typed through the OpenAPI document
Field controls share one props interface (FieldControlProps) and are resolved by type through the registry
Promise-based confirmation (useConfirm) over a Reka AlertDialog
Global toast queue mounted by AppShell so toasts survive navigation
RouterView keyed by route path; list query changes keep the view, a new record or controller remounts it
created modified
admin/src/app/winterUrl.ts
admin/src/app/listQuery.ts
admin/src/state/useToasts.ts
admin/src/state/useSettings.ts
admin/src/views/FormView.vue
admin/src/views/SettingsIndexView.vue
admin/src/views/SettingsFormView.vue
admin/src/components/form/registry.ts
admin/src/components/form/formState.ts
admin/src/components/form/FormGrid.vue
admin/src/components/form/FormField.vue
admin/src/components/form/FieldRenderer.vue
admin/src/components/form/FormTabs.vue
admin/src/components/form/FormErrorBanner.vue
admin/src/components/form/fields/TextField.vue
admin/src/components/form/fields/TextareaField.vue
admin/src/components/form/fields/NumberField.vue
admin/src/components/form/fields/DropdownField.vue
admin/src/components/form/fields/SwitchField.vue
admin/src/components/form/fields/CheckboxField.vue
admin/src/components/form/fields/RelationField.vue
admin/src/components/form/fields/UnsupportedField.vue
admin/src/components/list/ListToolbar.vue
admin/src/components/list/FilterBar.vue
admin/src/components/list/Pagination.vue
admin/src/components/list/CellValue.vue
admin/src/components/ui/Button.vue
admin/src/components/ui/Toast.vue
admin/src/components/ui/ConfirmDialog.vue
admin/src/components/ui/confirm.ts
admin/tests/helpers.ts
admin/tests/fixtures/lang.json
admin/tests/fixtures/widgets.form-schema.json
admin/tests/fixtures/widgets.record.json
admin/tests/fixtures/widgets.options.json
admin/tests/fixtures/settings.json
admin/tests/smoke/edit.smoke.test.ts
admin/tests/smoke/list.smoke.test.ts
admin/tests/smoke/form.smoke.test.ts
admin/tests/smoke/settings.smoke.test.ts
admin/src/main.ts
admin/src/App.vue
admin/src/app/router.ts
admin/src/app/i18n.ts
admin/src/api/types.ts
admin/src/api/schema.d.ts
admin/openapi/admin.json
admin/src/views/ListView.vue
admin/src/views/LoginView.vue
admin/src/components/list/DataTable.vue
admin/src/components/shell/AppShell.vue
admin/src/components/shell/PluginRail.vue
admin/tests/fixtures/widgets.list-schema.json
admin/tests/fixtures/widgets.list.json
admin/tests/smoke/tracer.smoke.test.ts
cabana/admin_openapi.go
internal/tools/swagger2openapi/main.go
phrasebook/backend/lang/pl/lang.yaml
phrasebook/backend/lang/en/lang.yaml
boardwalk/dist/**
The admin OpenAPI document now declares the write bodies (AdminRecord for create, update and settings PUT; AdminIDsRequest for bulk delete and relation link/unlink) and the list filter query as a deepObject of strings, so openapi-fetch sends them typed instead of the SPA declaring shapes
A form field without span fills the whole row (Winter's default span is full); auto and row take the next free slot
The save body holds every context-allowed field that has a value, except read-only fields and types without a renderer, so the relation manager and unknown types are never sent
Winter attributes: pass through an allowlist only (readonly, placeholder, maxlength, minlength, min, max, step, autocomplete, spellcheck, pattern)
Toggle fields keep a numeric stored value numeric (0/1) and send booleans otherwise
aria-sort reflects only the explicit URL sort; the list's defaultSort is applied by the server without a header indicator, so the asc, desc, none cycle stays predictable
A 422 focuses the first invalid field in schema order, preferring the active tab, and switches to its tab when needed
Settings saves toast backend::lang.settings.saved; settings are loaded at boot and after login alongside navigation
The form's Usuń button shows in update mode for every controller; the server enforces the delete permission (T-10-18)
New SPA strings go into phrasebook/backend/lang/{pl,en}/lang.yaml; tests/fixtures/lang.json is generated from the pl file
Smoke tests share tests/helpers.ts (mockApi keyed by METHOD path, mountApp at a route with a signed-in admin)
ADMIN-06
id description requirement verification human_judgment
D1 Strings bundle loaded before auth, document language, plural selection and phrasebook-compatible interpolation ADMIN-06
kind ref status
unit admin/tests/smoke/edit.smoke.test.ts#strings pass
kind ref status
unit phrasebook/phase10_test.go#TestPhase10SPAKeysResolve pass
false
id description requirement verification human_judgment
D2 Row opens the record route; typed PUT keyed by field name; saved toast; 422 mapping with focus, banner and clearing; unsupported box; Winter URL mapping ADMIN-06
kind ref status
unit admin/tests/smoke/edit.smoke.test.ts pass
false
id description requirement verification human_judgment
D3 List URL state, sort cycle with aria-sort, debounced search, tri-state selection, confirmed bulk delete and 409 toast, three filter shapes, per-page and pager, empty, empty-search and skeleton states ADMIN-06
kind ref status
unit admin/tests/smoke/list.smoke.test.ts pass
false
id description requirement verification human_judgment
D4 Tabs with error badges, toggle cards, single, multiple and read-only relations, context filtering, delete confirm, dirty guard and beforeunload, create and close redirect mapping ADMIN-06
kind ref status
unit admin/tests/smoke/form.smoke.test.ts pass
false
id description requirement verification human_judgment
D5 Ustawienia rail item visibility, settings index by category, settings save and 422 through the shared renderer ADMIN-06
kind ref status
unit admin/tests/smoke/settings.smoke.test.ts pass
false
id description requirement verification human_judgment
D6 Every field type the five fonoteka controllers serve has a renderer except relation-manager (10-04); committed dist and OpenAPI outputs match fresh builds ADMIN-06
kind ref status
integration ../fonoteka.go/plugins/golem15/fonoteka/admin_phase10_controllers_test.go#TestPhase10Controllers pass
kind ref status
other scripts/check-admin-dist.sh; scripts/check-admin-openapi.sh --check pass
false
id description verification human_judgment rationale
D7 Real rendering of Albumy, Artyści, Kolekcje, Gatunki, Style and Ustawienia against the Go backend at /plytadmin (genre dropdown, artist chips, read-only owner, toast, required-name error)
true The plan's human-check: no browser e2e in Phase 10 (D-23); visual fidelity and real rendering can only be judged in a browser
23min 2026-09-27 complete

Phase 10 Plan 03: Admin lists, forms, filters and settings Summary

Any cabana controller now gets a working admin list and form from its server schema. Lists have URL-driven search, sort, filters and paging, plus selection and confirmed bulk delete. Forms have tabs and every built-in field type, including searchable relation selects and chips. They show 422 messages under their fields, toast on save, and follow the Winter redirects mapped onto SPA routes. Settings pages render through the same form renderer.

Performance

  • Duration: 23 min
  • Started: 2026-09-27T14:37:22Z
  • Completed: 2026-09-27T15:00:38Z
  • Tasks: 3
  • Files modified: 64 in summercms.go (including rebuilt dist assets and generated OpenAPI outputs); fonoteka.go untouched

Accomplishments

  • Strings (D-20, D-24). main.ts loads GET /lang first, then /auth/me, then navigation and settings. i18n.ts stores the bundle and meta.locale and sets <html lang>. t, tc and message pick CLDR forms with Intl.PluralRules and fall back to other. interpolate mirrors phrasebook.interpolate for :name, :Name and :NAME, replacing longest placeholders first.
  • Routes and URLs (D-10). New routes /:vendor/:plugin/:controller/create, /:id(\d+), /settings and /settings/:code. mapWinterUrl only produces the current controller's list, create or record route. Foreign or unknown shapes fall back to the list (T-10-20).
  • Forms (D-05, D-09, D-18). FormView loads the form schema and the record (data plus meta.labels) and shows the fields whose context allows the mode. Fields are grouped into tabs, and FormGrid lays them out by span. Saving POSTs or PUTs values keyed by field name, including relation ids. A 422 maps error.details onto fields (aria-invalid, aria-describedby), shows the plural banner, badges tabs, focuses the first invalid field and clears a field's error when it changes. The sticky footer has Usuń (confirm, DELETE), Anuluj, Zapisz i zamknij (mapped redirectClose) and Zapisz (mapped create.redirect after a create). A dirty form confirms before any route leave and sets a beforeunload guard.
  • Registry (D-05, D-17, D-26). text, textarea, number, dropdown, switch, checkbox and relation are registered. Any other type, including relation-manager for now, renders the dashed UnsupportedField box with the type in DM Mono. Relation fields cover three modes. A read-only field shows only its label. A single field is a searchable combobox over fields/{field}/options, with emptyOption first and muted, a 300 ms debounce, 20 per page and more on scroll or a button. A multiple field shows ordered chips with initials avatars and "Usuń: :name" remove buttons, plus an appending search.
  • Lists (D-12, D-13, D-14, D-22, D-27). Search (300 ms debounce, resets the page), sort, page, per_page and filter[<name>] live in the URL, and any change clears the selection. DataTable renders exactly the schema columns: a tri-state page checkbox, a sort cycle with aria-sort, selected rows, a sticky header, eight skeleton rows and an empty slot. CellValue renders text, datetime and switch pills. The heading shows the plural recordCount and the create button. The toolbar holds search and delete, which is disabled without a selection and otherwise confirms with the plural deleteConfirm before POST bulk-delete. FilterBar sends JSON.stringify of switch values, from..to date ranges and scope values loaded from filters/{scope}/options. Pagination shows the range, the per-page select (hidden with one option) and a pager with ellipsis.
  • Settings (D-21). The rail pins Ustawienia to the bottom when /settings is non-empty. The index groups pages by category, and each page renders its schema through FormGrid and the registry, PUTs its values and maps a 422.
  • Contract. The admin OpenAPI document now types the write bodies and the list filter query (see Deviations), so the SPA sends typed payloads and declares no API shapes of its own.

Task Commits

  1. Task 1 (tracer): open, edit and save a record with toast and 422 feedback: 126ca5b (feat)
  2. Task 2: search, sort, filter, page and bulk-delete any list: 8b5f856 (feat)
  3. Task 3: tabs, toggles, relation fields, form lifecycle and settings pages: 8f32416 (feat)

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

Tracer gate (Task 1): the Task 1 <verify> (typecheck, edit smoke test, TestPhase10SPAKeysResolve, check-admin-dist) was re-run after its commit and passed before Task 2 started (human_verify_mode end-of-phase, automated verify).

Decisions Made

See key-decisions in the frontmatter. The most consequential one is documenting the write bodies and the filter query in the framework OpenAPI document instead of loosening types in the SPA.

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Blocking] The admin OpenAPI document had no request bodies and no filter parameter

  • Found during: Task 1
  • Issue: schema.d.ts typed create, update, settings PUT and bulk delete with requestBody?: never, so openapi-fetch rejected any body. The list query had no filter parameter. The plan requires typed PUT and bulk-delete bodies and forbids SPA-declared payload shapes.
  • Fix: cabana/admin_openapi.go declares @Param body body AdminRecord on create, update and settings PUT, and a new doc type AdminIDsRequest {ids: uint64[]} on bulk delete and relation link/unlink. A filter object query parameter is also declared. internal/tools/swagger2openapi converts an object query parameter into style: deepObject, explode: true with string values. Both generated outputs were regenerated and the drift check passes. Handlers and the conformance test are unchanged, since the conformance test checks responses only.
  • Files: cabana/admin_openapi.go, internal/tools/swagger2openapi/main.go, admin/openapi/admin.json, admin/src/api/schema.d.ts
  • Commit: 126ca5b

2. [Rule 3] Helper modules not named in the plan's file list

  • admin/src/components/form/formState.ts (context filtering, save payload, 422 mapping, focus and tab ids) and FormErrorBanner.vue are shared by FormView and SettingsFormView, as the plan requires ("maps a 422 like the record form").
  • admin/src/components/ui/confirm.ts is the promise wrapper both views use around ConfirmDialog.
  • admin/tests/helpers.ts holds the fetch mock and app mount the four new smoke files share.
  • Commits: 126ca5b, 8b5f856, 8f32416

3. [Rule 1] Tracer smoke test adjusted to the new list

  • Found during: Task 2
  • Issue: The shared list fixture now has a checkbox column (showCheckboxes: true renders one), a datetime column and a switch column. The 10-01 tracer assertions compared every th and td.
  • Fix: The tracer test skips [data-select] cells and compares the first three schema columns of each row. Its intent (schema columns and rows through the typed client) is unchanged.
  • Commit: 8b5f856

4. [Rule 2 - Security] Winter attributes: pass through an allowlist

  • A field's attributes map is plugin YAML. Binding it wholesale onto inputs would let a plugin set arbitrary attributes, including on* handlers. Only readonly, placeholder, length and range limits, step, autocomplete, spellcheck and pattern are applied.
  • Commit: 126ca5b

5. [Process] Not a TDD plan

  • The plan's tasks carry no tdd="true", so no RED evidence was recorded. Each task's tests and code were committed together, keeping every commit green (CLAUDE.md).

6. [Process] Commits land on master

  • As in 10-01 and 10-02 (branching_strategy: none), the orchestrator directed commits onto master.

Total deviations: 4 auto-fixed (2 blocking, 1 bug, 1 security), 2 process notes. Impact on plan: None on scope. The OpenAPI change adds request documentation only, and wire behaviour is unchanged.

Issues Encountered

  • The known fonoteka.go parity failures (TestMigrateSeedsCanonicalGenres, TestSchemaMatchesPHPSnapshot) remain, as logged in deferred-items.md. Every other package passes go vet and go test, including the fonoteka plugin modules. TestPhase10Controllers passes.
  • gofmt -l internal/build/registry.go is still listed (pre-existing, deferred).

Known Stubs

  • admin/src/components/form/registry.ts: relation-manager is not registered, so the Collections "Redaktorzy" tab renders the unsupported-field box. The plan requires this: Plan 10-04 adds the relation manager. It is recorded in .planning/WINDOWS.md (entry 4).
  • WINDOWS entries 2 (untranslated keys until the bundle loads) and 3 (read-only tracer list) are marked fixed by this plan.

Threat Flags

None. No new endpoint, auth path or schema change. The OpenAPI change documents existing request bodies. Plugin strings render as text only (no v-html), all requests go through the typed client, and Winter URLs are mapped, never followed verbatim.

User Setup Required

None.

Next Phase Readiness

  • Plan 10-04 can register relation-manager in registry.ts, which removes the fallback on the Collections editors tab. It can reuse DataTable (selection, sorting and states), ConfirmDialog with useConfirm, Button, the toasts and the typed AdminIDsRequest body for link and unlink. RelationSchema.messages is already typed.
  • Plan 10-05's unit tests can target i18n.ts, winterUrl.ts, listQuery.ts, formState.ts and registry.ts directly. The smoke tests already cover most component behaviour.
  • The human browser check (five controllers and Ustawienia at /plytadmin) is left for phase verification.

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

Self-Check: PASSED

All created files listed above exist; commits 126ca5b, 8b5f856 and 8f32416 (summercms.go) are present. Plan verification re-run: typecheck, all 51 smoke tests, go test ./phrasebook, check-admin-dist.sh, check-admin-openapi.sh --check, go vet ./... and go test ./... pass; TestPhase10Controllers passes in fonoteka.go.