mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-06 17:57:43 +02:00
[eric] apps: a preview whose dev server never binds shows an honest failure card with retry instead of spinning Starting preview forever (Alex's report)
This commit is contained in:
@@ -87,6 +87,21 @@ interface Props {
|
||||
}
|
||||
|
||||
// The app card's loading state while its runtime spins up. One soft pulse, calm copy, and an honest hint only after 9s, a freshly-imported app installs its deps on first open, which is the slow case worth explaining instead of leaving the user staring at a dead screen.
|
||||
const BootFailedBody: React.FC<{ onRetry: () => void }> = ({ onRetry }) => {
|
||||
const c = useClaudeTokens();
|
||||
return (
|
||||
<Box sx={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 1.25, px: 3, textAlign: 'center' }}>
|
||||
<Typography sx={{ fontSize: '0.875rem', color: c.text.primary }}>Preview didn't start</Typography>
|
||||
<Typography sx={{ fontSize: '0.75rem', color: c.text.muted, maxWidth: 260 }}>
|
||||
The app's dev server never came up. The Terminal tab has the exact error.
|
||||
</Typography>
|
||||
<Box role="button" onClick={onRetry} sx={{ mt: 0.5, px: 1.5, py: 0.5, borderRadius: 1, border: `1px solid ${c.border.medium}`, cursor: 'pointer', fontSize: '0.8rem', color: c.text.primary, '&:hover': { bgcolor: c.bg.elevated } }}>
|
||||
Try again
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const BootingBody: React.FC = () => {
|
||||
const c = useClaudeTokens();
|
||||
const [slow, setSlow] = useState(false);
|
||||
@@ -1012,7 +1027,7 @@ const DashboardOutputPreview: React.FC<{
|
||||
const tokens = useClaudeTokens();
|
||||
const dispatch = useAppDispatch();
|
||||
const workspaceId = output.workspace_id ?? null;
|
||||
const { frontendUrl, isNewMode, isHydrating } = useRuntimePreviewUrl({
|
||||
const { frontendUrl, isNewMode, isHydrating, bootFailed } = useRuntimePreviewUrl({
|
||||
workspaceId,
|
||||
enabled: !!workspaceId,
|
||||
onLog: onRuntimeLog,
|
||||
@@ -1111,6 +1126,9 @@ const DashboardOutputPreview: React.FC<{
|
||||
}
|
||||
|
||||
if (isBooting) {
|
||||
if (bootFailed) {
|
||||
return <BootFailedBody onRetry={() => { const tok = getAuthToken(); void fetch(`${API_BASE}/outputs/workspace/${workspaceId}/runtime/restart?instance=${instance}`, { method: 'POST', headers: tok ? { Authorization: `Bearer ${tok}` } : {} }).catch(() => {}); }} />;
|
||||
}
|
||||
return <BootingBody />;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ export interface RuntimePreviewState {
|
||||
isNewMode: boolean;
|
||||
// True until the runtime:status frame lands; prevents placeholder flash on remount when Vite is up.
|
||||
isHydrating: boolean;
|
||||
// The bind poll gave up: the spinner must become an honest failure state, not spin forever.
|
||||
bootFailed: boolean;
|
||||
}
|
||||
|
||||
export interface RuntimePreviewOptions {
|
||||
@@ -31,6 +33,7 @@ export function useRuntimePreviewUrl(opts: RuntimePreviewOptions): RuntimePrevie
|
||||
const [frontendUrl, setFrontendUrl] = useState<string | null>(null);
|
||||
const [isNewMode, setIsNewMode] = useState(false);
|
||||
const [isHydrating, setIsHydrating] = useState(true);
|
||||
const [bootFailed, setBootFailed] = useState(false);
|
||||
// Pin latest onLog so callback identity changes don't tear down/respawn the runtime.
|
||||
const onLogRef = useRef(onLog);
|
||||
onLogRef.current = onLog;
|
||||
@@ -80,6 +83,7 @@ export function useRuntimePreviewUrl(opts: RuntimePreviewOptions): RuntimePrevie
|
||||
const fu = msg.data?.frontend_url ?? null;
|
||||
setFrontendUrl(fu || null);
|
||||
setIsNewMode(!!msg.data?.is_new_mode);
|
||||
setBootFailed(!!msg.data?.boot_failed && !fu);
|
||||
setIsHydrating(false);
|
||||
} else if (msg.event === 'runtime:log') {
|
||||
const stream = msg.data?.stream || 'stdout';
|
||||
@@ -120,7 +124,7 @@ export function useRuntimePreviewUrl(opts: RuntimePreviewOptions): RuntimePrevie
|
||||
};
|
||||
}, [workspaceId, enabled, instance]);
|
||||
|
||||
return { frontendUrl, isNewMode, isHydrating };
|
||||
return { frontendUrl, isNewMode, isHydrating, bootFailed };
|
||||
}
|
||||
|
||||
export interface PickPreviewUrlOptions {
|
||||
|
||||
Reference in New Issue
Block a user