← back to Exo
Fix custom model add requiring two attempts + enlarge sidebar buttons (#1805)
5327bdde846647cb29591c10fc3cac0f22a0554a · 2026-03-26 17:57:00 -0700 · Alex Cheema
## Motivation
Adding a custom model from the Hub tab shows "Added" toast but the model
doesn't appear in the All tab. You have to add it a second time for it
to work. Also, the "All" button in the model picker sidebar is too small
to read comfortably.
## Changes
**Race condition fix (`src/exo/api/main.py`):**
- Call `add_to_card_cache(card)` directly in `add_custom_model()` after
sending the `ForwarderCommand`, before the API response returns
**Sidebar sizing
(`dashboard/src/lib/components/FamilySidebar.svelte`):**
- Increased sidebar min-width from 72/64px to 80/72px
- Increased "All" icon from `w-5 h-5` to `w-6 h-6`
- Increased all sidebar labels from 9px to 11px
## Why It Works
`POST /models/add` sends a `ForwarderCommand(AddCustomModelCard)` and
returns immediately. The frontend then calls `GET /models` which reads
from `_card_cache`. But the cache was only updated by the worker event
handler after the event round-trips through the master — a race the
frontend almost always loses. By updating the cache directly in the API
handler, `GET /models` immediately reflects the new model. The worker's
later `add_to_card_cache` call is idempotent (dict key assignment).
## Test Plan
### Manual Testing
<!-- Hardware: any Mac -->
- Open model picker → Hub tab → add a custom model → verify it appears
in All tab on the first attempt
- Verify sidebar "All" button and other labels are visually larger and
readable
### Automated Testing
- `uv run basedpyright` passes with 0 errors
- `uv run ruff check` passes
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Files touched
M dashboard/src/lib/components/FamilySidebar.svelteM dashboard/src/lib/components/ModelFilterPopover.svelteM src/exo/api/main.py
Diff
commit 5327bdde846647cb29591c10fc3cac0f22a0554a
Author: Alex Cheema <41707476+AlexCheema@users.noreply.github.com>
Date: Thu Mar 26 17:57:00 2026 -0700
Fix custom model add requiring two attempts + enlarge sidebar buttons (#1805)
## Motivation
Adding a custom model from the Hub tab shows "Added" toast but the model
doesn't appear in the All tab. You have to add it a second time for it
to work. Also, the "All" button in the model picker sidebar is too small
to read comfortably.
## Changes
**Race condition fix (`src/exo/api/main.py`):**
- Call `add_to_card_cache(card)` directly in `add_custom_model()` after
sending the `ForwarderCommand`, before the API response returns
**Sidebar sizing
(`dashboard/src/lib/components/FamilySidebar.svelte`):**
- Increased sidebar min-width from 72/64px to 80/72px
- Increased "All" icon from `w-5 h-5` to `w-6 h-6`
- Increased all sidebar labels from 9px to 11px
## Why It Works
`POST /models/add` sends a `ForwarderCommand(AddCustomModelCard)` and
returns immediately. The frontend then calls `GET /models` which reads
from `_card_cache`. But the cache was only updated by the worker event
handler after the event round-trips through the master — a race the
frontend almost always loses. By updating the cache directly in the API
handler, `GET /models` immediately reflects the new model. The worker's
later `add_to_card_cache` call is idempotent (dict key assignment).
## Test Plan
### Manual Testing
<!-- Hardware: any Mac -->
- Open model picker → Hub tab → add a custom model → verify it appears
in All tab on the first attempt
- Verify sidebar "All" button and other labels are visually larger and
readable
### Automated Testing
- `uv run basedpyright` passes with 0 errors
- `uv run ruff check` passes
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
dashboard/src/lib/components/FamilySidebar.svelte | 25 ++++++----------------
.../src/lib/components/ModelFilterPopover.svelte | 2 +-
src/exo/api/main.py | 5 +++++
3 files changed, 13 insertions(+), 19 deletions(-)
diff --git a/dashboard/src/lib/components/FamilySidebar.svelte b/dashboard/src/lib/components/FamilySidebar.svelte
index 4a7307a0..62633a5e 100644
--- a/dashboard/src/lib/components/FamilySidebar.svelte
+++ b/dashboard/src/lib/components/FamilySidebar.svelte
@@ -42,31 +42,20 @@
</script>
<div
- class="flex flex-col gap-1 py-2 px-1 border-r border-exo-yellow/10 bg-exo-medium-gray/30 min-w-[72px] sm:min-w-[64px] overflow-y-auto scrollbar-hide"
+ class="flex flex-col gap-1 py-2 px-1 border-r border-exo-yellow/10 bg-exo-medium-gray/30 min-w-[80px] sm:min-w-[72px] overflow-y-auto scrollbar-hide"
>
<!-- All models (no filter) -->
<button
type="button"
onclick={() => onSelect(null)}
- class="group flex flex-col items-center justify-center p-2 sm:p-2 rounded transition-all duration-200 cursor-pointer min-h-[44px] sm:min-h-0 {selectedFamily ===
+ class="group flex items-center justify-center px-3 py-2.5 rounded transition-all duration-200 cursor-pointer min-h-[44px] sm:min-h-0 {selectedFamily ===
null
? 'bg-exo-yellow/20 border-l-2 border-exo-yellow'
: 'hover:bg-white/5 border-l-2 border-transparent'}"
title="All models"
>
- <svg
- class="w-5 h-5 {selectedFamily === null
- ? 'text-exo-yellow'
- : 'text-white/50 group-hover:text-white/70'}"
- viewBox="0 0 24 24"
- fill="currentColor"
- >
- <path
- d="M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z"
- />
- </svg>
<span
- class="text-[9px] font-mono mt-0.5 {selectedFamily === null
+ class="text-[12px] font-mono font-medium {selectedFamily === null
? 'text-exo-yellow'
: 'text-white/40 group-hover:text-white/60'}">All</span
>
@@ -90,7 +79,7 @@
: "text-white/50 group-hover:text-amber-400/70"}
/>
<span
- class="text-[9px] font-mono mt-0.5 {selectedFamily === 'favorites'
+ class="text-[11px] font-mono mt-0.5 {selectedFamily === 'favorites'
? 'text-amber-400'
: 'text-white/40 group-hover:text-white/60'}">Faves</span
>
@@ -115,7 +104,7 @@
: "text-white/50 group-hover:text-white/70"}
/>
<span
- class="text-[9px] font-mono mt-0.5 {selectedFamily === 'recents'
+ class="text-[11px] font-mono mt-0.5 {selectedFamily === 'recents'
? 'text-exo-yellow'
: 'text-white/40 group-hover:text-white/60'}">Recent</span
>
@@ -139,7 +128,7 @@
: "text-white/50 group-hover:text-orange-400/70"}
/>
<span
- class="text-[9px] font-mono mt-0.5 {selectedFamily === 'huggingface'
+ class="text-[11px] font-mono mt-0.5 {selectedFamily === 'huggingface'
? 'text-orange-400'
: 'text-white/40 group-hover:text-white/60'}">Hub</span
>
@@ -165,7 +154,7 @@
: "text-white/50 group-hover:text-white/70"}
/>
<span
- class="text-[9px] font-mono mt-0.5 truncate max-w-full {selectedFamily ===
+ class="text-[11px] font-mono mt-0.5 truncate max-w-full {selectedFamily ===
family
? 'text-exo-yellow'
: 'text-white/40 group-hover:text-white/60'}"
diff --git a/dashboard/src/lib/components/ModelFilterPopover.svelte b/dashboard/src/lib/components/ModelFilterPopover.svelte
index 8c36af03..a1cd6e16 100644
--- a/dashboard/src/lib/components/ModelFilterPopover.svelte
+++ b/dashboard/src/lib/components/ModelFilterPopover.svelte
@@ -73,7 +73,7 @@
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
- class="filter-popover absolute right-0 top-full mt-2 w-64 bg-exo-dark-gray border border-exo-yellow/10 rounded-lg shadow-xl z-10"
+ class="filter-popover absolute right-0 top-full mt-2 w-64 bg-exo-dark-gray border border-exo-yellow/10 rounded-lg shadow-xl z-20"
transition:fly={{ y: -10, duration: 200, easing: cubicOut }}
onclick={(e) => e.stopPropagation()}
role="dialog"
diff --git a/src/exo/api/main.py b/src/exo/api/main.py
index 3927ee5e..7862801f 100644
--- a/src/exo/api/main.py
+++ b/src/exo/api/main.py
@@ -129,6 +129,7 @@ from exo.shared.logging import InterceptLogger
from exo.shared.models.model_cards import (
ModelCard,
ModelId,
+ add_to_card_cache,
get_card,
get_model_cards,
)
@@ -1587,6 +1588,10 @@ class API:
)
)
+ # Immediately update the local cache so the subsequent GET /models
+ # returns the new model without waiting for the event round-trip.
+ add_to_card_cache(card)
+
return ModelListModel(
id=card.model_id,
hugging_face_id=card.model_id,
← 15f1b61f Rework model storage directory management (for external stor
·
back to Exo
·
chore: bump exo-version with release version (#1807) 1e51dc89 →