feat(FN-3399): route desktop remote mode into shell onboarding flow

Removes the standalone `DesktopShellBootstrap` component from the desktop package, routing desktop remote mode into the shell onboarding flow instead. Updates the corresponding test file to reflect the component removal and adjusts the README.

Fusion-Task-Id: FN-3399
This commit is contained in:
Fusion
2026-05-04 22:53:57 -07:00
committed by gsxdsm
parent 8565cdcff7
commit f7995bd4e7
22 changed files with 521 additions and 40 deletions

View File

@@ -0,0 +1,30 @@
.desktop-mode-chooser {
max-width: 640px;
margin: 0 auto;
padding: var(--space-2xl);
display: flex;
flex-direction: column;
gap: var(--space-lg);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
background: var(--card);
box-shadow: var(--shadow-md);
}
.desktop-mode-chooser__title {
margin: 0;
font-size: 1.5rem;
line-height: 1.2;
color: var(--text);
}
.desktop-mode-chooser__subtitle {
margin: 0;
color: var(--text-muted);
}
.desktop-mode-chooser__actions {
display: flex;
gap: var(--space-md);
flex-wrap: wrap;
}

View File

@@ -0,0 +1,48 @@
import React, { useState } from "react";
import "./DesktopModeChooser.css";
export type DesktopModeChoice = "local" | "remote";
interface DesktopModeChooserProps {
onSelectMode: (mode: DesktopModeChoice) => Promise<void>;
}
export function DesktopModeChooser({ onSelectMode }: DesktopModeChooserProps) {
const [pendingMode, setPendingMode] = useState<DesktopModeChoice | null>(null);
const handleSelect = async (mode: DesktopModeChoice) => {
setPendingMode(mode);
try {
await onSelectMode(mode);
} finally {
setPendingMode(null);
}
};
return (
<section className="desktop-mode-chooser" data-testid="desktop-mode-chooser">
<h1 className="desktop-mode-chooser__title">How do you want to run Fusion?</h1>
<p className="desktop-mode-chooser__subtitle">
Run Fusion locally in this app, or continue with remote server setup.
</p>
<div className="desktop-mode-chooser__actions">
<button
type="button"
className="btn btn-primary"
onClick={() => void handleSelect("local")}
disabled={pendingMode !== null}
>
Continue with Local Fusion
</button>
<button
type="button"
className="btn"
onClick={() => void handleSelect("remote")}
disabled={pendingMode !== null}
>
Continue to Remote Connection
</button>
</div>
</section>
);
}

View File

@@ -0,0 +1,78 @@
import React, { useEffect, useMemo, useState } from "react";
import { DesktopWrapper } from "./DesktopWrapper";
import { DesktopModeChooser, type DesktopModeChoice } from "./DesktopModeChooser";
interface DesktopModeState {
isFirstRun: boolean;
desktopMode: DesktopModeChoice | null;
}
interface FusionShellWithModeApi {
getDesktopModeState: () => Promise<DesktopModeState>;
setDesktopMode: (mode: DesktopModeChoice) => Promise<unknown>;
}
function getFusionShell(): FusionShellWithModeApi | null {
if (typeof window === "undefined") {
return null;
}
const api = (window as Window & { fusionShell?: Partial<FusionShellWithModeApi> }).fusionShell;
if (!api?.getDesktopModeState || !api?.setDesktopMode) {
return null;
}
return api as FusionShellWithModeApi;
}
export function DesktopShellBootstrap({ DashboardApp }: { DashboardApp: React.ComponentType }) {
const fusionShell = useMemo(() => getFusionShell(), []);
const [modeState, setModeState] = useState<DesktopModeState | null>(null);
useEffect(() => {
if (!fusionShell) {
setModeState({ isFirstRun: false, desktopMode: "local" });
return;
}
let cancelled = false;
void fusionShell.getDesktopModeState().then((state) => {
if (!cancelled) {
setModeState(state);
}
}).catch(() => {
if (!cancelled) {
setModeState({ isFirstRun: false, desktopMode: "local" });
}
});
return () => {
cancelled = true;
};
}, [fusionShell]);
const handleModeSelect = async (mode: DesktopModeChoice) => {
if (fusionShell) {
await fusionShell.setDesktopMode(mode);
}
setModeState({ isFirstRun: false, desktopMode: mode });
};
if (!modeState) {
return null;
}
if (modeState.isFirstRun) {
return (
<DesktopWrapper>
<DesktopModeChooser onSelectMode={handleModeSelect} />
</DesktopWrapper>
);
}
return (
<DesktopWrapper>
<DashboardApp />
</DesktopWrapper>
);
}