diff --git a/.planning/phases/10-admin-vue-spa/10-CONTEXT.md b/.planning/phases/10-admin-vue-spa/10-CONTEXT.md new file mode 100644 index 0000000..0b5f656 --- /dev/null +++ b/.planning/phases/10-admin-vue-spa/10-CONTEXT.md @@ -0,0 +1,149 @@ +# Phase 10: Admin Vue SPA - Context + +**Gathered:** 2026-09-26 +**Status:** Ready for planning (after Phase 9 plans 09-11 and 09-12 complete) + + +## Phase Boundary + +Phase 10 delivers the framework's admin SPA: a Vue 3 + TypeScript single-page app, living in `summercms.go`, embedded in the binary and served at a configurable backend URI. It renders login, permission-gated plugin navigation, schema-driven lists and forms, the relation manager, the settings screen and the filter bar, consuming the Phase 9 `cabana` admin API. The five Płytarium controllers (Albums, Artists, Collections, Genres, Styles) are the acceptance targets; the Collections editors relation manager must search, link and unlink. + +The phase also carries the backend changes the SPA needs, all in `cabana` (framework) plus YAML/registry updates in `fonoteka.go`: a configurable admin prefix for both SPA and API, cookie transport for the admin JWT, a relation-field options endpoint, relation values in form saves, `messages:` and declarative `toolbar.buttons` in controller config, a server-served SPA string bundle, and a framework-owned admin OpenAPI document the SPA's types are generated from. + +Requirement: ADMIN-06. Not in scope: runtime plugin extensions (plugin JS/CSS assets, custom widget and partial field types), which are Phase 10.1; browser e2e tests. + + + + +## Implementation Decisions + +### Packaging and serving +- **D-01:** The SPA source lives in `summercms.go/admin` (Vite project). Its built `dist/` is embedded through `embed.FS` in a framework package and served by the binary. No separate deploy, no CORS. +- **D-02:** The mount path is configurable (`backend.uri`, Winter-style), e.g. `/plytadmin`, `/manage`, `/horoadmin`, chosen per project so the admin location is not guessable. The committed build is path-agnostic: the Go server injects the configured base path into `index.html` at serve time. Client-side routes are handled by an SPA fallback under the prefix. — **Reversibility:** reversible +- **D-03:** The admin API moves with the prefix: `{backend.uri}/api/v1/...` replaces the hardcoded `/_admin/api/v1` from Phase 9 (D-09 there). This is safe because nothing outside the SPA calls the admin API yet (Nuxt and fonoteka-mcp never touch it). The `api` segment under the prefix is reserved and never used as an SPA route. OpenAPI paths are written relative (`/navigation`, `/auth/login`) and the SPA client gets its base URL at runtime. — **Reversibility:** costly — every admin route, test and the OpenAPI document are keyed to the prefix scheme. +- **D-04:** The built `dist/` is committed, with a check script that rebuilds and fails on drift (same pattern as `check-openapi.sh`). Rationale agreed with the user: everything an app plugin changes in the admin (YAML columns/fields, navigation, permissions, hooks, and in 10.1 partials and JS/CSS assets) is embedded in the plugin and needs only a Go binary rebuild; the Node build is needed only when the framework SPA itself changes (new built-in field type, layout). App developers never need Node. +- **D-05:** Extension seam only: a field-type renderer registry (`FieldRenderer` maps `type` to component). Any unregistered type renders the design's `UnsupportedField` box ("Nieobsługiwany typ pola: ``") instead of breaking the form. The full extension point is Phase 10.1 (see Deferred). + +### Look and components +- **D-06:** The design reference is Direction C v2 from claude.ai/design, stored at `.planning/phases/10-admin-vue-spa/design/` (README.md is the spec; `Direction C v2.dc.html` + `support.js` is the viewable reference). High fidelity: tokens, typography, radii, spacing, control heights, focus rings, copy, a11y roles, states (selected, empty, loading, 422, toast, modal), responsive rules (collapse below ~1100px, tablet width), light and dark mode. Build the generic components it lists (PluginRail, SectionPanel, SectionFlyout, AppShell, DataTable, ListToolbar, Pagination, FormTabs, FormGrid, FormField, FieldRenderer, RelationManager, RelationPickerModal, Toast, UserMenu). +- **D-07:** Stack: Vue 3 + Vite + TypeScript + Tailwind + Reka UI primitives + lucide-vue-next icons (same family as `vue-fonoteka-app`). DM Sans and DM Mono are self-hosted inside `dist/`, not loaded from Google Fonts (works offline, no third-party requests from a hidden admin URL). +- **D-08:** Real YAML only. Płytarium screens render exactly what their YAML declares (Albums: name, artists, format, genre, shelf; no tabs). The mock's extra fields, tabs and columns (Opis, Wypożyczony, Ulubiony, Utworzono, Szczegóły tab) are component fixtures for tests, not additions to Płytarium. +- **D-09:** The SPA follows Phase 9's API contract where the mock differs: error envelope `{"error": {code, message, details}}` with 422 details as field → messages (not the mock's `{errors}`); list query and meta per Phase 9 D-11. +- **D-10:** Routes derive from controller IDs: `golem15.fonoteka.albums` → `{backend.uri}/golem15/fonoteka/albums`, `/create`, `/:id`. The rail groups by plugin from `/navigation`; the active rail item comes from the route's owning plugin. Items the admin lacks permission for are absent (server-filtered), and a plugin whose side menu is empty after filtering is absent from the rail. +- **D-11:** Navigation icons are lucide names in the registry. The SPA also carries a small map from Winter `icon-*` names to lucide so ported Winter plugins work unchanged; unknown names get a neutral fallback icon. `fonoteka.go` switches its navigation and settings icons to lucide names. +- **D-12:** The Albums format column renders as plain text in Phase 10 (no icon pills). + +### Controller copy and toolbar +- **D-13:** `config_list.yaml` and `config_form.yaml` accept an optional `messages:` block of phrasebook keys (e.g. `recordCount`, `searchPrompt`, `deleteConfirm`, `empty` on lists; `create`, `saved` on forms). Keys are resolved server-side in the admin's locale. Plural keys are served with all their CLDR forms (`{"one": ..., "few": ..., "many": ..., "other": ...}`) and the SPA picks the form with `Intl.PluralRules(locale)`, because only the client knows the count (e.g. selected rows). Placeholders `{count}`, `{name}`, `{term}` are interpolated by the SPA. Every key is optional; the framework ships generic defaults (e.g. "Nowy rekord", "Usunąć zaznaczone ({count})?", "Zapisano"). Unknown `messages` keys fail at boot (Phase 9 `DisallowUnknownField` rule). +- **D-14:** Toolbar is declarative: `toolbar.buttons: [create, delete]` lists built-in actions in display order (`create` gated by create permission; `delete` is bulk delete, enabled with a selection, confirming with `deleteConfirm`), plus `toolbar.search`. A string value (Winter's `buttons: list_toolbar` partial) is a boot error pointing at the new syntax. The five Płytarium config files are updated accordingly. Custom actions are Phase 10.1. — **Reversibility:** costly — changes the YAML contract every ported controller writes. + +### Types and relations +- **D-15:** The SPA's types come from a framework-owned admin OpenAPI document: swag v1 annotations on `cabana` handlers, generated in `summercms.go`, converted to OpenAPI 3 (reuse the `swagger2openapi.go` approach), committed, and fed to openapi-typescript, with a drift check. The framework SPA never reads an app repo's document. `fonoteka.go/docs/openapi.json` remains the parity API document. +- **D-16:** Records are typed generically: generated types cover envelopes, form/list/filter/relation/navigation/settings schemas, list meta and errors; a record is `Record` read through its field schema. No per-controller TypeScript types and no hand-maintained duplicates of API shapes (success criterion 4). +- **D-17:** Relation fields get their choices from a new endpoint, `GET {prefix}/api/v1/{vendor}/{plugin}/{controller}/fields/{field}/options?search=&page=&per_page=`, returning `{value, label}` with the label from `nameFrom`, on the Phase 9 D-11 list contract. An optional `RelationExtendOptionsQuery(ctx, field, *gorm.DB) *gorm.DB` hook on the admin controller lets apps scope options (e.g. to the active collection). Permission gating is the controller's. The `emptyOption` stays a schema property rendered by the SPA. +- **D-18:** Form saves send relation values keyed by the YAML field name with ids: `{"genre": 3, "artists": [4, 9]}`. `cabana` maps belongs-to to the foreign key and syncs many-to-many through the Phase 5 join-table contract inside the save transaction, after `FormBeforeCreate`/`FormBeforeUpdate`. Record GET returns the same shape plus display labels so the form can show chips and the selected dropdown value without extra calls. + +### Session and UX +- **D-19:** The admin JWT moves into an httpOnly, Secure, SameSite=Strict cookie scoped to `{backend.uri}`, set by login and refresh and cleared by logout. JS never reads the token (this matters once 10.1 lets plugin JS run in the admin origin). The `backend` guard accepts the cookie and still accepts `Authorization: Bearer` for CLI and tests. State-changing requests must carry a custom header (e.g. `X-Requested-With`) as CSRF defence in depth. It is still a JWT with the Phase 7 blacklist and sliding refresh; no session store. — **Reversibility:** costly — changes Phase 9's auth transport and the security matrix tests that pin it. +- **D-20:** The SPA's own UI strings (Zapisz, Anuluj, Wyloguj, pagination, confirmations, defaults from D-13) live in the framework's phrasebook (`backend::lang`, pl and en) and are fetched as a resolved bundle for the admin's locale at startup. Projects can override or add locales without a Node rebuild. +- **D-21:** The settings screen is in scope: the rail's "Ustawienia" lists `HasSettings` pages the admin may manage and renders each through the same form renderer against the Phase 9 settings endpoints (fonoteka: `search_use_typesense`). +- **D-22:** The filter bar is in scope: it renders `config_filter.yaml` scopes of the three Phase 9 shapes (switch, daterange, model-backed) and drives `filter[]`. Płytarium has no filters, so it is verified with fixtures. +- **D-23:** Testing follows the lean rules: Vitest component and unit tests as the phase's last plan; Go tests for every `cabana` change. Browser e2e (Playwright) is not part of Phase 10. + +### Claude's Discretion +- SPA state management (Pinia or composables), router setup, HTTP client (openapi-fetch or a thin wrapper over generated types). +- Dev loop: Vite dev server proxying the API vs rebuilding into the embedded path; recommended a Vite proxy to a running `summer serve` for development. +- Exact `messages` key vocabulary beyond the examples, and the generic default wording. +- Refresh timing (proactive before expiry plus one retry on 401, then redirect to login with the return URL). +- Per-page options (design says 12/24/48/96; reconcile with `recordsPerPage` and the Phase 9 cap). +- Unsaved-changes confirmation, create-then-redirect behaviour (use `config_form` `create.redirect`/`redirectClose` semantics mapped to SPA routes), toast queue. +- Whether admin paths are removed from `fonoteka.go/docs/openapi.json` once the framework document exists (recommended: yes, one owner per path). +- Package layout inside `summercms.go` for the embed/serve package, following beach-themed naming. + + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +### Design +- `.planning/phases/10-admin-vue-spa/design/README.md` — the full UI spec: tokens, screens, states, interactions, component list. +- `.planning/phases/10-admin-vue-spa/design/Direction C v2.dc.html` (+ `support.js`) — viewable reference; props switch screens and states. + +### Roadmap and requirements +- `.planning/ROADMAP.md` §Phase 10 — goal and success criteria. +- `.planning/REQUIREMENTS.md` — ADMIN-06. +- `.planning/PROJECT.md` — two-repo split (framework holds the admin SPA shell), stack constraints. +- `.planning/research/STACK.md` — swag v1, openapi-typescript, goccy/go-yaml. + +### Phase 9 contract this phase consumes and amends +- `.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-CONTEXT.md` — D-02 guard, D-06 typed schema, D-07 server-side labels, D-09/D-10/D-11 API paths, envelope and list contract, D-12 filters, D-13 hooks, D-15/D-16 relation manager, D-17 settings, D-18 navigation. +- `.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-11-PLAN.md`, `09-12-PLAN.md` and their SUMMARYs once written — final state of navigation/settings endpoints, security matrix and OpenAPI generation. +- `cabana/http.go` — route mounting (hardcoded `/_admin/api/v1` changed by D-03). +- `cabana/auth.go` — login/refresh/logout/me (transport changed by D-19). +- `cabana/form_schema.go`, `cabana/list_schema.go`, `cabana/filter_schema.go`, `cabana/relation.go`, `cabana/settings.go`, `cabana/navigation.go`, `cabana/schema_types.go` — the JSON the SPA renders. + +### OpenAPI tooling +- `../fonoteka.go/scripts/check-openapi.sh` and `../fonoteka.go/scripts/swagger2openapi.go` — the generation/conversion/validation pattern to replicate for the framework document. + +### Płytarium config the SPA renders +- `../fonoteka.go/plugins/golem15/fonoteka/controllers/{albums,artists,collections,genres,styles}/` — config_form/config_list (updated by D-13/D-14). +- `../fonoteka.go/plugins/golem15/fonoteka/admin_navigation.go`, `admin_settings.go` — icons switched to lucide (D-11). +- `/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/models/album/fields.yaml`, `columns.yaml` — PHP originals (real field set, D-08). + +### UI stack reference +- `/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/package.json` — the Vue/Tailwind/Reka/lucide versions the team already uses. + + + + +## Existing Code Insights + +### Reusable Assets +- `cabana` (~10k lines): complete admin API: auth, navigation, list/form/filter/relation schemas, CRUD, bulk delete, relation linked/candidates/link/unlink, settings. The SPA is its first client. +- `bouncer.MintAudience` / `RefreshAudience` / blacklist: stay the token mechanism under cookie transport (D-19). +- `phrasebook`: resolves labels and the new `messages` and SPA string bundle (D-13, D-20); CLDR plural data matches `Intl.PluralRules`. +- `pact.DropdownOptionsProvider` and model-method options: pattern for the new relation options hook (D-17). +- Phase 5 join-table contract: many-to-many sync on save (D-18). +- `check-openapi.sh` pipeline (swag v1.16.6 → swagger2openapi → openapi-typescript 7.13.0). + +### Established Patterns +- Optional capability interfaces type-asserted by the consumer (`RelationExtendOptionsQuery` follows Phase 9 D-13). +- Fail-loud boot on malformed plugin YAML (`DisallowUnknownField`): applies to `messages` and `toolbar.buttons`. +- Committed generated artifact + drift check (OpenAPI doc; now also `dist/` and the admin OpenAPI doc). +- Framework stays app-agnostic: no Płytarium names in `summercms.go`, including SPA code. + +### Integration Points +- `cabana` mount: prefix from config (D-03), SPA static serving + index.html base injection + history fallback (D-01, D-02), same prefix. +- `backend` guard in the bouncer registry: cookie extraction + CSRF header check (D-19). +- `/auth/me` already returns name/email; the user menu needs the role name too. +- `fonoteka.go`: YAML toolbar/messages updates, lucide icons, optional `RelationExtendOptionsQuery` on Albums (scope to active collection, consistent with Phase 9 D-14). + + + + +## Specific Ideas + +- The user wants the admin URL changeable per project for security ("/plytadmin", "/manage", "/horoadmin"), as in WinterCMS. +- The user asked for "a best solution" to per-controller copy instead of Winter's `_list_toolbar.htm` partial: declarative actions plus translatable messages with proper Polish plurals. +- The design was produced in claude.ai/design from a prompt listing three directions; the user chose Direction C v2 ("Generic modern dashboard", navy + sunny yellow) and treats it as the target. +- The shell is multi-plugin by design (rail per registered main-menu item); Płytarium is one plugin among future ones (Użytkownicy, Media in the mock). + + + + +## Deferred Ideas + +- **Phase 10.1: runtime admin extension point.** `AdminAssets()` on controllers (Winter `addJs`/`addCss`), served under the admin prefix and loaded when the controller opens; `type: widget` rendered as custom elements (plugins ship plain JS, no Vue coupling); `type: partial` rendered server-side via html/template; custom toolbar actions; a dev-mode switch serving plugin assets from disk. Needs a roadmap entry. +- Ctrl+K command palette across all plugins' side menus. +- Generic badge/icon column type (Albums format pills with disc/cassette icons). +- Playwright browser e2e for the admin SPA (joins the Phase 8 carried-forward Playwright follow-up). +- Admin navigation for the user and media plugins (shown in the mock) arrives with those ports. + + + +--- + +*Phase: 10-admin-vue-spa* +*Context gathered: 2026-09-26* diff --git a/.planning/phases/10-admin-vue-spa/10-DISCUSSION-LOG.md b/.planning/phases/10-admin-vue-spa/10-DISCUSSION-LOG.md new file mode 100644 index 0000000..2f9f63e --- /dev/null +++ b/.planning/phases/10-admin-vue-spa/10-DISCUSSION-LOG.md @@ -0,0 +1,90 @@ +# Phase 10: Admin Vue SPA - Discussion Log + +> **Audit trail only.** Do not use as input to planning, research, or execution agents. +> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered. + +**Date:** 2026-09-26 +**Phase:** 10-admin-vue-spa +**Areas discussed:** Packaging & serving, UI kit & look, Types & relation options, Session & UX details + +--- + +## Packaging & serving + +| Option | Description | Selected | +|--------|-------------|----------| +| summercms.go/admin, embedded | Vite project in framework repo, dist embedded and served by the binary | ✓ | +| summercms.go/admin, served separately | Static deploy, CORS | | +| fonoteka.go/admin | SPA in the app repo | | + +**User's choice:** Embedded, with the admin path configurable per project (e.g. /plytadmin, /manage) for security, as in WinterCMS. + +| Option | Description | Selected | +|--------|-------------|----------| +| Commit dist | `go build` stays pure Go, drift check | ✓ | +| Build via summer build | dist gitignored, Node needed at every build | | + +**User's choice:** Commit dist, after clarification. The user asked twice whether plugin changes (and Winter's `partial` field, `addJs`/`addCss`) need a rebuild. Resolved: they need a Go binary rebuild (plugin assets are embedded), never a framework SPA Node rebuild once the 10.1 extension point exists; Node is only for framework SPA changes. + +| Option | Description | Selected | +|--------|-------------|----------| +| Both SPA and API move with backend.uri | Nothing reveals the admin location | ✓ | +| Only the SPA moves | Fixed /_admin/api/v1 | | + +**User's choice:** Both move, conditional on it being safe; confirmed non-breaking (no external admin API consumer yet). + +| Option | Description | Selected | +|--------|-------------|----------| +| Seam now, build later | Renderer registry + unsupported fallback now | ✓ | +| Build it in Phase 10 | Assets, widgets, partials now | | +| Only AdminAssets now | | | + +**User's choice:** Seam now, full implementation in Phase 10.1. + +--- + +## UI kit & look + +The user generated three directions in claude.ai/design from a prompt Claude wrote, and chose Direction C v2 (handoff zip reviewed and copied to `design/`). + +| Question | Options | Selected | +|----------|---------|----------| +| Mock vs real YAML | Real YAML only / Extend Płytarium YAML | Real YAML only | +| Nav icons | Lucide + Winter map / Lucide only / Winter names mapped | Lucide + Winter map | +| Format badge icons | Plain text now / Badge column now | Plain text now | +| Per-controller copy | Config keys + generic fallback / Generic only | User asked for the best solution beyond Winter's toolbar partial; Claude proposed `messages:` with CLDR plural forms chosen client-side via Intl.PluralRules plus declarative `toolbar.buttons`; locked | + +--- + +## Types & relation options + +| Question | Options | Selected | +|----------|---------|----------| +| OpenAPI source | Framework-owned admin doc / Fonoteka's doc | Framework-owned admin doc | +| Record typing | Generic schema-driven / Per-controller DTOs | Generic schema-driven | +| Relation field options | Options endpoint / Embed in schema / Hybrid | Options endpoint | +| Relation save payload | Field name with ids / Raw columns | Field name with ids | + +--- + +## Session & UX details + +| Question | Options | Selected | +|----------|---------|----------| +| Token storage | httpOnly cookie / sessionStorage bearer / localStorage bearer | httpOnly cookie | +| SPA chrome strings | Server phrasebook bundle / Bundled in SPA | Server phrasebook bundle | +| Extras in scope | Settings screen / Filter bar / Playwright e2e | Settings screen, Filter bar | + +--- + +## Claude's Discretion + +State management, router, HTTP client, dev loop, messages vocabulary and default wording, refresh timing, per-page options, dirty-form handling, redirects, removal of admin paths from the fonoteka OpenAPI doc, package layout. + +## Deferred Ideas + +- Phase 10.1 runtime extension point (AdminAssets, `type: widget`, `type: partial`, custom toolbar actions, dev-mode disk assets) +- Ctrl+K command palette +- Badge/icon column type +- Playwright e2e for the admin SPA +- User/media plugin admin navigation diff --git a/.planning/phases/10-admin-vue-spa/design/Direction C v2.dc.html b/.planning/phases/10-admin-vue-spa/design/Direction C v2.dc.html new file mode 100644 index 0000000..3fc8688 --- /dev/null +++ b/.planning/phases/10-admin-vue-spa/design/Direction C v2.dc.html @@ -0,0 +1,461 @@ + + + + + + + + + + + + + + + +
+ + +
+
+
SummerCMS
+
+

Witaj ponownie

+
+ + +
+
+ + +
+ + +
+
+
+
+ + + + + + + +
+
{{ pluginLabel }}
+ + {{ item.label }} + +
+
+ +
+
+ +
+ +
+ +
+ +
+
+

Albumy

348 pozycji w katalogu

+ +
+
+
+
+ + + +
+
+ Zaznaczono {{ selCount }} + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + NazwaArtyściFormatGatunekPółkaUtworzonoWypożyczony
+ + + {{ row.name }}{{ row.artists }}{{ row.format }}{{ row.genre }}{{ row.shelf }}{{ row.created }} + Tak + Nie +
+ +
+ +
Nic nie znaleziono
+

Żaden album nie pasuje do frazy „kaseta 1983”. Spróbuj innej nazwy lub artysty.

+ +
+
+
+
+ {{ range }} + +
Na stronę
+
+ +
+
+
+
+
+ + +
+
+

Użytkownicy

42 konta administracyjne

+ +
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
Imię i nazwiskoE-mailGrupyOstatnie logowanieAktywny
{{ u.ini }}{{ u.name }}{{ u.email }}{{ u.groups }}{{ u.last }} + Tak + Nie +
+
+
1–10 z 42
+ +
+
+
+
+ + +
+
+
+
+ +

{{ recordName }}

{{ formTitle }}

+
+ + + +
+
+ +
Nie udało się zapisać. Popraw 2 pola oznaczone poniżej.
+
+
+ +
+
+ + +

Pole Nazwa jest wymagane.

+
+
+ +
+ CNCzesław Niemen + AKAkwarium + +
+
+
+ + +
+
+ + +
+
+ + +

Pole Półka może mieć maksymalnie 12 znaków.

+
+
+
+ + +
+
+
Wypożyczony
Płyta jest aktualnie poza domem
+ +
+ +
+
+ +
+
+
+
+
+
+ Kolor okładki +
Nieobsługiwany typ pola: colorpicker
+
+
+
+ +
+
+

Redaktorzy

Osoby, które mogą edytować tę kolekcję

+
+ + +
+
+ + + + + + + + + + + + + + + + + +
Imię i nazwiskoE-mailDodano
+ + + {{ e.ini }}{{ e.name }}{{ e.email }}{{ e.added }}
+
+
+
+
+
+
+
+ +
+ + + +
+
+
+
+
+ + +
+
TW
Tomasz Wójcik
tomasz.wojcik@plytarium.pl
+
+ +
+
+ + +
+ + Album „Enigmatic” został zapisany + +
+
+ + +
+
+
+

Dodaj redaktora

Wybierz osoby spoza obecnych redaktorów. Właściciel kolekcji nie jest wyświetlany.

+ +
+
+
+ +
+ {{ c.ini }} +
{{ c.name }}
{{ c.email }}
+ + +
+
+
+
+ 1–5 z 14
+ + +
+
+ + +
+
+
+
+
+
+
+ + + diff --git a/.planning/phases/10-admin-vue-spa/design/README.md b/.planning/phases/10-admin-vue-spa/design/README.md new file mode 100644 index 0000000..035daeb --- /dev/null +++ b/.planning/phases/10-admin-vue-spa/design/README.md @@ -0,0 +1,387 @@ +# Handoff: SummerCMS Admin Panel — Direction C ("Generic modern dashboard") + +## Overview +This is the admin panel for SummerCMS, a CMS framework. The mocks use data from one real app: **Płytarium**, a home catalogue of vinyl, CD and cassette releases. The panel is a **schema-driven Vue 3 SPA**. Every list and form is generated from YAML-defined `columns` and `fields`. Build generic components, not screens: one DataTable, one FormField row, one Tabs component, one RelationManager. The UI language is **Polish**, and all copy below is final. + +Screens covered: Login, Navigation shell (multi-plugin: Płytarium + Użytkownicy), List (Albumy) with its selected/empty/loading states, Form (Edycja albumu) with tabs and 422 errors, and the relation manager (Kolekcje → Redaktorzy) with its "Dodaj redaktora" modal. Light and dark mode are both included, plus tablet width. + +## About the Design Files +`Direction C v2.dc.html` is a **design reference built in HTML**, not production code. Recreate it in the target Vue 3 codebase using its existing patterns. If the codebase has no UI setup yet, use Vue 3 + Vite + Tailwind with headless primitives (e.g. Radix Vue / Reka UI) and lucide-vue-next icons. + +To view it, open `Direction C v2.dc.html` in a browser. `support.js` must be in the same folder, and you need internet access for the fonts and icons. The component's props act as a state switcher: +- `plugin`: `plytarium | users` (use `users` with `screen=list` to see the Users plugin list) +- `flyout`: boolean; with `narrow`, shows the collapsed-rail flyout +- `screen`: `login | list | form | relation` +- `listState`: `default | selected | empty | loading` +- `tab`: `basic | details` +- `dark`, `narrow` (tablet), `errors` (422), `toast`, `modal`, `userMenu`: booleans + +All sample data sits in the `ALBUMS`, `EDITORS` and `CANDS` constants in the logic class. + +## Fidelity +**High-fidelity.** Colours, type, spacing, radii and copy are final. Match them closely. + +--- + +## Design Tokens + +### Colours +| Token | Light | Dark | Use | +|---|---|---|---| +| bg | `#f4f6f9` | `#111726` | App background behind cards | +| surface | `#ffffff` | `#182033` | Cards, header, inputs, footer bar | +| subtle | `#f3f5f8` | `#1f283d` | Table header, search input fill, chips, avatar bg | +| border | `#e6e9ef` | `#29334b` | Card borders, row dividers | +| borderStrong | `#d2d8e2` | `#3a4661` | Input borders, unchecked checkbox, switch off | +| text | `#141b2d` | `#eef1f6` | Primary text | +| muted | `#566175` | `#a9b3c6` | Secondary text, column headers, icons | +| placeholder | `#6b7588` | `#8a95ab` | Input placeholder | +| primary | `#22304d` (navy) | `#fcd34d` (yellow) | Primary buttons, checked checkbox | +| onPrimary | `#ffffff` | `#1b2540` | Text on primary | +| danger | `#c62828` | `#f58a8a` | Destructive, errors | +| dangerSoft | `#fdf0f0` | `#3a1d24` | Error banner bg, destructive hover | +| okBg / okText | `#e3f4e8` / `#1c6b35` | `#173826` / `#8fdfa8` | "Tak" badge | +| ring | `rgba(252,196,40,.55)` | `rgba(252,211,77,.45)` | Focus ring (3px) | +| hover | `#f1f3f7` | `#212b42` | Hover bg for ghost buttons/rows | +| sel | `#fdf3cf` | `#3a3622` | Selected row bg, current page button | +| skel | `#eceff4` | `#263049` | Skeleton bars | +| overlay | `rgba(20,27,45,.5)` | `rgba(5,8,16,.7)` | Modal backdrop | +| side | `#1d2740` | `#0d1320` | Sidebar bg (sidebar is dark in both modes; dark mode adds a `#222b40` right border) | +| accent | `#fcd34d` | same | Brand yellow: logo, active nav, avatar | +| onAccent | `#1b2540` | same | Text on accent | +| sidebar text | `#c3cbda` idle, `#9aa6bd` section label, `#ffffff` hover, `#fcd34d` active | | | +| card shadow | `0 1px 2px rgba(20,27,45,.04), 0 4px 16px rgba(20,27,45,.05)` | `none` | | + +The brand is **dark navy + sunny yellow**. In light mode the primary is navy and yellow is only an accent. In dark mode yellow becomes the primary. + +### Typography +- UI font: **DM Sans** (Google Fonts, opsz 9..40, weights 400/500/600/700). Base size 14px, line-height 1.5. +- Mono: **DM Mono** 400/500. Used for the Półka shelf code and the `colorpicker` token. +- Datetimes use DM Sans with `font-variant-numeric: tabular-nums`, 13px, muted colour. + +| Role | Size / weight / tracking | +|---|---| +| Page title (list) | 26px / 700 / -0.02em | +| Record title (form) | 24px / 700 / -0.02em | +| Login title | 22px / 700 / -0.02em | +| Modal title | 20px / 700 / -0.01em | +| Section title (Redaktorzy) | 17px / 700 | +| Body / cell | 14px / 400 (Nazwa cell 600) | +| Field label | 14px / 600 | +| Column header | 12px / 600, muted (the sorted column uses the text colour) | +| Helper / meta / error text | 13px | + +### Radii +Inputs, buttons and selects: 10px. Cards: 16px. Login card: 16px. Modal: 20px. Inner table container and toggle cards: 12px. Tabs track: 12px, tab pill: 9px. Checkbox: 5px (6px in the form and modal). Pagination buttons: 8px. Badges, chips and switch: 999px. Avatars: 50%. + +### Spacing +Page padding is 28px top/bottom and 32px left/right. Gaps between cards and blocks are 20px. Form card padding is 28px. The form grid gap is 22px (rows) × 24px (columns). Table cells use 14px horizontal padding, with 20px on the first and last columns. + +### Control heights +- Primary/secondary buttons and search: **42px** +- Form inputs, selects and login inputs: **44px** +- Table header row: 44px. Body row: **54px** (56px in the relation manager). Modal option row: 54px. +- Pagination buttons: 34px +- Top header: 64px. Sidebar nav item: 40px. + +### Focus +Every interactive element shows a visible **3px yellow ring** (`box-shadow: 0 0 0 3px var(--ring)`). Inputs also switch their border to `primary`. Buttons use `outline: 3px solid var(--ring); outline-offset: 2px`. Style `:focus-visible` this way and never remove it. + +--- + +## Screens + +### 1. Login (`screen=login`) +- Full-viewport background: `radial-gradient(ellipse at 50% 35%, #2b3a5c 0%, #1d2740 60%)`. +- A centred column, max-width 400px, gap 24px: + - **Wordmark**: sun icon (lucide `sun`, 20px, yellow) in a 34px circle filled `rgba(252,211,77,.14)`, followed by "Summer**CMS**" at 18px/700 in white, with "CMS" in yellow. This is the only branding, because the admin URL is custom (e.g. `/plytadmin`). + - **Card**: white background, radius 16, padding 32, gap 18, shadow `0 24px 60px rgba(0,0,0,.35)`. + - Title: "Witaj ponownie" + - Field "Login lub e-mail" (input 44px) + - Field "Hasło" (password) + - Error state: the password input gets a danger border and `aria-invalid`. Below it sits an alert block (`role="alert"`, dangerSoft bg, danger text, radius 10, padding 12×14, `circle-alert` icon 18px) reading **"Nieprawidłowe dane logowania"**. + - Full-width primary button **"Zaloguj"** (44px). + +### 2. Navigation shell — multi-plugin (rail + section panel) +SummerCMS replaces WinterCMS. It is a backend that hosts many plugins, and each plugin registers navigation the way Winter's `registerNavigation()` does: **one main-menu item** (label, icon, order, permissions) plus **its own side menu** (items, icons, permissions). Płytarium is just one of these plugins. The shell is built from that registry, and nothing in it is hardcoded per plugin. + +**Plugin rail (level 1 = Winter main menu)** +- Always visible. Width 80px, colour `side` (`#1d2740` light / `#0d1320` dark), padding 14px 0 12px, column gap 6, centred. +- Top: brand badge, a 40px rounded square (radius 12) filled `rgba(252,211,77,.14)` holding a `sun` icon (22px, yellow). `title="SummerCMS"`, bottom margin 14. +- One item per registered main-menu entry, sorted by `order`: + - 68px wide, padding 8/6, radius 12, letter-spacing -0.01em. + - 20px icon, with the label below it at 11px (line-height 1.2, centred). + - Idle: `#c3cbda`. Hover: `rgba(255,255,255,.08)` with white text. + - Active: bg `rgba(252,211,77,.14)`, text/icon `#fcd34d`, weight 700, `aria-current="page"`. + - Focus: 3px ring. + - Mock entries: **Płytarium** (`disc-3`), **Użytkownicy** (`users`), **Media** (`image`). +- A flex spacer, then **Ustawienia** (`settings`) pinned to the bottom. Settings is a separate area where plugins register settings pages, the same as Winter. +- When the panel is collapsed, a 40px "Rozwiń menu" button (`panel-left-open`) appears above Ustawienia. +- Wrap the rail in `