fix(tui): give the /mcp panel's detail pane the rows its text wraps to

The Extensions panel (/mcp) shows the selected row's name, description
and detail in a pane fixed at three rows. Each part wraps, so whenever
one of them takes two lines the rest is cut with no ellipsis. At 80x24
that happens on four of the five tabs:

- MCP: "... review it before /mcp" and the sentence stops; the
  "restart connects anything." half of the safety note is gone.
- Plugins: the review line for computer-use ("Codewhale runs a plugin's
  skills, MCP servers and hooks only after you review exactly what it
  can do...") never shows.
- Marketplace: publisher, components and source never show.
- Skills: the research description stops at "synthesis, and
  unresolved" and the skill's path never shows.

It still happens at 100x32 and on the longer rows at 140x40.

Size the pane from the wrapped text instead: at least the three rows it
has now, at most a third of the panel, and the list keeps the rest.
Rows whose text fits in three lines render exactly as before, and the
one-row compact layout under 16 rows is unchanged. Scrolling the pane
instead would need keys of its own; giving up a few list rows is
cheaper, since the list already scrolls to keep the selection in view.

Signed-off-by: Lstarsky0 <[email protected]>
This commit is contained in:
Lstarsky0 committed 2026-10-17 16:48:27 +08:00
1 parent 15f1804fe8
commit 1f92c8c5c7
1 file changed
+30 -22
+30 -22
View File
@@ -2227,13 +2227,41 @@ impl ModalView for ExtensionsView {
if body.width == 0 || body.height < 5 {
return;
}
let spacious = body.height >= 16;
let status = if spacious && self.pending_remove.is_none() {
self.selected_item().map_or_else(
|| self.selected_status(),
|item| {
format!(
"{} · {}\n{}\n{}",
item.label, item.state, item.description, item.detail
)
},
)
} else {
self.selected_status()
};
let status = Paragraph::new(status)
.style(Style::default().fg(if self.pending_remove.is_some() {
palette::STATUS_WARNING
} else {
palette::TEXT_MUTED
}))
.wrap(Wrap { trim: false });
let status_height = if spacious {
u16::try_from(status.line_count(body.width))
.unwrap_or(u16::MAX)
.clamp(3, (body.height / 3).max(3))
} else {
1
};
let rows = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(1),
Constraint::Length(1),
Constraint::Min(1),
Constraint::Length(if body.height >= 16 { 3 } else { 1 }),
Constraint::Length(status_height),
Constraint::Length(1),
])
.split(body);
@@ -2418,27 +2446,7 @@ impl ModalView for ExtensionsView {
hits.rows.push((row_area, entry_index));
}
let status = if rows[3].height > 1 && self.pending_remove.is_none() {
self.selected_item().map_or_else(
|| self.selected_status(),
|item| {
format!(
"{} · {}\n{}\n{}",
item.label, item.state, item.description, item.detail
)
},
)
} else {
self.selected_status()
};
Paragraph::new(status)
.style(Style::default().fg(if self.pending_remove.is_some() {
palette::STATUS_WARNING
} else {
palette::TEXT_MUTED
}))
.wrap(Wrap { trim: false })
.render(rows[3], buf);
status.render(rows[3], buf);
let mut compact_hints = vec![
super::ActionHint::new("Tab", tr(self.locale, MessageId::ExtensionsActionTabs)),
super::ActionHint::new("/", tr(self.locale, MessageId::SessionsActionSearch)),