Files
kite/.planning/phases/08-stack-upgrade-backend-decomposition/08-UI-SPEC.md
T

228 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 8
slug: stack-upgrade-backend-decomposition
status: draft
shadcn_initialized: false
preset: none
created: 2026-06-07
---
# Phase 8 — UI Design Contract
> Visual and interaction contract for Phase 8: Stack Upgrade & Backend Decomposition.
> Generated by gsd-ui-researcher. Verified by gsd-ui-checker.
## Scope Note
Phase 8 is a refactoring phase. The backend router decomposition, frontend API client
decomposition, and dependency bumps are invisible to users. The ONLY user-facing UI
surface in this phase is:
1. **sessions-revoked notification**`SettingsAccountTab.vue` and `TotpEnrollment.vue`
already display inline session-revoked feedback using local `ref` state. Phase 8 (Wave 1 —
Phase 7.1 absorbed) replaces that local state with a call to `toastStore.show(...)`.
2. **`useToastStore` stub** — a new Pinia store with a defined `show()` API contract that
Phase 10 will implement fully. The stub must not render anything; it only defines the call
contract so Phase 7.1 call sites and Phase 10 implementation are aligned.
All other design contract sections (spacing, typography, color) document the **existing
design system** inherited by Phase 8. No new visual patterns are introduced.
---
## Design System
| Property | Value |
|----------|-------|
| Tool | none — Tailwind CSS utility classes only |
| Preset | not applicable |
| Component library | none (hand-rolled Vue 3 components) |
| Icon library | Heroicons — inline SVG paths (`stroke="currentColor"`, `stroke-width="1.5"` or `"2"`) |
| Font | system-ui / browser default (no custom font loaded) |
Source: `frontend/tailwind.config.js` (no plugins, no theme extensions), `App.vue`, existing component audit.
---
## Spacing Scale
Declared values (must be multiples of 4):
| Token | Value | Usage |
|-------|-------|-------|
| xs | 4px | Icon gaps (`gap-1`, `gap-1.5`), badge inner padding |
| sm | 8px | Compact element spacing (`gap-2`, `mb-1`, `py-2`) |
| md | 16px | Default element spacing (`space-y-4`, `gap-4`, `px-3 py-3` inputs) |
| lg | 24px | Section padding (`p-6`), section gaps (`space-y-6`) |
| xl | 32px | Layout gaps (sidebar + main content separation) |
| 2xl | 48px | Not actively used in Phase 8 scope |
| 3xl | 64px | Not actively used in Phase 8 scope |
Exceptions: none for Phase 8 scope.
Source: `SettingsAccountTab.vue` (`p-6`, `space-y-6`, `space-y-4`, `gap-3`), `TotpEnrollment.vue` (`space-y-4`, `gap-2`, `px-6 py-2.5`).
---
## Typography
| Role | Size | Weight | Line Height |
|------|------|--------|-------------|
| Body | 14px (`text-sm`) | 400 (regular) | 1.5 |
| Label / caption | 12px (`text-xs`) | 400 (regular) | 1.5 |
| Section heading | 14px (`text-sm`) | 600 (`font-semibold`) | 1.2 |
| Form label | 14px (`text-sm`) | 600 (`font-semibold`) | 1.2 |
Note: Phase 8 introduces no new typography. The system uses exactly 2 sizes (14px, 12px)
and exactly 2 weights (400, 600) for the settings/auth component surface touched in Wave 1.
Source: `SettingsAccountTab.vue` (`text-sm font-semibold text-gray-800` for headings,
`text-sm text-gray-600/700` for body, `text-xs text-red-600` for error captions).
---
## Color
| Role | Value | Usage |
|------|-------|-------|
| Dominant (60%) | `#ffffff` / `bg-white` | Page background, card surfaces |
| Secondary (30%) | `#f9fafb` / `bg-gray-50` | Input backgrounds, code blocks, secondary surfaces |
| Accent (10%) | `#4f46e5` / `bg-indigo-600` | Primary action buttons, focus rings (`focus:ring-indigo-500`) |
| Success semantic | `#16a34a` / `text-green-600`, `border-green-200` | Session-revoked notification, success confirmations |
| Destructive | `#dc2626` / `text-red-600`, `border-red-300` | Destructive action buttons ("Disable 2FA", "Sign out all devices"), error states |
Accent (`indigo-600`) reserved for:
- Primary submit buttons (`bg-indigo-600 hover:bg-indigo-700`)
- Input focus rings (`focus:ring-indigo-500 focus:border-indigo-500`)
- Role badge for admin users (`bg-indigo-100 text-indigo-700`)
Source: `SettingsAccountTab.vue`, `TotpEnrollment.vue` — exhaustive class audit.
---
## `useToastStore` API Contract
This is the primary design deliverable for Phase 8 (Wave 1). The stub Pinia store must
define and export exactly this `show()` signature. Phase 10 will implement the rendering.
### Store location
`frontend/src/stores/toast.js`
### `show()` method signature
```js
toastStore.show(message, type = 'success', duration = 4000)
```
| Parameter | Type | Values | Default | Notes |
|-----------|------|--------|---------|-------|
| `message` | `string` | Any non-empty string | required | Plain text only — no HTML |
| `type` | `string` | `'success'` \| `'error'` \| `'info'` | `'success'` | Controls icon and border color in Phase 10 |
| `duration` | `number` | Milliseconds until auto-dismiss | `4000` | `0` = persist until manually dismissed (Phase 10 contract) |
### Stub implementation contract
The stub MUST:
- Export `useToastStore` as a named export from `stores/toast.js`
- Expose `show(message, type, duration)` as a callable method
- NOT throw, NOT warn, NOT render anything — silently no-op in Phase 8
The stub MUST NOT:
- Accept an object argument shape (e.g. `show({ message, type })`) — positional parameters only, for simplicity
- Render a DOM element or inject CSS
- Import or depend on any component
### Phase 10 rendering contract (locked now to align implementor)
When Phase 10 implements the full store, it MUST honor the same `show()` signature without
modification to any Phase 8 call site. The rendering target is a fixed-positioned stack at
`top-4 right-4 z-50` (matching the existing inline toast placement in `SettingsAccountTab.vue`).
Auto-dismiss fires after `duration` ms. Manual dismiss on click. Toasts stack vertically with
`gap-2` between items. No interaction blocking.
---
## Sessions-Revoked Notification — Interaction Contract
### Current state (before Phase 8 Wave 1)
Both `SettingsAccountTab.vue` and `TotpEnrollment.vue` implement sessions-revoked feedback
with identical local state: `const sessionRevokedToast = ref(false)` + `setTimeout(..., 5000)`.
### Target state (after Phase 8 Wave 1)
The local `sessionRevokedToast` ref and `setTimeout` are removed from both components.
The API response handler calls `toastStore.show(...)` instead.
### Trigger conditions
| Component | Trigger | Call |
|-----------|---------|------|
| `SettingsAccountTab.vue``changePassword()` | `data.sessions_revoked > 0` | `toastStore.show('Other sessions have been terminated.', 'success')` |
| `SettingsAccountTab.vue``disableTotp()` | `data.sessions_revoked > 0` | `toastStore.show('Other sessions have been terminated.', 'success')` |
| `TotpEnrollment.vue``confirmEnrollment()` | `data.sessions_revoked > 0` | `toastStore.show('Other sessions have been terminated.', 'success')` |
### Message copy (locked)
`'Other sessions have been terminated.'`
This exact string is used in all three call sites. It matches the copy already displayed
by the existing inline implementation (confirmed in both component files).
### Notification visual spec (Phase 10 will render; locked here for alignment)
| Property | Value |
|----------|-------|
| Position | Fixed, `top-4 right-4`, `z-50` |
| Background | `bg-white` |
| Border | `border border-green-200` |
| Border radius | `rounded-xl` |
| Shadow | `shadow-lg` |
| Padding | `px-5 py-4` |
| Max width | `max-w-sm` |
| Icon | Heroicons `check-circle` (outline, `w-5 h-5 text-green-500`) |
| Text | `text-sm font-semibold text-gray-900` |
| Dismiss button | `text-gray-400 hover:text-gray-600`, Heroicons `x-mark` `w-4 h-4` |
| Auto-dismiss | After 4000ms (using `duration` param default) |
Source: existing inline implementation in `SettingsAccountTab.vue` lines 525, adapted to
use the `duration` param default of 4000ms instead of the current hardcoded 5000ms.
---
## Copywriting Contract
| Element | Copy |
|---------|------|
| Sessions-revoked notification | `Other sessions have been terminated.` |
| Toast dismiss aria-label | `Dismiss notification` |
No other new user-facing copy is introduced in Phase 8. All other interactions (backend
decomposition, client.js refactor, dependency bumps) are invisible to the user.
---
## Registry Safety
| Registry | Blocks Used | Safety Gate |
|----------|-------------|-------------|
| shadcn official | none — not initialized | not applicable |
| Third-party | none | not applicable |
No third-party component registries are used. No new UI components are added beyond the
`useToastStore` stub (a store, not a component).
---
## Checker Sign-Off
- [ ] Dimension 1 Copywriting: PASS
- [ ] Dimension 2 Visuals: PASS
- [ ] Dimension 3 Color: PASS
- [ ] Dimension 4 Typography: PASS
- [ ] Dimension 5 Spacing: PASS
- [ ] Dimension 6 Registry Safety: PASS
**Approval:** pending