diff --git a/src/ui/app.rs b/src/ui/app.rs index 4b27799e..34638ca1 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -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 { + /// 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 { 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 } diff --git a/src/ui/tab_strip.rs b/src/ui/tab_strip.rs index 7b09ac13..d10522ed 100644 --- a/src/ui/tab_strip.rs +++ b/src/ui/tab_strip.rs @@ -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, status: Option, unread: bool, - ssh: Option, + ssh: Option, 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