fix(FN-2402): preserve onboarding stepper done state on completion

- Treat the completion view as an effective final step so all prior indicators/connectors remain marked done
- Mark the first-task step as completed before tracking onboarding completion and clear it from skipped state
- Replace hardcoded success colors in setup wizard stepper done styles with theme-aware done status tokens
- Add regression assertions verifying all step indicators and connectors stay in done state on the completion screen
This commit is contained in:
Fusion
2026-04-24 21:48:08 -07:00
committed by gsxdsm
parent 2ef472f551
commit 554353b8c6
3 changed files with 26 additions and 9 deletions

View File

@@ -614,8 +614,10 @@ export function ModelOnboardingModal({
{ key: "first-task" as const, label: "First Task" },
];
// Get current step index for progress indicator
// Get current step index for progress indicator.
// Keep the stepper in a fully-complete visual state when the completion screen is shown.
const currentStepIndex = steps.findIndex((s) => s.key === step);
const effectiveStepIndex = step === "complete" ? steps.length : currentStepIndex;
// Persist step state whenever it changes (for resume functionality)
useEffect(() => {
@@ -1327,10 +1329,14 @@ export function ModelOnboardingModal({
// Complete onboarding
const handleComplete = useCallback(async () => {
const nextCompletedSteps = [...new Set([...completedSteps, "first-task"])] as OnboardingStep[];
setSaving(true);
try {
await completeOnboarding();
trackOnboardingEvent("onboarding:completed", { completedSteps, skippedSteps });
trackOnboardingEvent("onboarding:completed", { completedSteps: nextCompletedSteps, skippedSteps });
setCompletedSteps(nextCompletedSteps);
setSkippedSteps((prev) => prev.filter((s) => s !== "first-task"));
setStep("complete");
} finally {
setSaving(false);
@@ -1757,9 +1763,10 @@ export function ModelOnboardingModal({
{/* Step indicator - 3 progress steps + complete */}
<div className="model-onboarding-steps">
{steps.map((s, index) => {
// A step is done if it's in completedSteps AND is before current position
const isDone = completedSteps.includes(s.key) && currentStepIndex > index;
const isSkipped = skippedSteps.includes(s.key) && !completedSteps.includes(s.key) && currentStepIndex > index;
// A step is done/skipped only once we have progressed beyond it.
const hasProgressedPastStep = effectiveStepIndex > index;
const isDone = completedSteps.includes(s.key) && hasProgressedPastStep;
const isSkipped = skippedSteps.includes(s.key) && !completedSteps.includes(s.key) && hasProgressedPastStep;
// Clickable if it's a completed/skipped step (can review)
const isClickable = isDone || isSkipped;
return (
@@ -1767,7 +1774,7 @@ export function ModelOnboardingModal({
{index > 0 && (
<div
className={`model-onboarding-step-connector ${
index <= currentStepIndex ? "done" : ""
index <= effectiveStepIndex ? "done" : ""
}`}
/>
)}

View File

@@ -565,8 +565,9 @@
}
.model-onboarding-step-indicator.done .step-number {
background: var(--success, #22c55e);
color: #fff;
background: var(--status-done-bg-deep);
border: 1px solid color-mix(in srgb, var(--done) 45%, transparent);
color: var(--done);
}
.model-onboarding-step-indicator.done {
@@ -618,7 +619,7 @@
}
.model-onboarding-step-connector.done {
background: var(--success, #22c55e);
background: var(--done);
}
/* Content area */

View File

@@ -2094,6 +2094,15 @@ describe("ModelOnboardingModal", () => {
expect(screen.getByText("All Set!")).toBeTruthy();
});
// Completed indicators should remain in done state on the completion screen.
const aiSetupIndicator = screen.getByRole("button", { name: "Go back to AI Setup" });
const githubIndicator = screen.getByRole("button", { name: "Go back to GitHub" });
const firstTaskIndicator = screen.getByRole("button", { name: "Go back to First Task" });
expect(aiSetupIndicator).toHaveClass("done");
expect(githubIndicator).toHaveClass("done");
expect(firstTaskIndicator).toHaveClass("done");
expect(document.querySelectorAll(".model-onboarding-step-connector.done")).toHaveLength(2);
// Click Get Started to close
fireEvent.click(screen.getByText("Get Started"));
expect(onComplete).toHaveBeenCalled();