fix(onboarding): simplify first-run project setup
BIN
docs/screenshots/onboarding-improve/01-brand-new-ai-setup.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 212 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 209 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 169 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 257 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 207 KiB |
|
Before Width: | Height: | Size: 236 KiB |
BIN
docs/screenshots/onboarding-improve/06-brand-new-agent-step.png
Normal file
|
After Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 232 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
Before Width: | Height: | Size: 145 KiB |
BIN
docs/screenshots/onboarding-improve/08-new-project-details.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 148 KiB |
@@ -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}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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…",
|
||||
|
||||
@@ -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…",
|
||||
|
||||
@@ -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…",
|
||||
|
||||
@@ -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 인증 대기 중…",
|
||||
|
||||
@@ -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 授权…",
|
||||
|
||||
@@ -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 授權…",
|
||||
|
||||
14
packages/i18n/src/resources.d.ts
vendored
@@ -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…",
|
||||
|
||||