fix(ui): kick every local window's sync when the local link comes up

A window built while the local control link was still dialing parks as
Unprimed { dirty } — start_prime's unreachable arm leaves the retry to
"the reconnect-triggered save", but the local link supervisor never
triggered one. On a first launch (window built before the auto-spawned
daemon binds its socket) nothing else re-enters sync_window until the
next structural change, so quitting before one loses the window's
layout: the machine never heard of it.

Reproduced end-to-end on a scratch daemon: fresh launch, no user
action, quit — the relaunch came up empty. With the link supervisor
calling tree_sync::on_link_up on connect, the same launch syncs the
tree within one pump tick.
This commit is contained in:
l0ng-ai
2026-07-30 01:04:43 +08:00
parent 978e1cc67d
commit 1045e1d07c
2 changed files with 27 additions and 0 deletions
+5
View File
@@ -162,6 +162,11 @@ impl LocalLink {
cx,
tty7_core::host::HostId::LOCAL,
);
// …and re-runs every local window's sync: a window
// built while this link was still dialing is parked
// `Unprimed { dirty }` with nothing else scheduled to
// wake it (see `tree_sync::on_link_up`).
crate::ui::tree_sync::on_link_up(cx, tty7_core::host::HostId::LOCAL);
}
Err(e) => {
// The next tick schedules the following attempt off
+22
View File
@@ -852,6 +852,28 @@ pub(crate) fn sync_window(app: &Tty7App, cx: &mut App) {
}
}
/// A control link to `host` just came up (or came back): re-run the sync for
/// every window bound to that machine.
///
/// This is the retry [`start_prime`]'s unreachable arm leaves behind. A window
/// built while the link was still dialing parks as `Unprimed { dirty }`, and
/// the only other thing that re-enters [`sync_window`] is the *next*
/// structural change — on a first launch that may never come, and a quit
/// before it comes loses the window's layout (the machine never heard of it).
/// The link supervisor calling this on connect is what turns "the reconnect
/// gets there first" from a hope into a mechanism. Harmless for windows that
/// are already synced: their diff is empty and queues nothing.
pub(crate) fn on_link_up(cx: &mut App, host: HostId) {
for (workspace, app) in crate::ui::windows::WindowRegistry::open_windows(cx) {
if WorkspaceStore::host_of(cx, workspace) != host {
continue;
}
if let Some(app) = app.upgrade() {
app.update(cx, |app, cx| sync_window(app, cx));
}
}
}
/// Whether `client_ws`'s window has seen its machine's tree (or was declared
/// authoritative). The gate for destructive acts an *empty* window licenses —
/// a window whose hydration has not answered is empty because it is waiting,