From 2cfeb744078ee038c9ea700c05e636f2622c675c Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 10 Jul 2026 16:08:04 -0700 Subject: [PATCH] fix(dashboard): polish first-run setup wizard (AI step, GitHub step, project step) - AI Setup: connected providers now render first; 'Skip for now' hides once a provider is connected; footer buttons share one size. - GitHub step: state-driven (no install/pitch content when connected), one type scale, real literals, per-OS gh instructions behind a disclosure, single skip affordance; gh/OAuth status revalidates on window focus and OAuth relogin so later steps stop showing stale 'not connected'. - Project step: onboarding-driven opens keep 'Step 3 of 5' context; runtime-node picker no longer lists 'Local node' and 'local (local)' duplicates and hides itself when only the local node exists; isolation-mode cards no longer render a stretched native radio or uppercased descriptions. Co-Authored-By: Claude Fable 5 --- .changeset/setup-wizard-polish.md | 7 + .../app/components/ModelOnboardingModal.css | 88 +++++-- .../app/components/ModelOnboardingModal.tsx | 245 ++++++++++++------ .../app/components/SetupProjectForm.tsx | 45 ++-- .../app/components/SetupWizardModal.css | 66 ++++- .../app/components/SetupWizardModal.tsx | 74 ++++-- .../__tests__/ModelOnboardingModal.test.tsx | 159 +++++++++++- .../__tests__/SetupProjectForm.test.tsx | 18 +- .../__tests__/SetupWizardModal.test.tsx | 40 ++- .../__tests__/onboarding-flow.test.tsx | 12 +- .../__tests__/setupWizardNodes.test.ts | 45 ++++ .../app/components/setupWizardNodes.ts | 24 ++ packages/i18n/locales/en/app.json | 10 +- packages/i18n/locales/es/app.json | 7 +- packages/i18n/locales/fr/app.json | 7 +- packages/i18n/locales/ko/app.json | 7 +- packages/i18n/locales/zh-CN/app.json | 7 +- packages/i18n/locales/zh-TW/app.json | 7 +- 18 files changed, 693 insertions(+), 175 deletions(-) create mode 100644 .changeset/setup-wizard-polish.md create mode 100644 packages/dashboard/app/components/__tests__/setupWizardNodes.test.ts create mode 100644 packages/dashboard/app/components/setupWizardNodes.ts diff --git a/.changeset/setup-wizard-polish.md b/.changeset/setup-wizard-polish.md new file mode 100644 index 0000000000..4e737e5dcc --- /dev/null +++ b/.changeset/setup-wizard-polish.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Overhaul first-run setup: connected providers on top, state-driven GitHub step, fixed isolation-mode radios, deduped runtime-node picker. +category: fix +dev: New setupWizardNodes.ts (getSelectableRuntimeNodes/shouldShowRuntimeNodeSelector) shared by SetupWizardModal and SetupProjectForm; GitHub status revalidates on window focus and OAUTH_RELOGIN_SUCCESS_EVENT; 4 new i18n keys. diff --git a/packages/dashboard/app/components/ModelOnboardingModal.css b/packages/dashboard/app/components/ModelOnboardingModal.css index 528c69c1a8..05ddcd7b3c 100644 --- a/packages/dashboard/app/components/ModelOnboardingModal.css +++ b/packages/dashboard/app/components/ModelOnboardingModal.css @@ -667,20 +667,31 @@ gap: var(--space-lg); } +/* +FNXC:Onboarding 2026-07-10-10:45: +First-run review: the GitHub step mixed tiny bold micro-headers with helper-text bullets in one flat +list. The with/without comparison now renders as two labeled groups on a single consistent type scale +(font-size-sm headings + font-size-sm items), side by side on desktop and stacked on mobile. +*/ .onboarding-feature-list { - display: flex; - flex-direction: column; - gap: var(--space-sm); + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: var(--space-lg); padding: var(--space-md); border-radius: var(--radius-md); background: var(--surface); border: 1px solid var(--border); - font-size: calc(var(--space-md) + (var(--space-xs) / 4)); - color: var(--text-muted); line-height: 1.5; } -.onboarding-feature-list ul { +.onboarding-feature-list__group h4 { + margin: 0 0 var(--space-xs); + font-size: var(--font-size-sm); + font-weight: 600; + color: var(--text); +} + +.onboarding-feature-list__group ul { list-style: none; padding: 0; margin: 0; @@ -689,33 +700,19 @@ gap: var(--space-xs); } -.onboarding-feature-list .onboarding-feature-list-heading strong { - display: block; - font-size: var(--space-md); - font-weight: 600; - color: var(--text); - margin-top: var(--space-sm); -} - -.onboarding-feature-list .onboarding-feature-list-heading:first-child strong { - margin-top: 0; -} - -.onboarding-feature-list li:not(.onboarding-feature-list-heading) { +.onboarding-feature-list__group li { + font-size: var(--font-size-sm); + color: var(--text-muted); padding-left: var(--space-md); position: relative; } -.onboarding-feature-list li:not(.onboarding-feature-list-heading)::before { +.onboarding-feature-list__group li::before { content: "•"; position: absolute; left: 0; } -.onboarding-feature-list .onboarding-feature-list-item--with-github { - color: var(--text-muted); -} - /* === Onboarding GitHub Step === */ .onboarding-github-git-prerequisite { display: flex; @@ -875,6 +872,24 @@ font-family: var(--font-mono); } +/* +FNXC:Onboarding 2026-07-10-10:50: +Inline command fragments (`gh`, `brew install gh`, `sudo apt install git`) in git/gh install guidance +render as real elements (see renderWithInlineCode) instead of raw backtick literals. +*/ +.onboarding-github-setup-card p code, +.onboarding-github-setup-card li code, +.onboarding-github-git-prerequisite p code, +.onboarding-github-git-prerequisite li code { + padding: 1px var(--space-xs); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + background: var(--surface-raised); + color: var(--text); + font-family: var(--font-mono); + font-size: 0.9em; +} + /* === GitHub Connection Status Feedback (FN-1934) === */ .onboarding-github-feedback { margin-top: var(--space-sm); @@ -1271,12 +1286,25 @@ background: var(--surface); } -.model-onboarding-footer .btn-primary { - display: flex; +/* +FNXC:Onboarding 2026-07-10-10:55: +First-run review: footer buttons had wildly different sizes ("Next →" much larger than the skip +buttons). Every footer control shares the same 38px height and 14px type; the primary action keeps +its filled emphasis and wider padding only. +*/ +.model-onboarding-footer .btn, +.model-onboarding-footer .onboarding-skip-step-link { + display: inline-flex; align-items: center; + justify-content: center; gap: var(--space-sm); - padding: 10px 24px; + min-height: 38px; + padding: 8px 16px; font-size: 14px; +} + +.model-onboarding-footer .btn-primary { + padding: 8px 24px; font-weight: 600; } @@ -1526,6 +1554,12 @@ gap: var(--space-sm); } + /* FNXC:Onboarding 2026-07-10-10:45: stack the with/without GitHub comparison on phones. */ + .onboarding-feature-list { + grid-template-columns: 1fr; + gap: var(--space-md); + } + .model-onboarding-footer .btn-primary { flex: 1; min-width: 0; diff --git a/packages/dashboard/app/components/ModelOnboardingModal.tsx b/packages/dashboard/app/components/ModelOnboardingModal.tsx index d3b92f0316..8c477dcfd2 100644 --- a/packages/dashboard/app/components/ModelOnboardingModal.tsx +++ b/packages/dashboard/app/components/ModelOnboardingModal.tsx @@ -1,6 +1,6 @@ import "./ModelOnboardingModal.css"; import "./SetupWizardModal.css"; -import { lazy, Suspense, useState, useEffect, useCallback, useRef, useMemo, type KeyboardEvent } from "react"; +import { lazy, Suspense, useState, useEffect, useCallback, useRef, useMemo, type KeyboardEvent, type ReactNode } from "react"; import { X, Loader2, CheckCircle, Key, Zap, GitPullRequest, Rocket, Plus, Sparkles, UserRound } from "lucide-react"; import { getErrorMessage, type Task } from "@fusion/core"; import type { AuthProvider, ManualOAuthCodeInfo, ModelInfo, CustomProvider, CustomProviderConfig, OAuthDeviceCodeInfo } from "../api"; @@ -228,6 +228,23 @@ const PROVIDER_DISPLAY_NAMES: Record = { const getManualCodeLoginWarningMessage = (providerName: string) => `After you sign in with ${providerName}, the browser will try to redirect to a localhost address that this dashboard can't reach. The redirect tab will look like it failed. Before that happens, copy the full URL from the browser address bar — you'll paste it back here to finish login. Continue?`; +/* +FNXC:Onboarding 2026-07-10-10:05: +Install-guidance i18n strings (git/gh CLI) contain backtick-quoted commands like `gh` and `brew install gh`. +Previously those backticks rendered as literal characters in plain text (first-run review called this out as slop). +Render backtick spans as real elements so commands are visually distinct, without splitting every +translated string into prefix/suffix keys (all existing locale catalogs keep working unchanged). +*/ +function renderWithInlineCode(text: string): ReactNode { + const parts = text.split("`"); + if (parts.length < 3) { + return text; + } + return parts.map((part, index) => + index % 2 === 1 ? {part} : {part}, + ); +} + function getProviderDisplayName(providerId: string): string { if (PROVIDER_DISPLAY_NAMES[providerId]) { return PROVIDER_DISPLAY_NAMES[providerId]; @@ -1006,7 +1023,6 @@ export function ModelOnboardingModal({ const hasGithubProvider = githubActionState.oauth.providerAvailable; const isGithubAuthenticated = githubActionState.oauth.authenticated; const isGithubLoginInProgress = githubActionState.oauth.loginInProgress; - const isGithubCliAuthenticated = githubActionState.ghCli.authenticated; const gitInstallUrl = gitCliStatus?.installUrl ?? GIT_INSTALL_URL; const gitVersionLabel = gitCliStatus?.version ? t("setup.gitPrerequisiteInstalledVersion", "Git is installed on the Fusion host ({{version}}).", { version: gitCliStatus.version }) @@ -1121,6 +1137,27 @@ export function ModelOnboardingModal({ ); }, [loadAuthStatus, loadModels, loadGlobalSettings]); + /* + FNXC:Onboarding 2026-07-10-10:10: + Bug from first-run review: connecting GitHub mid-session (e.g. running `gh auth login` in a terminal, + or completing an OAuth login from another surface) left later wizard steps (Agent / First Task readiness + summary) showing GitHub as "not connected" because provider/gh-CLI status was only fetched once and only + re-fetched when returning to the AI Setup step. Revalidate the cached auth/gh/git status whenever the + window regains focus (the user just came back from a terminal or an OAuth tab) and whenever any surface + broadcasts a successful OAuth re-login, so every step reflects the current connection state. + */ + useEffect(() => { + const refreshAuthStatus = () => { + void loadAuthStatus(); + }; + window.addEventListener("focus", refreshAuthStatus); + window.addEventListener(OAUTH_RELOGIN_SUCCESS_EVENT, refreshAuthStatus); + return () => { + window.removeEventListener("focus", refreshAuthStatus); + window.removeEventListener(OAUTH_RELOGIN_SUCCESS_EVENT, refreshAuthStatus); + }; + }, [loadAuthStatus]); + // Restore login outcomes from persisted state on mount useEffect(() => { const persistedStepData = getStepData("ai-setup"); @@ -2147,8 +2184,19 @@ export function ModelOnboardingModal({ const visibleOrderedAiProviders = orderedAiProviders.filter( (provider) => !(provider.id === "anthropic" && hasSeparatedAnthropicProvider), ); + /* + FNXC:Onboarding 2026-07-10-10:15: + First-run review: after connecting a provider the "Connected providers" section rendered BELOW the + quick-start cards and the long provider list, so users could not see what was already connected without + scrolling. ALL connected providers (quick-start and advanced alike) now render in a single "Connected + providers" section at the TOP of the AI Setup step, and the quick-start list only offers providers that + still need connecting. The "N of M providers connected" summary above is unchanged. + */ + const connectedVisibleProviders = visibleOrderedAiProviders.filter( + (provider) => provider.authenticated, + ); const quickStartProviders = visibleOrderedAiProviders - .filter((provider) => quickStartSet.has(provider.id)) + .filter((provider) => quickStartSet.has(provider.id) && !provider.authenticated) .sort((a, b) => { const rankA = QUICK_START_PROVIDER_IDS.indexOf(a.id as (typeof QUICK_START_PROVIDER_IDS)[number]); const rankB = QUICK_START_PROVIDER_IDS.indexOf(b.id as (typeof QUICK_START_PROVIDER_IDS)[number]); @@ -2157,9 +2205,6 @@ export function ModelOnboardingModal({ } return compareOnboardingProviders(a, b); }); - const connectedNonQuickStartProviders = visibleOrderedAiProviders.filter( - (provider) => provider.authenticated && !quickStartSet.has(provider.id), - ); const advancedProviders = visibleOrderedAiProviders.filter( (provider) => !provider.authenticated && !quickStartSet.has(provider.id), ); @@ -2625,12 +2670,30 @@ export function ModelOnboardingModal({ actions={{ refreshAuthProviders: () => { void loadAuthStatus(); } }} /> + {/* + FNXC:Onboarding 2026-07-10-10:15: + Connected providers render FIRST so that reopening the step (or returning after an + OAuth login) immediately shows what is already connected without scrolling. + */} + {connectedVisibleProviders.length > 0 && ( +
+

{t("setup.connectedProviders", "Connected providers")}

+
+ {connectedVisibleProviders.map((provider) => renderAiProviderCard(provider))} +
+
+ )} +

{t("setup.quickStartProviders", "Quick start providers")}

{quickStartProviders.length > 0 ? (
{quickStartProviders.map((provider) => renderAiProviderCard(provider))}
+ ) : connectedVisibleProviders.length > 0 ? ( +

+ {t("setup.quickStartAllConnected", "All quick-start providers are already connected.")} +

) : (

{t("setup.noQuickStartProviders", "No quick-start providers are available in this environment.")} @@ -2680,16 +2743,7 @@ export function ModelOnboardingModal({

- {connectedNonQuickStartProviders.length > 0 && ( -
-

{t("setup.connectedProviders", "Connected providers")}

-
- {connectedNonQuickStartProviders.map((provider) => renderAiProviderCard(provider))} -
-
- )} - - {/* Model Selection — placed directly after the authenticated provider list */} + {/* Model Selection — placed directly after the provider sections */}

{t("setup.defaultModelOptional", "Default Model (Optional)")} @@ -2793,13 +2847,20 @@ export function ModelOnboardingModal({

{gitVersionLabel}

) : ( <> + {/* + FNXC:Onboarding 2026-07-10-10:20: + First-run review flagged raw backtick literals rendering as plain text. + Commands in these strings now render as real elements via renderWithInlineCode. + Missing git is a hard blocker for project setup, so its per-OS instructions stay + expanded (unlike the optional gh CLI guidance which is collapsed behind a disclosure). + */}

- {t("setup.gitPrerequisiteMissingBody", "Fusion could not find `git` on the server host running Fusion. Install Git there before cloning repositories, initializing projects, or registering Git-backed workspaces.")} + {renderWithInlineCode(t("setup.gitPrerequisiteMissingBody", "Fusion could not find `git` on the server host running Fusion. Install Git there before cloning repositories, initializing projects, or registering Git-backed workspaces."))}

    -
  • {t("setup.gitPrerequisiteMac", "macOS: install Xcode Command Line Tools with `xcode-select --install`, Homebrew with `brew install git`, or the Git installer.")}
  • -
  • {t("setup.gitPrerequisiteWindows", "Windows: install Git for Windows and restart the Fusion host shell or service.")}
  • -
  • {t("setup.gitPrerequisiteLinux", "Linux: install with your package manager, for example `sudo apt install git`, `sudo dnf install git`, or `sudo pacman -S git`.")}
  • +
  • {renderWithInlineCode(t("setup.gitPrerequisiteMac", "macOS: install Xcode Command Line Tools with `xcode-select --install`, Homebrew with `brew install git`, or the Git installer."))}
  • +
  • {renderWithInlineCode(t("setup.gitPrerequisiteWindows", "Windows: install Git for Windows and restart the Fusion host shell or service."))}
  • +
  • {renderWithInlineCode(t("setup.gitPrerequisiteLinux", "Linux: install with your package manager, for example `sudo apt install git`, `sudo dnf install git`, or `sudo pacman -S git`."))}
{t("setup.gitPrerequisiteInstallLink", "Open Git install downloads")} @@ -2809,22 +2870,31 @@ export function ModelOnboardingModal({

)} + {/* + FNXC:Onboarding 2026-07-10-10:25: + First-run review called the GitHub step "slop-coded": it mixed tiny bold micro-headers, + bullet items, and helper-text sizes inside one flat list. The with/without comparison is + now two labeled groups on one consistent type scale (heading + plain list items), rendered + only while GitHub is NOT connected — a connected user does not need the sales pitch. + */} {!isGitHubReady && (
-
    -
  • - {t("setup.withoutGitHubHeading", "Without GitHub (available now):")} -
  • -
  • {t("setup.withoutGitHub1", "Create tasks manually")}
  • -
  • {t("setup.withoutGitHub2", "Describe work for AI agents")}
  • -
  • {t("setup.withoutGitHub3", "Track progress on the board")}
  • -
  • - {t("setup.withGitHubHeading", "With GitHub (after connecting):")} -
  • -
  • {t("setup.withGitHub1", "Import issues as tasks")}
  • -
  • {t("setup.withGitHub2", "Sync pull request status")}
  • -
  • {t("setup.withGitHub3", "Link code changes to tasks")}
  • -
+
+

{t("setup.withoutGitHubHeading", "Without GitHub (available now)")}

+
    +
  • {t("setup.withoutGitHub1", "Create tasks manually")}
  • +
  • {t("setup.withoutGitHub2", "Describe work for AI agents")}
  • +
  • {t("setup.withoutGitHub3", "Track progress on the board")}
  • +
+
+
+

{t("setup.withGitHubHeading", "With GitHub (after connecting)")}

+
    +
  • {t("setup.withGitHub1", "Import issues as tasks")}
  • +
  • {t("setup.withGitHub2", "Sync pull request status")}
  • +
  • {t("setup.withGitHub3", "Link code changes to tasks")}
  • +
+
)} @@ -2849,27 +2919,36 @@ export function ModelOnboardingModal({ GitHub onboarding must give users in-flow paths for both supported auth modes instead of Settings-only explanatory copy. The dashboard can start OAuth when the GitHub provider exists; `gh` CLI setup remains explicit host-side guidance because Fusion does not install third-party binaries automatically. */} - {!isGithubCliAuthenticated && githubActionState.ghCli.state === "missing" && ( + {/* + FNXC:Onboarding 2026-07-10-10:30: + State-driven GitHub step (first-run review): the gh CLI cards only render while GitHub is + NOT ready overall — an OAuth-connected user must never be told to install the GitHub CLI. + The per-OS install wall of text is collapsed behind a single disclosure; the card leads + with one sentence and commands render as instead of raw backtick literals. + */} + {!isGitHubReady && githubActionState.ghCli.state === "missing" && (
{t("setup.githubCliInstallTitle", "Install GitHub CLI")}

- {t("setup.githubCliInstallBody", "Fusion could not find `gh` on the host running Fusion. Install GitHub CLI there to import issues and track pull requests with CLI authentication.")} + {renderWithInlineCode(t("setup.githubCliInstallBody", "Fusion could not find `gh` on the host running Fusion. Install GitHub CLI there to import issues and track pull requests with CLI authentication."))}

-
    -
  • {t("setup.githubCliInstallMac", "macOS: `brew install gh` or download the installer from GitHub CLI releases.")}
  • -
  • {t("setup.githubCliInstallWindows", "Windows: install GitHub CLI with WinGet, Chocolatey, or the GitHub CLI installer, then restart the Fusion host shell or service.")}
  • -
  • {t("setup.githubCliInstallLinux", "Linux: install `gh` with your distribution package manager or the packages from cli.github.com.")}
  • -
-
- {t("setup.githubCliInstallLink", "Open GitHub CLI releases")} - + +
    +
  • {renderWithInlineCode(t("setup.githubCliInstallMac", "macOS: `brew install gh` or download the installer from GitHub CLI releases."))}
  • +
  • {renderWithInlineCode(t("setup.githubCliInstallWindows", "Windows: install GitHub CLI with WinGet, Chocolatey, or the GitHub CLI installer, then restart the Fusion host shell or service."))}
  • +
  • {renderWithInlineCode(t("setup.githubCliInstallLinux", "Linux: install `gh` with your distribution package manager or the packages from cli.github.com."))}
  • +
+ + {t("setup.githubCliInstallLink", "Open GitHub CLI releases")} + +
)} - {!isGithubCliAuthenticated && githubActionState.ghCli.state === "unauthenticated" && ( + {!isGitHubReady && githubActionState.ghCli.state === "unauthenticated" && (
@@ -2898,32 +2977,40 @@ export function ModelOnboardingModal({
{isGitHubReadyViaCli ? ( -

- {t("setup.githubCliAuthNote", "GitHub CLI is already authenticated, so imports and PR tracking work now. OAuth from the dashboard is optional and only controls dashboard-managed connect/disconnect.")} -

+ <> +

+ {t("setup.githubCliAuthNote", "GitHub CLI is already authenticated, so imports and PR tracking work now. OAuth from the dashboard is optional and only controls dashboard-managed connect/disconnect.")} +

+
+ +
+ ) : ( + /* + * FNXC:Onboarding 2026-07-10-10:35: + * First-run review: the step offered THREE overlapping escape hatches at once — + * an in-body "Continue without GitHub →" CTA plus footer "Skip GitHub →" plus + * "Next →". The in-body button is removed entirely; the footer "Skip GitHub →" + * is the single skip affordance. The explanatory sentence (and its wrapper) stays, + * and its former actions
is dropped with it so no empty button shell remains + * on desktop or mobile. + */

- {t("setup.githubOauthUnavailable", "Dashboard GitHub OAuth is not configured on this Fusion host. You can still continue without GitHub, or use the GitHub CLI setup guidance above when available.")} + {t("setup.githubOauthUnavailable", "Dashboard GitHub OAuth is not configured on this Fusion host. You can still skip GitHub below, or use the GitHub CLI setup guidance above when available.")}

)} -
- -
) : ( <> @@ -3358,13 +3445,23 @@ export function ModelOnboardingModal({ {step === "ai-setup" && ( <> - + {/* + FNXC:Onboarding 2026-07-10-10:40: + First-run review: the AI Setup footer showed BOTH "Skip for now" (dismiss onboarding) and + "Skip setup →" (skip this step) next to "Next →". Once at least one AI provider is + connected the user has effectively completed this step, so "Skip for now" is redundant + noise and is hidden; "Skip setup →" and "Next →" remain. No wrapper/aria-label is left + behind — the button subtree is omitted entirely (desktop and mobile share this markup). + */} + {!hasAiProvider && ( + + )} diff --git a/packages/dashboard/app/components/SetupProjectForm.tsx b/packages/dashboard/app/components/SetupProjectForm.tsx index 5d469fb952..d52addb75e 100644 --- a/packages/dashboard/app/components/SetupProjectForm.tsx +++ b/packages/dashboard/app/components/SetupProjectForm.tsx @@ -4,6 +4,7 @@ import { Check, Loader2 } from "lucide-react"; import { validateProjectPath, validateProjectName, suggestProjectName } from "../utils/projectDetection"; import type { ProjectCreateInput, NodeInfo } from "../api"; import { DirectoryPicker } from "./DirectoryPicker"; +import { getSelectableRuntimeNodes, shouldShowRuntimeNodeSelector } from "./setupWizardNodes"; export interface SetupProjectFormProps { /** Called when the form is submitted with valid data */ @@ -119,24 +120,34 @@ export function SetupProjectForm({ return (
- {/* Node selector */} -
-
- {t("setup.runtimeNode", "Runtime Node")} - + {/* + FNXC:SetupWizard 2026-07-10-11:00: + Same Runtime Node dedupe as SetupWizardModal (Surface Enumeration for the duplicate + "Local node" / "local (local)" option bug): local-type node records never render as + options and the selector is hidden when only the local machine exists. + */} + {shouldShowRuntimeNodeSelector(nodes) && ( +
+
+ {t("setup.runtimeNode", "Runtime Node")} + +
+

+ {t("setup.runtimeNodeHint", "A runtime node is the machine where this project's tasks run. \"Local node\" is this computer; pick a remote node to run tasks elsewhere.")} +

-
+ )} {/* Path input */}
diff --git a/packages/dashboard/app/components/SetupWizardModal.css b/packages/dashboard/app/components/SetupWizardModal.css index cb930cad92..6b01b427d7 100644 --- a/packages/dashboard/app/components/SetupWizardModal.css +++ b/packages/dashboard/app/components/SetupWizardModal.css @@ -106,6 +106,19 @@ line-height: 1.1; } +/* +FNXC:SetupWizard 2026-07-10-11:05: +Eyebrow shown when this wizard runs as the onboarding "Project" step sub-flow, so the user keeps the +"Step 3 of 5" context instead of feeling ejected into an unrelated modal. +*/ +.setup-wizard-step-context { + font-size: 12px; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--text-muted); +} + .setup-wizard-content { padding: var(--space-md) var(--space-xl); flex: 1 1 auto; @@ -250,7 +263,13 @@ margin-bottom: var(--space-xs); } -.setup-wizard-manual .form-group input, +/* +FNXC:SetupWizard 2026-07-10-11:10: +Exclude radios/checkboxes from the full-width text-input styling. This rule previously matched the +isolation-mode radio inputs too, stretching the native radio into a full-width accent-colored bar +across the option card on click/focus (first-run review bug). +*/ +.setup-wizard-manual .form-group input:not([type="radio"]):not([type="checkbox"]), .setup-wizard-manual .form-group select { width: 100%; padding: 10px 12px; @@ -262,7 +281,7 @@ transition: border-color var(--transition-fast), box-shadow var(--transition-fast); } -.setup-wizard-manual .form-group input:focus, +.setup-wizard-manual .form-group input:not([type="radio"]):not([type="checkbox"]):focus, .setup-wizard-manual .form-group select:focus { outline: none; border-color: var(--todo); @@ -560,7 +579,17 @@ flex-direction: column; } +/* +FNXC:SetupWizard 2026-07-10-11:10: +Isolation-mode option cards ARE the selectable control (first-run review): the native radio used to +float centered in the card and stretched into a full-width orange bar on focus/click. The radio is now +visually hidden (kept in the DOM for form semantics and keyboard selection), the card shows the +selected state via border/ring, and focus-visible on the hidden radio draws the focus ring on the card. +Descriptions render in sentence case — the global ".form-group label" uppercase transform is +explicitly neutralized here because each option card is itself a
+ {/* + FNXC:SetupWizard 2026-07-10-11:05: + First-run review: opening project registration from the 5-step onboarding wizard (its + Project step) replaced the stepper chrome with a bare "Welcome to Fusion" modal, which read + as being thrown out of setup. When this wizard runs as that sub-flow (includeAgentStep is + false — see AppModals: it is only false while ModelOnboarding is driving), the header keeps + the onboarding context explicit: a "Step 3 of 5 — Project" eyebrow plus the onboarding + step's own title instead of the standalone welcome title. + */} + {!includeAgentStep && state.step === "manual" && ( + + {t("setup.projectStepContext", "Step 3 of 5 — Project · Fusion setup")} + + )}

- {state.step === "manual" && t("setup.welcomeToFusion", "Welcome to Fusion")} + {state.step === "manual" && (includeAgentStep + ? t("setup.welcomeToFusion", "Welcome to Fusion") + : t("setup.titleSetUpProject", "Set Up Your Project"))} {state.step === "agent" && t("setup.firstAgentTitle", "Create your first agent")} {state.step === "complete" && t("setup.setupCompleteTitle", "Setup Complete!")}

@@ -614,26 +631,49 @@ export function SetupWizardModal({ {showAdvancedSettings && (
-
-
- {t("setup.runtimeNode", "Runtime Node")} - + {/* + FNXC:SetupWizard 2026-07-10-11:00: + Runtime Node dedupe (first-run review): registered local-type node records used to + render as a second "local (local)" option next to the built-in "Local node" default. + Local-type records are filtered out (see setupWizardNodes.ts) and the whole selector + is hidden when only the local machine is available, with a plain-language description + of what a runtime node is when the choice does exist. + */} + {shouldShowRuntimeNodeSelector(nodes) && ( +
+
+ {t("setup.runtimeNode", "Runtime Node")} + +
+

+ {t("setup.runtimeNodeHint", "A runtime node is the machine where this project's tasks run. \"Local node\" is this computer; pick a remote node to run tasks elsewhere.")} +

-
+ )}
+ {/* + FNXC:SetupWizard 2026-07-10-11:10: + First-run review: the isolation-mode cards rendered a raw radio dot floating in the + card, and clicking/focusing stretched the native radio into a full-width accent bar + (a global ".form-group input { width: 100% }" rule hit the radio). The whole card is + the selectable control: the radio input is visually hidden (kept for semantics and + keyboard toggling), selection/focus is shown via the card's border/ring, and the + descriptions must read in sentence case — the global ".form-group label" uppercase + transform is neutralized for these option cards. See SetupWizardModal.css. + */}