diff --git a/apps/desktop/src/renderer/src/components/window-overlay.tsx b/apps/desktop/src/renderer/src/components/window-overlay.tsx index b8d0bb715e..18ac1d835f 100644 --- a/apps/desktop/src/renderer/src/components/window-overlay.tsx +++ b/apps/desktop/src/renderer/src/components/window-overlay.tsx @@ -74,6 +74,13 @@ function WindowOverlayInner() { push(paths.root()); } }} + // Restart the bundled daemon when the user hits Refresh on + // Step 3. The daemon's PATH probe runs once at boot, so a + // newly-installed CLI (Claude / Codex / Cursor) doesn't show + // up until the daemon is bounced. + onRuntimeRefresh={async () => { + await window.daemonAPI?.restart?.(); + }} /> )} diff --git a/packages/views/locales/en/onboarding.json b/packages/views/locales/en/onboarding.json index a74c8eb7e0..e01e509e99 100644 --- a/packages/views/locales/en/onboarding.json +++ b/packages/views/locales/en/onboarding.json @@ -39,7 +39,8 @@ "common": { "back": "Back", "continue": "Continue", - "skip": "Skip" + "skip": "Skip", + "cancel": "Cancel" }, "questions": { "eyebrow_about_you": "About you", @@ -265,26 +266,22 @@ "status_n_online": "{{count}} online", "online_label": "online", "offline_label": "offline", - "empty_headline": "No AI coding tools found on this computer yet.", - "empty_lede_prefix": "Multica works through AI coding tools like ", - "empty_lede_suffix": ", and others — none are installed on this computer. Install one and come back, or pick a path below.", + "empty_headline": "No agent runtime found on this computer yet.", + "empty_lede_prefix": "An agent runtime is an AI coding tool we connect to — like ", + "empty_lede_suffix": ". Install one and refresh, or pick a path below.", "empty_skip_title": "Skip for now", "empty_skip_subtitle": "Enter your workspace in read-only mode. Your agent can't run tasks until an agent runtime is connected on this computer — but you can still browse, plan, and invite teammates.", "empty_skip_action": "Skip", "empty_waitlist_title": "Use a cloud computer", - "empty_waitlist_subtitle": "We'll run a computer for you in the cloud — no local install, no setup. Not live yet; click to leave your email and get notified.", - "empty_waitlist_action": "Join waitlist", - "empty_waitlist_done": "On the waitlist", - "dialog_title": "Join the cloud-computer waitlist", - "dialog_description": "Cloud computers aren't live yet. Leave your email and we'll email you when they are.", - "dialog_close": "Close", - "dialog_cancel": "Cancel", + "empty_waitlist_subtitle": "We'll run a computer for you in the cloud. Not live yet.", + "empty_waitlist_action": "Coming soon", "skip": "Skip for now", + "refresh": "Refresh", + "refreshing": "Refreshing…", "hint_selected": "Selected: {{name}}", "hint_pick": "Pick an agent runtime above to continue.", "hint_waiting": "Waiting for the first result…", - "hint_waitlist_done": "You're on the waitlist — skip to keep exploring.", - "hint_skip_or_waitlist": "Skip to enter your workspace, or join the cloud waitlist above." + "hint_skip_or_refresh": "Refresh after installing one, or skip to enter your workspace." }, "step_platform": { "eyebrow": "Connect a computer", diff --git a/packages/views/locales/zh-Hans/onboarding.json b/packages/views/locales/zh-Hans/onboarding.json index 7d9848bad7..11f1d74997 100644 --- a/packages/views/locales/zh-Hans/onboarding.json +++ b/packages/views/locales/zh-Hans/onboarding.json @@ -39,7 +39,8 @@ "common": { "back": "返回", "continue": "继续", - "skip": "跳过" + "skip": "跳过", + "cancel": "取消" }, "questions": { "eyebrow_about_you": "关于你", @@ -264,26 +265,22 @@ "status_n_online": "{{count}} 个在线", "online_label": "在线", "offline_label": "离线", - "empty_headline": "这台电脑上还没装支持的 AI 编程工具。", - "empty_lede_prefix": "Multica 借助本地的 AI 编程工具运行,比如 ", - "empty_lede_suffix": " 等 —— 这台电脑上一个都没装。装一个再回来,或在下面挑一条路。", + "empty_headline": "这台电脑上还没有可用的 agent 运行时。", + "empty_lede_prefix": "agent 运行时是我们接管的 AI 编程工具,比如 ", + "empty_lede_suffix": "。装好其中之一后点刷新,或在下面挑一条路。", "empty_skip_title": "暂时跳过", "empty_skip_subtitle": "进入工作区(只读模式)。在这台电脑上连接 agent 运行时之前,智能体无法执行 task —— 但你仍可以浏览、规划、邀请同事。", "empty_skip_action": "跳过", "empty_waitlist_title": "改用云电脑", - "empty_waitlist_subtitle": "我们会在云上为你运行一台电脑 —— 无需本地安装,无需配置。尚未上线,点击留下邮箱。", - "empty_waitlist_action": "加入候补", - "empty_waitlist_done": "已在候补", - "dialog_title": "加入云电脑候补名单", - "dialog_description": "云电脑尚未上线。留下邮箱,上线时通过邮件通知你。", - "dialog_close": "关闭", - "dialog_cancel": "取消", + "empty_waitlist_subtitle": "我们会在云上为你运行一台电脑。尚未上线。", + "empty_waitlist_action": "敬请期待", "skip": "暂时跳过", + "refresh": "刷新", + "refreshing": "正在刷新…", "hint_selected": "已选择:{{name}}", "hint_pick": "在上方选一个 agent 运行时继续。", "hint_waiting": "等待第一个结果...", - "hint_waitlist_done": "已在候补名单 —— 跳过即可继续探索。", - "hint_skip_or_waitlist": "跳过进入工作区,或在上方加入云候补。" + "hint_skip_or_refresh": "装好后点刷新,或跳过进入工作区。" }, "step_platform": { "eyebrow": "连接一台电脑", diff --git a/packages/views/onboarding/onboarding-flow.tsx b/packages/views/onboarding/onboarding-flow.tsx index 1363bde199..0b735c6b51 100644 --- a/packages/views/onboarding/onboarding-flow.tsx +++ b/packages/views/onboarding/onboarding-flow.tsx @@ -71,9 +71,15 @@ function mergeQuestionnaire( export function OnboardingFlow({ onComplete, runtimeInstructions, + onRuntimeRefresh, }: { onComplete: (workspace?: Workspace) => void; runtimeInstructions?: React.ReactNode; + /** Desktop wires this to restart the bundled daemon so a freshly + * installed agent CLI gets picked up on the runtime step. Web omits + * it — its CLI install flow already runs on the user's machine and + * the embedded picker reacts to daemon:register events. */ + onRuntimeRefresh?: () => void | Promise; }) { const { t } = useT("onboarding"); const user = useAuthStore((s) => s.user); @@ -94,11 +100,6 @@ export function OnboardingFlow({ const [workspace, setWorkspace] = useState(null); const [runtime, setRuntime] = useState(null); const [, setAgent] = useState(null); - // Sticky flag: Step 3's cloud-waitlist dialog only lives inside - // StepPlatformFork's local state, so the completion path for - // `runtime=null && waitlist submitted` would be invisible here without - // a shell-level record. One way latch; never cleared once set. - const [waitlistSubmitted, setWaitlistSubmitted] = useState(false); // Fetched at Step 0 + Step 2. Step 2 uses it to detect a pre-existing // workspace from an earlier abandoned onboarding (so StepWorkspace shows @@ -293,7 +294,7 @@ export function OnboardingFlow({ wsId={workspace.id} onNext={handleRuntimeNext} onBack={() => handleBack("runtime")} - onWaitlistSubmitted={() => setWaitlistSubmitted(true)} + onRefresh={onRuntimeRefresh} /> ); } @@ -325,17 +326,12 @@ export function OnboardingFlow({ ); } - // Derive the completion-path label for Step 5 here — runtime + - // waitlist state both live in this shell, StepFirstIssue doesn't - // have the visibility to compute it itself. - // runtime set → "full" - // no runtime + waitlist → "cloud_waitlist" - // no runtime, no waitlist → "runtime_skipped" + // Derive the completion-path label for Step 5. The cloud-waitlist + // exit was removed from Step 3 (replaced with a "Coming soon" badge) + // so this is now a binary: runtime → "full", no runtime → "runtime_skipped". const completionPath: OnboardingCompletionPath = runtime ? "full" - : waitlistSubmitted - ? "cloud_waitlist" - : "runtime_skipped"; + : "runtime_skipped"; return (
diff --git a/packages/views/onboarding/steps/step-platform-fork.tsx b/packages/views/onboarding/steps/step-platform-fork.tsx index 79b17d1ba0..b8afe4daa7 100644 --- a/packages/views/onboarding/steps/step-platform-fork.tsx +++ b/packages/views/onboarding/steps/step-platform-fork.tsx @@ -415,7 +415,7 @@ function CliInstallDialog({
- - -
); } +/** + * Static, non-interactive variant of EmptyCard used for the cloud-computer + * row. The card is dimmed and the pill is rendered as a badge so the user + * understands the option exists but isn't actionable yet. Mirrors the + * "Coming soon" treatment on the web platform fork. + */ +function ComingSoonCard({ + title, + subtitle, + badgeLabel, +}: { + title: string; + subtitle: string; + badgeLabel: string; +}) { + return ( +
+
+
{title}
+

+ {subtitle} +

+
+ + {badgeLabel} + +
+ ); +} + +function RefreshButton({ + onClick, + refreshing, + className, +}: { + onClick: () => void; + refreshing: boolean; + className?: string; +}) { + const { t } = useT("onboarding"); + return ( + + ); +} + /** * Card with a prominent right-side button. Mirrors the ForkAlt pattern * from the web fork step — whole card is clickable, but the pill is