fix(onboarding): simplify first-run project setup

This commit is contained in:
gsxdsm
2026-06-22 05:20:22 -07:00
parent 6fe4b2dffc
commit 1432724a2b
42 changed files with 145 additions and 425 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

View File

@@ -475,11 +475,13 @@ export function AppModals({
onProjectRegistered={projectActions.handleSetupComplete}
onClose={closeSetupWizardWithNav}
agentOnboardingEnabled={agentOnboardingEnabled}
includeAgentStep={!modalManager.modelOnboardingOpen}
/>
</Suspense>
)}
{modalManager.modelOnboardingOpen && (
{/* FNXC:Onboarding 2026-06-22-05:06: Brand-new onboarding owns AI/GitHub first, then opens the project setup wizard only as the Project step sub-flow. Hide model onboarding while that project wizard is mounted so users never see both flows at once. */}
{modalManager.modelOnboardingOpen && !modalManager.setupWizardOpen && (
<ModelOnboardingModal
onComplete={projectActions.handleModelOnboardingComplete}
addToast={addToast}

View File

@@ -569,7 +569,8 @@ const MAX_POLL_CYCLES = 150;
* 1. AI Setup - Provider credential setup (OAuth login or API key entry) and default model selection
* 2. GitHub (Optional) - GitHub connection status and login
* 3. Project Setup - Register a project directory (or clone a repository URL via setup wizard)
* 4. First Task - CTA to create first task or import from GitHub
* 4. Agent - Optional persistent coordinating agent from a template or AI-generated draft
* 5. First Task - CTA to create first task or import from GitHub
*
* Dismissing the modal marks onboarding as complete to prevent repeated popups.
*/

View File

@@ -4,7 +4,6 @@ import { X, Loader2, CheckCircle, ChevronRight, Sparkles } from "lucide-react";
import { useTranslation } from "react-i18next";
import type { AgentOnboardingSummary, ProjectInfo, ProjectCreateInput } from "../api";
import { createAgent, registerProject } from "../api";
import { getAuthToken, setAuthToken, clearAuthToken } from "../auth";
import { DirectoryPicker } from "./DirectoryPicker";
import { suggestProjectName } from "../utils/projectDetection";
import { useNodes } from "../hooks/useNodes";
@@ -29,9 +28,11 @@ export interface SetupWizardModalProps {
onClose?: () => void;
/** Enables the existing AI interview entry point for first-agent drafting. */
agentOnboardingEnabled?: boolean;
/** When false, register the project and return control to a parent onboarding flow. */
includeAgentStep?: boolean;
}
type WizardStep = "auth" | "manual" | "agent" | "complete";
type WizardStep = "manual" | "agent" | "complete";
type ManualSetupMode = "existing" | "clone";
type AgentOutcome = "created" | "skipped" | null;
@@ -54,22 +55,27 @@ interface WizardState {
}
/**
* Setup wizard for first-run project registration.
* Setup wizard for project registration.
*
* Provides a polished onboarding experience with a directory picker
* for selecting the project directory and auto-name suggestion.
* Provides a focused project-details -> project-agent flow with a directory
* picker for selecting the project directory and auto-name suggestion.
*/
export function SetupWizardModal({
onProjectRegistered,
onClose,
agentOnboardingEnabled = false,
includeAgentStep = true,
}: SetupWizardModalProps) {
const { t } = useTranslation("app");
const helpUrl = "https://discord.gg/ksrfuy7WYR";
/*
FNXC:Onboarding 2026-06-22-03:11:
First-run project setup must offer a first persistent agent after registration, defaulting to the CEO preset while still letting users choose another template or skip creation.
New-project setup must collect project details first, then offer a project-specific persistent agent after registration, defaulting to the CEO preset while still letting users choose another template or skip creation.
The AI interview entry point is feature-flagged by `agentOnboardingEnabled`; preset creation and skip remain available without it.
FNXC:Onboarding 2026-06-22-05:16:
Brand-new onboarding already has its own Agent step after AI, GitHub, and Project setup.
When this wizard is opened as that Project sub-flow, register the project and return immediately so users do not see two agent prompts.
*/
const ceoPreset = useMemo(
() => getPresetById("ceo") ?? AGENT_PRESETS[0]!,
@@ -77,7 +83,7 @@ export function SetupWizardModal({
);
const [isOpen, setIsOpen] = useState(true);
const [state, setState] = useState<WizardState>(() => ({
step: getAuthToken() ? "manual" : "auth",
step: "manual",
manualMode: "existing",
manualPath: "",
manualCloneUrl: "",
@@ -94,8 +100,6 @@ export function SetupWizardModal({
agentOutcome: null,
}));
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
const [authTokenInput, setAuthTokenInput] = useState("");
const [storedAuthToken, setStoredAuthToken] = useState(() => getAuthToken());
const [isInterviewOpen, setIsInterviewOpen] = useState(false);
const agentErrorRef = useRef<HTMLDivElement | null>(null);
@@ -153,6 +157,11 @@ export function SetupWizardModal({
const result = await registerProject(input);
if (!includeAgentStep) {
onProjectRegistered(result);
return;
}
setState((prev) => ({
...prev,
step: "agent",
@@ -166,27 +175,7 @@ export function SetupWizardModal({
error: err instanceof Error ? err.message : "Failed to register project",
}));
}
}, [state.manualPath, state.manualName, state.manualCloneUrl, state.manualMode, state.manualIsolationMode, state.manualNodeId]);
const handleSetAuthToken = useCallback(() => {
const token = authTokenInput.trim();
if (!token) return;
setAuthToken(token);
setStoredAuthToken(token);
setAuthTokenInput("");
// If we're on the auth step, advance to the manual step
setState((prev) => prev.step === "auth" ? { ...prev, step: "manual" } : prev);
}, [authTokenInput]);
const handleResetAuthToken = useCallback(() => {
clearAuthToken();
setStoredAuthToken(undefined);
setAuthTokenInput("");
}, []);
const handleSkipAuth = useCallback(() => {
setState((prev) => ({ ...prev, step: "manual" }));
}, []);
}, [includeAgentStep, onProjectRegistered, state.manualPath, state.manualName, state.manualCloneUrl, state.manualMode, state.manualIsolationMode, state.manualNodeId]);
const handlePresetSelect = useCallback((presetId: string) => {
const preset = getPresetById(presetId);
@@ -310,7 +299,6 @@ export function SetupWizardModal({
<span className="setup-wizard-brand-name">{t("setup.brandName", "Fusion")}</span>
</div>
<h2 id="wizard-title" className="setup-wizard-title">
{state.step === "auth" && t("setup.setAuthToken", "Set Auth Token")}
{state.step === "manual" && t("setup.welcomeToFusion", "Welcome to Fusion")}
{state.step === "agent" && t("setup.firstAgentTitle", "Create your first agent")}
{state.step === "complete" && t("setup.setupCompleteTitle", "Setup Complete!")}
@@ -329,36 +317,6 @@ export function SetupWizardModal({
{/* Content */}
<div className="setup-wizard-content">
{/* Auth Step */}
{state.step === "auth" && (
<div className="setup-wizard-auth-step">
<p className="setup-wizard-auth-step-description">
{t("setup.authDescription", "This dashboard requires an auth token to communicate with the Fusion daemon. Paste the token below to continue.")}
</p>
<div className="form-group">
<label htmlFor="setup-auth-token">{t("setup.authToken", "Auth Token")}</label>
<input
id="setup-auth-token"
type="password"
value={authTokenInput}
onChange={(e) => setAuthTokenInput(e.target.value)}
placeholder={t("setup.pasteTokenPlaceholder", "Paste the daemon auth token")}
autoComplete="off"
spellCheck={false}
autoFocus
/>
<p className="form-hint">
{t("setup.tokenEnvVar", "The token was set via the {{env}} environment variable when starting the dashboard.", { env: "FUSION_DAEMON_TOKEN" })}
</p>
</div>
{state.error && (
<div className="wizard-error" role="alert">
{state.error}
</div>
)}
</div>
)}
{/* Manual Step */}
{state.step === "manual" && (
<div className="setup-wizard-manual">
@@ -511,44 +469,6 @@ export function SetupWizardModal({
</div>
</div>
<div className="form-group">
<label htmlFor="advanced-auth-token">{t("setup.browserAuthToken", "Browser Auth Token")}</label>
<div className="setup-wizard-auth-token">
<input
id="advanced-auth-token"
type="password"
value={authTokenInput}
onChange={(e) => setAuthTokenInput(e.target.value)}
placeholder={storedAuthToken ? t("setup.replaceTokenPlaceholder", "Enter a new token to replace the stored one") : t("setup.pasteTokenForBrowserPlaceholder", "Paste the auth token for this browser")}
autoComplete="off"
spellCheck={false}
/>
<div className="setup-wizard-auth-token-actions">
<button
type="button"
className="btn"
onClick={handleSetAuthToken}
disabled={authTokenInput.trim().length === 0}
>
{storedAuthToken ? t("setup.updateToken", "Update token") : t("setup.setToken", "Set token")}
</button>
{storedAuthToken && (
<button
type="button"
className="btn"
onClick={handleResetAuthToken}
>
{t("setup.resetToken", "Reset token")}
</button>
)}
</div>
</div>
<p className="form-hint">
{storedAuthToken
? t("setup.tokenStoredHint", "A token is already stored in this browser. You can update or reset it below.")
: t("setup.noTokenHint", "No token is stored. Use the auth prompt at the top of the wizard, or set one here.")}
</p>
</div>
</div>
)}
</div>
@@ -682,23 +602,6 @@ export function SetupWizardModal({
>
{t("setup.needHelp", "Need help?")}
</a>
{state.step === "auth" && (
<>
<button
className="btn"
onClick={handleSkipAuth}
>
{t("setup.skip", "Skip")}
</button>
<button
className="btn btn-primary"
onClick={handleSetAuthToken}
disabled={authTokenInput.trim().length === 0}
>
<span>{t("setup.setTokenContinue", "Set Token & Continue")}</span>
</button>
</>
)}
{state.step === "manual" && (
<button
className="btn btn-primary"

View File

@@ -88,8 +88,12 @@ vi.mock("../AgentListModal", () => ({
AgentListModal: () => null,
}));
const mockSetupWizardModalProps = vi.fn();
vi.mock("../SetupWizardModal", () => ({
SetupWizardModal: () => null,
SetupWizardModal: (props: any) => {
mockSetupWizardModalProps(props);
return <div data-testid="setup-wizard-modal" />;
},
}));
const mockModelOnboardingModalProps = vi.fn();
@@ -335,6 +339,11 @@ describe("AppModals", () => {
});
describe("ModelOnboardingModal wiring", () => {
beforeEach(() => {
mockModelOnboardingModalProps.mockClear();
mockSetupWizardModalProps.mockClear();
});
it("passes empty project id and setup-wizard callback into onboarding modal when no project is selected", () => {
const handleAddProject = vi.fn();
const manager = { ...mockModalManager, modelOnboardingOpen: true };
@@ -363,6 +372,61 @@ describe("AppModals", () => {
expect(props.onOpenSetupWizard).toBe(handleAddProject);
});
it("hides model onboarding while setup wizard is open as its project sub-flow", async () => {
const manager = { ...mockModalManager, modelOnboardingOpen: true, setupWizardOpen: true };
render(
<AppModals
projectId={undefined}
tasks={[]}
projects={[]}
currentProject={null}
addToast={vi.fn()}
toasts={mockToasts}
removeToast={vi.fn()}
modalManager={manager}
projectActions={{ handleAddProject: vi.fn(), handleSetupComplete: vi.fn(), handleModelOnboardingComplete: vi.fn() }}
taskHandlers={{ handleModalCreate: vi.fn(), handlePlanningTaskCreated: vi.fn(), handlePlanningTasksCreated: vi.fn(), handleSubtaskTasksCreated: vi.fn(), handleGitHubImport: vi.fn() }}
taskOperations={{ moveTask: vi.fn(), deleteTask: vi.fn(), mergeTask: vi.fn(), retryTask: vi.fn(), duplicateTask: vi.fn() }}
deepLink={{ handleDetailClose: vi.fn() }}
settings={mockSettings}
/>,
);
await waitFor(() => {
expect(mockSetupWizardModalProps).toHaveBeenCalledTimes(1);
});
expect(mockModelOnboardingModalProps).not.toHaveBeenCalled();
expect(mockSetupWizardModalProps.mock.calls[0][0].includeAgentStep).toBe(false);
});
it("keeps the standalone setup wizard agent step for new projects", async () => {
const manager = { ...mockModalManager, setupWizardOpen: true };
render(
<AppModals
projectId={undefined}
tasks={[]}
projects={[]}
currentProject={null}
addToast={vi.fn()}
toasts={mockToasts}
removeToast={vi.fn()}
modalManager={manager}
projectActions={{ handleAddProject: vi.fn(), handleSetupComplete: vi.fn(), handleModelOnboardingComplete: vi.fn() }}
taskHandlers={{ handleModalCreate: vi.fn(), handlePlanningTaskCreated: vi.fn(), handlePlanningTasksCreated: vi.fn(), handleSubtaskTasksCreated: vi.fn(), handleGitHubImport: vi.fn() }}
taskOperations={{ moveTask: vi.fn(), deleteTask: vi.fn(), mergeTask: vi.fn(), retryTask: vi.fn(), duplicateTask: vi.fn() }}
deepLink={{ handleDetailClose: vi.fn() }}
settings={mockSettings}
/>,
);
await waitFor(() => {
expect(mockSetupWizardModalProps).toHaveBeenCalledTimes(1);
});
expect(mockSetupWizardModalProps.mock.calls[0][0].includeAgentStep).toBe(true);
});
it("passes active project id into onboarding modal when a project is selected", () => {
const manager = { ...mockModalManager, modelOnboardingOpen: true };

View File

@@ -78,21 +78,11 @@ vi.mock("../ExperimentalAgentOnboardingModal", () => ({
),
}));
vi.mock("../../auth", () => ({
getAuthToken: vi.fn(() => undefined),
setAuthToken: vi.fn(),
clearAuthToken: vi.fn(),
}));
import { createAgent, registerProject } from "../../api";
import { getAuthToken, setAuthToken, clearAuthToken } from "../../auth";
import { useNodes } from "../../hooks/useNodes";
const mockRegisterProject = vi.mocked(registerProject);
const mockCreateAgent = vi.mocked(createAgent);
const mockGetAuthToken = vi.mocked(getAuthToken);
const mockSetAuthToken = vi.mocked(setAuthToken);
const mockClearAuthToken = vi.mocked(clearAuthToken);
const mockUseNodes = vi.mocked(useNodes);
function buildMockProject(overrides = {}) {
@@ -133,42 +123,13 @@ async function registerProjectFromWizard() {
}
describe("SetupWizardModal", () => {
let reloadMock: ReturnType<typeof vi.fn>;
beforeEach(() => {
vi.clearAllMocks();
mockRegisterProject.mockReset();
mockCreateAgent.mockReset();
// Default: auth token is stored so wizard starts on the project form step.
// Tests for the auth step explicitly unset this.
mockGetAuthToken.mockReturnValue("stored-token");
reloadMock = vi.fn();
vi.stubGlobal("location", { ...window.location, reload: reloadMock });
});
it("renders with auth step when no token is stored", () => {
mockGetAuthToken.mockReturnValue(undefined);
render(
<SetupWizardModal
onProjectRegistered={vi.fn()}
onClose={vi.fn()}
/>
);
// No token stored → shows auth step first
expect(screen.getByText("Set Auth Token")).toBeDefined();
expect(screen.getByText("Skip")).toBeDefined();
expect(screen.getByText("Set Token & Continue")).toBeDefined();
expect(screen.getByRole("link", { name: "Need help?" })).toHaveAttribute(
"href",
"https://discord.gg/ksrfuy7WYR"
);
});
it("skips auth step and shows project form when auth token is already stored", () => {
mockGetAuthToken.mockReturnValue("stored-token");
it("starts on the project form without an auth token step", () => {
render(
<SetupWizardModal
onProjectRegistered={vi.fn()}
@@ -180,46 +141,12 @@ describe("SetupWizardModal", () => {
expect(screen.getByText("Project Name")).toBeDefined();
expect(screen.getByLabelText("Fusion logo")).toBeDefined();
expect(screen.getByText("Advanced settings")).toBeDefined();
});
it("auth step advances to project form after setting token", () => {
mockGetAuthToken.mockReturnValue(undefined);
render(
<SetupWizardModal
onProjectRegistered={vi.fn()}
onClose={vi.fn()}
/>
expect(screen.queryByText("Set Auth Token")).toBeNull();
expect(screen.queryByText("Set Token & Continue")).toBeNull();
expect(screen.getByRole("link", { name: "Need help?" })).toHaveAttribute(
"href",
"https://discord.gg/ksrfuy7WYR"
);
// On auth step initially
expect(screen.getByText("Set Auth Token")).toBeDefined();
// Set a token
fireEvent.change(screen.getByPlaceholderText("Paste the daemon auth token"), {
target: { value: "my-daemon-token" },
});
fireEvent.click(screen.getByText("Set Token & Continue"));
expect(mockSetAuthToken).toHaveBeenCalledWith("my-daemon-token");
// Should now show the project form
expect(screen.getByText("Welcome to Fusion")).toBeDefined();
expect(screen.getByText("Project Name")).toBeDefined();
});
it("auth step can be skipped", () => {
mockGetAuthToken.mockReturnValue(undefined);
render(
<SetupWizardModal
onProjectRegistered={vi.fn()}
onClose={vi.fn()}
/>
);
expect(screen.getByText("Set Auth Token")).toBeDefined();
fireEvent.click(screen.getByText("Skip"));
expect(screen.getByText("Welcome to Fusion")).toBeDefined();
});
it("has DirectoryPicker for path selection", () => {
@@ -480,6 +407,34 @@ describe("SetupWizardModal", () => {
expect(onProjectRegistered).not.toHaveBeenCalled();
});
it("can register project only when embedded in brand-new onboarding", async () => {
const mockProject = buildMockProject();
mockRegisterProject.mockResolvedValueOnce(mockProject);
const onProjectRegistered = vi.fn();
render(
<SetupWizardModal
onProjectRegistered={onProjectRegistered}
onClose={vi.fn()}
includeAgentStep={false}
/>
);
fireEvent.change(screen.getByPlaceholderText("/path/to/your/project"), {
target: { value: "/home/user/project" },
});
fireEvent.change(screen.getByPlaceholderText("my-project"), {
target: { value: "test-project" },
});
fireEvent.click(screen.getByText("Register Project"));
await waitFor(() => {
expect(mockRegisterProject).toHaveBeenCalled();
expect(onProjectRegistered).toHaveBeenCalledWith(mockProject);
});
expect(screen.queryByText("Create your first agent")).toBeNull();
});
it("can skip first-agent creation and finish setup", async () => {
const mockProject = buildMockProject();
mockRegisterProject.mockResolvedValueOnce(mockProject);
@@ -758,77 +713,6 @@ describe("SetupWizardModal", () => {
expect(childProcessRadio.checked).toBe(true);
});
it("shows a set token action when no browser auth token is stored", () => {
mockGetAuthToken.mockReturnValue(undefined);
render(
<SetupWizardModal
onProjectRegistered={vi.fn()}
onClose={vi.fn()}
/>
);
// Skip auth step to get to the project form
fireEvent.click(screen.getByText("Skip"));
fireEvent.click(screen.getByText("Advanced settings"));
expect(screen.getByLabelText("Browser Auth Token")).toBeDefined();
expect(screen.getByRole("button", { name: "Set token" })).toBeDefined();
expect(screen.queryByRole("button", { name: "Reset token" })).toBeNull();
});
it("stores a browser auth token without reloading", () => {
render(
<SetupWizardModal
onProjectRegistered={vi.fn()}
onClose={vi.fn()}
/>
);
fireEvent.click(screen.getByText("Advanced settings"));
fireEvent.change(screen.getByLabelText("Browser Auth Token"), {
target: { value: "daemon-token" },
});
fireEvent.click(screen.getByRole("button", { name: "Update token" }));
expect(mockSetAuthToken).toHaveBeenCalledWith("daemon-token");
expect(reloadMock).not.toHaveBeenCalled();
});
it("shows reset when a browser auth token is already stored", () => {
mockGetAuthToken.mockReturnValue("stored-token");
render(
<SetupWizardModal
onProjectRegistered={vi.fn()}
onClose={vi.fn()}
/>
);
fireEvent.click(screen.getByText("Advanced settings"));
expect(screen.getByRole("button", { name: "Update token" })).toBeDefined();
expect(screen.getByRole("button", { name: "Reset token" })).toBeDefined();
});
it("resets the stored browser auth token without reloading", () => {
mockGetAuthToken.mockReturnValue("stored-token");
render(
<SetupWizardModal
onProjectRegistered={vi.fn()}
onClose={vi.fn()}
/>
);
fireEvent.click(screen.getByText("Advanced settings"));
fireEvent.click(screen.getByRole("button", { name: "Reset token" }));
expect(mockClearAuthToken).toHaveBeenCalledTimes(1);
expect(reloadMock).not.toHaveBeenCalled();
});
describe("node selector", () => {
const localNode = {
id: "local-1",

View File

@@ -253,7 +253,7 @@ describe("useAuthOnboarding", () => {
});
});
it("does not auto-trigger before a projectId exists (fresh install pre-wizard race)", async () => {
it("auto-triggers model onboarding before a projectId exists so brand-new users start with AI setup", async () => {
mockFetchAuthStatus.mockResolvedValue({
providers: [{ id: "openai", name: "OpenAI", authenticated: false }],
});
@@ -263,31 +263,17 @@ describe("useAuthOnboarding", () => {
defaultModelId: undefined,
} as never);
const { rerender } = renderHook(
({ projectId, setupWizardOpen }: { projectId: string | undefined; setupWizardOpen: boolean }) =>
useAuthOnboarding({
projectId,
setupWizardOpen,
openModelOnboarding,
openSettings,
}),
{
initialProps: { projectId: undefined as string | undefined, setupWizardOpen: false },
},
renderHook(() =>
useAuthOnboarding({
projectId: undefined,
setupWizardOpen: false,
openModelOnboarding,
openSettings,
}),
);
// No project yet — the setup wizard owns this phase. Don't fetch or open.
await waitFor(() => {
expect(mockFetchAuthStatus).not.toHaveBeenCalled();
expect(openModelOnboarding).not.toHaveBeenCalled();
});
// Setup wizard opens, user fills it out…
rerender({ projectId: undefined, setupWizardOpen: true });
// …completes it: project registered, wizard closes.
rerender({ projectId: "proj_new", setupWizardOpen: false });
await waitFor(() => {
expect(mockFetchAuthStatus).toHaveBeenCalledTimes(1);
expect(openModelOnboarding).toHaveBeenCalledTimes(1);
});
});

View File

@@ -234,7 +234,7 @@ describe("useViewState", () => {
});
});
it("calls openSetupWizard when no projects and no current project after loading", async () => {
it("does NOT call openSetupWizard automatically when no projects exist", async () => {
vi.useFakeTimers();
const openSetupWizard = vi.fn();
@@ -252,7 +252,7 @@ describe("useViewState", () => {
vi.advanceTimersByTime(500);
});
expect(openSetupWizard).toHaveBeenCalledTimes(1);
expect(openSetupWizard).not.toHaveBeenCalled();
vi.useRealTimers();
});

View File

@@ -24,7 +24,6 @@ export interface UseAuthOnboardingOptions {
* - Already configured: no auto-open
*/
export function useAuthOnboarding({
projectId,
setupWizardOpen,
openModelOnboarding,
openSettings,
@@ -41,13 +40,11 @@ export function useAuthOnboarding({
// Defer auto-triggering while setup wizard is open.
// Important: this must run before consuming the one-shot flag.
if (setupWizardOpen) return;
// Hold off until the user has a project. On a fresh install the setup
// wizard opens ~500ms after mount, so without this gate the effect would
// race ahead, lock the one-shot flag, and (a) potentially stack both
// modals, or (b) never re-trigger model onboarding once the wizard
// closes. With a project in scope, either the wizard already finished
// or it was never going to open.
if (!projectId) return;
/*
FNXC:Onboarding 2026-06-22-05:06:
Brand-new users should be prompted to set up AI and GitHub before project details, then continue through Project, Agent, and First Task.
Allow model onboarding to auto-open without a projectId; its Project step owns opening the project-only setup wizard when needed.
*/
// 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
@@ -106,5 +103,5 @@ export function useAuthOnboarding({
// Fail silently - non-blocking behavior preserves dashboard usability.
// Onboarding can be manually triggered later via Settings if needed.
});
}, [projectId, setupWizardOpen, openModelOnboarding, openSettings]);
}, [setupWizardOpen, openModelOnboarding, openSettings]);
}

View File

@@ -128,12 +128,8 @@ export interface UseViewStateResult {
export function useViewState(options: UseViewStateOptions): UseViewStateResult {
const {
projectsLoading,
projectsError,
currentProjectLoading,
currentProject,
projectsLength,
setupWizardOpen,
openSetupWizard,
themeMode,
setThemeMode,
} = options;
@@ -217,26 +213,11 @@ export function useViewState(options: UseViewStateOptions): UseViewStateResult {
}
}, [projectsLoading, currentProjectLoading, currentProject, viewMode]);
useEffect(() => {
if (projectsLoading || currentProjectLoading) return;
if (setupWizardOpen) return;
if (projectsError) return;
if (projectsLength > 0 || currentProject) return;
const timer = window.setTimeout(() => {
openSetupWizard();
}, 500);
return () => window.clearTimeout(timer);
}, [
projectsLoading,
projectsError,
projectsLength,
currentProjectLoading,
currentProject,
setupWizardOpen,
openSetupWizard,
]);
/*
FNXC:Onboarding 2026-06-22-05:06:
Brand-new users should enter the unified onboarding sequence first: AI setup, GitHub, Project, Agent, then First Task.
Do not auto-open the project-only setup wizard just because there are zero projects; that wizard is opened from the Project step or explicit Add Project actions.
*/
const handleChangeTaskView = useCallback((newView: TaskView) => {
setTaskView(newView);

View File

@@ -6486,11 +6486,8 @@
"ariaSetupRecommendations": "Setup recommendations",
"authCodeAlreadySubmitted": "That authorization code was already submitted. Waiting for login…",
"authCodeReceived": "Authorization code received. Finishing login…",
"authDescription": "This dashboard requires an auth token to communicate with the Fusion daemon. Paste the token below to continue.",
"authToken": "Auth Token",
"authTokenOptional": "Auth token (optional)",
"back": "← Back",
"browserAuthToken": "Browser Auth Token",
"cancelLogin": "Cancel",
"childProcess": "Child-Process",
"childProcessDesc": "Isolated execution with crash containment.",
@@ -6665,14 +6662,11 @@
"noProvidersConfigured": "No AI providers are configured. Please check your Fusion configuration.",
"noProvidersConnectedYet": "No providers connected yet",
"noQuickStartProviders": "No quick-start providers are available in this environment.",
"noTokenHint": "No token is stored. Use the auth prompt at the top of the wizard, or set one here.",
"onlyNeedOneProvider": "You only need one provider to get started.",
"openGitHub": "Open GitHub",
"openManager": "Open manager",
"optionalBadge": "Optional",
"pasteRedirectUrlFirst": "Paste the full redirect URL or authorization code first.",
"pasteTokenForBrowserPlaceholder": "Paste the auth token for this browser",
"pasteTokenPlaceholder": "Paste the daemon auth token",
"pathHint": "Enter the absolute path to your project directory",
"pathPlaceholder": "/path/to/your/project",
"pleaseEnterTaskDescription": "Please enter a task description.",
@@ -6719,12 +6713,10 @@
"remoteServerProfileSaved": "Remote server profile saved",
"removeKey": "Remove Key",
"removingKey": "Removing…",
"replaceTokenPlaceholder": "Enter a new token to replace the stored one",
"repositoryUrl": "Repository URL",
"repositoryUrlPlaceholder": "https://github.com/owner/repo.git",
"requiresGitHubConnection": "Requires GitHub connection",
"researchRunsNote": "Research runs require provider credentials and an enabled Research View. After onboarding, verify these in Settings → Authentication and Settings → Experimental Features.",
"resetToken": "Reset token",
"retry": "Retry",
"reviewStep": "Review {{label}}",
"runtimeNode": "Runtime Node",
@@ -6740,9 +6732,6 @@
"selectedModel": "Selected:",
"serverUrl": "Server URL",
"serverUrlPlaceholder": "https://your-fusion-host",
"setAuthToken": "Set Auth Token",
"setToken": "Set token",
"setTokenContinue": "Set Token & Continue",
"setUpAi": "Set Up AI",
"setupComplete": "Setup complete! Head to the board to create your first task, or explore the dashboard to see what's available.",
"setupMode": "Setup Mode",
@@ -6778,9 +6767,6 @@
"titleCreateFirstAgent": "Create Your First Agent",
"titleCreateFirstTask": "Create Your First Task",
"titleSetUpProject": "Set Up Your Project",
"tokenEnvVar": "The token was set via the {{env}} environment variable when starting the dashboard.",
"tokenStoredHint": "A token is already stored in this browser. You can update or reset it below.",
"updateToken": "Update token",
"useExistingDirectory": "Use Existing Directory",
"viewTask": "View Task",
"waitingForGitHubAuth": "Waiting for GitHub authorization…",

View File

@@ -6486,11 +6486,8 @@
"ariaSetupRecommendations": "Recomendaciones de configuración",
"authCodeAlreadySubmitted": "Ese código de autorización ya fue enviado. Esperando inicio de sesión…",
"authCodeReceived": "Código de autorización recibido. Finalizando inicio de sesión…",
"authDescription": "Este panel requiere un token de autenticación para comunicarse con el daemon de Fusion. Pegue el token a continuación para continuar.",
"authToken": "Token de autenticación",
"authTokenOptional": "Token de autenticación (opcional)",
"back": "← Atrás",
"browserAuthToken": "Token de autenticación del navegador",
"cancelLogin": "Cancelar",
"childProcess": "Proceso hijo",
"childProcessDesc": "Aislamiento fuerte. Las tareas se ejecutan en procesos separados.",
@@ -6665,14 +6662,11 @@
"noProvidersConfigured": "No hay proveedores de IA configurados. Verifica tu configuración de Fusion.",
"noProvidersConnectedYet": "No hay proveedores conectados aún",
"noQuickStartProviders": "No hay proveedores de inicio rápido disponibles en este entorno.",
"noTokenHint": "No hay token almacenado. Use el aviso de autenticación en la parte superior del asistente o establezca uno aquí.",
"onlyNeedOneProvider": "Solo necesitas un proveedor para empezar.",
"openGitHub": "Abrir GitHub",
"openManager": "Abrir gestor",
"optionalBadge": "Opcional",
"pasteRedirectUrlFirst": "Pega primero la URL de redireccionamiento completa o el código de autorización.",
"pasteTokenForBrowserPlaceholder": "Pegue el token de autenticación de este navegador",
"pasteTokenPlaceholder": "Pegue el token de autenticación del daemon",
"pathHint": "Introduce la ruta absoluta de tu directorio de proyecto",
"pathPlaceholder": "/ruta/a/tu/proyecto",
"pleaseEnterTaskDescription": "Por favor ingresa una descripción de la tarea.",
@@ -6719,12 +6713,10 @@
"remoteServerProfileSaved": "Perfil del servidor remoto guardado",
"removeKey": "Eliminar clave",
"removingKey": "Eliminando…",
"replaceTokenPlaceholder": "Ingrese un nuevo token para reemplazar el almacenado",
"repositoryUrl": "URL del repositorio",
"repositoryUrlPlaceholder": "https://github.com/propietario/repo.git",
"requiresGitHubConnection": "Requiere conexión con GitHub",
"researchRunsNote": "Las búsquedas requieren credenciales del proveedor y una vista de investigación habilitada. Después de la configuración inicial, verifica esto en Ajustes → Autenticación y Ajustes → Funciones experimentales.",
"resetToken": "Restablecer token",
"retry": "Reintentar",
"reviewStep": "Revisar {{label}}",
"runtimeNode": "Nodo en tiempo de ejecución",
@@ -6740,9 +6732,6 @@
"selectedModel": "Seleccionado:",
"serverUrl": "URL del servidor",
"serverUrlPlaceholder": "https://your-fusion-host",
"setAuthToken": "Establecer token de autenticación",
"setToken": "Establecer token",
"setTokenContinue": "Establecer token y continuar",
"setUpAi": "Configurar IA",
"setupComplete": "¡Configuración completada!",
"setupMode": "Modo de configuración",
@@ -6778,9 +6767,6 @@
"titleCreateFirstAgent": "",
"titleCreateFirstTask": "Crear tu primera tarea",
"titleSetUpProject": "Configurar tu proyecto",
"tokenEnvVar": "El token se estableció a través de la variable de entorno {{env}} al iniciar el panel.",
"tokenStoredHint": "Ya hay un token almacenado en este navegador. Puede actualizarlo o restablecerlo a continuación.",
"updateToken": "Actualizar token",
"useExistingDirectory": "Usar directorio existente",
"viewTask": "Ver tarea",
"waitingForGitHubAuth": "Esperando autorización de GitHub…",

View File

@@ -6486,11 +6486,8 @@
"ariaSetupRecommendations": "Recommandations de configuration",
"authCodeAlreadySubmitted": "Ce code d'autorisation a déjà été soumis. En attente de la connexion…",
"authCodeReceived": "Code d'autorisation reçu. Finalisation de la connexion…",
"authDescription": "Ce tableau de bord nécessite un jeton d'authentification pour communiquer avec le daemon Fusion. Collez le jeton ci-dessous pour continuer.",
"authToken": "Jeton d'authentification",
"authTokenOptional": "Jeton d'authentification (facultatif)",
"back": "← Retour",
"browserAuthToken": "Jeton d'authentification du navigateur",
"cancelLogin": "Annuler",
"childProcess": "Processus enfant",
"childProcessDesc": "Isolation forte. Les tâches s'exécutent dans des processus séparés.",
@@ -6665,14 +6662,11 @@
"noProvidersConfigured": "Aucun fournisseur IA n'est configuré. Vérifiez votre configuration Fusion.",
"noProvidersConnectedYet": "Aucun fournisseur connecté pour l'instant",
"noQuickStartProviders": "Aucun fournisseur de démarrage rapide n'est disponible dans cet environnement.",
"noTokenHint": "Aucun jeton stocké. Utilisez l'invite d'authentification en haut de l'assistant ou définissez-en une ici.",
"onlyNeedOneProvider": "Un seul fournisseur suffit pour commencer.",
"openGitHub": "Ouvrir GitHub",
"openManager": "Ouvrir le gestionnaire",
"optionalBadge": "Facultatif",
"pasteRedirectUrlFirst": "Collez d'abord l'URL de redirection complète ou le code d'autorisation.",
"pasteTokenForBrowserPlaceholder": "Collez le jeton d'authentification de ce navigateur",
"pasteTokenPlaceholder": "Collez le jeton d'authentification du daemon",
"pathHint": "Entrez le chemin absolu de votre répertoire de projet",
"pathPlaceholder": "/chemin/vers/votre/projet",
"pleaseEnterTaskDescription": "Veuillez saisir une description de tâche.",
@@ -6719,12 +6713,10 @@
"remoteServerProfileSaved": "Profil de serveur distant enregistré",
"removeKey": "Supprimer la clé",
"removingKey": "Suppression…",
"replaceTokenPlaceholder": "Entrez un nouveau jeton pour remplacer celui stocké",
"repositoryUrl": "URL du référentiel",
"repositoryUrlPlaceholder": "https://github.com/proprietaire/repo.git",
"requiresGitHubConnection": "Nécessite une connexion GitHub",
"researchRunsNote": "Les recherches nécessitent des identifiants de fournisseur et une vue Recherche activée. Après l'intégration, vérifiez-les dans Paramètres → Authentification et Paramètres → Fonctionnalités expérimentales.",
"resetToken": "Jeton de réinitialisation",
"retry": "Réessayer",
"reviewStep": "Revoir {{label}}",
"runtimeNode": "Nœud d'exécution",
@@ -6740,9 +6732,6 @@
"selectedModel": "Sélectionné :",
"serverUrl": "URL du serveur",
"serverUrlPlaceholder": "https://your-fusion-host",
"setAuthToken": "Définir le jeton d'authentification",
"setToken": "Jeton défini",
"setTokenContinue": "Définir le jeton et continuer",
"setUpAi": "Configurer l'IA",
"setupComplete": "Configuration terminée !",
"setupMode": "Mode de configuration",
@@ -6778,9 +6767,6 @@
"titleCreateFirstAgent": "",
"titleCreateFirstTask": "Créer votre première tâche",
"titleSetUpProject": "Configurer votre projet",
"tokenEnvVar": "Le jeton a été défini via la variable d'environnement {{env}} au démarrage du tableau de bord.",
"tokenStoredHint": "Un jeton est déjà stocké dans ce navigateur. Vous pouvez le mettre à jour ou le réinitialiser ci-dessous.",
"updateToken": "Jeton de mise à jour",
"useExistingDirectory": "Utiliser le répertoire existant",
"viewTask": "Voir la tâche",
"waitingForGitHubAuth": "En attente de l'autorisation GitHub…",

View File

@@ -6486,11 +6486,8 @@
"ariaSetupRecommendations": "설정 추천",
"authCodeAlreadySubmitted": "이미 제출된 인증 코드입니다. 로그인을 기다리는 중…",
"authCodeReceived": "인증 코드를 받았습니다. 로그인을 완료하는 중…",
"authDescription": "이 대시보드는 Fusion 데몬과 통신하기 위해 인증 토큰이 필요합니다. 아래에 토큰을 붙여넣어 계속하세요.",
"authToken": "인증 토큰",
"authTokenOptional": "인증 토큰 (선택 사항)",
"back": "← 뒤로",
"browserAuthToken": "브라우저 인증 토큰",
"cancelLogin": "취소",
"childProcess": "Child-Process",
"childProcessDesc": "충돌 격리 기능이 있는 독립 실행 환경입니다.",
@@ -6665,14 +6662,11 @@
"noProvidersConfigured": "AI 제공자가 구성되어 있지 않습니다. Fusion 설정을 확인해 주세요.",
"noProvidersConnectedYet": "아직 연결된 제공자 없음",
"noQuickStartProviders": "이 환경에서는 빠른 시작 제공자를 사용할 수 없습니다.",
"noTokenHint": "저장된 토큰이 없습니다. 마법사 상단의 인증 프롬프트를 사용하거나 여기서 직접 설정하세요.",
"onlyNeedOneProvider": "시작하려면 제공자 하나만 있으면 됩니다.",
"openGitHub": "GitHub 열기",
"openManager": "관리자 열기",
"optionalBadge": "선택 사항",
"pasteRedirectUrlFirst": "전체 리디렉션 URL 또는 인증 코드를 먼저 붙여넣으세요.",
"pasteTokenForBrowserPlaceholder": "이 브라우저의 인증 토큰 붙여넣기",
"pasteTokenPlaceholder": "데몬 인증 토큰 붙여넣기",
"pathHint": "프로젝트 디렉터리의 절대 경로를 입력하세요",
"pathPlaceholder": "/path/to/your/project",
"pleaseEnterTaskDescription": "작업 설명을 입력해 주세요.",
@@ -6719,12 +6713,10 @@
"remoteServerProfileSaved": "원격 서버 프로필 저장됨",
"removeKey": "키 제거",
"removingKey": "제거 중…",
"replaceTokenPlaceholder": "저장된 토큰을 교체할 새 토큰 입력",
"repositoryUrl": "저장소 URL",
"repositoryUrlPlaceholder": "https://github.com/owner/repo.git",
"requiresGitHubConnection": "GitHub 연결 필요",
"researchRunsNote": "리서치 실행에는 제공자 자격 증명과 활성화된 리서치 보기가 필요합니다. 온보딩 후 설정 → 인증 및 설정 → 실험적 기능에서 확인하세요.",
"resetToken": "토큰 초기화",
"retry": "재시도",
"reviewStep": "{{label}} 검토",
"runtimeNode": "런타임 노드",
@@ -6740,9 +6732,6 @@
"selectedModel": "선택됨:",
"serverUrl": "서버 URL",
"serverUrlPlaceholder": "https://your-fusion-host",
"setAuthToken": "인증 토큰 설정",
"setToken": "토큰 설정",
"setTokenContinue": "토큰 설정 및 계속",
"setUpAi": "AI 설정",
"setupComplete": "설정 완료! 보드로 이동하여 첫 번째 작업을 만들거나, 대시보드를 탐색해 보세요.",
"setupMode": "설정 모드",
@@ -6778,9 +6767,6 @@
"titleCreateFirstAgent": "",
"titleCreateFirstTask": "첫 번째 작업 만들기",
"titleSetUpProject": "프로젝트 설정",
"tokenEnvVar": "대시보드 시작 시 {{env}} 환경 변수를 통해 토큰이 설정되었습니다.",
"tokenStoredHint": "이 브라우저에 이미 토큰이 저장되어 있습니다. 아래에서 업데이트하거나 초기화할 수 있습니다.",
"updateToken": "토큰 업데이트",
"useExistingDirectory": "기존 디렉터리 사용",
"viewTask": "작업 보기",
"waitingForGitHubAuth": "GitHub 인증 대기 중…",

View File

@@ -6486,11 +6486,8 @@
"ariaSetupRecommendations": "设置建议",
"authCodeAlreadySubmitted": "该授权码已提交,等待登录完成…",
"authCodeReceived": "已收到授权码,正在完成登录…",
"authDescription": "此仪表板需要认证令牌与 Fusion 守护程序通信。请在下方粘贴令牌以继续。",
"authToken": "认证令牌",
"authTokenOptional": "认证令牌(可选)",
"back": "← 返回",
"browserAuthToken": "浏览器认证令牌",
"cancelLogin": "取消",
"childProcess": "子进程",
"childProcessDesc": "强隔离。任务在单独的进程中运行。",
@@ -6665,14 +6662,11 @@
"noProvidersConfigured": "未配置任何 AI 提供商,请检查您的 Fusion 配置。",
"noProvidersConnectedYet": "尚未连接任何提供商",
"noQuickStartProviders": "此环境中没有可用的快速启动提供商。",
"noTokenHint": "未存储令牌。使用向导顶部的认证提示或在此处设置一个。",
"onlyNeedOneProvider": "您只需一个提供商即可开始。",
"openGitHub": "打开 GitHub",
"openManager": "打开管理器",
"optionalBadge": "可选",
"pasteRedirectUrlFirst": "请先粘贴完整的重定向 URL 或授权码。",
"pasteTokenForBrowserPlaceholder": "粘贴此浏览器的认证令牌",
"pasteTokenPlaceholder": "粘贴守护程序认证令牌",
"pathHint": "输入项目目录的绝对路径",
"pathPlaceholder": "/path/to/your/project",
"pleaseEnterTaskDescription": "请输入任务描述。",
@@ -6719,12 +6713,10 @@
"remoteServerProfileSaved": "远程服务器配置文件已保存",
"removeKey": "删除密钥",
"removingKey": "正在删除…",
"replaceTokenPlaceholder": "输入新令牌以替换存储的令牌",
"repositoryUrl": "存储库 URL",
"repositoryUrlPlaceholder": "https://github.com/owner/repo.git",
"requiresGitHubConnection": "需要连接 GitHub",
"researchRunsNote": "研究运行需要提供商凭证和启用的研究视图。入门后,请在设置 → 身份验证和设置 → 实验性功能中进行验证。",
"resetToken": "重置令牌",
"retry": "重试",
"reviewStep": "查看{{label}}",
"runtimeNode": "运行时节点",
@@ -6740,9 +6732,6 @@
"selectedModel": "已选:",
"serverUrl": "服务器 URL",
"serverUrlPlaceholder": "https://your-fusion-host",
"setAuthToken": "设置认证令牌",
"setToken": "设置令牌",
"setTokenContinue": "设置令牌并继续",
"setUpAi": "设置 AI",
"setupComplete": "设置完成!",
"setupMode": "设置模式",
@@ -6778,9 +6767,6 @@
"titleCreateFirstAgent": "",
"titleCreateFirstTask": "创建您的第一个任务",
"titleSetUpProject": "设置您的项目",
"tokenEnvVar": "启动仪表板时通过 {{env}} 环境变量设置令牌。",
"tokenStoredHint": "此浏览器中已存储令牌。您可以在下面更新或重置它。",
"updateToken": "更新令牌",
"useExistingDirectory": "使用现有目录",
"viewTask": "查看任务",
"waitingForGitHubAuth": "等待 GitHub 授权…",

View File

@@ -6486,11 +6486,8 @@
"ariaSetupRecommendations": "設定建議",
"authCodeAlreadySubmitted": "該授權碼已提交,等待登入完成…",
"authCodeReceived": "已收到授權碼,正在完成登入…",
"authDescription": "此儀表板需要認證令牌與 Fusion 守護程序通訊。請在下方貼上令牌以繼續。",
"authToken": "認證令牌",
"authTokenOptional": "驗證令牌(選用)",
"back": "← 返回",
"browserAuthToken": "瀏覽器認證令牌",
"cancelLogin": "取消",
"childProcess": "子進程",
"childProcessDesc": "強隔離。任務在單獨的程序中執行。",
@@ -6665,14 +6662,11 @@
"noProvidersConfigured": "未設定任何 AI 提供商,請檢查您的 Fusion 設定。",
"noProvidersConnectedYet": "尚未連接任何提供商",
"noQuickStartProviders": "此環境中沒有可用的快速啟動提供商。",
"noTokenHint": "未儲存令牌。使用精靈頂部的認證提示或在此處設定一個。",
"onlyNeedOneProvider": "您只需要一個提供商即可開始。",
"openGitHub": "開啟 GitHub",
"openManager": "開啟管理員",
"optionalBadge": "選用",
"pasteRedirectUrlFirst": "請先貼上完整的重新導向 URL 或授權碼。",
"pasteTokenForBrowserPlaceholder": "貼上此瀏覽器的認證令牌",
"pasteTokenPlaceholder": "貼上守護程序認證令牌",
"pathHint": "輸入專案目錄的絕對路徑",
"pathPlaceholder": "/path/to/your/project",
"pleaseEnterTaskDescription": "請輸入任務描述。",
@@ -6719,12 +6713,10 @@
"remoteServerProfileSaved": "遠端伺服器設定檔已儲存",
"removeKey": "移除金鑰",
"removingKey": "正在移除…",
"replaceTokenPlaceholder": "輸入新令牌以取代儲存的令牌",
"repositoryUrl": "存儲庫 URL",
"repositoryUrlPlaceholder": "https://github.com/owner/repo.git",
"requiresGitHubConnection": "需要連接 GitHub",
"researchRunsNote": "研究執行需要提供商憑證和已啟用的研究檢視。入門後,請在設定 → 驗證和設定 → 實驗性功能中確認。",
"resetToken": "重設令牌",
"retry": "重試",
"reviewStep": "查看{{label}}",
"runtimeNode": "執行時節點",
@@ -6740,9 +6732,6 @@
"selectedModel": "已選:",
"serverUrl": "伺服器 URL",
"serverUrlPlaceholder": "https://your-fusion-host",
"setAuthToken": "設定認證令牌",
"setToken": "設定令牌",
"setTokenContinue": "設定令牌並繼續",
"setUpAi": "設定 AI",
"setupComplete": "設定完成!",
"setupMode": "設定模式",
@@ -6778,9 +6767,6 @@
"titleCreateFirstAgent": "",
"titleCreateFirstTask": "建立您的第一個任務",
"titleSetUpProject": "設定您的專案",
"tokenEnvVar": "啟動儀表板時通過 {{env}} 環境變數設定令牌。",
"tokenStoredHint": "此瀏覽器中已儲存令牌。您可以在下面更新或重設它。",
"updateToken": "更新令牌",
"useExistingDirectory": "使用現有目錄",
"viewTask": "查看任務",
"waitingForGitHubAuth": "等待 GitHub 授權…",

View File

@@ -6488,13 +6488,10 @@ export default interface Resources {
"ariaSetupRecommendations": "Setup recommendations",
"authCodeAlreadySubmitted": "That authorization code was already submitted. Waiting for login…",
"authCodeReceived": "Authorization code received. Finishing login…",
"authDescription": "This dashboard requires an auth token to communicate with the Fusion daemon. Paste the token below to continue.",
"authToken": "Auth Token",
"authTokenOptional": "Auth token (optional)",
"back": "← Back",
"brandLogo": "Fusion logo",
"brandName": "Fusion",
"browserAuthToken": "Browser Auth Token",
"cancelLogin": "Cancel",
"childProcess": "Child-Process",
"childProcessDesc": "Isolated execution with crash containment.",
@@ -6669,14 +6666,11 @@ export default interface Resources {
"noProvidersConfigured": "No AI providers are configured. Please check your Fusion configuration.",
"noProvidersConnectedYet": "No providers connected yet",
"noQuickStartProviders": "No quick-start providers are available in this environment.",
"noTokenHint": "No token is stored. Use the auth prompt at the top of the wizard, or set one here.",
"onlyNeedOneProvider": "You only need one provider to get started.",
"openGitHub": "Open GitHub",
"openManager": "Open manager",
"optionalBadge": "Optional",
"pasteRedirectUrlFirst": "Paste the full redirect URL or authorization code first.",
"pasteTokenForBrowserPlaceholder": "Paste the auth token for this browser",
"pasteTokenPlaceholder": "Paste the daemon auth token",
"pathHint": "Enter the absolute path to your project directory",
"pathPlaceholder": "/path/to/your/project",
"pleaseEnterTaskDescription": "Please enter a task description.",
@@ -6723,12 +6717,10 @@ export default interface Resources {
"remoteServerProfileSaved": "Remote server profile saved",
"removeKey": "Remove Key",
"removingKey": "Removing…",
"replaceTokenPlaceholder": "Enter a new token to replace the stored one",
"repositoryUrl": "Repository URL",
"repositoryUrlPlaceholder": "https://github.com/owner/repo.git",
"requiresGitHubConnection": "Requires GitHub connection",
"researchRunsNote": "Research runs require provider credentials and an enabled Research View. After onboarding, verify these in Settings → Authentication and Settings → Experimental Features.",
"resetToken": "Reset token",
"retry": "Retry",
"reviewStep": "Review {{label}}",
"runtimeNode": "Runtime Node",
@@ -6744,9 +6736,6 @@ export default interface Resources {
"selectedModel": "Selected:",
"serverUrl": "Server URL",
"serverUrlPlaceholder": "https://your-fusion-host",
"setAuthToken": "Set Auth Token",
"setToken": "Set token",
"setTokenContinue": "Set Token & Continue",
"setUpAi": "Set Up AI",
"setUpProject": "Set Up Project",
"setupComplete": "Setup complete! Head to the board to create your first task, or explore the dashboard to see what's available.",
@@ -6783,9 +6772,6 @@ export default interface Resources {
"titleCreateFirstAgent": "Create Your First Agent",
"titleCreateFirstTask": "Create Your First Task",
"titleSetUpProject": "Set Up Your Project",
"tokenEnvVar": "The token was set via the {{env}} environment variable when starting the dashboard.",
"tokenStoredHint": "A token is already stored in this browser. You can update or reset it below.",
"updateToken": "Update token",
"useExistingDirectory": "Use Existing Directory",
"viewTask": "View Task",
"waitingForGitHubAuth": "Waiting for GitHub authorization…",