Initial Commit
This commit is contained in:
74
openspec/archive/2026-05-19-add-react-auth-pages/proposal.md
Normal file
74
openspec/archive/2026-05-19-add-react-auth-pages/proposal.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Add @suaveplan/react-auth — pages + PermissionGate (Wave 3)
|
||||
|
||||
**Package**: `@suaveplan/react-auth`
|
||||
**Status**: PROPOSED
|
||||
**Type**: feature
|
||||
|
||||
- **Spec delta**: ./specs/react-auth/spec.md
|
||||
|
||||
## Why
|
||||
|
||||
Wave 1 shipped the `@suaveplan/react-auth` core (client, provider, context, hooks, guard) and Wave 2 shipped the five pure-UI form components. Downstream apps still cannot drop the package into a `react-router-dom` app without re-implementing the routing-aware adapter layer on every page. Wave 3 closes the migration by shipping the six page components (`LoginPage`, `RegisterPage`, `ForgotPasswordPage`, `ResetPasswordPage`, `EmailVerificationPage`, `ProfilePage`) that wire Wave 2 forms to the Wave 1 `useAuth()` actions plus `useNavigate` / `useSearchParams` redirects, and the `PermissionGate` component that conditionally renders children against the Wave 1 `usePermission` hook. After this wave the public surface of `@suaveplan/react-auth` reaches full parity with the upstream `../suaveplan/packages/react/react-auth/` package and the migration is complete.
|
||||
|
||||
## What changes
|
||||
|
||||
- ADDED: `LoginPage` — wraps `<LoginForm>`, calls `useAuth().signIn`, redirects to `?returnTo` or `/dashboard` on success, applies `useAuthGuard({ requireGuest: true })`.
|
||||
- ADDED: `RegisterPage` — wraps `<RegisterForm>`, strips `confirmPassword`, calls `useAuth().signUp`, redirects to `/dashboard` on success, applies `useAuthGuard({ requireGuest: true })`.
|
||||
- ADDED: `ForgotPasswordPage` — wraps `<ForgotPasswordForm>`, calls `useAuth().forgotPassword(email)`, manages local success state + reset.
|
||||
- ADDED: `ResetPasswordPage` — wraps `<ResetPasswordForm>`, extracts `?token` from search params, calls `useAuth().resetPassword(token, password)`, manages success state, navigates to `/auth/login` and `/auth/forgot-password`, applies `useAuthGuard({ requireGuest: true })`.
|
||||
- ADDED: `EmailVerificationPage` — wraps `<EmailVerificationView>`, extracts `?token`, auto-runs `useAuth().verifyEmail(token)` on mount, classifies errors into `expired` vs `error`, applies `useAuthGuard({ requireGuest: true })`.
|
||||
- ADDED: `ProfilePage` — three-tab profile surface (profile / password / account) calling `useAuth().updateProfile`, `changePassword`, `deleteAccount`; applies `useAuthGuard({ requireAuth: true })`; uses `react-ui` Tabs/Card primitives + a new `profilePage` translation group.
|
||||
- ADDED: `PermissionGate` — renders children when `usePermission()` reports the configured bitmask permissions (any-of by default, all-of when `requireAll`); otherwise renders a fallback (default `null`).
|
||||
- ADDED: `AuthProfilePageStrings` interface + `profilePage` group on every locale pack (`enGb` / `cyGb` / `zhCn`) — purely additive, the existing Wave 1 `profile` group is left untouched.
|
||||
- ADDED: Subpath exports under `./pages/<page>` for every page and `./components/permission-gate` for the gate, mirroring the Wave 1 + Wave 2 per-symbol convention.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- MFA / passkey / magic-link flows — enterprise audit §4 enhancements, never part of upstream.
|
||||
- `RoleGuard` companion to `PermissionGate` — enterprise audit §4 enhancement.
|
||||
- Device-management / audit-trail pages — enterprise audit §4 enhancements.
|
||||
|
||||
## Source mapping
|
||||
|
||||
This change derives from `../suaveplan/enterprise/react-auth.md` and `../suaveplan/packages/react/react-auth/src/`.
|
||||
|
||||
| Requirement | Source lines | Notes |
|
||||
|---|---|---|
|
||||
| `LoginPage` shape | `pages/login/login.tsx:1-42` | Verbatim semantics; uses Wave 2 `<LoginForm>`. |
|
||||
| `RegisterPage` shape | `pages/register/register.tsx:1-41` | Verbatim semantics; strips `confirmPassword` before calling `signUp`. |
|
||||
| `ForgotPasswordPage` shape | `pages/forgot-password/forgot-password.tsx:1-43` | Verbatim semantics. |
|
||||
| `ResetPasswordPage` shape | `pages/reset-password/reset-password.tsx:1-61` | Verbatim semantics; `?token` from search params. |
|
||||
| `EmailVerificationPage` shape | `pages/email-verification/email-verification.tsx:1-96` | Verbatim semantics; upstream's `useTranslation` swapped for Wave 2 `translations` prop pattern (consistent with Wave 1 i18n decision). The dual `EmailVerification` legacy fork (lines 99-254) is NOT ported — it duplicated the `EmailVerificationView` which already shipped in Wave 2. |
|
||||
| `ProfilePage` shape | `pages/profile/profile.tsx:1-638` | Three-tab profile / password / account surface. Upstream's `useTranslation` swapped for a `translations` prop defaulting to `enGb.auth.profilePage`. The implementation is split into `profile-page.tsx` (shell) + `profile-page-sections.tsx` (three section renderers) to honour the 300-line hard cap (§3). |
|
||||
| `PermissionGate` shape | `components/permission-gate/permission-gate.tsx:1-28` | Verbatim semantics. |
|
||||
|
||||
### Requirements with no source line
|
||||
|
||||
| Requirement | Justification |
|
||||
|---|---|
|
||||
| `translations?: Partial<...>` prop on `EmailVerificationPage` and `ProfilePage` | Genesis-specific — Wave 1 dropped `@suaveplan/react-translate`; Wave 2 established the prop-injection precedent. Wave 3 follows. |
|
||||
| New `profilePage` translation group + `AuthProfilePageStrings` interface | Required because the upstream profile page used 20+ string keys not in Wave 1's slim `profile` group; rather than mutate the Wave 1 group (which Wave 2 also reads), Wave 3 adds a sibling `profilePage` group. |
|
||||
| 100% statement / branch / function / line coverage | Genesis-wide policy. |
|
||||
| Sibling `<page>.md` per implementation file | Genesis rule §4. |
|
||||
| Sibling `<page>.stories.tsx` per page + permission-gate | CLAUDE.md: "Every React component needs `.stories.tsx`." |
|
||||
| Sibling `.test.tsx` per page + permission-gate | Genesis-wide — upstream's pages had no co-located unit tests at this layer either. |
|
||||
|
||||
## Verification approach
|
||||
|
||||
Every page and `PermissionGate` ships a sibling `.test.tsx` that uses `@suaveplan/testing/runner` + `@testing-library/react` with real `react-hook-form`, real Zod, real `<MemoryRouter>`, and a real `<AuthProvider>` wrapped around an injectable `AuthClient` stub. No `vi.mock`. The `wrapForm` helper from Wave 2 is reused / extended to mount the auth context.
|
||||
|
||||
- `src/pages/login/login.test.tsx` — guard redirect when already authenticated, happy-path navigate to `returnTo`, happy-path default to `/dashboard`, signIn failure swallowed by the page.
|
||||
- `src/pages/register/register.test.tsx` — guard redirect, happy-path strips `confirmPassword`, navigate to `/dashboard`, signUp failure swallowed.
|
||||
- `src/pages/forgot-password/forgot-password.test.tsx` — happy-path submit renders success view with email, `onReset` clears, error surfaces.
|
||||
- `src/pages/reset-password/reset-password.test.tsx` — missing-token branch shows invalid view, happy-path renders success, login navigation CTA, forgot navigation CTA.
|
||||
- `src/pages/email-verification/email-verification.test.tsx` — missing-token sets `error` state, success sets `success` state, expired-token error classifies as `expired`, `onContinueToLogin` / `onRegisterAgain` / `onTryAgain` navigate correctly.
|
||||
- `src/pages/profile/profile.test.tsx` — guard redirect for unauthenticated, three tab switches, profile update success, password change validation (required, length, mismatch), delete-account confirmation flow.
|
||||
- `src/components/permission-gate/permission-gate.test.tsx` — renders children when permitted, renders fallback when denied, `requireAll` AND semantics, unauthenticated denies, numeric permission accepted.
|
||||
- `src/index.test.ts` — extended to assert the 7 new symbols (`LoginPage`, `RegisterPage`, `ForgotPasswordPage`, `ResetPasswordPage`, `EmailVerificationPage`, `ProfilePage`, `PermissionGate`) appear in the root barrel.
|
||||
|
||||
## Migration impact
|
||||
|
||||
- `package.json` gains 7 new subpath exports under `./pages/<page>` and `./components/permission-gate`. No new runtime dependencies — every dep is already present from Wave 1 or Wave 2 (`react-router-dom` is a peer + dev dep on Wave 1).
|
||||
- `vite.config.ts` `lib.entry` gains 7 new entries; `rollupOptions.external` already lists every consumed package.
|
||||
- No Wave 1 or Wave 2 file is modified except `src/index.ts` (named re-exports for the 7 new symbols added — strictly additive), `src/components/index.ts` (one new entry for `PermissionGate`), `src/index.test.ts` (asserts the new symbols are present), and the three locale-pack files which gain the new `profilePage` group as a strictly additive sibling under `auth`.
|
||||
- No downstream consumers yet — `@suaveplan/react-auth-whitelabel` is the only sibling and it ships independently. Verified by `bunx turbo ls --filter=...@suaveplan/react-auth` (empty list).
|
||||
@@ -0,0 +1,176 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# Capability Spec Delta: react-auth (add-react-auth-pages)
|
||||
|
||||
**Package**: `@suaveplan/react-auth`
|
||||
**Status**: PROPOSED
|
||||
|
||||
> Every requirement below carries a `**Verified by:**` line at authoring time.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: LoginPage wires LoginForm to the auth context with router redirect
|
||||
|
||||
The package SHALL expose a `<LoginPage>` component that renders the Wave 2 `<LoginForm>`, calls `useAuth().signIn(values)` on submit, redirects to `useSearchParams().get("returnTo")` (or `/dashboard` if absent) via `useNavigate`, and applies `useAuthGuard({ requireGuest: true })` so already-authenticated visitors are redirected away. The page SHALL swallow rejections from `signIn` — the underlying `useAuth().error` field carries the surfaced message into the form's destructive alert.
|
||||
|
||||
#### Scenario: happy path navigates to returnTo
|
||||
|
||||
- **WHEN** `<LoginPage>` is mounted under `/auth/login?returnTo=/inbox` and the user submits valid credentials
|
||||
- **THEN** `useAuth().signIn` is called with the typed credentials AND `useNavigate` is called with `"/inbox"`
|
||||
|
||||
#### Scenario: happy path defaults to /dashboard
|
||||
|
||||
- **WHEN** `<LoginPage>` is mounted under `/auth/login` (no `returnTo`) and the user submits valid credentials
|
||||
- **THEN** `useNavigate` is called with `"/dashboard"`
|
||||
|
||||
#### Scenario: signIn rejection does not throw
|
||||
|
||||
- **WHEN** `useAuth().signIn` rejects
|
||||
- **THEN** the page does not throw and the error message renders inside the `<LoginForm>` alert
|
||||
|
||||
**Verified by:** `src/pages/login/login.test.tsx::"navigates to returnTo on happy path"` AND `src/pages/login/login.test.tsx::"defaults to /dashboard when returnTo is absent"` AND `src/pages/login/login.test.tsx::"swallows signIn rejection and surfaces the error in the form"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: RegisterPage wires RegisterForm to signUp and strips confirmPassword
|
||||
|
||||
The package SHALL expose a `<RegisterPage>` component that renders the Wave 2 `<RegisterForm>`, strips `confirmPassword` from the submitted values before calling `useAuth().signUp(data)`, redirects to `/dashboard` on success, and applies `useAuthGuard({ requireGuest: true })`. The page SHALL swallow `signUp` rejections (errors surface via the form alert).
|
||||
|
||||
#### Scenario: happy path strips confirmPassword
|
||||
|
||||
- **WHEN** the form submits `{ email, password, confirmPassword, username, ... }`
|
||||
- **THEN** `useAuth().signUp` is called with the payload minus `confirmPassword` AND `useNavigate("/dashboard")` is invoked
|
||||
|
||||
#### Scenario: signUp rejection does not throw
|
||||
|
||||
- **WHEN** `useAuth().signUp` rejects
|
||||
- **THEN** the page does not throw
|
||||
|
||||
**Verified by:** `src/pages/register/register.test.tsx::"strips confirmPassword and navigates to /dashboard on happy path"` AND `src/pages/register/register.test.tsx::"swallows signUp rejection"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: ForgotPasswordPage wires ForgotPasswordForm to forgotPassword with success view
|
||||
|
||||
The package SHALL expose a `<ForgotPasswordPage>` component that renders the Wave 2 `<ForgotPasswordForm>`, calls `useAuth().forgotPassword(email)` on submit, drives the `isSuccess` / `emailSent` props from local state on resolution, and clears that state when the form's `onReset` callback fires.
|
||||
|
||||
#### Scenario: happy path flips to success view
|
||||
|
||||
- **WHEN** the form submits and `forgotPassword` resolves
|
||||
- **THEN** the form is re-rendered with `isSuccess={true}` AND `emailSent` equals the submitted email
|
||||
|
||||
#### Scenario: reset returns to entry view
|
||||
|
||||
- **WHEN** `isSuccess` is true and the success view's reset callback fires
|
||||
- **THEN** the form is re-rendered with `isSuccess={false}` AND `emailSent=""`
|
||||
|
||||
**Verified by:** `src/pages/forgot-password/forgot-password.test.tsx::"flips to success view on happy path"` AND `src/pages/forgot-password/forgot-password.test.tsx::"reset returns to entry view"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: ResetPasswordPage extracts ?token and drives ResetPasswordForm state
|
||||
|
||||
The package SHALL expose a `<ResetPasswordPage>` component that reads `?token` from `useSearchParams`, passes `hasToken={!!token}` to the Wave 2 `<ResetPasswordForm>`, calls `useAuth().resetPassword(token, password)` on submit when a token exists, flips `isSuccess` on resolution, and exposes `onNavigateToLogin` (`/auth/login`) and `onNavigateToForgotPassword` (`/auth/forgot-password`) callbacks for the form's success / invalid-token CTAs. The page SHALL apply `useAuthGuard({ requireGuest: true })`.
|
||||
|
||||
#### Scenario: missing token renders invalid-token view
|
||||
|
||||
- **WHEN** the page is mounted under `/auth/reset-password` (no `?token`)
|
||||
- **THEN** `<ResetPasswordForm>` is rendered with `hasToken={false}`
|
||||
|
||||
#### Scenario: happy path flips to success
|
||||
|
||||
- **WHEN** the form submits with `?token=abc` present and `resetPassword` resolves
|
||||
- **THEN** `useAuth().resetPassword` is called with `("abc", password)` AND the form re-renders with `isSuccess={true}`
|
||||
|
||||
#### Scenario: navigation CTAs fire
|
||||
|
||||
- **WHEN** the form invokes `onNavigateToLogin` or `onNavigateToForgotPassword`
|
||||
- **THEN** `useNavigate` is called with `/auth/login` or `/auth/forgot-password` respectively
|
||||
|
||||
**Verified by:** `src/pages/reset-password/reset-password.test.tsx::"renders invalid-token view when ?token is absent"` AND `src/pages/reset-password/reset-password.test.tsx::"flips to success on happy path"` AND `src/pages/reset-password/reset-password.test.tsx::"navigates on login and forgot-password CTAs"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: EmailVerificationPage auto-verifies ?token and classifies errors
|
||||
|
||||
The package SHALL expose an `<EmailVerificationPage>` component that reads `?token` from `useSearchParams`, calls `useAuth().verifyEmail(token)` once on mount, drives the Wave 2 `<EmailVerificationView>` status via local state (`verifying` → `success` / `error` / `expired`), classifies any error whose message contains `expired` or `invalid` (case-insensitive) as `expired`, and exposes navigation callbacks (`onContinueToLogin` → `/auth/login`, `onRegisterAgain` → `/auth/register`, `onTryAgain` reloads). The page SHALL apply `useAuthGuard({ requireGuest: true })`.
|
||||
|
||||
#### Scenario: missing token sets error status
|
||||
|
||||
- **WHEN** the page mounts with no `?token`
|
||||
- **THEN** `<EmailVerificationView status="error">` is rendered AND `useAuth().verifyEmail` is NOT called
|
||||
|
||||
#### Scenario: happy-path sets success status
|
||||
|
||||
- **WHEN** `verifyEmail` resolves
|
||||
- **THEN** `<EmailVerificationView status="success">` is rendered
|
||||
|
||||
#### Scenario: expired token classifies as expired
|
||||
|
||||
- **WHEN** `verifyEmail` rejects with `new Error("token has expired")`
|
||||
- **THEN** `<EmailVerificationView status="expired">` is rendered
|
||||
|
||||
**Verified by:** `src/pages/email-verification/email-verification.test.tsx::"renders error status when ?token is absent"` AND `src/pages/email-verification/email-verification.test.tsx::"renders success status on happy path"` AND `src/pages/email-verification/email-verification.test.tsx::"classifies expired-token rejection as expired"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: ProfilePage renders a three-tab account surface bound to useAuth
|
||||
|
||||
The package SHALL expose a `<ProfilePage>` (aliased as `Profile` for upstream compatibility) component that renders a three-tab surface (`profile`, `password`, `account`) using `@suaveplan/react-ui` `<Tabs>`, hydrates the `profile` form from `useAuth().user`, calls `useAuth().updateProfile({ name, email })` on profile-tab submit, calls `useAuth().changePassword({ currentPassword, newPassword })` on password-tab submit (after local validation: required, ≥8 chars, match), and calls `useAuth().deleteAccount(currentPassword)` on account-tab confirmation (then navigates to `/auth/login`). The page SHALL apply `useAuthGuard({ requireAuth: true })`. The page SHALL accept a `translations?: Partial<AuthProfilePageStrings>` prop defaulting to `enGb.auth.profilePage`.
|
||||
|
||||
#### Scenario: guards unauthenticated visitors
|
||||
|
||||
- **WHEN** the page is mounted with `useAuth().isAuthenticated === false`
|
||||
- **THEN** `useNavigate("/auth/login")` is invoked
|
||||
|
||||
#### Scenario: tab switching renders the requested section
|
||||
|
||||
- **WHEN** the user clicks the `password` tab
|
||||
- **THEN** the password form is rendered AND the profile form is hidden
|
||||
|
||||
#### Scenario: profile update calls updateProfile and shows success
|
||||
|
||||
- **WHEN** the profile form is submitted with edited name + email
|
||||
- **THEN** `updateProfile({ name, email })` is called AND a success alert appears
|
||||
|
||||
#### Scenario: password change validates locally before calling changePassword
|
||||
|
||||
- **WHEN** newPassword and confirmPassword do not match
|
||||
- **THEN** `changePassword` is NOT called AND a mismatch error renders
|
||||
|
||||
#### Scenario: delete-account flow
|
||||
|
||||
- **WHEN** the user confirms delete with a password
|
||||
- **THEN** `deleteAccount(password)` is called AND `useNavigate("/auth/login")` is invoked
|
||||
|
||||
**Verified by:** `src/pages/profile/profile.test.tsx::"guards unauthenticated visitors"` AND `src/pages/profile/profile.test.tsx::"switches tabs on click"` AND `src/pages/profile/profile.test.tsx::"profile update success"` AND `src/pages/profile/profile.test.tsx::"password change validates mismatch locally"` AND `src/pages/profile/profile.test.tsx::"delete account confirmation calls deleteAccount and navigates"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: PermissionGate conditionally renders by bitmask permission check
|
||||
|
||||
The package SHALL expose a `<PermissionGate>` component that consumes `usePermission()` from Wave 1 and renders `children` when the configured `permissions` array passes the check, otherwise renders `fallback` (default `null`). The default semantics SHALL be ANY-of; setting `requireAll={true}` SHALL switch to ALL-of semantics. Each entry in `permissions` MAY be a named `Permission` key OR a raw `number` bitmask.
|
||||
|
||||
#### Scenario: any-of permits when one bit matches
|
||||
|
||||
- **WHEN** the user has `Permissions.USER_READ` and the gate requires `["USER_READ", "ADMIN_WRITE"]`
|
||||
- **THEN** the children are rendered
|
||||
|
||||
#### Scenario: requireAll denies when one bit missing
|
||||
|
||||
- **WHEN** the user has only `Permissions.USER_READ` and the gate requires `["USER_READ", "ADMIN_WRITE"]` with `requireAll={true}`
|
||||
- **THEN** the fallback is rendered
|
||||
|
||||
#### Scenario: numeric permission accepted
|
||||
|
||||
- **WHEN** the gate is given a raw numeric bitmask matching the user's bits
|
||||
- **THEN** the children are rendered
|
||||
|
||||
#### Scenario: unauthenticated user always denied
|
||||
|
||||
- **WHEN** `useAuth().user === null`
|
||||
- **THEN** the fallback is rendered
|
||||
|
||||
**Verified by:** `src/components/permission-gate/permission-gate.test.tsx::"renders children when any-of matches"` AND `src/components/permission-gate/permission-gate.test.tsx::"renders fallback when requireAll fails"` AND `src/components/permission-gate/permission-gate.test.tsx::"accepts numeric permission"` AND `src/components/permission-gate/permission-gate.test.tsx::"renders fallback when unauthenticated"`
|
||||
|
||||
<!-- markdownlint-enable MD024 -->
|
||||
72
openspec/archive/2026-05-19-add-react-auth-pages/tasks.md
Normal file
72
openspec/archive/2026-05-19-add-react-auth-pages/tasks.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Tasks — Add @suaveplan/react-auth — pages + PermissionGate (Wave 3)
|
||||
|
||||
> No retroactive ticks. Each box reflects work done in this change.
|
||||
|
||||
## 1. Spec authoring
|
||||
|
||||
- [ ] 1.1 Read `../suaveplan/enterprise/react-auth.md` end-to-end
|
||||
- [ ] 1.2 Read `openspec/specs/react-auth/spec.md` (Wave 1 + Wave 2 canonical)
|
||||
- [ ] 1.3 Read `openspec/AGENTS.md` §§ 2a, 6a, 7, 12, 13, 14
|
||||
- [ ] 1.4 Draft `proposal.md` from template with `## Source mapping`
|
||||
- [ ] 1.5 Draft `specs/react-auth/spec.md` delta with `## ADDED Requirements`, one per page + PermissionGate
|
||||
- [ ] 1.6 Every requirement carries a `**Verified by:**` line at draft time
|
||||
|
||||
## 2. Implementation
|
||||
|
||||
- [ ] 2.1 Add 7 subpath exports to `package.json` `exports` block
|
||||
- [ ] 2.2 Extend `vite.config.ts` `lib.entry` with 7 new entries
|
||||
- [ ] 2.3 Ship `src/pages/login/` (page.tsx, .test.tsx, .md, .stories.tsx, index.ts)
|
||||
- [ ] 2.4 Ship `src/pages/register/`
|
||||
- [ ] 2.5 Ship `src/pages/forgot-password/`
|
||||
- [ ] 2.6 Ship `src/pages/reset-password/`
|
||||
- [ ] 2.7 Ship `src/pages/email-verification/` (also includes .types.ts)
|
||||
- [ ] 2.8 Ship `src/pages/profile/` (split into `profile.tsx` + `profile-sections.tsx` to honour the 300-line cap; .types.ts; .md; .stories.tsx; index.ts; .test.tsx)
|
||||
- [ ] 2.9 Ship `src/components/permission-gate/`
|
||||
- [ ] 2.10 Extend `src/components/index.ts` with `PermissionGate` named re-export
|
||||
- [ ] 2.11 Extend `src/index.ts` with the 7 new symbols (named re-exports only)
|
||||
- [ ] 2.12 Extend `src/index.test.ts` expected-keys list with the 7 new symbols
|
||||
- [ ] 2.13 Add `profilePage` group to `en-gb.ts` / `cy-gb.ts` / `zh-cn.ts` under `auth.profilePage`
|
||||
|
||||
## 3. Tests
|
||||
|
||||
- [ ] 3.1 `src/pages/login/login.test.tsx` — guard redirect, happy path, returnTo, error swallow
|
||||
- [ ] 3.2 `src/pages/register/register.test.tsx` — guard redirect, strip confirmPassword, navigate, error swallow
|
||||
- [ ] 3.3 `src/pages/forgot-password/forgot-password.test.tsx` — happy path, reset, error
|
||||
- [ ] 3.4 `src/pages/reset-password/reset-password.test.tsx` — missing token, happy path, CTAs
|
||||
- [ ] 3.5 `src/pages/email-verification/email-verification.test.tsx` — missing token, success, expired, CTAs
|
||||
- [ ] 3.6 `src/pages/profile/profile.test.tsx` — guard, tab switching, profile update, password change validation, delete account
|
||||
- [ ] 3.7 `src/components/permission-gate/permission-gate.test.tsx` — allow, deny, requireAll, numeric, unauthenticated
|
||||
- [ ] 3.8 Every `it(...)` carries `{ timeout: 5000 }`
|
||||
- [ ] 3.9 All tests import from `@suaveplan/testing/runner`
|
||||
|
||||
## 4. Documentation
|
||||
|
||||
- [ ] 4.1 Sibling `.md` for every page + PermissionGate + ProfileSections (≥200 words, Basic + Advanced examples, full API reference)
|
||||
- [ ] 4.2 No README changes (Wave 1's README already lists subpaths generically)
|
||||
|
||||
## 5. Pre-archive 6-gate verification
|
||||
|
||||
- [ ] 5.1 `bun run check` — Biome clean across the monorepo
|
||||
- [ ] 5.2 `bunx turbo lint typecheck test --filter=@suaveplan/react-auth --force` — green
|
||||
- [ ] 5.3 `bunx turbo test:coverage --filter=@suaveplan/react-auth` — 100/100/100/100 istanbul
|
||||
- [ ] 5.4 `bun scripts/openspec-conformance.ts @suaveplan/react-auth` — exit 0
|
||||
- [ ] 5.5 `bunx turbo build --filter=@suaveplan/react-auth --force` — green
|
||||
- [ ] 5.6 `bun scripts/lint/check-test-runner-imports.ts` — exit 0
|
||||
|
||||
## 6. Pre-archive 9-row checklist
|
||||
|
||||
- [ ] 6.1 Generics threaded — pages thread Wave 2 form prop generics; no `as any`
|
||||
- [ ] 6.2 N/A — no new boundary input; pages re-use Wave 2's Zod boundaries
|
||||
- [ ] 6.3 No new error codes introduced; pages swallow `useAuth()` action rejections (errors surface via the existing context `error` field)
|
||||
- [ ] 6.4 N/A — no strict-mode behaviour toggle in this wave
|
||||
- [ ] 6.5 N/A — no new OTel instruments in this wave (pages are routing adapters; metrics live on Wave 1 actions)
|
||||
- [ ] 6.6 N/A — no lifecycle dedup claims on pages
|
||||
- [ ] 6.7 No retroactive ticks
|
||||
- [ ] 6.8 No `export *` in any feature-module barrel
|
||||
- [ ] 6.9 No direct `vitest` / `bun:test` imports in test files
|
||||
|
||||
## 7. Archive
|
||||
|
||||
- [ ] 7.1 Fold spec delta — APPEND new requirements to `openspec/specs/react-auth/spec.md` under `## ADDED Requirements`; do NOT modify Wave 1 / Wave 2 requirements
|
||||
- [ ] 7.2 Move `openspec/changes/add-react-auth-pages/` → `openspec/archive/2026-05-19-add-react-auth-pages/`
|
||||
- [ ] 7.3 No MANIFEST.md / STATUS.md updates (not a new package)
|
||||
Reference in New Issue
Block a user