From 51854d9063d4e8a2fa4a292cee78dbae385e2d0c Mon Sep 17 00:00:00 2001
From: Alex Cheema
Date: Sun, 15 Feb 2026 15:56:16 -0800
Subject: [PATCH] =?UTF-8?q?fix:=205=20CTO=20review=20issues=20=E2=80=94=20?=
=?UTF-8?q?double=20open,=20derived=20bug,=20cached=20race,=20error=20UX,?=
=?UTF-8?q?=20sharding=20visibility?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. Suppress Python webbrowser.open() when running inside native macOS app
(EXO_RUNTIME_DIR set) to prevent double browser open with FirstLaunchPopout
2. Fix $derived(() => ...) → $derived.by(() => ...) for onboardingModels and
onboardingDownloadProgress so they cache properly instead of returning thunks
3. Handle cached/already-downloaded models: step 4 effect checks for READY
status and skips directly to step 6 instead of flashing download UI
4. Show error banner on step 3 when placement or launch fails instead of
silently reverting
5. Remove debugEnabled guard from sharding/instanceType info — always visible
for power users in both welcome and chat sidebars
Co-Authored-By: Claude Opus 4.6
---
dashboard/src/routes/+page.svelte | 84 ++++++++++++++++++-------------
src/exo/utils/banner.py | 14 ++++--
2 files changed, 58 insertions(+), 40 deletions(-)
diff --git a/dashboard/src/routes/+page.svelte b/dashboard/src/routes/+page.svelte
index 73de742b..031f2575 100644
--- a/dashboard/src/routes/+page.svelte
+++ b/dashboard/src/routes/+page.svelte
@@ -229,7 +229,7 @@
const showOnboarding = $derived(onboardingStep > 0);
// Recommended models for onboarding (sorted by fit, then size desc, limited to 6)
- const onboardingModels = $derived(() => {
+ const onboardingModels = $derived.by(() => {
if (models.length === 0) return [];
return [...models]
.filter((m) => getModelMemoryFitStatus(m) !== "too_large")
@@ -242,19 +242,30 @@
.slice(0, 6);
});
- // Track onboarding instance status for auto-advancing steps
+ // Track onboarding instance status for auto-advancing steps.
+ // Handles cached models: if no download is needed, skip step 4 entirely.
$effect(() => {
if (onboardingStep === 4 && instanceCount > 0) {
- // Check if any instance is past downloading
let anyDownloading = false;
+ let anyReady = false;
for (const [id, inst] of Object.entries(instanceData)) {
const status = getInstanceDownloadStatus(id, inst);
if (status.isDownloading) {
anyDownloading = true;
- break;
+ }
+ if (
+ status.statusText === "READY" ||
+ status.statusText === "LOADED" ||
+ status.statusText === "RUNNING"
+ ) {
+ anyReady = true;
}
}
- if (!anyDownloading) {
+ // Model already cached & ready — skip download AND loading steps
+ if (anyReady) {
+ onboardingStep = 6;
+ } else if (!anyDownloading) {
+ // Download finished (or was never needed) but not ready yet
onboardingStep = 5;
}
}
@@ -285,8 +296,11 @@
}
}
+ let onboardingError = $state(null);
+
async function onboardingLaunchModel(modelId: string) {
onboardingModelId = modelId;
+ onboardingError = null;
selectPreviewModel(modelId);
onboardingStep = 4;
// Launch via API
@@ -295,10 +309,8 @@
`/instance/placement?model_id=${encodeURIComponent(modelId)}&sharding=${selectedSharding}&instance_meta=${selectedInstanceType}&min_nodes=1`,
);
if (!placementResponse.ok) {
- console.error(
- "Onboarding placement failed:",
- await placementResponse.text(),
- );
+ const errorText = await placementResponse.text();
+ onboardingError = `Could not place model: ${errorText}`;
onboardingStep = 3;
return;
}
@@ -309,20 +321,21 @@
body: JSON.stringify({ instance: placementData }),
});
if (!response.ok) {
- console.error("Onboarding launch failed:", await response.text());
+ const errorText = await response.text();
+ onboardingError = `Failed to launch: ${errorText}`;
onboardingStep = 3;
return;
}
setSelectedChatModel(modelId);
recordRecentLaunch(modelId);
} catch (error) {
- console.error("Onboarding launch error:", error);
+ onboardingError = `Network error: ${error}`;
onboardingStep = 3;
}
}
// Helper to get onboarding download progress
- const onboardingDownloadProgress = $derived(() => {
+ const onboardingDownloadProgress = $derived.by(() => {
if (instanceCount === 0) return null;
for (const [id, inst] of Object.entries(instanceData)) {
const status = getInstanceDownloadStatus(id, inst);
@@ -2628,7 +2641,16 @@
- {#if onboardingModels().length === 0}
+ {#if onboardingError}
+
Loading models...
@@ -2636,7 +2658,7 @@
{:else}
- {#each onboardingModels() as model}
+ {#each onboardingModels as model}
{@const sizeGB = getModelSizeGB(model)}
{@const fitsNow = hasEnoughMemory(model)}
{@const tags = modelTags()[model.id] || []}
@@ -2712,28 +2734,24 @@
- {#if onboardingDownloadProgress()}
+ {#if onboardingDownloadProgress}
+ {onboardingDownloadProgress.percentage.toFixed(1)}%
{onboardingDownloadProgress()!.percentage.toFixed(1)}%
- {formatBytes(onboardingDownloadProgress()!.downloadedBytes)} /
- {formatBytes(onboardingDownloadProgress()!.totalBytes)}{formatBytes(onboardingDownloadProgress.downloadedBytes)} /
+ {formatBytes(onboardingDownloadProgress.totalBytes)}
- {formatSpeed(onboardingDownloadProgress()!.speed)}
- ETA: {formatEta(onboardingDownloadProgress()!.etaMs)}
+ {formatSpeed(onboardingDownloadProgress.speed)}
+ ETA: {formatEta(onboardingDownloadProgress.etaMs)}
{:else}
@@ -3373,11 +3391,9 @@
>
{getInstanceModelId(instance)}
- {#if debugEnabled}
-
- {instanceInfo.sharding} · {instanceInfo.instanceType}
-
- {/if}
+
+ {instanceInfo.sharding} · {instanceInfo.instanceType}
+
{#if instanceModelId && instanceModelId !== "Unknown" && instanceModelId !== "Unknown Model"}
{getInstanceModelId(instance)}
- {#if debugEnabled}
-
- {instanceInfo.sharding} · {instanceInfo.instanceType}
-
- {/if}
+
+ {instanceInfo.sharding} · {instanceInfo.instanceType}
+
{#if instanceModelId && instanceModelId !== "Unknown" && instanceModelId !== "Unknown Model"}