--- phase: 10-ux-interaction plan: 04 type: execute wave: 0 depends_on: [] files_modified: - frontend/src/stores/toast.js - frontend/src/stores/__tests__/toast.test.js - frontend/src/components/ui/ToastContainer.vue - frontend/src/components/ui/__tests__/ToastContainer.test.js - frontend/src/App.vue autonomous: true requirements: [UX-10] must_haves: truths: - "Calling useToastStore().show('msg', 'success', 4000) appends a toast and auto-dismisses after the duration" - "Calling dismiss(id) removes that toast from the array immediately" - "Existing Phase 8 call sites (SettingsAccountTab, TotpEnrollment) work unchanged with the locked signature show(message, type, duration)" - "ToastContainer renders one DOM node per toast, teleported to body, with bottom-right positioning" - "ToastContainer is mounted at App.vue and visible across all routes (including admin layout)" artifacts: - path: "frontend/src/stores/toast.js" provides: "Reactive toast store with toasts array + show + dismiss" contains: "toasts" - path: "frontend/src/components/ui/ToastContainer.vue" provides: "Visual renderer for the toast stack" - path: "frontend/src/stores/__tests__/toast.test.js" provides: "Toast store tests (timers + signature)" - path: "frontend/src/components/ui/__tests__/ToastContainer.test.js" provides: "ToastContainer rendering tests" key_links: - from: "App.vue" to: "ToastContainer.vue" via: " mounted alongside " pattern: " Implement the toast notification system (per D-01..D-04): reactive store, visual container, and mount in App.vue. This replaces the Phase 8 no-op stub WITHOUT changing the locked `show(message, type, duration)` signature so that Phase 8 call sites (`SettingsAccountTab.vue`, `TotpEnrollment.vue`) continue to work. Purpose: Wire UX-10 (toast notification system). Phase 8 only stubbed the store; Phase 10 makes toasts actually visible. Toasts appear bottom-right, stack upward (D-01), use colored left-border accent + inline icon per type (D-02), are teleported to body (D-03). Output: - `stores/toast.js` — reactive `toasts` array, `show` action (auto-dismiss via setTimeout), `dismiss` action - `components/ui/ToastContainer.vue` — Teleport-to-body container with TransitionGroup - `App.vue` — mount `` - Vitest tests for both store and container @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @CLAUDE.md @.planning/phases/10-ux-interaction/10-CONTEXT.md @.planning/phases/10-ux-interaction/10-RESEARCH.md @.planning/phases/10-ux-interaction/10-PATTERNS.md @frontend/src/stores/toast.js @frontend/src/App.vue @frontend/src/components/ui/SearchableModelSelect.vue @frontend/src/components/settings/SettingsAccountTab.vue @frontend/src/components/auth/TotpEnrollment.vue **Toast store (setup-store form — D-04 LOCKED signature):** ```js import { ref } from 'vue' import { defineStore } from 'pinia' export const useToastStore = defineStore('toast', () => { const toasts = ref([]) // [{ id, message, type, duration }] function show(message, type = 'success', duration = 4000) { const id = Date.now() + Math.random() toasts.value.push({ id, message, type, duration }) if (duration > 0) setTimeout(() => dismiss(id), duration) } function dismiss(id) { toasts.value = toasts.value.filter(t => t.id !== id) } return { toasts, show, dismiss } }) ``` **Type → visual classes (D-02):** | type | accent (left-bar) | icon name | icon color | |------|-------------------|-----------|------------| | success | bg-green-500 | checkCircle | text-green-500 | | error | bg-red-500 | exclamationCircle | text-red-500 | | warning | bg-amber-400 | warning | text-amber-500 | | info | bg-sky-400 | exclamationCircle | text-sky-500 | **Container layout (D-01, D-03):** - Teleport target: `to="body"` - Wrapper: `fixed bottom-4 right-4 z-[9999] flex flex-col-reverse gap-2 pointer-events-none` - Each toast: `pointer-events-auto flex items-center gap-3 bg-white rounded-xl shadow-lg border border-gray-100 overflow-hidden max-w-sm min-w-[280px]` with `@click="dismiss(toast.id)"` **App.vue mount point (current state):** - App.vue uses `