fix(FN-000): address onboarding review feedback
This commit is contained in:
@@ -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({
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<Suspense fallback={null}>
|
||||
<Suspense fallback={(
|
||||
<div className="wizard-error setup-wizard-agent-interview-error" role="status">
|
||||
{t("setup.firstAgentInterviewLoading", "Loading AI Interview...")}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<ExperimentalAgentOnboardingModal
|
||||
isOpen={isAgentInterviewOpen}
|
||||
onClose={() => setIsAgentInterviewOpen(false)}
|
||||
|
||||
@@ -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({
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<Suspense fallback={null}>
|
||||
<Suspense fallback={(
|
||||
<div className="wizard-error setup-wizard-agent-interview-error" role="status">
|
||||
{t("setup.firstAgentInterviewLoading", "Loading AI Interview...")}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<ExperimentalAgentOnboardingModal
|
||||
isOpen={isInterviewOpen}
|
||||
onClose={() => setIsInterviewOpen(false)}
|
||||
|
||||
@@ -127,6 +127,36 @@ vi.mock("../ProviderIcon", () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("../ExperimentalAgentOnboardingModal", () => ({
|
||||
ExperimentalAgentOnboardingModal: ({ isOpen, onClose, onUseDraft }: { isOpen: boolean; onClose: () => void; onUseDraft: (draft: any) => void }) => (
|
||||
isOpen ? (
|
||||
<div data-testid="agent-interview-modal">
|
||||
AI Interview Modal
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onUseDraft({
|
||||
name: "Launch Coordinator",
|
||||
title: "Launch Planning Agent",
|
||||
icon: "◇",
|
||||
role: "not-a-real-role",
|
||||
instructionsText: "Coordinate launch tasks.",
|
||||
soul: "Strategic launch planner.",
|
||||
skills: ["planning", "review"],
|
||||
runtimeHint: "codex-local",
|
||||
maxTurns: 24,
|
||||
thinkingLevel: "medium",
|
||||
});
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
Use Draft
|
||||
</button>
|
||||
</div>
|
||||
) : null
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock lucide-react icons - preserve actual icons for other components
|
||||
vi.mock("lucide-react", async (importOriginal) => {
|
||||
const actual = await importOriginal() as Record<string, unknown>;
|
||||
@@ -3250,6 +3280,35 @@ describe("ModelOnboardingModal", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps one agent template tabbable after applying an AI draft", async () => {
|
||||
render(
|
||||
<ModelOnboardingModal
|
||||
onComplete={vi.fn()}
|
||||
addToast={vi.fn()}
|
||||
projectId="proj_123"
|
||||
agentOnboardingEnabled
|
||||
/>,
|
||||
);
|
||||
|
||||
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({
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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",
|
||||
|
||||
1
packages/i18n/src/resources.d.ts
vendored
1
packages/i18n/src/resources.d.ts
vendored
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user