fix(ui): show SSH state as a corner status dot in semantic colors

The SSH avatar drew a 2px border ring in theme tokens, which read as a
second avatar shape next to the flat shell badge — and "connected" used
theme.accent, the list-selection grey in this app, so the ring showed no
state at all. Reuse the agent status_dot on the badge corner instead,
colored from the same hardcoded palette as agent dots (amber connecting,
green connected, red failed/disconnected, neutral for a foreground ssh);
the tab strip's inline 6px dot picks up the same RGB values.
This commit is contained in:
l0ng-ai
2026-07-15 17:45:59 +08:00
parent 611b671a5d
commit 2029f2ccf7
2 changed files with 38 additions and 21 deletions
+17 -12
View File
@@ -2793,29 +2793,34 @@ impl Tty7App {
}
/// The status-dot colour for a tab whose representative pane is an SSH
/// session (PRD FR-E2): native panes are phase-coloured (connecting = warning,
/// connected = accent, failed/disconnected = red); a foreground `ssh` typed
/// into a shell gets a plain neutral dot. `None` for non-SSH tabs (no dot).
pub(crate) fn tab_ssh_dot(&self, tab: &Tab, cx: &App) -> Option<gpui::Hsla> {
/// session (PRD FR-E2), as an RGB value from the same hardcoded semantic
/// palette as [`AgentStatus::dot_rgb`] — not the theme's UI tokens, which
/// in this app are soft neutral fills (accent is the list-selection grey)
/// and read as no state at all. Native panes are phase-coloured
/// (connecting = amber, connected = green, failed/disconnected = red); a
/// foreground `ssh` typed into a shell gets a plain neutral dot. `None`
/// for non-SSH tabs (no dot).
///
/// [`AgentStatus::dot_rgb`]: crate::core::cli_agent::AgentStatus::dot_rgb
pub(crate) fn tab_ssh_dot(&self, tab: &Tab, cx: &App) -> Option<u32> {
use crate::daemon::protocol::SshPhase;
let leaf = tab.pane.first_leaf()?;
let v = leaf.read(cx);
let theme = cx.theme();
if let Some(phase) = v.ssh_phase() {
// Native pane.
let color = if v.ssh_disconnected() {
theme.danger
let rgb = if v.ssh_disconnected() {
0xEF4444 // red: link lost
} else {
match phase {
SshPhase::Connecting | SshPhase::Authenticating => theme.warning,
SshPhase::Connected => theme.accent,
SshPhase::Failed { .. } => theme.danger,
SshPhase::Connecting | SshPhase::Authenticating => 0xF59E0B, // amber: in flight
SshPhase::Connected => 0x22C55E, // green: link up
SshPhase::Failed { .. } => 0xEF4444, // red: never made it
}
};
Some(color)
Some(rgb)
} else if v.remote_context().is_some() {
// A foreground `ssh` typed into a shell: a plain neutral dot.
Some(theme.muted_foreground)
Some(0x9CA3AF)
} else {
None
}
+21 -9
View File
@@ -147,7 +147,8 @@ impl Render for DragTab {
}
impl Tty7App {
/// The status dot pinned to an agent avatar's bottom-right corner: a solid
/// The status dot pinned to a tab avatar's bottom-right corner (an agent's
/// live status, or an SSH pane's connection phase): a solid
/// `rgb` disc with a surface-colored separator ring so it reads as sitting
/// on the badge. When `unread` (a finished turn you haven't looked at), the
/// dot gains a crisp outer ring of the same hue — the dot's separator ring
@@ -204,16 +205,17 @@ impl Tty7App {
/// The leading avatar for a tab row/chip: a rounded badge that brands the
/// tab by what's running in it — each session fronted with an icon. A
/// recognized coding agent gets its brand mark — a white silhouette
/// (gpui tints SVGs as an alpha mask) on the vendor accent; an SSH pane gets
/// a terminal glyph ringed in its connection-status colour; a plain shell
/// gets a neutral terminal glyph. An agent's live status rides the corner as
/// a [`status_dot`](Self::status_dot). `size` is the badge's edge in px.
/// (gpui tints SVGs as an alpha mask) on the vendor accent; a plain shell
/// gets a neutral terminal glyph. Live status rides the corner as a
/// [`status_dot`](Self::status_dot) — the agent's working/waiting/done, or
/// an SSH pane's connection phase (`ssh`) — one corner-dot language for
/// the whole avatar column. `size` is the badge's edge in px.
pub(crate) fn tab_avatar(
&self,
agent: Option<crate::core::cli_agent::CLIAgent>,
status: Option<crate::core::cli_agent::AgentStatus>,
unread: bool,
ssh: Option<gpui::Hsla>,
ssh: Option<u32>,
size: f32,
cx: &App,
) -> gpui::AnyElement {
@@ -251,11 +253,11 @@ impl Tty7App {
.into_any_element()
}
None => base
.relative()
.rounded_full()
// A clearly-visible neutral disc (a neutral grey shell badge), not a
// near-transparent tint — so the avatar column reads as a column.
.bg(cx.theme().muted)
.when_some(ssh, |d, c| d.border_2().border_color(c))
.child(
// A flush `>_` prompt (not the boxed `square-terminal`) so it
// fills the badge at the same visual weight as a brand mark.
@@ -264,6 +266,10 @@ impl Tty7App {
.size(px(size * 0.56))
.text_color(cx.theme().foreground.opacity(0.65)),
)
// SSH connection phase as a corner status dot — the same
// element as an agent's, not a border ring around the badge
// (a ring read as a second, differently-shaped avatar style).
.when_some(ssh, |b, rgb| b.child(Self::status_dot(rgb, false, size, cx)))
.into_any_element(),
}
}
@@ -560,8 +566,14 @@ impl Tty7App {
}),
)
// Leading SSH status dot when this tab hosts an SSH session.
.when_some(ssh_dot, |c, color| {
c.child(div().flex_shrink_0().size(px(6.)).rounded_full().bg(color))
.when_some(ssh_dot, |c, rgb| {
c.child(
div()
.flex_shrink_0()
.size(px(6.))
.rounded_full()
.bg(gpui::rgb(rgb)),
)
})
// Leading agent brand avatar, when a coding agent runs in this
// tab — the vendor mark on its accent. Only agents get an avatar