diff --git a/.planning/phases/16.3-backend-admin-personal-api-tokens-with-scopes/16.3-UI-SPEC.md b/.planning/phases/16.3-backend-admin-personal-api-tokens-with-scopes/16.3-UI-SPEC.md new file mode 100644 index 0000000..ece3714 --- /dev/null +++ b/.planning/phases/16.3-backend-admin-personal-api-tokens-with-scopes/16.3-UI-SPEC.md @@ -0,0 +1,367 @@ +--- +phase: "16.3" +slug: "backend-admin-personal-api-tokens-with-scopes" +status: approved +reviewed_at: "2026-10-11" +shadcn_initialized: false +preset: none +created: "2026-10-10" +--- + +# Phase 16.3 — UI Design Contract + +> Visual and interaction contract for frontend phases. Generated by msd-ui-researcher, verified by msd-ui-checker. + +This phase adds one surface to the existing admin SPA (`admin/`): an **API tokens** card on the My account page (`AccountView.vue`, route `/account`). The card lists the signed-in user's own tokens, creates a token in a two-step dialog (form, then show-once plaintext), and revokes tokens (D-16). There is no other screen: no superuser view and no backend-user management screen (deferred). The CLI (`admin:token:*`, D-19) has no visual contract beyond its stdout rule in CONTEXT.md. + +The SPA already has a complete design system: Direction C v2 (`.planning/phases/10-admin-vue-spa/design/README.md`), implemented as Tailwind 4 `@theme` tokens in `admin/src/styles/main.css` and built on Reka UI primitives. **This contract adds no new tokens, colours, fonts, radii or shadows.** Every value below reuses an existing token or copies a class string from an existing component (`AccountView.vue`, `RelationPickerModal.vue`, `ConfirmDialog.vue`, `PermissionEditorField.vue`, `CheckboxField.vue`, `Button.vue`, `control.ts`). + +Sources: 16.3-CONTEXT.md D-06, D-08, D-09, D-11, D-16, D-17, D-18 (locked); Phase 12.2 UI-SPEC (approved house conventions for dialogs, chips, alert blocks, skeletons and copy); the codebase scan listed above; `modules/phrasebook/backend/lang/{en,pl}/lang.yaml` (`myaccount.*`, `form.*`). + +--- + +## Design System + +| Property | Value | +|----------|-------| +| Tool | none (no shadcn; Vue 3 SPA with its own Direction C system; the shadcn gate does not apply to a Vue codebase with an established system, same ruling as Phases 10.1, 12.1, 12.2) | +| Preset | not applicable | +| Component library | Reka UI 2.9.10 (headless primitives, the Vue port of Radix) plus the local components in `admin/src/components/` | +| Icon library | `@lucide/vue` 1.17.0 (named imports only; 16px in buttons and inline notices, 14px in error and note lines, 20px in the empty-state circle) | +| Font | DM Sans 400/600/700 (self-hosted `@fontsource/dm-sans`); DM Mono 400 (`font-mono`) for the token plaintext, the token hint and scope names; `tabular-nums` on every date and time | + +--- + +## Component Inventory + +Enumerated by `node --input-type=module -e "import * as r from 'reka-ui'; console.log(Object.keys(r).length)"` (run in `admin/`) — 478 exports — reka-ui@2.9.10 — 2026-10-10. +Local components enumerated by `find admin/src/components -name '*.vue' | wc -l` — 51 components — summercms-admin@unversioned (private workspace package with no `version` field in `admin/package.json`; pinned by repo commit a04ba35) — 2026-10-10. + +Icon presence verified by `node --input-type=module -e "import * as l from '@lucide/vue'; ..."` (run in `admin/`) against @lucide/vue@1.17.0 on 2026-10-10: `Plus`, `KeyRound`, `Copy`, `Check`, `Ban`, `TriangleAlert`, `CircleAlert`, `Info`, `LoaderCircle`, `X` all exported. + +This table is a **non-exhaustive** list of known-good components for this phase, not a closed allowlist. Checking for another Reka export or local component is the expected path. + +| Component | Import path | Notes | +|-----------|-------------|-------| +| DialogRoot, DialogPortal, DialogOverlay, DialogContent, DialogTitle, DialogDescription, DialogClose | `reka-ui` | The create-token dialog (both steps). Same parts and classes as `RelationPickerModal.vue`. Verified exported. | +| RadioGroupRoot, RadioGroupItem | `reka-ui` | The expiry segmented control. Same segmented classes as `PermissionEditorField.vue`. Verified exported. | +| AlertDialog* via ConfirmDialog | `admin/src/components/ui/ConfirmDialog.vue` + `useConfirm` from `confirm.ts` | Revoke confirmation and the "close without copying" confirmation. `AccountView` already mounts one `ConfirmDialog` bound to `useConfirm()`; reuse that instance. Do not build a second confirm. | +| Button | `admin/src/components/ui/Button.vue` | Every text button. Variants `primary` / `outline` / `ghost` / `danger`, sizes `md` (42px) / `sm` (38px). | +| `controlClass` | `admin/src/components/form/control.ts` | Border, background and padding of the name input and the plaintext input. | +| `fieldErrors`, `focusField` | `admin/src/components/form/formState.ts` | Map a 422 `error.details` to field errors and focus the first invalid field (same as `AccountView.onSave`). | +| `showToast` | `admin/src/state/useToasts.ts` | `success` toast after revoke; `danger` toasts for failures. Never carries the plaintext. | +| `t`, `tc` | `admin/src/app/i18n.ts` | All copy. No literal UI strings in the component. | +| `api` (openapi-fetch client) | `admin/src/api/client.ts` | Typed calls to the D-18 endpoints after `npm run gen:api` regenerates `schema.d.ts`; keeps the 401 refresh-and-replay. | +| Native checkbox (`size-5 shrink-0 rounded-[6px] accent-primary`) | class string from `CheckboxField.vue` | Scope picker rows and the "Show revoked tokens" toggle. Not a field-registry control: used directly. | + +New code this phase creates (planner names the file): one component, suggested `admin/src/components/account/ApiTokensPanel.vue` (card, list, revoke), with the dialog either inline or in a sibling `ApiTokenCreateDialog.vue`. `AccountView.vue` renders the panel after the profile/password `