# Dashboard Password Reset Flow ## TL;DR > **Summary**: Add an authenticated password-change flow reachable from the dashboard, with the actual form hosted on a protected `/settings` page that follows the repo's existing server-page plus client-component auth pattern. > **Deliverables**: > - Dashboard entry affordance to account security settings > - Protected `/settings` page with localized password-change UI > - Better Auth `changePassword` integration with explicit success/error handling > - Agent-executable manual QA evidence for auth protection, validation, and credential rotation > **Effort**: Medium > **Parallel**: YES - 2 waves > **Critical Path**: 1 -> 3 -> 4 -> 5 -> 6 ## Context ### Original Request Implement a user-facing password reset flow from `src/app/dashboard/page.tsx`. ### Interview Summary - User selected the authenticated in-session flow, not forgot-password by email. - Dashboard is the entry point, but the actual form should live on `/settings`. - Manual QA only for now; no test infrastructure setup in this slice. ### Metis Review (gaps addressed) - Protect `/settings` explicitly instead of assuming auth. - Keep scope to password/security only; do not expand into general settings architecture. - Use Better Auth's existing `changePassword` flow instead of inventing custom backend plumbing. - Define concrete browser QA with fixed credentials and explicit redirect/session assertions. ## Work Objectives ### Core Objective Provide a secure, authenticated password-change experience that users can reach from the dashboard and complete on a dedicated settings page. ### Deliverables - Auth-protected `src/app/settings/page.tsx` - Dashboard entry UI from `src/app/dashboard/page.tsx` to `/settings` - Localized client password-change component under `src/components/` - Better Auth client submission flow with `revokeOtherSessions: true` - Updated translation files for all new strings - QA evidence captured for happy path and failure cases ### Definition of Done (verifiable conditions with commands) - `pnpm lint` completes successfully - `pnpm build` completes successfully - Visiting `/settings` while unauthenticated redirects to sign-in with a callback back to `/settings` - A signed-in user can open `/settings`, submit a valid current/new password pair, and subsequently sign in with only the new password - Wrong current password and mismatched confirmation both fail with clear user feedback ### Must Have - Protected `/settings` route with explicit redirect behavior - Dashboard affordance linking to `/settings` - Fields for `currentPassword`, `newPassword`, and `confirmPassword` - Client validation for required fields, confirmation match, and rejecting `newPassword === currentPassword` - Backend-driven password validation through Better Auth's configured policies, including HIBP plugin - Success/error feedback and loading state consistent with existing auth UI - New strings added to both `messages/en.json` and `messages/pl.json` ### Must NOT Have (guardrails, AI slop patterns, scope boundaries) - No forgot-password email flow, token flow, reset page, or mail templates - No full settings IA, profile editor, preferences, or navigation redesign - No new test framework, Playwright project, or CI setup in this slice - No custom Convex mutation/server action for password change unless Better Auth client API proves unusable during execution - No route-protection behavior left implicit or undocumented ## Verification Strategy > ZERO HUMAN INTERVENTION - all verification is agent-executed. - Test decision: none + existing repo has no test framework; use browser-driven QA and build/lint verification - QA policy: Every task includes agent-executed scenarios with exact credentials, selectors, and expected outcomes - Evidence: `.sisyphus/evidence/task-{N}-{slug}.{ext}` ## Execution Strategy ### Parallel Execution Waves > Target: 5-8 tasks per wave. <3 per wave (except final) = under-splitting. > Extract shared dependencies as Wave-1 tasks for max parallelism. Wave 1: 1) route protection and settings page scaffold, 2) dashboard entry affordance, 3) settings UI shell + translations Wave 2: 4) client validation and field UX, 5) Better Auth submission + session behavior, 6) polish and end-to-end manual QA capture ### Dependency Matrix (full, all tasks) - 1 blocks 4, 5, 6 - 2 is independent after route constants are confirmed; feeds 6 QA coverage - 3 blocks 4 and 5 - 4 blocks 5 and 6 - 5 blocks 6 - 6 blocks final verification wave ### Agent Dispatch Summary (wave -> task count -> categories) - Wave 1 -> 3 tasks -> unspecified-high, visual-engineering, visual-engineering - Wave 2 -> 3 tasks -> quick, unspecified-high, unspecified-high ## TODOs > Implementation + Test = ONE task. Never separate. > EVERY task MUST have: Agent Profile + Parallelization + QA Scenarios. - [ ] 1. Add protected `/settings` page scaffold **What to do**: Read the relevant Next.js App Router docs under `node_modules/next/dist/docs/` before coding, then create `src/app/settings/page.tsx` as a server page that checks authentication with the existing server auth helpers from `src/lib/auth-server.ts`. If unauthenticated, redirect to `/sign-in?callbackURL=/settings`. If authenticated, render a dedicated client settings/password component and keep the page focused on security only. **Must NOT do**: Do not implement forgot-password here. Do not place the full form directly in `src/app/settings/page.tsx`. Do not create middleware or a broader settings layout in this slice. **Recommended Agent Profile**: - Category: `unspecified-high` - Reason: auth-aware App Router work with redirect behavior and server/client boundary decisions - Skills: `[]` - No special skill required beyond repo pattern matching - Omitted: `playwright` - UI automation belongs in QA, not scaffolding **Parallelization**: Can Parallel: YES | Wave 1 | Blocks: 4, 5, 6 | Blocked By: none **References** (executor has NO interview context - be exhaustive): - Pattern: `src/app/sign-in/page.tsx` - thin server page wrapper pattern already used for auth routes - Pattern: `src/app/sign-up/page.tsx` - same page composition pattern for route-level wrappers - Auth helper: `src/lib/auth-server.ts` - server-side auth utilities available for checking session/auth state - Route contract: `src/lib/routes.ts` - `/settings` already exists as a private route constant - Existing target: `src/app/dashboard/page.tsx` - current dashboard stub that will link into this route - External: `node_modules/next/dist/docs/` - project instruction requires reading relevant Next.js docs before implementation **Acceptance Criteria** (agent-executable only): - [ ] `src/app/settings/page.tsx` exists and remains a server page wrapper rather than a client-heavy file - [ ] Unauthenticated access to `/settings` redirects to `/sign-in?callbackURL=/settings` - [ ] Authenticated access to `/settings` renders the dedicated password settings component - [ ] `pnpm lint` passes after the page scaffold is added **QA Scenarios** (MANDATORY - task incomplete without these): ``` Scenario: Unauthenticated redirect from settings Tool: Playwright Steps: Open a fresh browser context; visit http://localhost:3000/settings directly. Expected: Browser lands on `/sign-in` and preserves a callback URL containing `/settings`. Evidence: .sisyphus/evidence/task-1-settings-redirect.png Scenario: Authenticated settings page render Tool: Playwright Steps: Sign up `changeflow@example.com` with password `OldPass123!`; sign in; visit http://localhost:3000/settings. Expected: The page renders a password/security card instead of redirecting away. Evidence: .sisyphus/evidence/task-1-settings-render.png ``` **Commit**: YES | Message: `add protected settings page scaffold and dashboard entry` | Files: `src/app/settings/page.tsx`, `src/lib/routes.ts` (only if needed), related imports - [ ] 2. Add dashboard entry to account security **What to do**: Replace the dashboard stub with a minimal, intentional dashboard card or section that exposes a single clear affordance to account security settings. Link to `/settings` via existing route constants. Keep the page intentionally narrow: one CTA, one short description, no full settings UI embedded here. **Must NOT do**: Do not turn dashboard into a general settings page. Do not add unrelated profile, billing, or preferences UI. **Recommended Agent Profile**: - Category: `visual-engineering` - Reason: small UI composition task that must feel deliberate, not boilerplate - Skills: `[]` - Existing component library provides all primitives needed - Omitted: `playwright` - verification happens via QA scenario, not implementation **Parallelization**: Can Parallel: YES | Wave 1 | Blocks: 6 QA coverage only | Blocked By: none **References** (executor has NO interview context - be exhaustive): - Target page: `src/app/dashboard/page.tsx` - currently only a stub and should become the entry point - Route contract: `src/lib/routes.ts` - use the existing private route constant for settings - UI pattern: `src/components/ui/card.tsx` - use existing card primitives for a compact dashboard section - UI pattern: `src/components/ui/button.tsx` - use existing button variants for the CTA **Acceptance Criteria** (agent-executable only): - [ ] `/dashboard` contains a visible CTA linking to `/settings` - [ ] The CTA uses route constants rather than a duplicated hard-coded path - [ ] The dashboard change stays focused on password/security entry only - [ ] `pnpm lint` passes after the dashboard update **QA Scenarios** (MANDATORY - task incomplete without these): ``` Scenario: Dashboard exposes security entry Tool: Playwright Steps: Sign in as `changeflow@example.com` with `OldPass123!`; open http://localhost:3000/dashboard; inspect the visible security/settings CTA. Expected: Clicking the CTA navigates to `/settings`. Evidence: .sisyphus/evidence/task-2-dashboard-entry.png Scenario: Dashboard does not embed the password form Tool: Playwright Steps: Load http://localhost:3000/dashboard while signed in. Expected: No `currentPassword`, `newPassword`, or `confirmPassword` input fields are present on the dashboard page itself. Evidence: .sisyphus/evidence/task-2-dashboard-no-form.png ``` **Commit**: YES | Message: `add protected settings page scaffold and dashboard entry` | Files: `src/app/dashboard/page.tsx` - [ ] 3. Create the localized password settings component shell **What to do**: Create a dedicated client component for password change under `src/components/settings/` and wire it into `src/app/settings/page.tsx`. Use the same card, field, button, spinner, and password input-group patterns already used in `src/components/auth/AuthForm.tsx`. Add all new translation keys to both locale files up front so no hard-coded strings remain. **Must NOT do**: Do not reuse `AuthForm` directly for password change. Do not leave untranslated labels, helper text, button copy, or toast messages. **Recommended Agent Profile**: - Category: `visual-engineering` - Reason: new client UI should blend with existing auth UI while staying scoped - Skills: `[]` - Existing component system is sufficient - Omitted: `writing` - copy additions are straightforward translation entries, not long-form docs **Parallelization**: Can Parallel: YES | Wave 1 | Blocks: 4, 5, 6 | Blocked By: 1 **References** (executor has NO interview context - be exhaustive): - Pattern: `src/components/auth/AuthForm.tsx` - existing client auth form conventions for card layout, field composition, spinner, toast, and password visibility controls - UI primitives: `src/components/ui/field.tsx`, `src/components/ui/input-group.tsx`, `src/components/ui/button.tsx`, `src/components/ui/spinner.tsx` - Translation files: `messages/en.json`, `messages/pl.json` - extend with a dedicated settings/security namespace or equivalent flat keys - Existing i18n usage: `src/components/auth/AuthForm.tsx` - `useTranslations(...)` pattern **Acceptance Criteria** (agent-executable only): - [ ] A dedicated client component exists under `src/components/settings/` - [ ] `/settings` renders a single password/security card with localized heading, description, and button text - [ ] Both `messages/en.json` and `messages/pl.json` contain all strings needed for the new UI - [ ] No new hard-coded user-facing strings remain in the component **QA Scenarios** (MANDATORY - task incomplete without these): ``` Scenario: Password settings shell renders expected fields Tool: Playwright Steps: Sign in; visit http://localhost:3000/settings; inspect the form. Expected: Inputs named `currentPassword`, `newPassword`, and `confirmPassword` are visible, plus a submit button. Evidence: .sisyphus/evidence/task-3-settings-shell.png Scenario: Localized shell does not regress rendering Tool: Playwright Steps: Load the page in the default locale; inspect heading, field labels, and submit button. Expected: All visible strings are rendered from translation data with no raw key names or empty labels. Evidence: .sisyphus/evidence/task-3-settings-i18n.png ``` **Commit**: YES | Message: `add password change form UI and localization` | Files: `src/components/settings/PasswordChangeCard.tsx`, `src/app/settings/page.tsx`, `messages/en.json`, `messages/pl.json` - [ ] 4. Implement client-side validation and field UX **What to do**: Define a dedicated Zod schema for the password-change form that requires `currentPassword`, reuses `defaultPasswordValidator()` for `newPassword`, enforces `confirmPassword === newPassword`, and rejects `newPassword === currentPassword`. Provide inline field errors and disable duplicate submissions with a loading state. Keep password visibility UX consistent with the existing auth form pattern. **Must NOT do**: Do not add uppercase/number/special-character composition rules back into the client. Do not rely only on toasts for validation errors that belong inline. **Recommended Agent Profile**: - Category: `quick` - Reason: bounded form-schema and field-state work inside one component - Skills: `[]` - Existing validation patterns are enough - Omitted: `ultrabrain` - no deep algorithmic work needed **Parallelization**: Can Parallel: NO | Wave 2 | Blocks: 5, 6 | Blocked By: 1, 3 **References** (executor has NO interview context - be exhaustive): - Validation helper: `src/constants.ts` - current shared minimum-length validator to reuse for `newPassword` - Pattern: `src/components/auth/AuthForm.tsx` - React Hook Form + Zod resolver usage and inline `FieldError` handling - UI primitives: `src/components/ui/field.tsx`, `src/components/ui/input-group.tsx`, `src/components/ui/spinner.tsx` **Acceptance Criteria** (agent-executable only): - [ ] Submitting mismatched `newPassword` and `confirmPassword` surfaces an inline error on the confirmation field - [ ] Submitting the same value for current and new password surfaces an inline error before any network request - [ ] Submitting an empty required field surfaces inline validation without a success toast - [ ] Submit button disables while the request is pending **QA Scenarios** (MANDATORY - task incomplete without these): ``` Scenario: Mismatched confirmation is blocked locally Tool: Playwright Steps: Sign in; open `/settings`; fill `currentPassword=OldPass123!`, `newPassword=NewPass123!`, `confirmPassword=Mismatch123!`; submit. Expected: Inline error appears for `confirmPassword`; no network-driven success state is shown. Evidence: .sisyphus/evidence/task-4-confirm-mismatch.png Scenario: Reusing the same password is blocked locally Tool: Playwright Steps: Fill `currentPassword=OldPass123!`, `newPassword=OldPass123!`, `confirmPassword=OldPass123!`; submit. Expected: Inline error indicates the new password must differ from the current password. Evidence: .sisyphus/evidence/task-4-same-password.png ``` **Commit**: YES | Message: `add password change form UI and localization` | Files: `src/components/settings/PasswordChangeCard.tsx`, related validation helpers only if needed - [ ] 5. Wire Better Auth password change submission **What to do**: Connect the settings form to Better Auth's client password-change API using the authenticated session. Submit `currentPassword`, `newPassword`, and `revokeOtherSessions: true`. On success, stay on `/settings`, clear sensitive fields, and show a localized success toast. On failure, surface the returned error cleanly without clearing the current user session. **Must NOT do**: Do not create a custom Convex mutation for password change unless the Better Auth client call is proven unusable. Do not redirect away from settings after success. Do not silently swallow backend errors. **Recommended Agent Profile**: - Category: `unspecified-high` - Reason: auth-sensitive submission flow with session behavior and backend error handling - Skills: `[]` - Existing Better Auth client is already configured in repo - Omitted: `writing` - this is behavior wiring, not docs work **Parallelization**: Can Parallel: NO | Wave 2 | Blocks: 6 | Blocked By: 1, 3, 4 **References** (executor has NO interview context - be exhaustive): - Client auth entry: `src/lib/auth-client.ts` - Better Auth client already configured with Convex plugin - Existing auth pattern: `src/components/auth/AuthForm.tsx` - request pending state, toast handling, Better Auth client invocation patterns - Backend policy: `convex/auth.ts` - Better Auth email/password configuration with HIBP plugin and min/max length - Settings component: `src/components/settings/PasswordChangeCard.tsx` - task 3/4 output **Acceptance Criteria** (agent-executable only): - [ ] Valid submission calls Better Auth password-change API and succeeds for the signed-in user - [ ] Wrong current password yields a visible error state without logging the user out of the current session - [ ] Successful submission clears all password inputs and leaves the user on `/settings` - [ ] `pnpm build` passes after the integration is complete **QA Scenarios** (MANDATORY - task incomplete without these): ``` Scenario: Wrong current password is rejected Tool: Playwright Steps: Sign in; open `/settings`; fill `currentPassword=WrongPass123!`, `newPassword=NewPass123!`, `confirmPassword=NewPass123!`; submit. Expected: Error feedback appears; the current session remains usable; revisiting `/dashboard` still works in the same tab. Evidence: .sisyphus/evidence/task-5-wrong-current-password.png Scenario: Valid password change succeeds Tool: Playwright Steps: Fill `currentPassword=OldPass123!`, `newPassword=NewPass123!`, `confirmPassword=NewPass123!`; submit. Expected: Success toast/message appears; fields clear; page remains on `/settings`. Evidence: .sisyphus/evidence/task-5-successful-change.png ``` **Commit**: YES | Message: `wire password change flow and verify session behavior` | Files: `src/components/settings/PasswordChangeCard.tsx`, any minimal auth client touch-ups only if required - [ ] 6. Finalize credential-rotation behavior and capture manual QA evidence **What to do**: Validate the full user journey end to end: dashboard -> settings -> change password -> sign out -> sign back in with the new credential. Because `revokeOtherSessions: true` is the default for this slice, also verify in a second browser context that another active session becomes unauthorized after the password change. Capture screenshots/logs into the evidence paths referenced below. **Must NOT do**: Do not add automated test infrastructure or CI in order to satisfy this task. Do not leave QA as a vague manual checklist. **Recommended Agent Profile**: - Category: `unspecified-high` - Reason: auth/stateful browser verification across two sessions - Skills: [`playwright`] - Use browser automation to validate auth and session transitions precisely - Omitted: `frontend-ui-ux` - this task is verification-focused, not design-focused **Parallelization**: Can Parallel: NO | Wave 2 | Blocks: Final verification wave | Blocked By: 1, 2, 3, 4, 5 **References** (executor has NO interview context - be exhaustive): - Entry page: `src/app/dashboard/page.tsx` - Protected page: `src/app/settings/page.tsx` - Form component: `src/components/settings/PasswordChangeCard.tsx` - Auth UI precedent: `src/components/auth/AuthForm.tsx` - Command surface: `package.json` - use existing `pnpm dev`, `pnpm lint`, and `pnpm build` **Acceptance Criteria** (agent-executable only): - [ ] After password change, signing in with `OldPass123!` fails and signing in with `NewPass123!` succeeds - [ ] A second active browser context is no longer authorized after the password change - [ ] Evidence files exist for redirect, validation failure, success state, old-password rejection, and second-session invalidation - [ ] `pnpm lint` and `pnpm build` both pass on the final implementation **QA Scenarios** (MANDATORY - task incomplete without these): ``` Scenario: Old password fails and new password succeeds Tool: Playwright Steps: Complete the password change; sign out; attempt sign-in with `OldPass123!`; then attempt sign-in with `NewPass123!`. Expected: Old password sign-in fails; new password sign-in succeeds and returns to an authenticated page. Evidence: .sisyphus/evidence/task-6-old-vs-new-credential.png Scenario: Other sessions are revoked Tool: Playwright Steps: Open session A and session B signed in as the same user; change the password in session A; in session B navigate to `/dashboard` or refresh. Expected: Session B is redirected to sign-in or otherwise loses authenticated access. Evidence: .sisyphus/evidence/task-6-revoke-other-sessions.png ``` **Commit**: YES | Message: `wire password change flow and verify session behavior` | Files: no net-new feature files expected beyond minimal polish; evidence output only ## Final Verification Wave (MANDATORY - after ALL implementation tasks) > 4 review agents run in PARALLEL. ALL must APPROVE. Present consolidated results to user and get explicit "okay" before completing. > **Do NOT auto-proceed after verification. Wait for user's explicit approval before marking work complete.** > **Never mark F1-F4 as checked before getting user's okay.** Rejection or user feedback -> fix -> re-run -> present again -> wait for okay. - [ ] F1. Plan Compliance Audit - oracle - [ ] F2. Code Quality Review - unspecified-high - [ ] F3. Real Manual QA - unspecified-high (+ playwright if UI) - [ ] F4. Scope Fidelity Check - deep ## Commit Strategy - Commit 1: `add protected settings page scaffold and dashboard entry` - Commit 2: `add password change form UI and localization` - Commit 3: `wire password change flow and verify session behavior` ## Success Criteria - The feature remains scoped to authenticated password change only - `/dashboard` clearly leads users into account security settings - `/settings` is inaccessible without authentication - Password change succeeds only with the correct current password and valid new password - QA evidence proves redirect, validation failure, success path, and new-credential sign-in behavior