docs(08): UI design contract — useToastStore API and sessions-revoked spec
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
6f2dba8478
commit
845b681b36
@@ -0,0 +1,227 @@
|
||||
---
|
||||
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 5–25, 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
|
||||
Reference in New Issue
Block a user