fix(FN-2704): gate auth onboarding behind setup wizard

- Prevent useAuthOnboarding from opening model onboarding while the setup wizard is active
- Add app-level wiring so onboarding flow respects setup wizard visibility state
- Add regression tests covering setup wizard gating and non-stacking onboarding behavior
This commit is contained in:
Fusion
2026-04-27 09:56:05 -07:00
committed by gsxdsm
parent 637f4350c9
commit bd59b1ff99
3 changed files with 78 additions and 1 deletions

View File

@@ -393,6 +393,7 @@ function AppInner() {
// Auth and onboarding bootstrap logic extracted to a dedicated hook.
useAuthOnboarding({
projectId: currentProject?.id,
setupWizardOpen: modalManager.setupWizardOpen,
openModelOnboarding: modalManager.openModelOnboarding,
openSettings: modalManager.openSettings,
});

View File

@@ -49,6 +49,7 @@ describe("useAuthOnboarding", () => {
renderHook(() =>
useAuthOnboarding({
projectId: "proj_123",
setupWizardOpen: false,
openModelOnboarding,
openSettings,
}),
@@ -76,6 +77,7 @@ describe("useAuthOnboarding", () => {
renderHook(() =>
useAuthOnboarding({
projectId: "proj_123",
setupWizardOpen: false,
openModelOnboarding,
openSettings,
}),
@@ -101,6 +103,7 @@ describe("useAuthOnboarding", () => {
renderHook(() =>
useAuthOnboarding({
projectId: "proj_123",
setupWizardOpen: false,
openModelOnboarding,
openSettings,
}),
@@ -127,6 +130,7 @@ describe("useAuthOnboarding", () => {
renderHook(() =>
useAuthOnboarding({
projectId: "proj_123",
setupWizardOpen: false,
openModelOnboarding,
openSettings,
}),
@@ -152,6 +156,7 @@ describe("useAuthOnboarding", () => {
renderHook(() =>
useAuthOnboarding({
projectId: "proj_123",
setupWizardOpen: false,
openModelOnboarding,
openSettings,
}),
@@ -173,6 +178,7 @@ describe("useAuthOnboarding", () => {
renderHook(() =>
useAuthOnboarding({
projectId: "proj_123",
setupWizardOpen: false,
openModelOnboarding,
openSettings,
}),
@@ -187,6 +193,66 @@ describe("useAuthOnboarding", () => {
expect(mockFetchGlobalSettings).not.toHaveBeenCalled();
});
it("does not auto-trigger model onboarding when setupWizardOpen is true", async () => {
mockFetchAuthStatus.mockResolvedValue({
providers: [{ id: "openai", name: "OpenAI", authenticated: false }],
});
mockFetchGlobalSettings.mockResolvedValue({
modelOnboardingComplete: false,
defaultProvider: undefined,
defaultModelId: undefined,
} as never);
renderHook(() =>
useAuthOnboarding({
projectId: "proj_123",
setupWizardOpen: true,
openModelOnboarding,
openSettings,
}),
);
await waitFor(() => {
expect(openModelOnboarding).not.toHaveBeenCalled();
expect(mockFetchAuthStatus).not.toHaveBeenCalled();
});
});
it("auto-triggers model onboarding when setupWizardOpen transitions from true to false", async () => {
mockFetchAuthStatus.mockResolvedValue({
providers: [{ id: "openai", name: "OpenAI", authenticated: false }],
});
mockFetchGlobalSettings.mockResolvedValue({
modelOnboardingComplete: false,
defaultProvider: undefined,
defaultModelId: undefined,
} as never);
const { rerender } = renderHook(
({ setupWizardOpen }: { setupWizardOpen: boolean }) =>
useAuthOnboarding({
projectId: "proj_123",
setupWizardOpen,
openModelOnboarding,
openSettings,
}),
{
initialProps: { setupWizardOpen: true },
},
);
await waitFor(() => {
expect(openModelOnboarding).not.toHaveBeenCalled();
expect(mockFetchAuthStatus).not.toHaveBeenCalled();
});
rerender({ setupWizardOpen: false });
await waitFor(() => {
expect(openModelOnboarding).toHaveBeenCalledTimes(1);
});
});
// --- One-shot guard ---
it("does not re-trigger onboarding when projectId changes after initial bootstrap", async () => {
@@ -204,6 +270,7 @@ describe("useAuthOnboarding", () => {
({ projectId }: { projectId: string }) =>
useAuthOnboarding({
projectId,
setupWizardOpen: false,
openModelOnboarding,
openSettings,
}),
@@ -238,6 +305,7 @@ describe("useAuthOnboarding", () => {
({ open }: { open: () => void }) =>
useAuthOnboarding({
projectId: "proj_123",
setupWizardOpen: false,
openModelOnboarding: open,
openSettings,
}),
@@ -273,6 +341,7 @@ describe("useAuthOnboarding", () => {
({ open }: { open: (section?: string) => void }) =>
useAuthOnboarding({
projectId: "proj_123",
setupWizardOpen: false,
openModelOnboarding,
openSettings: open,
}),
@@ -311,6 +380,7 @@ describe("useAuthOnboarding", () => {
renderHook(() =>
useAuthOnboarding({
projectId: "proj_123",
setupWizardOpen: false,
openModelOnboarding,
openSettings,
}),
@@ -341,6 +411,7 @@ describe("useAuthOnboarding", () => {
renderHook(() =>
useAuthOnboarding({
projectId: "proj_123",
setupWizardOpen: false,
openModelOnboarding,
openSettings,
}),

View File

@@ -6,6 +6,7 @@ import type { SectionId } from "../components/SettingsModal";
export interface UseAuthOnboardingOptions {
projectId?: string;
setupWizardOpen: boolean;
openModelOnboarding: () => void;
openSettings: (section?: SectionId) => void;
}
@@ -24,6 +25,7 @@ export interface UseAuthOnboardingOptions {
*/
export function useAuthOnboarding({
projectId,
setupWizardOpen,
openModelOnboarding,
openSettings,
}: UseAuthOnboardingOptions): void {
@@ -32,6 +34,9 @@ export function useAuthOnboarding({
const hasTriggeredRef = useRef(false);
useEffect(() => {
// Defer auto-triggering while setup wizard is open.
// Important: this must run before consuming the one-shot flag.
if (setupWizardOpen) return;
// Skip if we've already triggered (one-shot guard)
if (hasTriggeredRef.current) return;
// Mark as triggered immediately to prevent any race condition on re-runs
@@ -82,5 +87,5 @@ export function useAuthOnboarding({
// Fail silently - non-blocking behavior preserves dashboard usability.
// Onboarding can be manually triggered later via Settings if needed.
});
}, [projectId, openModelOnboarding, openSettings]);
}, [projectId, setupWizardOpen, openModelOnboarding, openSettings]);
}