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 {
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
+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`
// 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
// 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));
return;
}
+2 -2
View File
@@ -1599,7 +1599,7 @@ impl TerminalView {
/// machine.** The host id comes off the workspace's own `RemoteTarget`,
/// through the same `connection_key` the connection was opened under — so
/// 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
/// 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
@@ -8053,7 +8053,7 @@ mod tests {
/// (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
/// `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`.
#[test]
fn a_panes_host_is_its_workspaces_machine() {
+1 -1
View File
@@ -5894,7 +5894,7 @@ impl Tty7App {
if !host.is_connected() {
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)))
}
+7 -8
View File
@@ -1,16 +1,15 @@
//! The GUI's control link to **this machine's own daemon**.
//!
//! Local and remote machines are the same thing seen from different distances:
//! one machine, one daemon, one workspace tree, served over the control
//! dialect. The remote half of that has always held a `ControlClient` per
//! machine ([`crate::ui::remote_connect::RemoteConnections`]); this module is
//! the local half — the link over which the GUI receives the local daemon's
//! pushes (`ControlEvent::Layout` deltas, `Preempted`) and sends its semantic
//! tree operations.
//! one machine, one daemon, one workspace tree, one control link. The remote
//! machines' links live in [`crate::ui::remote_connect::HostLinks`]; this
//! module is the local machine's — the link over which the GUI receives the
//! local daemon's pushes (`ControlEvent::Layout` deltas, `Preempted`) and
//! sends its semantic 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
//! machine, while the local file tree and git must keep going through the
//! 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`] |
//! | 3 | Open a routed control connection through the local daemon | [`connect_blocking`] |
//! | 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
//!
@@ -471,16 +471,21 @@ pub fn rows_from_machine(machine: &tty7_core::core::machine::Machine) -> Vec<Rem
// 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
/// 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.
/// The name says the model: every machine this client talks to is reached
/// over exactly one control link, and the local machine is a machine like any
/// other — its link simply lives in its own global
/// ([`LocalLink`](crate::ui::local_link::LocalLink)) because it is in-process
/// rather than wire-backed. One entry per *machine*, not per workspace — the
/// 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)]
pub struct RemoteConnections {
pub struct HostLinks {
hosts: HashMap<HostId, Arc<RemoteHost>>,
/// Each machine's `$HOME`, as its handshake reported it.
///
@@ -496,24 +501,18 @@ pub struct RemoteConnections {
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.
pub fn get(cx: &mut App, id: HostId) -> Option<Arc<RemoteHost>> {
cx.default_global::<RemoteConnections>()
.hosts
.get(&id)
.cloned()
cx.default_global::<HostLinks>().hosts.get(&id).cloned()
}
/// Where a *new* workspace on `id` would start: that machine's own `$HOME`,
/// never this client's.
pub fn home(cx: &mut App, id: HostId) -> Option<PathBuf> {
cx.default_global::<RemoteConnections>()
.homes
.get(&id)
.cloned()
cx.default_global::<HostLinks>().homes.get(&id).cloned()
}
/// 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) {
let id = host.id();
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.homes.insert(id, home);
}
/// Drop a machine's connection once nothing is using it.
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.homes.remove(&id);
crate::ui::host_registry::HostRegistry::remove(cx, id);
@@ -540,7 +539,7 @@ impl RemoteConnections {
/// Machines currently connected. Diagnostics and teardown.
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));
// 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.
let _ = RemoteConnections::len(cx);
let _ = HostLinks::len(cx);
}
/// The oldest install waiting for an answer, if any.
+11 -11
View File
@@ -661,10 +661,10 @@ impl Tty7App {
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);
// 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.
self.connect = None;
}
@@ -762,7 +762,7 @@ impl Tty7App {
return;
};
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
// 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
@@ -1038,7 +1038,7 @@ pub(crate) const PUMP_TICK: Duration = Duration::from_millis(250);
/// One machine's link, as the supervisor sees it.
///
/// 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
/// per-workspace, and it is kept separately for exactly that reason.
struct MachineLink {
@@ -1234,7 +1234,7 @@ impl RemoteLinks {
.preempted
.remove(&workspace);
}
remote_connect::RemoteConnections::remove(cx, host);
remote_connect::HostLinks::remove(cx, host);
cx.default_global::<RemoteLinks>().machines.remove(&host);
log::info!("disconnected from a machine at the user's request");
cx.refresh_windows();
@@ -1295,8 +1295,8 @@ fn pump_tick(cx: &mut gpui::App) -> bool {
if suspended.contains(&host) {
continue;
}
let live = remote_connect::RemoteConnections::get(cx, host)
.is_some_and(|h| h.client().is_connected());
let live =
remote_connect::HostLinks::get(cx, host).is_some_and(|h| h.client().is_connected());
let attempting = cx
.try_global::<RemoteLinks>()
.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
// into it — a control connection that has gone is the whole
// workspace's lifeline, not one failed request.
if remote_connect::RemoteConnections::get(cx, host).is_some() {
remote_connect::RemoteConnections::remove(cx, host);
if remote_connect::HostLinks::get(cx, host).is_some() {
remote_connect::HostLinks::remove(cx, host);
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 {
return;
};
remote_connect::RemoteConnections::remove(cx, host);
remote_connect::HostLinks::remove(cx, host);
for (workspace, _) in workspaces_on(cx, host) {
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
// here is what left "New Workspace" missing on a machine the panel
// 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) {
cx.default_global::<RemoteLinks>().preempted.remove(&id);
if restarted {
+3 -3
View File
@@ -218,7 +218,7 @@ pub(crate) struct HostSnapshot {
/// it could never be given a group to appear in.
pub target: RemoteTarget,
/// 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.
pub rows: Vec<RemoteWorkspaceRow>,
}
@@ -454,7 +454,7 @@ impl Tty7App {
// 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
// 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) {
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,
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
/// [`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
/// transient (both holders have supervisors reconnecting), so callers treat it
/// 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() {
crate::ui::local_link::LocalLink::client(cx)
} else {
crate::ui::remote_connect::RemoteConnections::get(cx, host)
crate::ui::remote_connect::HostLinks::get(cx, host)
.map(|h| Arc::clone(h.client()))
.filter(|c| c.is_connected())
}
+1 -1
View File
@@ -570,7 +570,7 @@ fn release_unused_hosts(cx: &mut App) {
.collect();
for id in crate::ui::host_registry::HostRegistry::ids(cx) {
if !id.is_local() && !live.contains(&id) {
crate::ui::remote_connect::RemoteConnections::remove(cx, id);
crate::ui::remote_connect::HostLinks::remove(cx, id);
}
}
}