From 3c49f591b72842bccb60761610f8aa8ab33fc66f Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Mon, 22 Jun 2026 09:13:58 -0700 Subject: [PATCH] fix(FN-000): address onboarding review feedback --- .../app/components/ModelOnboardingModal.tsx | 14 ++++- .../app/components/SetupWizardModal.tsx | 18 +++++- .../__tests__/ModelOnboardingModal.test.tsx | 59 +++++++++++++++++++ .../__tests__/SetupWizardModal.test.tsx | 3 + packages/i18n/locales/en/app.json | 1 + packages/i18n/src/resources.d.ts | 1 + 6 files changed, 92 insertions(+), 4 deletions(-) diff --git a/packages/dashboard/app/components/ModelOnboardingModal.tsx b/packages/dashboard/app/components/ModelOnboardingModal.tsx index 59225ad140..e597ea1bb3 100644 --- a/packages/dashboard/app/components/ModelOnboardingModal.tsx +++ b/packages/dashboard/app/components/ModelOnboardingModal.tsx @@ -1807,6 +1807,11 @@ export function ModelOnboardingModal({ const hasAiProvider = connectedAiProviders.length > 0; const hasProjectSelected = Boolean(projectId); const selectedAgentPreset = selectedAgentPresetId ? getPresetById(selectedAgentPresetId) : undefined; + /* + FNXC:Onboarding 2026-06-22-06:03: + AI-generated agent drafts are custom and should not appear selected as a template, but the template radiogroup still needs one tabbable item for keyboard users. + */ + const agentPresetTabStopId = selectedAgentPresetId || ceoPreset.id; const isAgentActionDisabled = isCreatingAgent || !hasProjectSelected; // True when on GitHub step but skipped AI setup (no AI provider connected) const aiSetupSkipped = step === "github" && !hasAiProvider; @@ -2832,7 +2837,7 @@ export function ModelOnboardingModal({ role="radio" aria-checked={selected} aria-label={selected ? t("setup.selectedAgentTemplate", "{{name}} selected", { name: preset.name }) : preset.name} - tabIndex={selected ? 0 : -1} + tabIndex={preset.id === agentPresetTabStopId ? 0 : -1} data-model-onboarding-agent-preset-id={preset.id} disabled={isAgentActionDisabled} onClick={() => handleAgentPresetSelect(preset.id)} @@ -3185,7 +3190,12 @@ export function ModelOnboardingModal({ )} > - + + {t("setup.firstAgentInterviewLoading", "Loading AI Interview...")} + + )} + > setIsAgentInterviewOpen(false)} diff --git a/packages/dashboard/app/components/SetupWizardModal.tsx b/packages/dashboard/app/components/SetupWizardModal.tsx index f8c8a1eed1..324da042dc 100644 --- a/packages/dashboard/app/components/SetupWizardModal.tsx +++ b/packages/dashboard/app/components/SetupWizardModal.tsx @@ -158,6 +158,10 @@ export function SetupWizardModal({ const result = await registerProject(input); if (!includeAgentStep) { + setState((prev) => ({ + ...prev, + isRegistering: false, + })); onProjectRegistered(result); return; } @@ -267,6 +271,11 @@ export function SetupWizardModal({ ? getPresetById(state.selectedPresetId) : undefined; const isAgentActionDisabled = state.isCreatingAgent; + /* + FNXC:Onboarding 2026-06-22-06:03: + AI-generated agent drafts are custom and should not appear selected as a template, but the template radiogroup still needs one tabbable item for keyboard users. + */ + const agentPresetTabStopId = state.selectedPresetId || ceoPreset.id; /* FNXC:Onboarding 2026-06-22-05:37: The optional project-agent step needs more horizontal room than project details so templates and preview can be compared side by side. @@ -510,7 +519,7 @@ export function SetupWizardModal({ role="radio" aria-checked={selected} aria-label={selected ? t("setup.selectedAgentTemplate", "{{name}} selected", { name: preset.name }) : preset.name} - tabIndex={selected ? 0 : -1} + tabIndex={preset.id === agentPresetTabStopId ? 0 : -1} data-agent-preset-id={preset.id} disabled={isAgentActionDisabled} onClick={() => handlePresetSelect(preset.id)} @@ -672,7 +681,12 @@ export function SetupWizardModal({ )} > - + + {t("setup.firstAgentInterviewLoading", "Loading AI Interview...")} + + )} + > setIsInterviewOpen(false)} diff --git a/packages/dashboard/app/components/__tests__/ModelOnboardingModal.test.tsx b/packages/dashboard/app/components/__tests__/ModelOnboardingModal.test.tsx index b5f4ace23b..47f9015f1d 100644 --- a/packages/dashboard/app/components/__tests__/ModelOnboardingModal.test.tsx +++ b/packages/dashboard/app/components/__tests__/ModelOnboardingModal.test.tsx @@ -127,6 +127,36 @@ vi.mock("../ProviderIcon", () => ({ ), })); +vi.mock("../ExperimentalAgentOnboardingModal", () => ({ + ExperimentalAgentOnboardingModal: ({ isOpen, onClose, onUseDraft }: { isOpen: boolean; onClose: () => void; onUseDraft: (draft: any) => void }) => ( + isOpen ? ( +
+ AI Interview Modal + +
+ ) : null + ), +})); + // Mock lucide-react icons - preserve actual icons for other components vi.mock("lucide-react", async (importOriginal) => { const actual = await importOriginal() as Record; @@ -3250,6 +3280,35 @@ describe("ModelOnboardingModal", () => { }); }); + it("keeps one agent template tabbable after applying an AI draft", async () => { + render( + , + ); + + await navigateToProjectSetupStep(); + fireEvent.click(screen.getByText("Next →")); + + await waitFor(() => { + expect(screen.getByText("Create Your First Agent")).toBeTruthy(); + }); + + fireEvent.click(screen.getByText("AI Interview")); + expect(await screen.findByTestId("agent-interview-modal")).toBeTruthy(); + + fireEvent.click(screen.getByText("Use Draft")); + + expect(await screen.findByText("Launch Coordinator")).toBeTruthy(); + expect(screen.getByText("Launch Planning Agent")).toBeTruthy(); + const ceoRadio = screen.getByRole("radio", { name: "CEO" }); + expect(ceoRadio).toHaveAttribute("tabIndex", "0"); + expect(ceoRadio).toHaveAttribute("aria-checked", "false"); + }); + it("allows completing full onboarding flow without any setup", async () => { // All providers not authenticated, no model selected mockFetchAuthStatus.mockResolvedValueOnce({ diff --git a/packages/dashboard/app/components/__tests__/SetupWizardModal.test.tsx b/packages/dashboard/app/components/__tests__/SetupWizardModal.test.tsx index 78d727f424..37a4855b1f 100644 --- a/packages/dashboard/app/components/__tests__/SetupWizardModal.test.tsx +++ b/packages/dashboard/app/components/__tests__/SetupWizardModal.test.tsx @@ -636,6 +636,9 @@ describe("SetupWizardModal", () => { expect(await screen.findByText("Launch Coordinator")).toBeDefined(); expect(screen.getByText("Launch Planning Agent")).toBeDefined(); + const ceoRadio = screen.getByRole("radio", { name: "CEO" }); + expect(ceoRadio).toHaveAttribute("tabIndex", "0"); + expect(ceoRadio).toHaveAttribute("aria-checked", "false"); expect(mockCreateAgent).not.toHaveBeenCalled(); fireEvent.click(screen.getByText("Create Agent")); diff --git a/packages/i18n/locales/en/app.json b/packages/i18n/locales/en/app.json index 2e6f565b83..d14b252c48 100644 --- a/packages/i18n/locales/en/app.json +++ b/packages/i18n/locales/en/app.json @@ -6598,6 +6598,7 @@ "firstAgentDraftName": "Draft agent", "firstAgentContinueWithTemplates": "Continue with templates", "firstAgentInterviewLoadError": "AI interview could not load. You can still create an agent from a template or skip this step.", + "firstAgentInterviewLoading": "Loading AI Interview...", "firstAgentIntro": "Agents are optional. Fusion can build tasks without one by starting temporary agents for planning, coding, review, and merge. Create an agent only if you want help coordinating tasks and direction.", "firstAgentNoInstructions": "No inline instructions yet", "firstAgentPreview": "Preview", diff --git a/packages/i18n/src/resources.d.ts b/packages/i18n/src/resources.d.ts index 23895ce268..e5c0b53871 100644 --- a/packages/i18n/src/resources.d.ts +++ b/packages/i18n/src/resources.d.ts @@ -6602,6 +6602,7 @@ export default interface Resources { "firstAgentCustomDraft": "Custom agent draft", "firstAgentDraftName": "Draft agent", "firstAgentInterviewLoadError": "AI interview could not load. You can still create an agent from a template or skip this step.", + "firstAgentInterviewLoading": "Loading AI Interview...", "firstAgentIntro": "Agents are optional. Fusion can build tasks without one by starting temporary agents for planning, coding, review, and merge. Create an agent only if you want help coordinating tasks and direction.", "firstAgentNoInstructions": "No inline instructions yet", "firstAgentPreview": "Preview",