6.5 KiB
Onboarding Theme Persistence & Progress Bar Fix — Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Fix two independent bugs in the onboarding flow — theme not persisted to DB on selection, and progress bar not reflecting the user's true position in the full 15-step flow.
Architecture: Two surgical edits in two separate files. Task 1 adds a single authClient.updateUser({ theme }) call in StepPreferences. Task 2 replaces OnboardJS-relative progress values in OnboardingStepper with a manual calculation based on STEP_ORDER.
Tech Stack: Next.js (App Router), React, TypeScript, OnboardJS (@onboardjs/react), next-themes, better-auth (authClient)
Global Constraints
- TypeScript — no
anyintroduced, no type assertions added - No new dependencies
- No API, schema, or DB migration changes
- Follow existing import alias
@/for all internal imports - Commits in English, conventional-commits format (
fix:)
Task 1: Persist theme to DB on selection in StepPreferences
Files:
- Modify:
src/features/onboarding/steps/step-preferences.tsx(lines ~51-61)
Interfaces:
-
Consumes:
authClient.updateUserfrom@/lib/auth/auth-client(already imported in the file) -
Produces: nothing consumed by Task 2
-
Step 1: Locate the
selectThemefunctionOpen
src/features/onboarding/steps/step-preferences.tsx. Find theselectThemefunction (~line 51). It currently reads:const selectTheme = async (theme: ThemeKey) => { // Apply immediately to the UI setTheme(theme); //mettre à jour aussi en db! await updateContext({ flowData: { ...state?.context.flowData, preferences: { ...preferences, theme }, }, }); }; -
Step 2: Add
authClient.updateUser({ theme })callReplace the
selectThemefunction with:const selectTheme = async (theme: ThemeKey) => { // Apply immediately to the UI setTheme(theme); // Persist to DB so it survives page reload await authClient.updateUser({ theme }); await updateContext({ flowData: { ...state?.context.flowData, preferences: { ...preferences, theme }, }, }); };Note:
authClientis already imported at the top of the file (import { authClient } from "@/lib/auth/auth-client";). No new import needed. -
Step 3: Verify TypeScript compiles cleanly
cd /path/to/portabase && pnpm tsc --noEmit 2>&1 | grep step-preferencesExpected: no output (no errors on that file).
-
Step 4: Manual smoke test
- Start the dev server:
pnpm dev - Open the onboarding flow in the browser
- Navigate to the Preferences step
- Click a theme (e.g. Light)
- Verify the UI switches immediately
- Open the Network tab → confirm a
PATCH(orPOST) request to the auth user update endpoint was made withtheme: "light"in the payload - Hard-reload the page → theme should persist
- Start the dev server:
-
Step 5: Commit
git add src/features/onboarding/steps/step-preferences.tsx git commit -m "fix(onboarding): persist theme to db on selection in StepPreferences"
Task 2: Fix progress bar to reflect full 15-step position
Files:
- Modify:
src/features/onboarding/onboarding-stepper.tsx
Interfaces:
-
Consumes:
STEP_ORDERfrom@/features/onboarding/constants/steps(already exported, already used inonboarding-shell.tsx) -
Produces: nothing consumed by Task 1
-
Step 1: Open the current stepper
Open
src/features/onboarding/onboarding-stepper.tsx. It currently reads:"use client"; import { useOnboarding } from "@onboardjs/react"; import { Progress } from "@/components/ui/progress"; export const OnboardingStepper = () => { const { state } = useOnboarding(); if (!state) return null; return ( <div className="flex flex-col gap-2 w-full"> <div className="flex justify-between text-xs text-muted-foreground"> <span> Step {state.currentStepNumber} of {state.totalSteps} </span> <span>{Math.round(state.progressPercentage)}%</span> </div> <Progress value={state.progressPercentage} /> </div> ); }; -
Step 2: Replace with STEP_ORDER-based calculation
Replace the entire file content with:
"use client"; import { useOnboarding } from "@onboardjs/react"; import { Progress } from "@/components/ui/progress"; import { STEP_ORDER } from "@/features/onboarding/constants/steps"; export const OnboardingStepper = () => { const { state } = useOnboarding(); if (!state) return null; const currentId = String(state.currentStep?.id ?? ""); const currentIndex = Math.max(0, STEP_ORDER.indexOf(currentId)); const totalSteps = STEP_ORDER.length; const stepNumber = currentIndex + 1; const progress = Math.round((currentIndex / (totalSteps - 1)) * 100); return ( <div className="flex flex-col gap-2 w-full"> <div className="flex justify-between text-xs text-muted-foreground"> <span> Step {stepNumber} of {totalSteps} </span> <span>{progress}%</span> </div> <Progress value={progress} /> </div> ); };Why
Math.max(0, ...): IfcurrentIdis not inSTEP_ORDER(unknown step),indexOfreturns-1. Clamping to0gives a safe fallback of "Step 1 of 15 — 0%" rather than a negative/NaN value.Why
totalSteps - 1in the divisor: Atlogin(index 0) → 0%. Atfinish(index 14) → 14/14 = 100%. Without the-1the last step would be 93%. -
Step 3: Verify TypeScript compiles cleanly
pnpm tsc --noEmit 2>&1 | grep onboarding-stepperExpected: no output.
-
Step 4: Manual smoke test
- Simulate resuming onboarding mid-flow (e.g. log out, log back in as a user who already has an org — should resume at
invite-members, index 5) - Verify the stepper shows "Step 6 of 15 — 36%" and the progress bar is ~1/3 filled
- Click through a few steps and verify the number and percentage increase correctly each time
- Reach the
finishstep and verify "Step 15 of 15 — 100%"
- Simulate resuming onboarding mid-flow (e.g. log out, log back in as a user who already has an org — should resume at
-
Step 5: Commit
git add src/features/onboarding/onboarding-stepper.tsx git commit -m "fix(onboarding): recalculate progress bar from full STEP_ORDER position"