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

20 KiB
Raw Blame History

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 04 admin
vue
spa
relation-manager
reka-ui
shell
dark-mode
a11y
phase plan provides
10-admin-vue-spa 03 field registry, DataTable, Pagination, ConfirmDialog/useConfirm, toast queue, typed AdminIDsRequest body, backend::lang bundle
relation-manager field renderer (RelationManager) with linked list, debounced search, selection, confirmed unlink and toasts
RelationPickerModal
candidates five per page, selection across pages, Dodaj (N) link, Esc and focus return
typed search, sort, dir, page and per_page on the linked and candidate relation routes in the admin OpenAPI
useSidebar (viewport breakpoint plus persisted manual flag), SectionFlyout, collapse and expand buttons
Breadcrumbs (plugin, controller, record), UserMenu with role and logout, useAuth().logout()
applyColorScheme (prefers-color-scheme drives .dark), newest-first toasts, dialog and toast motion
10-05
tokens tasks commits
22100 2 2
6e1b6dd5bf 445404e394
added patterns
Record-bound field types (relation-manager) are registered like any control but dropped on create and never enter the save body
Dialog focus return is explicit
closeAutoFocus is prevented and the parent focuses its opener through a function ref
Shell UI state that must survive reloads lives in one module (useSidebar) and is the only localStorage user
Smoke tests run against a desktop, light matchMedia by default and spy on it to simulate narrow or dark screens
created modified
admin/src/components/relation/RelationManager.vue
admin/src/components/relation/RelationPickerModal.vue
admin/src/components/shell/SectionFlyout.vue
admin/src/components/shell/UserMenu.vue
admin/src/components/shell/Breadcrumbs.vue
admin/src/state/useSidebar.ts
admin/src/state/useBreadcrumbs.ts
admin/src/app/theme.ts
admin/tests/fixtures/widgets.relation-schema.json
admin/tests/fixtures/widgets.relation-linked.json
admin/tests/fixtures/widgets.relation-candidates.json
admin/tests/smoke/relation.smoke.test.ts
admin/tests/smoke/shell.smoke.test.ts
cabana/admin_openapi.go
admin/openapi/admin.json
admin/src/api/schema.d.ts
admin/src/api/types.ts
admin/src/components/form/registry.ts
admin/src/components/form/FormGrid.vue
admin/src/components/form/FormField.vue
admin/src/components/form/FieldRenderer.vue
admin/src/views/FormView.vue
admin/src/components/list/DataTable.vue
admin/src/components/shell/AppShell.vue
admin/src/components/shell/PluginRail.vue
admin/src/components/shell/SectionPanel.vue
admin/src/components/ui/Toast.vue
admin/src/state/useAuth.ts
admin/src/state/useToasts.ts
admin/src/main.ts
admin/src/styles/main.css
admin/tests/setup.ts
admin/tests/fixtures/lang.json
admin/tests/fixtures/widgets.form-schema.json
admin/tests/smoke/form.smoke.test.ts
phrasebook/backend/lang/pl/lang.yaml
phrasebook/backend/lang/en/lang.yaml
boardwalk/dist/**
relation-manager is a record-bound type: the form drops it (and its tab) on create even when the YAML has no context: update, and editablePayload never sends it
The admin OpenAPI document declares search, sort, dir, page and per_page on GET {id}/relations/{name} and .../candidates, so the relation manager and picker send typed queries
The picker keeps its selection as a set of ids across pages and search changes and clears it on close; it renders candidates exactly as served (owner exclusion stays server-side, T-10-21)
Focus returns to the link button explicitly: RelationPickerModal prevents Reka's closeAutoFocus and RelationManager focuses the opener through a function ref (a template ref inside v-for is an array)
With the panel collapsed, Enter or ArrowDown on a rail item opens the flyout and moves focus into it instead of navigating; a click still navigates
The expand button shows only for a manual collapse; below 1100px expanding is impossible, so the rail offers the flyout instead
The record crumb comes from the form view through a small useBreadcrumbs module; settings pages show Ustawienia › page label
logout(router) always clears user, navigation and settings and replaces the route with login, even on a failed or thrown call (T-10-23)
The UserMenu root stays mounted when the user clears; only its content reads the user, so logout never tears down a teleport mid-update
Dialog and toast motion is enter-only (200 ms fade plus scale from 0.98), so Reka's Presence never waits on an exit animation
Components that need the record id get it as FieldControlProps.recordId, passed through FormGrid, FormField and FieldRenderer
Shell tests reset localStorage, spy on window.matchMedia with a controllable fake and auto-unmount wrappers before the body is cleared
id description requirement verification human_judgment
D1 Relation manager: absent on create (even without context: update), label, comment, view columns, avatars, 56px rows, toolbar in declared order, debounced search, empty message ADMIN-06
kind ref status
unit admin/tests/smoke/relation.smoke.test.ts#relation manager field pass
false
id description requirement verification human_judgment
D2 Unlink: plural unlinkConfirm, POST unlink with ids, reload, plural unlinked toast; cancel sends nothing ADMIN-06
kind ref status
unit admin/tests/smoke/relation.smoke.test.ts#confirms with the plural unlinkConfirm pass
false
id description requirement verification human_judgment
D3 Picker: role dialog with aria-modal, search autofocus, listbox aria-multiselectable, five per page, range, selection across pages, Dodaj (N) disabled at 0, link body, Esc and Anuluj close with focus return, selection cleared ADMIN-06
kind ref status
unit admin/tests/smoke/relation.smoke.test.ts#relation picker modal pass
false
id description requirement verification human_judgment
D4 Server-side candidate scoping still holds through the prefix (T-10-21 regression) ADMIN-06
kind ref status
integration ../fonoteka.go/plugins/golem15/fonoteka#TestCollectionsAdmin (15 tests, incl. RelationSchema, Link, Unlink, CrossScope, RelationEdges) pass
false
id description requirement verification human_judgment
D5 Sidebar: persisted boolean under summer-admin.sidebar, viewport-forced collapse without overwriting it, flyout by keyboard (Enter, ArrowDown), hover and 200 ms mouse-leave, Esc with focus return, no flyout when expanded ADMIN-06
kind ref status
unit admin/tests/smoke/shell.smoke.test.ts#collapsible section panel, section flyout pass
false
id description requirement verification human_judgment
D6 Header: breadcrumbs on record and list routes, user menu initials, name, role and email, logout POSTs /auth/logout and ends on login on success, 500 and network failure ADMIN-06
kind ref status
unit admin/tests/smoke/shell.smoke.test.ts#header pass
false
id description requirement verification human_judgment
D7 Dark class follows a mocked prefers-color-scheme and its changes; newest toast first ADMIN-06
kind ref status
unit admin/tests/smoke/shell.smoke.test.ts#theme and toasts pass
false
id description verification human_judgment rationale
D8 Real link and unlink round trip on a Kolekcje record at /plytadmin, focus return in a browser, visual fidelity at 1280px and about 900px in light and dark mode, limited admin versus superuser menus
true Both task human-checks: focus management, responsive behaviour and visual fidelity are judged in a browser; D-23 excludes browser e2e
21min 2026-09-27 complete

Phase 10 Plan 04: Relation manager and shell polish Summary

An admin editing an existing Collection can now search its editors, add several through a five-per-page picker and remove selected ones, with confirmations and toasts. The shell now follows the design. The section panel collapses to the rail below 1100px or on request, with a keyboard-operable flyout. The header has breadcrumbs and a user menu that shows the role and logs out. Dark mode follows the system.

Performance

  • Duration: 21 min
  • Started: 2026-09-27T15:06:36Z
  • Completed: 2026-09-27T15:27:25Z
  • Tasks: 2
  • Files modified: 42 in summercms.go (37 excluding rebuilt dist assets); fonoteka.go untouched

Accomplishments

  • Relation manager (D-05, SC-3). relation-manager is registered in the field registry. The form passes the record id down, and the field renders only on an existing record. On create it is dropped along with its tab, even without context: update, and it never enters the save body. RelationManager loads schema/relation/{name} once. Its header shows the label (17px/700), the YAML comment as helper text, a 220×38px search (300 ms debounce, resets the page and the selection), and the view's toolbar buttons in declared order. link is the primary user-plus button; unlink is the danger user-minus button, disabled without a selection. The linked list reuses DataTable in a new relation variant (56px rows, 30px initials avatar, muted secondary columns) inside a 12px bordered container, with sort, pagination from the meta and the relation empty message. Unlink confirms with the plural unlinkConfirm, POSTs {ids}, clears the selection, reloads and toasts the plural unlinked.
  • Picker (design screen 5). The picker is a Reka Dialog with aria-modal="true", a focus trap and Esc to close. It has the title, helper, 34px close button and autofocused 44px search from the relation messages. The candidates form a role="listbox" with aria-multiselectable="true", five per page, as 54px option rows (the selected ones get the sel background and a #e0b020 border). Space or Enter toggles an option and arrow keys move between options. The pager shows :from–:to z :total. The selection is a set kept across pages and searches and cleared on close. Dodaj (N) is disabled at zero; confirming POSTs link, closes, reloads the list and toasts the plural linked. Focus returns to the link button.
  • Sidebar (D-06, D-10). useSidebar combines matchMedia('(max-width: 1099px)') with a manual flag persisted as 'true'/'false' under summer-admin.sidebar. The viewport forces the collapse but never writes the stored choice. SectionPanel has its 30px "Zwiń menu" button, and the rail shows the 40px "Rozwiń menu" above Ustawienia after a manual collapse. While the panel is collapsed, SectionFlyout (role="menu", 230px, radius 14, shadow 0 16px 40px rgba(20,27,45,.18)) opens when a rail item is hovered or focused, or on Enter or ArrowDown, which also moves focus into it. Arrow keys, Home and End move between items. It closes on Esc and returns focus to the rail item, and it closes about 200 ms after the pointer or focus leaves.
  • Header. Breadcrumbs shows plugin › controller › record: parent crumbs are muted links, the current crumb is 600 with aria-current="page", and the separators are 14px chevrons. UserMenu (Reka DropdownMenu) has the 34px accent initials avatar, and the name and role are hidden below 1100px. Its 280px menu holds the 40px avatar, the bold name, the muted e-mail and Wyloguj. useAuth().logout(router) POSTs /auth/logout, then clears the user, navigation and settings and replaces the route with login, whatever the call returned.
  • Theme and motion. applyColorScheme() runs before boot and toggles .dark on the document element from prefers-color-scheme, following changes (A6: no toggle). .dark also sets color-scheme: dark. The rail stays dark with its #222b40 border in dark mode. The toast queue shows the newest toast first. Dialogs scale from 0.98 and fade in over 200 ms, backdrops and toasts fade in, and reduced motion turns all of it off.

Task Commits

  1. Task 1 (tracer): search, link and unlink related records through the relation manager: f4e97cc (feat)
  2. Task 2: collapsible panel with flyout, user menu with logout, breadcrumbs and dark mode: 445404e (feat)

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

Tracer gate (Task 1): the whole Task 1 <verify> passed on the committed tree before Task 2 started (human_verify_mode end-of-phase, auto mode off): typecheck, the relation smoke tests, TestPhase10SPAKeysResolve, check-admin-dist and the five fonoteka Collections relation suites. The human-check moves to phase verification.

Decisions Made

See key-decisions in the frontmatter. The two with the widest effect: relation-manager is a record-bound type (never on create, never in the save body), and the admin OpenAPI now types the relation list query.

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Blocking] The relation list routes had no query parameters in the admin OpenAPI

  • Found during: Task 1
  • Issue: schema.d.ts typed GET {id}/relations/{name} and .../candidates with query?: never, so openapi-fetch could not send search, page or per_page. The plan requires typed calls, and 10-03 rejected SPA-declared shapes.
  • Fix: cabana/admin_openapi.go declares search, sort, dir, page and per_page on both routes, matching relationQuery in cabana/http.go. admin.json and schema.d.ts were regenerated, check-admin-openapi.sh --check passes, and handlers are unchanged. types.ts gains RelationQuery, RelationPanel and RelationColumn aliases.
  • Commit: f4e97cc

2. [Rule 1 - Bug] Registering relation-manager would have put it in the save body

  • Found during: Task 1
  • Issue: editablePayload sends every registered type, so registering the relation manager would have sent members/editors in create and update bodies.
  • Fix: A record-bound set in the registry: isRegistered excludes it, needsRecord drops it on create, and ownsLabel lets the component render its own heading. The existing edit smoke test still asserts the exact PUT body.
  • Commit: f4e97cc

3. [Rule 2 - Correctness] Relation manager dropped on create even without context: update

  • The design requires the tab to render only on an existing record. The form now filters record-bound types in create mode itself instead of relying on the YAML context. The relation smoke fixture omits the context to prove it.
  • Commit: f4e97cc

4. [Rule 1] Form smoke test and fixture updated for the real renderer

  • Issue: The 10-03 form test asserted the unsupported box on the relation-manager tab.
  • Fix: The form fixture's relation manager is now the neutral members relation (tab Members, with a comment and no context). The test asserts [data-relation-manager] and no unsupported box.
  • Commit: f4e97cc

5. [Rule 1] Test harness teardown with teleported menus

  • Found during: Task 2
  • Issue: Each smoke file clears document.body after a test while the app is still mounted. The next test's beforeEach (clearUser()) then unmounted the UserMenu's DropdownMenu teleport inside the detached body. That caused 80 unhandled rejections and a failing vitest exit, although every assertion passed.
  • Fix: The UserMenu root no longer depends on the user (the trigger is disabled without one). The shell test also auto-unmounts wrappers before its body cleanup. The full smoke run exits 0 with no unhandled errors.
  • Commit: 445404e

6. [Rule 3] Supporting files not named in the plan

  • admin/src/state/useBreadcrumbs.ts carries the record title from FormView to the header.
  • admin/tests/setup.ts gives every test a desktop, light matchMedia, because happy-dom's 1024px default viewport is below the 1100px breakpoint and would collapse the panel in the older tests.
  • FormGrid, FormField, FieldRenderer and api/types.ts pass recordId and carry the relation types.
  • Commits: f4e97cc, 445404e

7. [Rule 1] Acceptance grep for the registry

  • The acceptance check expects one 'relation-manager' literal in registry.ts. The type is now the exported RELATION_MANAGER constant, and FormGrid uses it too. This Task 1 file change landed in the Task 2 commit.
  • Commit: 445404e

8. [Scope note] Lang keys

  • Task 1 added backend::lang.relation.no_candidates and backend::lang.relation.load_failed (pl and en). Task 2 needed no new keys because nav.collapse, nav.expand, nav.user_menu, nav.breadcrumbs and auth.logout already existed from 10-01. Only the two new keys were inserted into tests/fixtures/lang.json, so its existing plural ordering is kept.

9. [Process] Not a TDD plan; commits on master

  • The tasks carry no tdd="true". Each task's tests and code were committed together, keeping every commit green. As directed, commits land on master (branching_strategy: none).

Total deviations: 7 auto-fixed (2 blocking, 4 bugs, 1 correctness), 2 notes. Impact on plan: None on scope. The OpenAPI change documents existing query parameters only.

Issues Encountered

  • The admin bundle grew from 229 kB to 297 kB (gzip 76 to 97 kB). Reka's DropdownMenu brings its popper (floating-ui), and the plan names DropdownMenu for the user menu. No new package was added and the lockfile is unchanged.
  • The known fonoteka.go parity failures (TestMigrateSeedsCanonicalGenres, TestSchemaMatchesPHPSnapshot) and gofmt -l internal/build/registry.go remain, as logged in deferred-items.md. go vet ./... and go test ./... pass in summercms.go, and the full TestCollectionsAdmin family (15 tests) passes in the fonoteka plugin module.

Known Stubs

None. WINDOWS entry 4 (relation-manager not registered) is marked fixed by this plan.

Threat Flags

None. No new endpoint, auth path or storage beyond the plan. The OpenAPI change documents existing query parameters. localStorage holds only the sidebar boolean (T-10-22 grep is empty). Candidates are rendered as served, with no client-side filtering (T-10-21). Logout clears local state and leaves the shell even on failure (T-10-23).

User Setup Required

None.

Next Phase Readiness

  • Plan 10-05 (unit tests) can target useSidebar.ts, theme.ts, useBreadcrumbs.ts, useAuth.logout, the registry's needsRecord/isRegistered and editablePayload directly. The smoke tests already cover the component behaviour.
  • The human browser checks of both tasks are left for phase verification: the Kolekcje editors round trip, focus return, and fidelity at 1280px and about 900px in light and dark mode for a limited admin and a superuser.

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

Self-Check: PASSED

All 13 created files listed above exist; commits f4e97cc and 445404e (summercms.go) are present. Plan verification re-run: typecheck, all 76 smoke tests (exit 0, no unhandled errors), go test ./phrasebook, check-admin-dist.sh, check-admin-openapi.sh --check, go vet ./... and go test ./... pass; the fonoteka TestCollectionsAdmin family (15 tests) passes.