Skip to content

Show the selected provider's status in a banner over the conversation - #25

Draft
krishhgg wants to merge 3 commits into
t3term/cli-plain-controlsfrom
t3term/nightly-provider-status-banner
Draft

krishhgg wants to merge 3 commits into
t3term/cli-plain-controlsfrom
t3term/nightly-provider-status-banner

Conversation

@krishhgg

@krishhgg krishhgg commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

Stacked on #24 at d1d7e87. The code, tests and docs are written. I ran no tests, Clippy, build, fixture or terminal check, so nothing here is verified yet. Sol runs every check and the visual verification, and only the user merges.

This PR adds the provider half of the "Error and provider status banners" row in group 1 of docs/parity.md. When the provider instance the composer would send the next message with isn't ready, or runs a version T3 marks broken or unsupported, a banner over the conversation says so, as the nightly desktop's ProviderStatusBanner does. It sits above #21's thread error banner, and each one shows and dismisses on its own.

What changes

  • src/tui/provider.rs (new) reads the selected provider from T3's config as the desktop decodes it. It works out the notice's title, message, severity and dismissal key as ProviderStatusBanner.tsx does.
  • The provider is the one the model chip names, picker::view with the open thread's draft. t3term finds it by instanceId alone, so a provider with no models still shows its status, and no other provider stands in for one T3 no longer lists.
  • Alt+N or a click on × dismisses the notice. One dismissed key holds for the window, as ChatView.tsx keeps it, until the selected provider has no notice. Alt+O opens the whole message.
  • src/tui/banner.rs moves its drawing, keys and clicks into a shared Card, which both banners use. src/tui/mod.rs stacks the two and routes their keys and clicks. It works the notice out again only when the config, the open thread's selection or its draft changes.
  • The README documents the banner and its keys, and docs/sprint.md gets row 16.

No CLI command, --json output, RPC or setting changes.

Commits: 39b8ad7 adds the banner, 84698b4 the App tests, and b9c1305 the README and sprint row.

Tests: 26 new, none run yet

18 in src/tui/provider.rs:

  • an_unsupported_version_warns_on_a_ready_provider_until_t3_relaxes_its_policy
  • a_broken_version_shows_its_advice_in_place_of_the_failure_it_causes
  • signing_in_again_comes_before_a_version_warning
  • antigravity_waits_for_its_auth_result_before_warning
  • a_warning_reads_as_one_and_an_error_as_an_error
  • the_messages_point_to_t3_codes_provider_setup_or_the_cli
  • a_provider_the_desktop_couldnt_read_or_cant_send_with_has_no_notice
  • driver_ids_read_as_words
  • one_dismissal_holds_until_the_selection_has_no_notice
  • the_same_notice_keeps_the_banner_as_it_was_and_cleans_nothing
  • dismissal_goes_by_the_whole_message_past_what_the_banner_prints
  • the_selection_is_stale_only_when_its_inputs_change
  • a_notice_draws_its_title_over_its_message_and_names_its_keys
  • a_long_message_shows_three_rows_and_opens_with_alt_o
  • its_keys_are_its_own_and_only_while_it_shows
  • it_takes_its_clicks_and_closes_with_its_x
  • a_frame_rewraps_only_for_a_new_width
  • long_unicode_and_control_text_stays_inside_the_banner_at_any_size

8 App tests in src/tui/mod.rs:

  • the_provider_banner_stacks_over_the_error_and_each_dismisses_alone
  • the_banner_follows_the_instance_the_composer_would_send_with
  • one_dismissal_holds_across_threads_until_a_selection_has_no_notice
  • the_banner_finds_the_threads_instance_without_models_and_after_it_leaves
  • streaming_frames_and_unchanged_statuses_do_no_provider_work
  • one_banner_is_open_at_a_time_and_alt_arrows_scroll_the_open_one_first
  • at_any_size_the_banners_stay_apart_inside_the_transcript
  • a_late_model_list_read_or_a_reconnect_never_brings_an_old_status_back

No existing test was renamed or removed.

Fixture for the visual check

/tmp/t3term-provider-banner-fixture.py is review tooling and isn't committed. I wrote it and never ran or compiled it, so its first run is Sol's. It runs the real TUI in tmux against a fake T3 server on 127.0.0.1. HOME and T3CODE_HOME are inside the fixture directory, T3TERM_NO_SAVED_LOGIN=1 keeps the Keychain out, and every provider, thread and credential in it is invented. It serves subscribeServerConfig, so provider statuses change live.

cargo build
python3 /tmp/t3term-provider-banner-fixture.py start --bin target/debug/t3term
python3 /tmp/t3term-provider-banner-fixture.py provider unauth codex
python3 /tmp/t3term-provider-banner-fixture.py threads    # each thread's expected banners
python3 /tmp/t3term-provider-banner-fixture.py status     # RPC counts, controlLeaks
python3 /tmp/t3term-provider-banner-fixture.py cleanup

Its docstring (--help) walks 17 steps in order. They cover:

  • two instances of the same driver
  • live changes and recovery
  • error with unauthenticated
  • a ready provider on an unsupported version
  • a broken version's startup failure
  • Antigravity's unknown auth
  • checkedAt ticks
  • dismissal across threads
  • an unsent model pick
  • thread moves
  • lost models and a removed instance
  • long Unicode and control text
  • a stacked thread error
  • sizes down to 10x5
  • a reconnect
  • a restart
  • a late config read

Decision log

What I noticed

docs/parity.md marks "Error and provider status banners" as partial. #21 added the thread error banner, and t3term had no provider banner. A provider's state showed only in the model menu, as "(status)" after the name of a provider that isn't ready. A message could go to a signed-out or broken provider with nothing about it on the conversation screen.

How I confirmed it

I read the desktop sources at the nightly docs/parity.md pins. ProviderStatusBanner.tsx decides when the banner shows and what its title, message and severity are. ChatView.tsx keeps the dismissed key, and resolveComposerProviderSelection picks the provider. In t3term I read how the config arrives (client.watch_config, and projection::apply_config for snapshot and providerStatuses items) and how the chip picks its model (picker::view). Each desktop rule has a unit test case. The App tests drive config, thread and draft changes through App. None of them has run yet.

Options

  • Which provider. I chose the one the model chip names, found by instanceId. I rejected porting the desktop's resolveComposerProviderSelection fallbacks (the running session, the project default, another available provider). t3term sends with the chip's selection, so a banner about another provider would describe one the send doesn't use.
  • Drawing. I chose one Card in banner.rs for both banners. I rejected a second copy of the error banner's wrap, clip, scroll and click code.
  • Dismissal. I chose one key per window, in memory, as ChatView.tsx keeps it. I rejected saving it in settings.json. The desktop doesn't, and a saved dismissal would hide the notice after a restart.
  • Space. On a short transcript the error banner keeps at least three rows, and below eight rows the closed provider banner gives way to it. I rejected an even split, which on a small screen leaves each banner a title and a row.
  • Work per frame. The notice is worked out again only when the config, the thread's selection or the draft changes, and rewrapped only for a new notice or width. I rejected working it out each frame, since streaming redraws many times a second.

What review changed

Nothing yet. Greptile and Sol haven't reviewed it.

Known limits

  • The banner follows the model chip, not the desktop's fallback resolver. Where the desktop falls back to a running session's provider, the project default or another provider, the two can name different providers.
  • There's no provider setup. The advice names T3 Code's provider setup or the CLI, and t3term can't install, sign in or update a provider.
  • A closed banner holds the message to three rows but not the title, so a very long provider name takes more rows.
  • Dismissal lives in memory for one window, so a restart shows the notice again.
  • There are no screenshots or recordings yet. This PR changes the TUI, so it needs them before it's ready. Sol captures them with the fixture.
  • Nothing here has been built, tested or linted. Sol runs the checks, and CI runs them on this pull request.

🤖 Generated with Claude Code

krishhgg and others added 3 commits October 10, 2026 02:23
Shows the notice the nightly desktop's ProviderStatusBanner gives for the
provider instance the composer would send with, above the thread error
banner. Alt+N dismisses it and Alt+O opens the whole message. The two
banners share one card for drawing, keys and clicks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Covers the stacked banners, same-driver instances, unsent choices, one
dismissal across threads, an instance with no models or none at all, a
thread moved by another client, no provider work for a streamed answer
or a frame, one banner open at a time, every screen size, and a late
model list read or a reconnect.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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