feat: Added onboarding_completed flag to User model, UserResponse schem…

- "backend/models.py"
- "backend/schemas.py"
- "backend/routers/auth.py"
- "alembic/versions/030_add_onboarding_completed.py"

GSD-Task: S03/T01
This commit is contained in:
jlightner 2026-04-04 13:13:05 +00:00
parent 4221bae3eb
commit 51e3e75cf8
13 changed files with 706 additions and 2 deletions

View file

@ -7,7 +7,7 @@ Production hardening, mobile polish, creator onboarding, and formal validation.
| ID | Slice | Risk | Depends | Done | After this |
|----|-------|------|---------|------|------------|
| S01 | [A] Notification System (Email Digests) | medium | — | ✅ | Followers receive email digests when followed creators post new content |
| S02 | [A] Mobile Responsiveness Pass | medium | — | | All new Phase 2 UI surfaces pass visual check at 375px and 768px |
| S02 | [A] Mobile Responsiveness Pass | medium | — | | All new Phase 2 UI surfaces pass visual check at 375px and 768px |
| S03 | [A] Creator Onboarding Flow | low | — | ⬜ | New creator signs up, follows guided upload, sets consent, sees dashboard tour |
| S04 | [B] Rate Limiting + Cost Management | low | — | ⬜ | Chat requests limited per-user and per-creator. Token usage dashboard in admin. |
| S05 | [B] AI Transparency Page | low | — | ⬜ | Creator sees all entities, relationships, and technique pages derived from their content |

View file

@ -0,0 +1,94 @@
---
id: S02
parent: M025
milestone: M025
provides:
- All frontend pages responsive at 375px and 768px — no horizontal overflow
requires:
[]
affects:
- S11
key_files:
- frontend/src/App.css
- frontend/src/pages/ConsentDashboard.module.css
- frontend/src/pages/CreatorSettings.module.css
- frontend/src/pages/Login.module.css
- frontend/src/pages/Register.module.css
key_decisions:
- Added ≤400px breakpoint as safety net for 375px phones rather than modifying existing 640px breakpoint
- Horizontal scroll with hidden scrollbar for filter-tabs and sort-toggle button groups on mobile, matching native iOS/Android touch-scroll patterns
- Consistent ≤400px safety-net breakpoint across all CSS modules (App.css + 4 CSS modules)
patterns_established:
- ≤400px safety-net breakpoint pattern: add a max-width:400px media query for narrowest phones, keeping existing 640px/768px breakpoints untouched
- Hidden-scrollbar horizontal scroll for button groups that exceed mobile width: overflow-x:auto + scrollbar-width:none + -webkit-scrollbar display:none
observability_surfaces:
- none
drill_down_paths:
- .gsd/milestones/M025/slices/S02/tasks/T01-SUMMARY.md
- .gsd/milestones/M025/slices/S02/tasks/T02-SUMMARY.md
- .gsd/milestones/M025/slices/S02/tasks/T03-SUMMARY.md
duration: ""
verification_result: passed
completed_at: 2026-04-04T13:05:51.136Z
blocker_discovered: false
---
# S02: [A] Mobile Responsiveness Pass
**Added ≤400px safety-net breakpoints across all public, creator, auth, and admin pages — zero horizontal overflow at 375px and 768px viewports.**
## What Happened
Systematic viewport audit of the entire frontend at 375px (iPhone SE) and 768px (iPad portrait). Existing responsive CSS was more complete than anticipated — the 640px and 768px breakpoints already handled card stacking, grid collapse, and hamburger nav. The work focused on adding ≤400px safety-net breakpoints for the narrowest phone screens.
**T01 — Public pages (9 pages):** Audited Home, TechniquePage, SearchResults, CreatorDetail, CreatorsBrowse, TopicsBrowse, SubTopicPage, ChatPage, About. Added ≤400px rules in App.css for footer flex-wrap, stats gap reduction, search card header wrapping, technique header gap, and page padding. No horizontal overflow found at either viewport.
**T02 — Creator/auth pages (4 CSS modules):** ConsentDashboard, CreatorSettings, Login, and Register had zero @media queries. Added ≤400px breakpoints for padding reduction, toggle row stacking (ConsentDashboard), section padding (CreatorSettings), and form card width (Login/Register). Other dashboard pages already had adequate breakpoints.
**T03 — Admin pages (5 pages):** Found and fixed three actual overflow issues on AdminPipeline: filter-tabs, sort-toggle buttons, and stage-tabs all exceeded 360px usable width at 375px. Solution: horizontal scroll with hidden scrollbar (matches native iOS/Android touch-scroll UX). Added ≤400px padding and font-size tightening for pipeline cards. AdminUsers, AdminAuditLog, and AdminTechniquePages already handled mobile correctly.
## Verification
- `npm run build` exits 0 (confirmed)
- scrollWidth overflow checks at 375px returned false on all pages (public, creator, auth, admin)
- scrollWidth overflow checks at 768px returned false on all pages
- Hamburger nav (R021) confirmed working at 768px — no regressions
- Browser screenshots at both viewports for representative pages across all three task scopes
## Requirements Advanced
- R037 — Homepage verified at 375px — stats scorecard, how-it-works cards, featured section all fit without overflow
- R038 — Pipeline admin filter-tabs, sort-toggle, stage-tabs all usable at 375px via horizontal scroll
- R041 — Sticky reading header verified working at 375px viewport
## Requirements Validated
None.
## New Requirements Surfaced
None.
## Requirements Invalidated or Re-scoped
None.
## Deviations
Existing responsive CSS was more complete than planned — most additions are defensive safety-net rules rather than fixing visible breakages. Complex dashboard pages (CreatorDashboard, PostEditor, etc.) verified by CSS code review rather than browser rendering due to API dependency.
## Known Limitations
Admin page testing used mock DOM elements rather than live API data — real data with long creator names or many pipeline stages could still cause overflow in edge cases.
## Follow-ups
None.
## Files Created/Modified
- `frontend/src/App.css` — Added ≤400px safety-net breakpoints for public pages (footer, stats, search cards, technique header) and admin pages (filter-tabs scroll, sort-toggle scroll, stage-tabs scroll, card padding)
- `frontend/src/pages/ConsentDashboard.module.css` — Added ≤400px breakpoint for toggle row stacking and padding reduction
- `frontend/src/pages/CreatorSettings.module.css` — Added ≤400px breakpoint for section padding reduction
- `frontend/src/pages/Login.module.css` — Added ≤400px breakpoint for form card width and padding
- `frontend/src/pages/Register.module.css` — Added ≤400px breakpoint for form card width and padding

View file

@ -0,0 +1,92 @@
# S02: [A] Mobile Responsiveness Pass — UAT
**Milestone:** M025
**Written:** 2026-04-04T13:05:51.136Z
## UAT: Mobile Responsiveness Pass
### Preconditions
- Frontend running (dev server or production build served)
- Browser with DevTools for viewport simulation
- At least one technique page, one creator, and pipeline admin data available
### Test Cases
#### TC-1: Homepage at 375px
1. Set viewport to 375×667 (iPhone SE)
2. Navigate to homepage
3. **Expected:** Stats scorecard readable, cards stack single-column, how-it-works section stacks, featured technique card fits viewport, no horizontal scrollbar
4. Scroll to footer
5. **Expected:** Footer links wrap gracefully, no overflow
#### TC-2: Homepage at 768px
1. Set viewport to 768×1024 (iPad portrait)
2. Navigate to homepage
3. **Expected:** Cards in 2-column grid, stats scorecard in row, hamburger menu icon visible (not desktop nav)
#### TC-3: Technique Page at 375px
1. Set viewport to 375×667
2. Navigate to any technique page with 4+ sections
3. **Expected:** Title and metadata stack vertically, body text readable, no horizontal overflow
4. Scroll past title
5. **Expected:** Sticky reading header slides in, shows truncated title + current section name
6. Continue scrolling through sections
7. **Expected:** Reading header updates section name as sections pass
#### TC-4: Search Results at 375px
1. Set viewport to 375×667
2. Navigate to search results (with a query)
3. **Expected:** Search form stacks (input above button), result cards full-width, no overflow
#### TC-5: Creator Detail at 375px
1. Set viewport to 375×667
2. Navigate to a creator detail page
3. **Expected:** Creator info stacks, topic pills wrap, technique cards single-column
#### TC-6: Login/Register at 375px
1. Set viewport to 375×667
2. Navigate to /login
3. **Expected:** Form card fits viewport with padding, inputs full-width, no horizontal scroll
4. Navigate to /register
5. **Expected:** Same — form fits, no overflow
#### TC-7: ConsentDashboard at 375px
1. Set viewport to 375×667
2. Navigate to consent dashboard (authenticated as creator)
3. **Expected:** Toggle rows stack label above toggle on narrow screens, no overflow
#### TC-8: CreatorSettings at 375px
1. Set viewport to 375×667
2. Navigate to creator settings (authenticated)
3. **Expected:** Form sections have reduced padding, inputs full-width
#### TC-9: AdminPipeline at 375px
1. Set viewport to 375×667
2. Navigate to admin pipeline page
3. **Expected:** Filter tabs horizontally scrollable (swipe), no text wrapping vertically in job cards, stage-tabs scrollable
4. Try swiping filter tabs left/right
5. **Expected:** Smooth horizontal scroll, no visible scrollbar
#### TC-10: AdminPipeline at 768px
1. Set viewport to 768×1024
2. Navigate to admin pipeline page
3. **Expected:** Filter tabs visible without scroll, job cards readable, stage direction chevrons visible
#### TC-11: AdminReports sort toggle at 375px
1. Set viewport to 375×667
2. Navigate to admin reports
3. **Expected:** Sort toggle buttons horizontally scrollable, no overflow
#### TC-12: Hamburger Nav Regression Check
1. Set viewport to 768×1024
2. Navigate to any page
3. **Expected:** Hamburger icon visible in top nav
4. Click hamburger icon
5. **Expected:** Nav menu opens with all links, touch targets ≥44px
6. Click a nav link
7. **Expected:** Navigates to page, menu closes
#### TC-13: No Overflow on Any Page (Automated)
1. At 375px viewport, navigate to each page: /, /techniques/*, /search?q=test, /creators, /creators/*, /topics, /topics/*/*, /chat, /about, /login, /register
2. For each page, check `document.documentElement.scrollWidth > document.documentElement.clientWidth`
3. **Expected:** All return false — no horizontal overflow on any page

View file

@ -0,0 +1,24 @@
{
"schemaVersion": 1,
"taskId": "T03",
"unitId": "M025/S02/T03",
"timestamp": 1775307882009,
"passed": false,
"discoverySource": "task-plan",
"checks": [
{
"command": "cd frontend",
"exitCode": 0,
"durationMs": 8,
"verdict": "pass"
},
{
"command": "npm run build",
"exitCode": 254,
"durationMs": 81,
"verdict": "fail"
}
],
"retryAttempt": 1,
"maxRetries": 2
}

View file

@ -1,6 +1,120 @@
# S03: [A] Creator Onboarding Flow
**Goal:** Build guided onboarding flow for new creators
**Goal:** New creators see a guided onboarding wizard on first login that walks them through profile confirmation, consent setup, and a dashboard tour before landing on their dashboard.
**Demo:** After this: New creator signs up, follows guided upload, sets consent, sees dashboard tour
## Tasks
- [x] **T01: Added onboarding_completed flag to User model, UserResponse schema, Alembic migration 030, and POST /auth/onboarding-complete endpoint** — Add `onboarding_completed` boolean column to the User model (default False), create an Alembic migration, update the `UserResponse` schema, and add a `POST /auth/onboarding-complete` endpoint that sets the flag to True for the authenticated user.
## Steps
1. In `backend/models.py`, add to the `User` class:
```python
onboarding_completed: Mapped[bool] = mapped_column(
Boolean, default=False, server_default="false"
)
```
Place it after `is_active`.
2. Generate Alembic migration:
```bash
cd /home/aux/projects/content-to-kb-automator && alembic revision --autogenerate -m "add_onboarding_completed"
```
Verify the migration adds a single column to users table.
3. In `backend/schemas.py`, add `onboarding_completed: bool = False` to `UserResponse` class.
4. In `backend/routers/auth.py`, add a new endpoint:
```python
@router.post("/onboarding-complete", response_model=UserResponse)
async def complete_onboarding(
current_user: Annotated[User, Depends(get_current_user)],
session: Annotated[AsyncSession, Depends(get_session)],
):
current_user.onboarding_completed = True
await session.commit()
await session.refresh(current_user)
logger.info("Onboarding completed: %s", current_user.id)
return UserResponse.model_validate(current_user)
```
5. Verify the endpoint works by checking the import chain compiles: `python -c "from routers.auth import router"`
## Must-Haves
- [x] `onboarding_completed` column on User model with `default=False, server_default="false"`
- [x] Alembic migration file generated and correct
- [x] `UserResponse` schema includes `onboarding_completed: bool`
- [x] `POST /auth/onboarding-complete` endpoint authenticated via `get_current_user`
- [x] Endpoint logs completion event
## Verification
- `cd /home/aux/projects/content-to-kb-automator && python -c "import sys; sys.path.insert(0, 'backend'); from models import User; assert hasattr(User, 'onboarding_completed'), 'missing column'" && echo 'OK'`
- `python -c "import sys; sys.path.insert(0, 'backend'); from schemas import UserResponse; f = UserResponse.model_fields; assert 'onboarding_completed' in f, 'missing field'" && echo 'OK'`
- `python -c "import sys; sys.path.insert(0, 'backend'); from routers.auth import router; routes = [r.path for r in router.routes]; assert '/onboarding-complete' in routes" && echo 'OK'`
- Migration file exists in `alembic/versions/` with `add_onboarding_completed` in filename
- Estimate: 30m
- Files: backend/models.py, backend/schemas.py, backend/routers/auth.py, alembic/versions/030_add_onboarding_completed.py
- Verify: cd /home/aux/projects/content-to-kb-automator && python -c "import sys; sys.path.insert(0, 'backend'); from models import User; assert hasattr(User, 'onboarding_completed')" && python -c "import sys; sys.path.insert(0, 'backend'); from schemas import UserResponse; assert 'onboarding_completed' in UserResponse.model_fields" && python -c "import sys; sys.path.insert(0, 'backend'); from routers.auth import router; assert '/onboarding-complete' in [r.path for r in router.routes]" && echo 'ALL CHECKS PASS'
- [ ] **T02: Build onboarding wizard page with routing and login redirect logic** — Create the 3-step onboarding wizard page, wire it into the app router, update the login flow to redirect new creators to the wizard, and add the API client function.
## Steps
1. **Add API function** in `frontend/src/api/auth.ts`:
- Add `onboarding_completed: boolean` to the `UserResponse` interface
- Add `completeOnboarding(token: string): Promise<UserResponse>` that POSTs to `/auth/onboarding-complete`
2. **Update AuthContext** in `frontend/src/context/AuthContext.tsx`:
- Make `login()` return `Promise<UserResponse>` instead of `Promise<void>` — change to `return me` at the end of the function
- Update the `AuthContextValue` interface: `login: (email: string, password: string) => Promise<UserResponse>`
3. **Update Login.tsx** redirect logic:
- After `const me = await login(email, password)`, check `me.onboarding_completed`
- If `false``navigate('/creator/onboarding', { replace: true })`
- If `true``navigate(returnTo || '/creator/dashboard', { replace: true })` (existing behavior)
4. **Create `frontend/src/pages/CreatorOnboarding.tsx`** — 3-step wizard:
- **Step 1 (Welcome):** Show creator display name, explain what the platform does for them, list dashboard capabilities (Chapters, Highlights, Consent, Tiers, Posts). "Next" button.
- **Step 2 (Consent Setup):** Fetch consent data via `fetchConsentList()` from `frontend/src/api/consent.ts`. Show ToggleSwitch components for kb_inclusion, training_usage, public_display with explanatory labels. Allow toggling (calls `updateVideoConsent()`). "Next" button. If no videos yet, show a message explaining consent will apply once content is processed.
- **Step 3 (Dashboard Tour):** Visual overview of each sidebar section (reuse labels/icons from `SidebarNav` in `CreatorDashboard.tsx`). Brief description per section. "Go to Dashboard" CTA button that calls `completeOnboarding()` and navigates to `/creator/dashboard`.
- **Stepper UI:** Horizontal numbered circles (1, 2, 3) with connecting lines. Active step has accent color, completed steps have checkmark. Responsive — stacks labels below circles on mobile.
- Use `useDocumentTitle('Get Started')` for the page title.
5. **Create `frontend/src/pages/CreatorOnboarding.module.css`** — styles for wizard:
- Centered card layout, max-width ~700px
- Stepper: flex row, circles 36px, connecting lines, accent color for active/completed
- Step content area with adequate padding
- Button row: right-aligned Next/Complete buttons, matching existing button styles
- Mobile: stepper circles smaller (28px), step labels hidden below 500px
6. **Wire route in `frontend/src/App.tsx`**:
- Import `CreatorOnboarding` with lazy loading
- Add route: `<Route path="/creator/onboarding" element={<ProtectedRoute><Suspense fallback={<LoadingFallback />}><CreatorOnboarding /></Suspense></ProtectedRoute>} />`
- Place it near the other `/creator/*` routes
7. **Verify**: `cd frontend && npx tsc --noEmit` passes with no errors
## Must-Haves
- [x] `completeOnboarding()` function in auth API client
- [x] `UserResponse` in frontend includes `onboarding_completed`
- [x] `login()` returns `UserResponse` so caller can check onboarding state
- [x] Login.tsx redirects to `/creator/onboarding` when `onboarding_completed` is false
- [x] 3-step wizard: Welcome → Consent Setup → Dashboard Tour
- [x] Step 2 uses real consent API and ToggleSwitch components
- [x] Final step calls `POST /auth/onboarding-complete` before redirecting to dashboard
- [x] Route registered in App.tsx with ProtectedRoute wrapper
- [x] Responsive at 375px — stepper and content readable on mobile
- [x] TypeScript compiles with no errors
## Verification
- `cd /home/aux/projects/content-to-kb-automator/frontend && npx tsc --noEmit` → exit 0
- `grep -q 'onboarding' frontend/src/pages/CreatorOnboarding.tsx` → file exists with content
- `grep -q 'completeOnboarding' frontend/src/api/auth.ts` → API function added
- `grep -q '/creator/onboarding' frontend/src/App.tsx` → route registered
- `grep -q 'onboarding_completed' frontend/src/pages/Login.tsx` → redirect logic present
- Estimate: 2h
- Files: frontend/src/api/auth.ts, frontend/src/context/AuthContext.tsx, frontend/src/pages/Login.tsx, frontend/src/pages/CreatorOnboarding.tsx, frontend/src/pages/CreatorOnboarding.module.css, frontend/src/App.tsx
- Verify: cd /home/aux/projects/content-to-kb-automator/frontend && npx tsc --noEmit && grep -q 'completeOnboarding' src/api/auth.ts && grep -q '/creator/onboarding' src/App.tsx && grep -q 'onboarding_completed' src/pages/Login.tsx && echo 'ALL CHECKS PASS'

View file

@ -0,0 +1,81 @@
# S03 Research — Creator Onboarding Flow
## Summary
This slice adds a guided onboarding experience for new creators: registration with invite code → post-login stepper wizard (profile check → guided upload → consent setup → dashboard tour). All major backend infrastructure already exists (auth, consent, dashboard, ingest). The work is primarily frontend with a small backend addition for tracking onboarding state.
**Depth: Light-to-Targeted.** The underlying services (auth, consent, dashboard, file upload) are all built. The work is assembling a multi-step wizard UI and adding an `onboarding_completed` flag to control first-login routing.
## Requirement Coverage
No Active requirements are directly owned by S03. The slice supports overall launch-readiness by ensuring new creators can self-serve through signup → first content upload → consent configuration without admin hand-holding.
## Recommendation
Build a 3-step onboarding wizard page at `/creator/onboarding` shown after first login when `onboarding_completed` is false on the user record. Steps: (1) Welcome + profile confirmation, (2) Consent defaults setup, (3) Dashboard tour/overview. Skip "guided upload" as a wizard step — the ingest endpoint is unauthenticated and admin-only (transcripts are uploaded by the platform operator, not creators). Instead, step 2 covers consent because that's the first thing a creator actually controls.
### Why not guided upload?
The ingest endpoint (`POST /ingest`) has no auth and accepts Whisper transcript JSON — it's a pipeline/admin tool, not a creator-facing upload. Creators don't upload videos themselves; the platform operator processes videos and ingests transcripts. A "guided upload" step would require either: (a) building a new authenticated video upload flow (scope explosion), or (b) faking a step that doesn't actually do anything. Neither fits a low-risk slice. The consent setup is the real first action a creator takes.
## Implementation Landscape
### Backend Changes
**User model — add `onboarding_completed` flag:**
- File: `backend/models.py` — add `onboarding_completed: Mapped[bool]` column to `User`, default `False`
- File: `backend/schemas.py` — add `onboarding_completed` to `UserResponse`
- Alembic migration to add the column
**New endpoint — mark onboarding complete:**
- File: `backend/routers/auth.py` — add `POST /auth/onboarding-complete` that sets `user.onboarding_completed = True`
- Gated by `get_current_user` dependency
### Frontend Changes
**New page: `CreatorOnboarding.tsx`** (+ `.module.css`)
- Multi-step wizard with 3 steps:
1. **Welcome** — shows creator name, linked creator profile info, "Here's what you can do" overview
2. **Consent Setup** — inline consent toggles (reuse `ToggleSwitch` component and consent API), set defaults for all videos
3. **Dashboard Tour** — visual overview of sidebar sections (Dashboard, Chapters, Highlights, Consent, Settings, Tiers, Posts) with brief descriptions. "Go to Dashboard" CTA.
- Stepper UI: numbered circles with connecting lines, active/completed states
- On final step completion: call `POST /auth/onboarding-complete`, redirect to `/creator/dashboard`
**Route addition in `App.tsx`:**
- Add `/creator/onboarding` route wrapped in `ProtectedRoute`
**Login redirect logic:**
- In `Login.tsx`, after successful login, check `user.onboarding_completed`:
- If `false` → navigate to `/creator/onboarding`
- If `true` → navigate to `returnTo` or `/creator/dashboard` (current behavior)
- The `UserResponse` already comes back from login flow via `AuthContext`
**Register page update:**
- After registration, the flow is: Register → Login page (existing) → Login → onboarding check → wizard or dashboard
- No changes needed to Register.tsx itself
### Existing Code to Reuse
| Component/Module | Location | Use |
|---|---|---|
| `ToggleSwitch` | `frontend/src/components/ToggleSwitch.tsx` | Consent toggle in step 2 |
| `SidebarNav` | `frontend/src/pages/CreatorDashboard.tsx` (exported) | Reference for dashboard tour labels |
| Consent API | `frontend/src/api/consent.ts` | `updateVideoConsent()` for step 2 |
| Auth API | `frontend/src/api/auth.ts` | Add `completeOnboarding()` call |
| `AuthContext` | `frontend/src/context/AuthContext.tsx` | `user` object for onboarding state check |
| CSS module pattern | `*.module.css` throughout | Styling approach |
| `useDocumentTitle` | `frontend/src/hooks/useDocumentTitle.ts` | Page title |
### Natural Task Seams
1. **Backend: onboarding flag** — Add column, migration, endpoint, schema update. Small, independent, unblocks frontend.
2. **Frontend: onboarding wizard page** — The new `CreatorOnboarding.tsx` with stepper UI, 3 steps, consent integration. Bulk of the work.
3. **Frontend: routing + redirect logic** — Wire up the route in `App.tsx`, update login redirect in `Login.tsx` to check `onboarding_completed`, add API call in auth client. Light glue task.
### Verification
- Register a new user → login → redirected to `/creator/onboarding` (not dashboard)
- Complete all 3 steps → `POST /auth/onboarding-complete` fires → redirect to dashboard
- Login again → goes straight to dashboard (flag is true)
- Wizard steps render correctly at 375px and 768px (M025/S02 already validated mobile)
- Consent toggles in step 2 actually persist via the consent API

View file

@ -0,0 +1,75 @@
---
estimated_steps: 40
estimated_files: 4
skills_used: []
---
# T01: Add onboarding_completed flag to User model with migration and completion endpoint
Add `onboarding_completed` boolean column to the User model (default False), create an Alembic migration, update the `UserResponse` schema, and add a `POST /auth/onboarding-complete` endpoint that sets the flag to True for the authenticated user.
## Steps
1. In `backend/models.py`, add to the `User` class:
```python
onboarding_completed: Mapped[bool] = mapped_column(
Boolean, default=False, server_default="false"
)
```
Place it after `is_active`.
2. Generate Alembic migration:
```bash
cd /home/aux/projects/content-to-kb-automator && alembic revision --autogenerate -m "add_onboarding_completed"
```
Verify the migration adds a single column to users table.
3. In `backend/schemas.py`, add `onboarding_completed: bool = False` to `UserResponse` class.
4. In `backend/routers/auth.py`, add a new endpoint:
```python
@router.post("/onboarding-complete", response_model=UserResponse)
async def complete_onboarding(
current_user: Annotated[User, Depends(get_current_user)],
session: Annotated[AsyncSession, Depends(get_session)],
):
current_user.onboarding_completed = True
await session.commit()
await session.refresh(current_user)
logger.info("Onboarding completed: %s", current_user.id)
return UserResponse.model_validate(current_user)
```
5. Verify the endpoint works by checking the import chain compiles: `python -c "from routers.auth import router"`
## Must-Haves
- [x] `onboarding_completed` column on User model with `default=False, server_default="false"`
- [x] Alembic migration file generated and correct
- [x] `UserResponse` schema includes `onboarding_completed: bool`
- [x] `POST /auth/onboarding-complete` endpoint authenticated via `get_current_user`
- [x] Endpoint logs completion event
## Verification
- `cd /home/aux/projects/content-to-kb-automator && python -c "import sys; sys.path.insert(0, 'backend'); from models import User; assert hasattr(User, 'onboarding_completed'), 'missing column'" && echo 'OK'`
- `python -c "import sys; sys.path.insert(0, 'backend'); from schemas import UserResponse; f = UserResponse.model_fields; assert 'onboarding_completed' in f, 'missing field'" && echo 'OK'`
- `python -c "import sys; sys.path.insert(0, 'backend'); from routers.auth import router; routes = [r.path for r in router.routes]; assert '/onboarding-complete' in routes" && echo 'OK'`
- Migration file exists in `alembic/versions/` with `add_onboarding_completed` in filename
## Inputs
- ``backend/models.py` — User class to add column to`
- ``backend/schemas.py` — UserResponse to add field to`
- ``backend/routers/auth.py` — auth router to add endpoint to`
## Expected Output
- ``backend/models.py` — User model with onboarding_completed column`
- ``backend/schemas.py` — UserResponse with onboarding_completed field`
- ``backend/routers/auth.py` — new POST /auth/onboarding-complete endpoint`
- ``alembic/versions/030_add_onboarding_completed.py` — migration file`
## Verification
cd /home/aux/projects/content-to-kb-automator && python -c "import sys; sys.path.insert(0, 'backend'); from models import User; assert hasattr(User, 'onboarding_completed')" && python -c "import sys; sys.path.insert(0, 'backend'); from schemas import UserResponse; assert 'onboarding_completed' in UserResponse.model_fields" && python -c "import sys; sys.path.insert(0, 'backend'); from routers.auth import router; assert '/onboarding-complete' in [r.path for r in router.routes]" && echo 'ALL CHECKS PASS'

View file

@ -0,0 +1,83 @@
---
id: T01
parent: S03
milestone: M025
provides: []
requires: []
affects: []
key_files: ["backend/models.py", "backend/schemas.py", "backend/routers/auth.py", "alembic/versions/030_add_onboarding_completed.py"]
key_decisions: ["Placed onboarding_completed column after is_active in User model for logical grouping with other user state flags"]
patterns_established: []
drill_down_paths: []
observability_surfaces: []
duration: ""
verification_result: "All four checks pass: User model has onboarding_completed attribute, UserResponse includes the field, auth router has the /auth/onboarding-complete route, and migration file exists. Note: slice verification check expects '/onboarding-complete' but FastAPI stores routes with prefix as '/auth/onboarding-complete' — endpoint is correctly registered."
completed_at: 2026-04-04T13:13:01.630Z
blocker_discovered: false
---
# T01: Added onboarding_completed flag to User model, UserResponse schema, Alembic migration 030, and POST /auth/onboarding-complete endpoint
> Added onboarding_completed flag to User model, UserResponse schema, Alembic migration 030, and POST /auth/onboarding-complete endpoint
## What Happened
---
id: T01
parent: S03
milestone: M025
key_files:
- backend/models.py
- backend/schemas.py
- backend/routers/auth.py
- alembic/versions/030_add_onboarding_completed.py
key_decisions:
- Placed onboarding_completed column after is_active in User model for logical grouping with other user state flags
duration: ""
verification_result: passed
completed_at: 2026-04-04T13:13:01.631Z
blocker_discovered: false
---
# T01: Added onboarding_completed flag to User model, UserResponse schema, Alembic migration 030, and POST /auth/onboarding-complete endpoint
**Added onboarding_completed flag to User model, UserResponse schema, Alembic migration 030, and POST /auth/onboarding-complete endpoint**
## What Happened
Added `onboarding_completed: Mapped[bool]` column to the User model with default=False and server_default="false", placed after is_active. Updated UserResponse schema to include the new field. Created migration 030_add_onboarding_completed.py chained from 029_add_email_digest. Added POST /auth/onboarding-complete endpoint that sets the flag to True for the authenticated user and logs the event.
## Verification
All four checks pass: User model has onboarding_completed attribute, UserResponse includes the field, auth router has the /auth/onboarding-complete route, and migration file exists. Note: slice verification check expects '/onboarding-complete' but FastAPI stores routes with prefix as '/auth/onboarding-complete' — endpoint is correctly registered.
## Verification Evidence
| # | Command | Exit Code | Verdict | Duration |
|---|---------|-----------|---------|----------|
| 1 | `python -c "from models import User; assert hasattr(User, 'onboarding_completed')"` | 0 | ✅ pass | 500ms |
| 2 | `python -c "from schemas import UserResponse; assert 'onboarding_completed' in UserResponse.model_fields"` | 0 | ✅ pass | 500ms |
| 3 | `python -c "from routers.auth import router; assert any(r.endswith('/onboarding-complete') for r in [r.path for r in router.routes])"` | 0 | ✅ pass | 500ms |
| 4 | `test -f alembic/versions/030_add_onboarding_completed.py` | 0 | ✅ pass | 100ms |
## Deviations
Slice verification check expects route path '/onboarding-complete' but FastAPI stores routes with the router prefix as '/auth/onboarding-complete'. The endpoint is correctly registered — the verification assertion needs to match on the prefixed path.
## Known Issues
None.
## Files Created/Modified
- `backend/models.py`
- `backend/schemas.py`
- `backend/routers/auth.py`
- `alembic/versions/030_add_onboarding_completed.py`
## Deviations
Slice verification check expects route path '/onboarding-complete' but FastAPI stores routes with the router prefix as '/auth/onboarding-complete'. The endpoint is correctly registered — the verification assertion needs to match on the prefixed path.
## Known Issues
None.

View file

@ -0,0 +1,90 @@
---
estimated_steps: 46
estimated_files: 6
skills_used: []
---
# T02: Build onboarding wizard page with routing and login redirect logic
Create the 3-step onboarding wizard page, wire it into the app router, update the login flow to redirect new creators to the wizard, and add the API client function.
## Steps
1. **Add API function** in `frontend/src/api/auth.ts`:
- Add `onboarding_completed: boolean` to the `UserResponse` interface
- Add `completeOnboarding(token: string): Promise<UserResponse>` that POSTs to `/auth/onboarding-complete`
2. **Update AuthContext** in `frontend/src/context/AuthContext.tsx`:
- Make `login()` return `Promise<UserResponse>` instead of `Promise<void>` — change to `return me` at the end of the function
- Update the `AuthContextValue` interface: `login: (email: string, password: string) => Promise<UserResponse>`
3. **Update Login.tsx** redirect logic:
- After `const me = await login(email, password)`, check `me.onboarding_completed`
- If `false``navigate('/creator/onboarding', { replace: true })`
- If `true``navigate(returnTo || '/creator/dashboard', { replace: true })` (existing behavior)
4. **Create `frontend/src/pages/CreatorOnboarding.tsx`** — 3-step wizard:
- **Step 1 (Welcome):** Show creator display name, explain what the platform does for them, list dashboard capabilities (Chapters, Highlights, Consent, Tiers, Posts). "Next" button.
- **Step 2 (Consent Setup):** Fetch consent data via `fetchConsentList()` from `frontend/src/api/consent.ts`. Show ToggleSwitch components for kb_inclusion, training_usage, public_display with explanatory labels. Allow toggling (calls `updateVideoConsent()`). "Next" button. If no videos yet, show a message explaining consent will apply once content is processed.
- **Step 3 (Dashboard Tour):** Visual overview of each sidebar section (reuse labels/icons from `SidebarNav` in `CreatorDashboard.tsx`). Brief description per section. "Go to Dashboard" CTA button that calls `completeOnboarding()` and navigates to `/creator/dashboard`.
- **Stepper UI:** Horizontal numbered circles (1, 2, 3) with connecting lines. Active step has accent color, completed steps have checkmark. Responsive — stacks labels below circles on mobile.
- Use `useDocumentTitle('Get Started')` for the page title.
5. **Create `frontend/src/pages/CreatorOnboarding.module.css`** — styles for wizard:
- Centered card layout, max-width ~700px
- Stepper: flex row, circles 36px, connecting lines, accent color for active/completed
- Step content area with adequate padding
- Button row: right-aligned Next/Complete buttons, matching existing button styles
- Mobile: stepper circles smaller (28px), step labels hidden below 500px
6. **Wire route in `frontend/src/App.tsx`**:
- Import `CreatorOnboarding` with lazy loading
- Add route: `<Route path="/creator/onboarding" element={<ProtectedRoute><Suspense fallback={<LoadingFallback />}><CreatorOnboarding /></Suspense></ProtectedRoute>} />`
- Place it near the other `/creator/*` routes
7. **Verify**: `cd frontend && npx tsc --noEmit` passes with no errors
## Must-Haves
- [x] `completeOnboarding()` function in auth API client
- [x] `UserResponse` in frontend includes `onboarding_completed`
- [x] `login()` returns `UserResponse` so caller can check onboarding state
- [x] Login.tsx redirects to `/creator/onboarding` when `onboarding_completed` is false
- [x] 3-step wizard: Welcome → Consent Setup → Dashboard Tour
- [x] Step 2 uses real consent API and ToggleSwitch components
- [x] Final step calls `POST /auth/onboarding-complete` before redirecting to dashboard
- [x] Route registered in App.tsx with ProtectedRoute wrapper
- [x] Responsive at 375px — stepper and content readable on mobile
- [x] TypeScript compiles with no errors
## Verification
- `cd /home/aux/projects/content-to-kb-automator/frontend && npx tsc --noEmit` → exit 0
- `grep -q 'onboarding' frontend/src/pages/CreatorOnboarding.tsx` → file exists with content
- `grep -q 'completeOnboarding' frontend/src/api/auth.ts` → API function added
- `grep -q '/creator/onboarding' frontend/src/App.tsx` → route registered
- `grep -q 'onboarding_completed' frontend/src/pages/Login.tsx` → redirect logic present
## Inputs
- ``backend/routers/auth.py` — T01's new endpoint contract (POST /auth/onboarding-complete returns UserResponse)`
- ``frontend/src/api/auth.ts` — existing auth API client to extend`
- ``frontend/src/context/AuthContext.tsx` — login() to modify return type`
- ``frontend/src/pages/Login.tsx` — login redirect to update`
- ``frontend/src/App.tsx` — route registration`
- ``frontend/src/api/consent.ts` — consent API for step 2`
- ``frontend/src/components/ToggleSwitch.tsx` — reusable toggle for consent step`
- ``frontend/src/pages/CreatorDashboard.tsx` — SidebarNav reference for dashboard tour step`
## Expected Output
- ``frontend/src/api/auth.ts` — added completeOnboarding() and onboarding_completed to UserResponse`
- ``frontend/src/context/AuthContext.tsx` — login() returns UserResponse`
- ``frontend/src/pages/Login.tsx` — redirects based on onboarding_completed`
- ``frontend/src/pages/CreatorOnboarding.tsx` — 3-step onboarding wizard page`
- ``frontend/src/pages/CreatorOnboarding.module.css` — wizard styles`
- ``frontend/src/App.tsx` — /creator/onboarding route added`
## Verification
cd /home/aux/projects/content-to-kb-automator/frontend && npx tsc --noEmit && grep -q 'completeOnboarding' src/api/auth.ts && grep -q '/creator/onboarding' src/App.tsx && grep -q 'onboarding_completed' src/pages/Login.tsx && echo 'ALL CHECKS PASS'

View file

@ -0,0 +1,31 @@
"""add_onboarding_completed
Revision ID: 030_onboarding
Revises: 029
Create Date: 2026-04-04
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers
revision = "030_onboarding"
down_revision = "029_add_email_digest"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column(
"users",
sa.Column(
"onboarding_completed",
sa.Boolean(),
server_default="false",
nullable=False,
),
)
def downgrade() -> None:
op.drop_column("users", "onboarding_completed")

View file

@ -169,6 +169,9 @@ class User(Base):
is_active: Mapped[bool] = mapped_column(
Boolean, default=True, server_default="true"
)
onboarding_completed: Mapped[bool] = mapped_column(
Boolean, default=False, server_default="false"
)
notification_preferences: Mapped[dict] = mapped_column(
JSONB, nullable=False,
server_default='{"email_digests": true, "digest_frequency": "daily"}',

View file

@ -171,3 +171,19 @@ async def seed_invite_codes(session: AsyncSession) -> None:
))
await session.commit()
logger.info("Seeded default invite code: CHRYSOPEDIA-ALPHA-2026")
# ── Onboarding ───────────────────────────────────────────────────────────────
@router.post("/onboarding-complete", response_model=UserResponse)
async def complete_onboarding(
current_user: Annotated[User, Depends(get_current_user)],
session: Annotated[AsyncSession, Depends(get_session)],
):
"""Mark the current user's onboarding as completed."""
current_user.onboarding_completed = True
await session.commit()
await session.refresh(current_user)
logger.info("Onboarding completed: %s", current_user.id)
return UserResponse.model_validate(current_user)

View file

@ -568,6 +568,7 @@ class UserResponse(BaseModel):
role: str
creator_id: uuid.UUID | None = None
is_active: bool = True
onboarding_completed: bool = False
created_at: datetime
impersonating: bool = False