Skip to content

fix: model catalog follows the session's backend (TUI)#22

Merged
saucam merged 1 commit into
mainfrom
fix/model-catalog-provider-switch
Jul 12, 2026
Merged

fix: model catalog follows the session's backend (TUI)#22
saucam merged 1 commit into
mainfrom
fix/model-catalog-provider-switch

Conversation

@saucam

@saucam saucam commented Jul 12, 2026

Copy link
Copy Markdown
Owner

The TUI half of the model-catalog-doesn't-refresh-on-backend-switch bug (web half: saucam/codeoid#160).

Root cause — worse than the web

The TUI's models.list frame had no provider field at all, and the catalog was fetched exactly once at connect. So it always showed the daemon's default backend (claude) for the entire session — /provider switches and focus changes never refetched. A session on codex showed claude's models permanently.

Fix

  • protocol (codeoid-protocol): ClientMessage::ModelsList gains provider: Option<String> (wire-additive, skip_serializing_if None); DaemonMessage::ModelsListResult exposes provider so the client can drop a stale result.
  • fetch_models() sends the focused session's backend and runs on connect, on focus change, and after a /provider switch.
  • stale-result guard: a catalog whose backend no longer matches the focused session is dropped (fast-switch race). Legacy daemons omit provider → accepted (can't guard), preserving old behavior.
  • /provider optimistic update: the switch stamps the new backend onto the focused session locally before refetching, so both the fetch and its guard key off the new backend without waiting for the daemon's session.info_update frame (a separate message that may arrive after the switch response). It also clears the stale catalog so the picker can't flash the old models.

A never-used backend shows an empty catalog until its first turn (models genuinely unknown until the backend spawns) — same honest behavior as the web fix.

Tests

  • result matching the focused backend is applied; stale result for the old backend is dropped; legacy (no-provider) result accepted; models.list frame carries the provider (and omits it when None).
  • cargo test --workspace: 269 tui + 13 protocol + 10 client green; fmt --check clean.

Depends on the daemon already keying models.list by provider (shipped in the provider-extension work) — no daemon change needed.

🤖 Generated with Claude Code

Same class of bug as the web fix: switching a session's backend left the
`/model` picker + validation showing the OLD backend's models. Worse
here — the TUI's `models.list` frame had NO provider field at all, and
the catalog was fetched exactly once at connect, so it always showed the
daemon's default backend (claude) for the whole session, regardless of
`/provider` switches or which session was focused.

- protocol: `ClientMessage::ModelsList` gains `provider: Option<String>`
  (wire-additive); `DaemonMessage::ModelsListResult` exposes `provider`
  so the client can drop a stale result after a fast switch.
- `fetch_models()` sends the FOCUSED session's backend, and runs on
  connect, on focus change, and after a `/provider` switch.
- the result handler drops a catalog whose backend no longer matches the
  focused session (fast-switch race); legacy daemons omit `provider`, so
  those results are accepted (can't guard) — unchanged behavior.
- `/provider` optimistically stamps the new backend onto the focused
  session before refetching, so the fetch + its guard both key off the
  new backend without waiting for the daemon's info_update frame; it also
  clears the stale catalog so the picker can't flash the old models.

A never-used backend still shows an empty catalog until its first turn
(models are genuinely unknown until the backend spawns).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🤖 Gemini code review

This PR fixes a bug in the TUI where the model catalog failed to update upon switching backends (/provider switches or session focus changes). It adds a provider field to the ModelsList client message and the ModelsListResult daemon message, implements stale-result guarding to prevent race conditions during fast switches, and includes optimistic local state updates to prevent model picker flashing. Well-structured tests have been added to verify these changes.

Findings: 🔴 0 · 🟠 0 · 🟡 0 · 🟢 0


Tokens spent · ⬆️ Input: 4,265 · ⬇️ Output: 105 · Σ Total: 7,525
Total may be higher due to thinking token counts.

@codecov

codecov Bot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.31343% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.15%. Comparing base (5f890ce) to head (c693cc9).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
crates/codeoid-tui/src/app.rs 87.87% 16 Missing ⚠️
crates/codeoid-protocol/src/client.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #22      +/-   ##
==========================================
+ Coverage   71.82%   72.15%   +0.32%     
==========================================
  Files          31       31              
  Lines        8434     8559     +125     
==========================================
+ Hits         6058     6176     +118     
- Misses       2376     2383       +7     
Flag Coverage Δ
tui 72.15% <87.31%> (+0.32%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
crates/codeoid-tui/src/state/mod.rs 86.22% <100.00%> (+0.02%) ⬆️
crates/codeoid-protocol/src/client.rs 37.03% <0.00%> (ø)
crates/codeoid-tui/src/app.rs 54.06% <87.87%> (+2.64%) ⬆️

@saucam saucam merged commit 211dc0f into main Jul 12, 2026
4 checks passed
This was referenced Jul 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant