mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-08 17:10:50 -10:00
fix(tui): show the blank Custom provider slot as setup, not a legacy route
On a fresh install the first-run provider picker listed the blank Custom
slot as `custom (custom) legacy · legacy` with the footer
`Enter unavailable`, although Enter opens the endpoint form. The slot fails
route admission by design (nothing is configured), and the unadmitted
branch skipped its catalog presentation. Keep its descriptor label
"Custom (OpenAI-compatible)", show `needs endpoint`, and name the real
Enter action. Configured custom tables and bundled descriptor rows are
unchanged.
Also make all six published website frames reproducible from the opt-in
`website_current_terminal_capture` test: it now captures the empty Fleet
and Tasks work bar at 100x32 instead of an unrecorded manual sequence.
Evidence: found by re-capturing the website frames from a 3a788990 build
in a sealed HOME (PTY probe frame: `▸ custom (custom) legacy · legacy`,
footer `Enter unavailable`; Enter opened "Custom provider" form).
`cargo test -p codewhale-tui --lib -- tui::provider_picker::tests::`
test result: ok. 145 passed; 0 failed (incl. new
blank_custom_slot_reads_as_setup_not_legacy). Capture test with
--features long-running-tests: 1 passed; 0 failed. rustfmt --check and
git diff --check passed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: CodeWhale Bot <[email protected]>
This commit is contained in:
1 parent
3a788990d0
commit
8ba2bbf805
2 files changed
+71
-2
No files matched your search
@@ -597,7 +597,18 @@ impl ProviderDashboardRow {
|
||||
provider,
|
||||
identity: None,
|
||||
provider_id: provider_id.clone(),
|
||||
// The blank `Custom` catalog slot is not a configured route,
|
||||
// so admission fails by design; it keeps its catalog label.
|
||||
display_name: presentation
|
||||
.or_else(|| {
|
||||
(provider == ProviderKind::Custom && configured.is_none())
|
||||
.then(|| {
|
||||
codewhale_config::descriptors::compatibility_for_id(
|
||||
&provider_id,
|
||||
)
|
||||
})
|
||||
.flatten()
|
||||
})
|
||||
.map(|row| row.label.to_string())
|
||||
.unwrap_or_else(|| format!("{provider_id} (custom)")),
|
||||
kind: configured
|
||||
@@ -1027,6 +1038,9 @@ impl ProviderDashboardRow {
|
||||
// A row you cannot use yet says what it needs, once. The
|
||||
// bundled-model count beside it only repeated itself down a
|
||||
// fifty-row list; the Details pane still carries it.
|
||||
if self.is_custom_placeholder() {
|
||||
return "needs endpoint".to_string();
|
||||
}
|
||||
match self.readiness {
|
||||
ResolvedProviderReadiness::MissingKey => return "needs key".to_string(),
|
||||
ResolvedProviderReadiness::MissingLogin => return "needs sign-in".to_string(),
|
||||
@@ -1043,6 +1057,14 @@ impl ProviderDashboardRow {
|
||||
}
|
||||
}
|
||||
|
||||
/// The blank `Custom` slot: Enter opens the endpoint form (see
|
||||
/// `activate_selected_row`), so it is neither a broken route nor legacy.
|
||||
fn is_custom_placeholder(&self) -> bool {
|
||||
self.provider == ProviderKind::Custom
|
||||
&& !self.is_configured
|
||||
&& provider_descriptor(&self.provider_id).is_none()
|
||||
}
|
||||
|
||||
fn catalog_label(&self) -> String {
|
||||
match self.catalog_status {
|
||||
ProviderCatalogStatus::Bundled => format!("{} bundled", self.available_model_count),
|
||||
@@ -2721,7 +2743,9 @@ impl ProviderPickerView {
|
||||
}
|
||||
|
||||
fn render_list(&self, area: Rect, buf: &mut Buffer) {
|
||||
let enter_action = if !self.selected_route_is_valid() {
|
||||
let enter_action = if self.rows[self.selected_idx].is_custom_placeholder() {
|
||||
self.tr(MessageId::PickerActionCustom)
|
||||
} else if !self.selected_route_is_valid() {
|
||||
self.tr(MessageId::PickerActionUnavailable)
|
||||
} else if self.selected_has_key() {
|
||||
self.tr(MessageId::PickerActionApply)
|
||||
@@ -5735,6 +5759,25 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// First-run catalog: the blank Custom slot fails route admission by
|
||||
/// design, but it is the way into the endpoint form, not a legacy route.
|
||||
#[test]
|
||||
fn blank_custom_slot_reads_as_setup_not_legacy() {
|
||||
let config = Config::default();
|
||||
let picker = ProviderPickerView::new(ProviderKind::Deepseek, &config);
|
||||
let row = picker
|
||||
.rows
|
||||
.iter()
|
||||
.find(|row| row.provider_id == ProviderKind::Custom.as_str())
|
||||
.expect("blank custom slot");
|
||||
assert_eq!(row.display_name, "Custom (OpenAI-compatible)");
|
||||
assert!(row.is_custom_placeholder());
|
||||
assert_eq!(
|
||||
row.list_row_hint(ProviderListView::Catalog),
|
||||
"needs endpoint"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_view_shows_only_configured_providers() {
|
||||
// #3830: with nothing but the active provider set up, the default
|
||||
|
||||
@@ -16,7 +16,8 @@ const SAVED_TEXT: &str = "Restored conversation proof";
|
||||
|
||||
/// Published codewhale.net terminal media. Default Underwater theme, no
|
||||
/// motion, a home-relative workspace, and only states a fresh install really
|
||||
/// reaches: the first-run provider picker, home, a typed draft, and /help.
|
||||
/// reaches: the first-run provider picker, home, a typed draft, /help, and
|
||||
/// the empty Fleet and Tasks work bar.
|
||||
#[test]
|
||||
#[ignore = "opt-in website media; empty isolated session, no provider calls"]
|
||||
fn website_current_terminal_capture() {
|
||||
@@ -59,6 +60,31 @@ fn website_current_terminal_capture() {
|
||||
wait(&mut tui, "/model");
|
||||
capture(&mut tui, "website-help");
|
||||
tui.shutdown();
|
||||
|
||||
// The work bar at the taller size the site shows it, opened by the
|
||||
// advertised chords on an emptied composer: Fleet (Ctrl+]), then Tasks.
|
||||
let workspace = make_sealed_workspace_in_home("my-project").unwrap();
|
||||
let (_workspace, mut tui) = start_in(
|
||||
workspace,
|
||||
Launch {
|
||||
rows: 32,
|
||||
cols: 100,
|
||||
keep_composer: true,
|
||||
..Launch::default()
|
||||
},
|
||||
|_| {},
|
||||
);
|
||||
tui.wait_for_idle(Duration::from_secs(1), WAIT).unwrap();
|
||||
tui.send(keys::key::backspaces(80)).unwrap();
|
||||
wait(&mut tui, "Type a message");
|
||||
assert_website_frame(&mut tui);
|
||||
tui.send([0x1d]).unwrap();
|
||||
wait(&mut tui, "no agents have run this session");
|
||||
capture(&mut tui, "website-workbar-fleet");
|
||||
tui.send(b"\x1b[9;6u").unwrap();
|
||||
wait(&mut tui, "no to-dos yet");
|
||||
capture(&mut tui, "website-workbar");
|
||||
tui.shutdown();
|
||||
}
|
||||
|
||||
/// The published frame names the project the way a user's shell would and
|
||||
|
||||
Reference in new issue
Block a user