Moves phases 08–11 execution artifacts from .planning/phases/ to .planning/milestones/v0.2-phases/ to keep .planning/phases/ clean for the next milestone. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8.7 KiB
phase, slug, status, shadcn_initialized, preset, created, reviewed_at
| phase | slug | status | shadcn_initialized | preset | created | reviewed_at |
|---|---|---|---|---|---|---|
| 8 | stack-upgrade-backend-decomposition | approved | false | none | 2026-06-07 | 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:
- sessions-revoked notification —
SettingsAccountTab.vueandTotpEnrollment.vuealready display inline session-revoked feedback using localrefstate. Phase 8 (Wave 1 — Phase 7.1 absorbed) replaces that local state with a call totoastStore.show(...). useToastStorestub — a new Pinia store with a definedshow()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
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
useToastStoreas a named export fromstores/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: approved 2026-06-07