--- phase: 08-stack-upgrade-backend-decomposition plan: 03 type: execute wave: 1 depends_on: [08-01] files_modified: - frontend/src/stores/toast.js - frontend/src/components/settings/SettingsAccountTab.vue - frontend/src/components/auth/TotpEnrollment.vue - backend/tests/test_auth.py autonomous: true requirements: [CR-01, CR-02, CR-03] tags: [phase-7.1, toast-stub, session-revocation, frontend] must_haves: truths: - "frontend/src/stores/toast.js exists, exports useToastStore, exposes show(message, type, duration)" - "show() is a silent no-op in this phase (Phase 10 will render); calling it never throws" - "SettingsAccountTab.vue calls toastStore.show('Other sessions have been terminated.', 'success') when data.sessions_revoked > 0 in both changePassword and disableTotp handlers" - "TotpEnrollment.vue calls toastStore.show('Other sessions have been terminated.', 'success') when data.sessions_revoked > 0 in confirmEnrollment handler" - "Inline sessionRevokedToast ref + setTimeout + inline toast template blocks are removed from both components" - "CR-01, CR-02, CR-03 tests run as plain passing tests (xfail decorator removed)" artifacts: - path: "frontend/src/stores/toast.js" provides: "Pinia toast store stub with the show() contract Phase 10 must honor" contains: "export const useToastStore" - path: "frontend/src/components/settings/SettingsAccountTab.vue" provides: "Refactored to use toastStore.show() in changePassword + disableTotp handlers" contains: "toastStore.show" - path: "frontend/src/components/auth/TotpEnrollment.vue" provides: "Refactored to use toastStore.show() in confirmEnrollment handler" contains: "toastStore.show" - path: "backend/tests/test_auth.py" provides: "Three session-revocation tests promoted from xfail to passing" contains: "test_change_password_revokes_other_sessions" key_links: - from: "SettingsAccountTab.vue changePassword handler" to: "useToastStore.show" via: "function call when data.sessions_revoked > 0" pattern: "toastStore\\.show\\(['\"]Other sessions have been terminated" - from: "TotpEnrollment.vue confirmEnrollment handler" to: "useToastStore.show" via: "function call when data.sessions_revoked > 0" pattern: "toastStore\\.show\\(['\"]Other sessions have been terminated" --- Complete the absorbed Phase 7.1 work. The backend code for CR-01/CR-02/CR-03 is already implemented in `backend/api/auth.py` (RESEARCH.md confirmed lines 518, 615, 662). This plan: (1) creates the `useToastStore` Pinia stub per the UI-SPEC.md contract, (2) replaces the inline `sessionRevokedToast` ref + `setTimeout` pattern in `SettingsAccountTab.vue` and `TotpEnrollment.vue` with `toastStore.show(...)` calls, (3) removes the inline toast HTML blocks from both components, and (4) promotes the three CR xfail stubs from plan 08-01 to passing tests. Purpose: Ship CR-01/CR-02/CR-03 to the user-visible behavior contract that the UI-SPEC.md locks (toast on `sessions_revoked > 0`), with the call signature Phase 10 must honor without modifying any Phase 8 call site. Output: New toast store, refactored two Vue components, promoted three backend tests. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/08-stack-upgrade-backend-decomposition/08-CONTEXT.md @.planning/phases/08-stack-upgrade-backend-decomposition/08-RESEARCH.md @.planning/phases/08-stack-upgrade-backend-decomposition/08-PATTERNS.md @.planning/phases/08-stack-upgrade-backend-decomposition/08-UI-SPEC.md @frontend/src/stores/topics.js @frontend/src/components/settings/SettingsAccountTab.vue @frontend/src/components/auth/TotpEnrollment.vue @backend/tests/test_auth.py toastStore.show(message: string, type: 'success'|'error'|'info' = 'success', duration: number = 4000): void SettingsAccountTab.vue / changePassword() → on data.sessions_revoked > 0 → toastStore.show('Other sessions have been terminated.', 'success') SettingsAccountTab.vue / disableTotp() → on data.sessions_revoked > 0 → toastStore.show('Other sessions have been terminated.', 'success') TotpEnrollment.vue / confirmEnrollment() → on data.sessions_revoked > 0 → toastStore.show('Other sessions have been terminated.', 'success') SettingsAccountTab.vue: lines 6, 17, 211, 226-227, 263-264 (sessionRevokedToast ref + setTimeout + inline HTML block) TotpEnrollment.vue: lines 6, 15, 149, 175-176 (sessionRevokedToast ref + setTimeout + inline HTML block) Task 1: Create useToastStore Pinia stub at frontend/src/stores/toast.js frontend/src/stores/toast.js - frontend/src/stores/topics.js (simplest existing Pinia store — copy the setup-store style with defineStore + named function exports) - .planning/phases/08-stack-upgrade-backend-decomposition/08-UI-SPEC.md §"useToastStore API Contract" (exact signature, default values, "MUST NOT" rules) - .planning/phases/08-stack-upgrade-backend-decomposition/08-PATTERNS.md §"frontend/src/stores/toast.js" (full stub template) - Importing `useToastStore` from `'../stores/toast.js'` resolves successfully - Calling `useToastStore().show('hello')` returns `undefined` and does not throw - Calling `useToastStore().show('hello', 'error')` returns `undefined` and does not throw - Calling `useToastStore().show('hello', 'success', 8000)` returns `undefined` and does not throw - The store does not render any DOM element (verified visually — Phase 10 implements rendering) Create `frontend/src/stores/toast.js`. Use the setup-store style (`defineStore('toast', () => { ... })`). Define a single function `show(message, type = 'success', duration = 4000)` with positional parameters only (the UI-SPEC explicitly forbids object-argument shape `show({message, type})`). The function body is empty (no-op stub). Return `{ show }` so the store exposes the method. Add a top-of-file docstring (JS comment block) stating: Phase 7.1 STUB — Phase 10 (UX-10) fills in the full implementation; the signature `show(message, type, duration)` is locked and Phase 10 must honor it without modifying Phase 8 call sites. Default values match UI-SPEC.md: `type = 'success'` (NOT `'info'`), `duration = 4000`. Export as named export: `export const useToastStore = defineStore(...)`. Do NOT import or depend on any component. cd frontend && node -e "import('./src/stores/toast.js').then(m => { const s = m.useToastStore; if (typeof s !== 'function') throw new Error('useToastStore not a function'); console.log('ok'); })" 2>&1 | tail -5 - File `frontend/src/stores/toast.js` exists - `grep -c "export const useToastStore" frontend/src/stores/toast.js` returns 1 - `grep -c "defineStore('toast'" frontend/src/stores/toast.js` returns 1 - `grep -c "function show(message, type = 'success', duration = 4000)" frontend/src/stores/toast.js` returns 1 - `cd frontend && npm test -- --run stores/toast 2>/dev/null || true` does not throw an import error - The file contains NO `import` of any Vue component or DOM API toast.js exists with the exact UI-SPEC signature; the module imports cleanly; show() is a silent no-op. Task 2: Refactor SettingsAccountTab.vue and TotpEnrollment.vue to use toastStore frontend/src/components/settings/SettingsAccountTab.vue, frontend/src/components/auth/TotpEnrollment.vue - frontend/src/components/settings/SettingsAccountTab.vue (full file — current inline toast HTML at lines 5-25, `sessionRevokedToast` ref at line 211, setTimeouts in changePassword at 225-228 and disableTotp at 261-264) - frontend/src/components/auth/TotpEnrollment.vue (full file — current inline toast HTML at lines 5-23, `sessionRevokedToast` ref at line 149, setTimeout in confirmEnrollment at 174-177) - frontend/src/stores/toast.js (the stub created in task 1) - .planning/phases/08-stack-upgrade-backend-decomposition/08-UI-SPEC.md §"Sessions-Revoked Notification — Interaction Contract" (message copy locked exactly: 'Other sessions have been terminated.') For `frontend/src/components/settings/SettingsAccountTab.vue`: 1. Delete the inline `
...
` template block (lines ~5-25 — the fixed-position toast). 2. Delete the line `const sessionRevokedToast = ref(false)` (~line 211). 3. In the `changePassword` handler (~lines 220-230), replace `sessionRevokedToast.value = true; setTimeout(() => { sessionRevokedToast.value = false }, 5000)` with `toastStore.show('Other sessions have been terminated.', 'success')`. Keep the surrounding `if (data.sessions_revoked > 0) { ... }` guard. 4. In the `disableTotp` handler (~lines 258-267), apply the same replacement. 5. Add `import { useToastStore } from '../../stores/toast.js'` at the top with the other imports. 6. Add `const toastStore = useToastStore()` near the other store/ref declarations in `