refactor(ui): rename RemoteConnections to HostLinks

Purely mechanical, plus the doc sentences that carry the model: the
table holds one control link per machine, and the local machine is a
machine like any other — its link just lives in its own global
(LocalLink) because it is in-process rather than wire-backed. The old
name framed the table as remote-only plumbing, which the tree
migration made false in spirit: local and remote windows speak the
same operations over whichever link their machine answers on.
This commit is contained in:
l0ng-ai
2026-07-30 00:37:26 +08:00
parent 382a46ac29
commit 9cac4b863b
10 changed files with 52 additions and 54 deletions
+1 -1
View File
@@ -216,7 +216,7 @@ impl WorkspaceStore {
let Some(host) = Self::remote_ref(cx, id) else { let Some(host) = Self::remote_ref(cx, id) else {
return true; return true;
}; };
crate::ui::remote_connect::RemoteConnections::get(cx, host.host_id()).is_some() crate::ui::remote_connect::HostLinks::get(cx, host.host_id()).is_some()
} }
/// The client-side entry for `host` — the existing one if this machine has /// The client-side entry for `host` — the existing one if this machine has
+2 -2
View File
@@ -308,10 +308,10 @@ fn probe_host(cx: &mut App, host: HostId, workspace: WorkspaceId) {
// //
// Recorded as a landed failure rather than returned from: a bare `return` // Recorded as a landed failure rather than returned from: a bare `return`
// would leave `needs_probe` true, so the next frame would re-decide this, // would leave `needs_probe` true, so the next frame would re-decide this,
// and `RemoteConnections::get` reaches its global mutably — which notifies, // and `HostLinks::get` reaches its global mutably — which notifies,
// which repaints, which sweeps. Storing the answer puts the decision behind // which repaints, which sweeps. Storing the answer puts the decision behind
// the same TTL as every other one. // the same TTL as every other one.
if !host.is_local() && crate::ui::remote_connect::RemoteConnections::get(cx, host).is_none() { if !host.is_local() && crate::ui::remote_connect::HostLinks::get(cx, host).is_none() {
cx.update_global::<PaneLivenessCache, _>(|cache, _| cache.finish_probe(host, None)); cx.update_global::<PaneLivenessCache, _>(|cache, _| cache.finish_probe(host, None));
return; return;
} }
+2 -2
View File
@@ -1599,7 +1599,7 @@ impl TerminalView {
/// machine.** The host id comes off the workspace's own `RemoteTarget`, /// machine.** The host id comes off the workspace's own `RemoteTarget`,
/// through the same `connection_key` the connection was opened under — so /// through the same `connection_key` the connection was opened under — so
/// the id resolves to the very host object /// the id resolves to the very host object
/// [`RemoteConnections::insert`](crate::ui::remote_connect::RemoteConnections::insert) /// [`HostLinks::insert`](crate::ui::remote_connect::HostLinks::insert)
/// registered, with no second source of truth to drift from it. Setting the /// registered, with no second source of truth to drift from it. Setting the
/// route and setting the host is one operation because a pane that ran its /// route and setting the host is one operation because a pane that ran its
/// shell on one machine and its `git` on another would be worse than /// shell on one machine and its `git` on another would be worse than
@@ -8053,7 +8053,7 @@ mod tests {
/// (which needs a window, a daemon and a pane): the derivation under test is /// (which needs a window, a daemon and a pane): the derivation under test is
/// the target → `HostId` one, and pinning it here is what catches a future /// the target → `HostId` one, and pinning it here is what catches a future
/// `set_workspace` that forgets the host half. The ids must agree with what /// `set_workspace` that forgets the host half. The ids must agree with what
/// `RemoteConnections::insert` registered — same `connection_key`, checked /// `HostLinks::insert` registered — same `connection_key`, checked
/// by `connection_keys_match_the_contract_table` in `tty7-core`. /// by `connection_keys_match_the_contract_table` in `tty7-core`.
#[test] #[test]
fn a_panes_host_is_its_workspaces_machine() { fn a_panes_host_is_its_workspaces_machine() {
+1 -1
View File
@@ -5894,7 +5894,7 @@ impl Tty7App {
if !host.is_connected() { if !host.is_connected() {
return None; return None;
} }
let home = crate::ui::remote_connect::RemoteConnections::home(cx, host_id)?; let home = crate::ui::remote_connect::HostLinks::home(cx, host_id)?;
Some((host, Some(home))) Some((host, Some(home)))
} }
+7 -8
View File
@@ -1,16 +1,15 @@
//! The GUI's control link to **this machine's own daemon**. //! The GUI's control link to **this machine's own daemon**.
//! //!
//! Local and remote machines are the same thing seen from different distances: //! Local and remote machines are the same thing seen from different distances:
//! one machine, one daemon, one workspace tree, served over the control //! one machine, one daemon, one workspace tree, one control link. The remote
//! dialect. The remote half of that has always held a `ControlClient` per //! machines' links live in [`crate::ui::remote_connect::HostLinks`]; this
//! machine ([`crate::ui::remote_connect::RemoteConnections`]); this module is //! module is the local machine's — the link over which the GUI receives the
//! the local half — the link over which the GUI receives the local daemon's //! local daemon's pushes (`ControlEvent::Layout` deltas, `Preempted`) and
//! pushes (`ControlEvent::Layout` deltas, `Preempted`) and sends its semantic //! sends its semantic tree operations.
//! tree operations.
//! //!
//! # Not a `RemoteConnections` entry //! # Not a `HostLinks` entry
//! //!
//! `RemoteConnections` doubles as the [`crate::ui::host_registry::HostRegistry`] //! `HostLinks` doubles as the [`crate::ui::host_registry::HostRegistry`]
//! feeder: inserting there would register a *wire-backed* `Host` for this //! feeder: inserting there would register a *wire-backed* `Host` for this
//! machine, while the local file tree and git must keep going through the //! machine, while the local file tree and git must keep going through the
//! in-process [`LocalHost`](tty7_core::host::local::LocalHost) — a socket //! in-process [`LocalHost`](tty7_core::host::local::LocalHost) — a socket
+22 -23
View File
@@ -15,7 +15,7 @@
//! | 2 | Resolve one into a self-contained SSH spec | [`spec_for`] | //! | 2 | Resolve one into a self-contained SSH spec | [`spec_for`] |
//! | 3 | Open a routed control connection through the local daemon | [`connect_blocking`] | //! | 3 | Open a routed control connection through the local daemon | [`connect_blocking`] |
//! | 4 | Read the machine's own workspace list | [`rows_from_list`] | //! | 4 | Read the machine's own workspace list | [`rows_from_list`] |
//! | 5 | Hold the connection for the workspaces bound to it | [`RemoteConnections`] | //! | 5 | Hold the connection for the workspaces bound to it | [`HostLinks`] |
//! //!
//! ## Machines are configured once //! ## Machines are configured once
//! //!
@@ -471,16 +471,21 @@ pub fn rows_from_machine(machine: &tty7_core::core::machine::Machine) -> Vec<Rem
// 5. Holding the connections // 5. Holding the connections
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/// The live remote machines, by [`HostId`]. /// The live control links, by [`HostId`] — one per machine, one machine per
/// entry.
/// ///
/// One entry per *machine*, not per workspace — the same granularity the SSH /// The name says the model: every machine this client talks to is reached
/// connection is pooled at and the same one [`crate::ui::host_registry`] uses, /// over exactly one control link, and the local machine is a machine like any
/// so two windows on one box share a connection, a host object and a git-status /// other — its link simply lives in its own global
/// cache. This table holds the concrete [`RemoteHost`] because pushing a layout /// ([`LocalLink`](crate::ui::local_link::LocalLink)) because it is in-process
/// needs its control client; `HostRegistry` holds the same object erased to /// rather than wire-backed. One entry per *machine*, not per workspace — the
/// `dyn Host` for the panels. /// same granularity the SSH connection is pooled at and the same one
/// [`crate::ui::host_registry`] uses, so two windows on one box share a
/// connection, a host object and a git-status cache. This table holds the
/// concrete [`RemoteHost`] because pushing a layout needs its control client;
/// `HostRegistry` holds the same object erased to `dyn Host` for the panels.
#[derive(Default)] #[derive(Default)]
pub struct RemoteConnections { pub struct HostLinks {
hosts: HashMap<HostId, Arc<RemoteHost>>, hosts: HashMap<HostId, Arc<RemoteHost>>,
/// Each machine's `$HOME`, as its handshake reported it. /// Each machine's `$HOME`, as its handshake reported it.
/// ///
@@ -496,24 +501,18 @@ pub struct RemoteConnections {
homes: HashMap<HostId, PathBuf>, homes: HashMap<HostId, PathBuf>,
} }
impl Global for RemoteConnections {} impl Global for HostLinks {}
impl RemoteConnections { impl HostLinks {
/// The connection to `id`, if this process has one. /// The connection to `id`, if this process has one.
pub fn get(cx: &mut App, id: HostId) -> Option<Arc<RemoteHost>> { pub fn get(cx: &mut App, id: HostId) -> Option<Arc<RemoteHost>> {
cx.default_global::<RemoteConnections>() cx.default_global::<HostLinks>().hosts.get(&id).cloned()
.hosts
.get(&id)
.cloned()
} }
/// Where a *new* workspace on `id` would start: that machine's own `$HOME`, /// Where a *new* workspace on `id` would start: that machine's own `$HOME`,
/// never this client's. /// never this client's.
pub fn home(cx: &mut App, id: HostId) -> Option<PathBuf> { pub fn home(cx: &mut App, id: HostId) -> Option<PathBuf> {
cx.default_global::<RemoteConnections>() cx.default_global::<HostLinks>().homes.get(&id).cloned()
.homes
.get(&id)
.cloned()
} }
/// Record a connection, and register the same object with the host registry /// Record a connection, and register the same object with the host registry
@@ -525,14 +524,14 @@ impl RemoteConnections {
pub fn insert(cx: &mut App, host: Arc<RemoteHost>, home: PathBuf) { pub fn insert(cx: &mut App, host: Arc<RemoteHost>, home: PathBuf) {
let id = host.id(); let id = host.id();
crate::ui::host_registry::HostRegistry::insert(cx, Arc::clone(&host).into_shared()); crate::ui::host_registry::HostRegistry::insert(cx, Arc::clone(&host).into_shared());
let table = cx.default_global::<RemoteConnections>(); let table = cx.default_global::<HostLinks>();
table.hosts.insert(id, host); table.hosts.insert(id, host);
table.homes.insert(id, home); table.homes.insert(id, home);
} }
/// Drop a machine's connection once nothing is using it. /// Drop a machine's connection once nothing is using it.
pub fn remove(cx: &mut App, id: HostId) { pub fn remove(cx: &mut App, id: HostId) {
let table = cx.default_global::<RemoteConnections>(); let table = cx.default_global::<HostLinks>();
table.hosts.remove(&id); table.hosts.remove(&id);
table.homes.remove(&id); table.homes.remove(&id);
crate::ui::host_registry::HostRegistry::remove(cx, id); crate::ui::host_registry::HostRegistry::remove(cx, id);
@@ -540,7 +539,7 @@ impl RemoteConnections {
/// Machines currently connected. Diagnostics and teardown. /// Machines currently connected. Diagnostics and teardown.
pub fn len(cx: &mut App) -> usize { pub fn len(cx: &mut App) -> usize {
cx.default_global::<RemoteConnections>().hosts.len() cx.default_global::<HostLinks>().hosts.len()
} }
} }
@@ -721,7 +720,7 @@ pub fn register(cx: &mut App) {
crate::daemon::router::set_route_auth_responder(Arc::new(GuiRouteAuth)); crate::daemon::router::set_route_auth_responder(Arc::new(GuiRouteAuth));
// Touch the globals so the first connect isn't also the first allocation of // Touch the globals so the first connect isn't also the first allocation of
// the table it writes into, on a thread that is holding a socket open. // the table it writes into, on a thread that is holding a socket open.
let _ = RemoteConnections::len(cx); let _ = HostLinks::len(cx);
} }
/// The oldest install waiting for an answer, if any. /// The oldest install waiting for an answer, if any.
+11 -11
View File
@@ -661,10 +661,10 @@ impl Tty7App {
rows: rows.clone(), rows: rows.clone(),
}, },
); );
remote_connect::RemoteConnections::insert(cx, connected.host, home.clone()); remote_connect::HostLinks::insert(cx, connected.host, home.clone());
self.prompt_remote_daemon_mismatch_later(cx); self.prompt_remote_daemon_mismatch_later(cx);
// Nothing left to *show* about the attempt: the machine is now // Nothing left to *show* about the attempt: the machine is now
// in `RemoteConnections` and its group in the switcher fills // in `HostLinks` and its group in the switcher fills
// itself from there and from the snapshot above. // itself from there and from the snapshot above.
self.connect = None; self.connect = None;
} }
@@ -762,7 +762,7 @@ impl Tty7App {
return; return;
}; };
remote_connect::register(cx); remote_connect::register(cx);
if remote_connect::RemoteConnections::get(cx, host.host_id()).is_some() { if remote_connect::HostLinks::get(cx, host.host_id()).is_some() {
// Another window on the same machine got there first. One connection // Another window on the same machine got there first. One connection
// per machine is the point — D7's "connect immediately" is about the // per machine is the point — D7's "connect immediately" is about the
// *machine*, and a second link to it would be a second SSH session // *machine*, and a second link to it would be a second SSH session
@@ -1038,7 +1038,7 @@ pub(crate) const PUMP_TICK: Duration = Duration::from_millis(250);
/// One machine's link, as the supervisor sees it. /// One machine's link, as the supervisor sees it.
/// ///
/// Per **machine**, not per workspace, because that is the granularity a /// Per **machine**, not per workspace, because that is the granularity a
/// connection actually has (`RemoteConnections` is keyed by [`HostId`], and two /// connection actually has (`HostLinks` is keyed by [`HostId`], and two
/// windows on one box share a link). Preemption is the one thing that is /// windows on one box share a link). Preemption is the one thing that is
/// per-workspace, and it is kept separately for exactly that reason. /// per-workspace, and it is kept separately for exactly that reason.
struct MachineLink { struct MachineLink {
@@ -1234,7 +1234,7 @@ impl RemoteLinks {
.preempted .preempted
.remove(&workspace); .remove(&workspace);
} }
remote_connect::RemoteConnections::remove(cx, host); remote_connect::HostLinks::remove(cx, host);
cx.default_global::<RemoteLinks>().machines.remove(&host); cx.default_global::<RemoteLinks>().machines.remove(&host);
log::info!("disconnected from a machine at the user's request"); log::info!("disconnected from a machine at the user's request");
cx.refresh_windows(); cx.refresh_windows();
@@ -1295,8 +1295,8 @@ fn pump_tick(cx: &mut gpui::App) -> bool {
if suspended.contains(&host) { if suspended.contains(&host) {
continue; continue;
} }
let live = remote_connect::RemoteConnections::get(cx, host) let live =
.is_some_and(|h| h.client().is_connected()); remote_connect::HostLinks::get(cx, host).is_some_and(|h| h.client().is_connected());
let attempting = cx let attempting = cx
.try_global::<RemoteLinks>() .try_global::<RemoteLinks>()
.and_then(|l| l.machines.get(&host)) .and_then(|l| l.machines.get(&host))
@@ -1326,8 +1326,8 @@ fn pump_tick(cx: &mut gpui::App) -> bool {
// The link is down. Drop the dead host object so nothing keeps calling // The link is down. Drop the dead host object so nothing keeps calling
// into it — a control connection that has gone is the whole // into it — a control connection that has gone is the whole
// workspace's lifeline, not one failed request. // workspace's lifeline, not one failed request.
if remote_connect::RemoteConnections::get(cx, host).is_some() { if remote_connect::HostLinks::get(cx, host).is_some() {
remote_connect::RemoteConnections::remove(cx, host); remote_connect::HostLinks::remove(cx, host);
log::info!("lost the control connection to {target}; reconnecting"); log::info!("lost the control connection to {target}; reconnecting");
} }
@@ -1473,7 +1473,7 @@ fn reconnect_after_restart(origin: &str, cx: &mut gpui::App) {
let Some(host) = remote_connect::origin_host(origin) else { let Some(host) = remote_connect::origin_host(origin) else {
return; return;
}; };
remote_connect::RemoteConnections::remove(cx, host); remote_connect::HostLinks::remove(cx, host);
for (workspace, _) in workspaces_on(cx, host) { for (workspace, _) in workspaces_on(cx, host) {
RemoteLinks::retry_now(cx, workspace); RemoteLinks::retry_now(cx, workspace);
} }
@@ -1569,7 +1569,7 @@ fn finish_attempt(
// comes back on after a restart or a dropped link, and dropping it // comes back on after a restart or a dropped link, and dropping it
// here is what left "New Workspace" missing on a machine the panel // here is what left "New Workspace" missing on a machine the panel
// was quite happily calling connected. // was quite happily calling connected.
remote_connect::RemoteConnections::insert(cx, connected.host, connected.home); remote_connect::HostLinks::insert(cx, connected.host, connected.home);
for (id, _key) in workspaces_on(cx, host) { for (id, _key) in workspaces_on(cx, host) {
cx.default_global::<RemoteLinks>().preempted.remove(&id); cx.default_global::<RemoteLinks>().preempted.remove(&id);
if restarted { if restarted {
+3 -3
View File
@@ -218,7 +218,7 @@ pub(crate) struct HostSnapshot {
/// it could never be given a group to appear in. /// it could never be given a group to appear in.
pub target: RemoteTarget, pub target: RemoteTarget,
/// What the remote said it had. The machine's `$HOME` is deliberately *not* /// What the remote said it had. The machine's `$HOME` is deliberately *not*
/// here — it lives in `RemoteConnections`, app-wide, because every window /// here — it lives in `HostLinks`, app-wide, because every window
/// needs it and only one of them ever did the connecting. /// needs it and only one of them ever did the connecting.
pub rows: Vec<RemoteWorkspaceRow>, pub rows: Vec<RemoteWorkspaceRow>,
} }
@@ -454,7 +454,7 @@ impl Tty7App {
// connect, and every reconnect, records the machine's `$HOME` — and // connect, and every reconnect, records the machine's `$HOME` — and
// that row is the only way to make a workspace on a machine, so it // that row is the only way to make a workspace on a machine, so it
// has no business depending on which window did the connecting. // has no business depending on which window did the connecting.
group.home = remote_connect::RemoteConnections::home(cx, id); group.home = remote_connect::HostLinks::home(cx, id);
if let Some(snapshot) = self.host_snapshots.get(&id) { if let Some(snapshot) = self.host_snapshots.get(&id) {
group.merge(&snapshot.rows, now); group.merge(&snapshot.rows, now);
} }
@@ -489,7 +489,7 @@ impl Tty7App {
} }
_ => {} _ => {}
} }
match remote_connect::RemoteConnections::get(cx, target.host_id()) { match remote_connect::HostLinks::get(cx, target.host_id()) {
Some(_) => Link::Connected, Some(_) => Link::Connected,
None => Link::Offline, None => Link::Offline,
} }
+2 -2
View File
@@ -66,7 +66,7 @@ use crate::ui::pane::{Pane, PaneSlot};
/// ///
/// The unification the whole design leans on: the local machine's link lives in /// The unification the whole design leans on: the local machine's link lives in
/// [`LocalLink`](crate::ui::local_link::LocalLink), a remote machine's in /// [`LocalLink`](crate::ui::local_link::LocalLink), a remote machine's in
/// [`RemoteConnections`](crate::ui::remote_connect::RemoteConnections), and /// [`HostLinks`](crate::ui::remote_connect::HostLinks), and
/// everything above this function stops caring which. `None` is always /// everything above this function stops caring which. `None` is always
/// transient (both holders have supervisors reconnecting), so callers treat it /// transient (both holders have supervisors reconnecting), so callers treat it
/// as "not now": mark dirty and let the re-pull that follows reconnection /// as "not now": mark dirty and let the re-pull that follows reconnection
@@ -75,7 +75,7 @@ pub(crate) fn control_for(cx: &mut App, host: HostId) -> Option<Arc<ControlClien
if host.is_local() { if host.is_local() {
crate::ui::local_link::LocalLink::client(cx) crate::ui::local_link::LocalLink::client(cx)
} else { } else {
crate::ui::remote_connect::RemoteConnections::get(cx, host) crate::ui::remote_connect::HostLinks::get(cx, host)
.map(|h| Arc::clone(h.client())) .map(|h| Arc::clone(h.client()))
.filter(|c| c.is_connected()) .filter(|c| c.is_connected())
} }
+1 -1
View File
@@ -570,7 +570,7 @@ fn release_unused_hosts(cx: &mut App) {
.collect(); .collect();
for id in crate::ui::host_registry::HostRegistry::ids(cx) { for id in crate::ui::host_registry::HostRegistry::ids(cx) {
if !id.is_local() && !live.contains(&id) { if !id.is_local() && !live.contains(&id) {
crate::ui::remote_connect::RemoteConnections::remove(cx, id); crate::ui::remote_connect::HostLinks::remove(cx, id);
} }
} }
} }