You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by lu...@apache.org on 2026/10/23 09:06:31 UTC

(maka) branch feat/desktop-nsh updated: feat(desktop): reopen the window where it last closed

This is an automated email from the ASF dual-hosted git repository.

UncertaintyDeterminesYou4ndMe pushed a commit to branch feat/desktop-nsh
in repository https://gitbox.apache.org/repos/asf/maka.git


The following commit(s) were added to refs/heads/feat/desktop-nsh by this push:
     new 73361f4165 feat(desktop): reopen the window where it last closed
73361f4165 is described below

commit 73361f41655ead26c49100a577c4bae14c8f4a97
Author: Xun Lu <lu...@apache.org>
AuthorDate: Fri Oct 23 17:06:26 2026 +0800

    feat(desktop): reopen the window where it last closed
    
    Closing or quitting records the window's display (by its stable identifier), its full-screen or maximized state and its bounds in `clients/desktop-window.json` in the Root. The next window opens there while that display is connected, sized between the minimum and the display; if too little of its top edge would be on the display to grab, it is centered there. Without a saved placement, or when its display is gone, the window opens centered on the main display as before.
    
    Generated-by: Claude Code
---
 Cargo.lock                                   |   2 +
 products/maka/cli/notices.json               |   4 +-
 products/maka/desktop-shell/Cargo.toml       |   2 +
 products/maka/desktop-shell/README.md        |   2 +
 products/maka/desktop-shell/README.zh-CN.md  |   2 +
 products/maka/desktop-shell/src/host.rs      |   6 +-
 products/maka/desktop-shell/src/main.rs      |   2 +
 products/maka/desktop-shell/src/placement.rs | 241 +++++++++++++++++++++++++++
 products/maka/desktop-shell/src/window.rs    |  67 +++++---
 9 files changed, 304 insertions(+), 24 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 0ef25eb916..5fc0ae3345 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8789,6 +8789,8 @@ dependencies = [
  "maka-workbench",
  "nsh-engine",
  "nsh-platform-gpui",
+ "serde",
+ "serde_json",
  "tokio",
  "uuid",
 ]
diff --git a/products/maka/cli/notices.json b/products/maka/cli/notices.json
index 583266a696..9bd89a8455 100644
--- a/products/maka/cli/notices.json
+++ b/products/maka/cli/notices.json
@@ -1,6 +1,6 @@
 {
   "inputs": {
-    "Cargo.lock": "c3e1b2b17f9bd15dcc6cd542a3dd0b16e7a562feb8c04615c002a18649876f43",
+    "Cargo.lock": "9a8cc7ce9c7a4d90586dc6228eb06e39511dd43c9495ac012abe05c672245983",
     "Cargo.toml": "228a8b3ac828ed6b9b280aa6bef9da44421ab83007bc74675fd7bf9f5c35c8cb",
     "package-lock.json": "2cd0e1423449277ccf0721dbfe554c6a6297d9242211f33e44b1a282a6d2b4b8",
     "package.json": "a6cf2b6e2d6c14ed98f1e09ba195f50260b7e1564a29a8e7477575654dc5d177",
@@ -118,7 +118,7 @@
     "products/maka/cli/build.rs": "75921c0669ba7ddc771c9bff95e410f8b0f6a0938a6958665b20a07fd20a76a7",
     "products/maka/cli/build/product.rs": "44d6ab9638f91a3db0615bd509e4386d5b3aef008f24e3cb1e4dc5a5247b3102",
     "products/maka/client/Cargo.toml": "33a6ff74d79ce70081336cad1f552f7705d0bf10ad82c341f3c0739332c0503c",
-    "products/maka/desktop-shell/Cargo.toml": "84ac0f89a8a72ea27f513801d487c81f437fb5d8f9c7cb5faac738b69376a8aa",
+    "products/maka/desktop-shell/Cargo.toml": "10430ccb60863e20fbf627e6435a803a4b864a82fd5bd7728555c48edc46dad8",
     "products/maka/native/Cargo.toml": "4dd1b880725ee18369785cc73852402e2a9f6808fb9e99779de07d2479caa169",
     "products/maka/terminal-shell/Cargo.toml": "ea8de9e4126e4ef8b4904d54dde79a7e2e14ea11fe2a79aa4224f83e67cdb8b7",
     "products/maka/workbench/Cargo.toml": "3edcf09d20c86aba6ad51cdabc63eb2503dbdf69bf9a9fe8fb904c2c3dac9e72",
diff --git a/products/maka/desktop-shell/Cargo.toml b/products/maka/desktop-shell/Cargo.toml
index 658091c912..53b7c4c6a9 100644
--- a/products/maka/desktop-shell/Cargo.toml
+++ b/products/maka/desktop-shell/Cargo.toml
@@ -27,6 +27,8 @@ maka-event-log.workspace = true
 maka-host-runtime.workspace = true
 maka-workbench.workspace = true
 nsh-engine.workspace = true
+serde.workspace = true
+serde_json.workspace = true
 tokio.workspace = true
 uuid.workspace = true
 
diff --git a/products/maka/desktop-shell/README.md b/products/maka/desktop-shell/README.md
index 1261d06eb5..a90530940a 100644
--- a/products/maka/desktop-shell/README.md
+++ b/products/maka/desktop-shell/README.md
@@ -42,4 +42,6 @@ Start it with the launcher:
 
 Closing the window, the quit menu item and the quit key all close the window; the shell then detaches from the Host and exits. The launcher detaches as well, so the Host finishes the work it accepted and stops once no client is left. Cancelling the launcher stops only its shell.
 
+The window opens on the display, at the position and size, and in the full-screen or maximized state it had when it last closed, as recorded in `clients/desktop-window.json` in the Root. If that display is gone it opens centered on the main display; if too little of its top edge would be on the display to grab, it opens centered there.
+
 If the Host stops while the window is open (it crashed, or was retired over its clients), the shell notices within a few seconds, connects to the Root's next Host, starting one through the launcher as `maka desktop` does, and loads the workbench again in the same window. The workbench starts over, as when the desktop opens; a turn the stopped Host was running shows as stopped. Until a Host is reached the window keeps the last workbench, whose actions fail, and the shell retries with grow [...]
diff --git a/products/maka/desktop-shell/README.zh-CN.md b/products/maka/desktop-shell/README.zh-CN.md
index d0edd43ba6..dc9e8ad9c3 100644
--- a/products/maka/desktop-shell/README.zh-CN.md
+++ b/products/maka/desktop-shell/README.zh-CN.md
@@ -42,4 +42,6 @@ just build
 
 关闭窗口、应用菜单的退出项和退出快捷键都会关闭窗口;shell 随后与 Host 断开并退出。启动器同样只断开连接,因此 Host 会完成已接受的工作,在没有客户端后停止。取消启动器只会停止它启动的 shell。
 
+窗口会在上次关闭时所在的显示器上打开,位置、大小以及全屏或最大化状态都与当时相同;这些记录在 Root 的 `clients/desktop-window.json` 里。那个显示器已断开时,窗口在主显示器上居中打开;窗口顶边留在显示器上的部分不够拖动时,窗口在那个显示器上居中打开。
+
 窗口打开期间如果 Host 停止(崩溃,或在仍有客户端时被强制退役),shell 会在几秒内察觉,连接该 Root 的下一个 Host(没有运行中的 Host 时像 `maka desktop` 一样经由启动器启动),并在同一个窗口里重新加载工作台。工作台从头开始,与打开桌面时相同;停止的 Host 正在执行的那一轮显示为已停止。连上下一个 Host 之前,窗口保留原来的工作台,其中的操作都会失败;shell 按逐步拉长的间隔重试,最长 30 秒。只有当它打开的那个 Root 仍在原路径时才会重连:被移走或删除的 Root 不会被重新初始化。
diff --git a/products/maka/desktop-shell/src/host.rs b/products/maka/desktop-shell/src/host.rs
index ef6e93effa..ef4ab5ffc2 100644
--- a/products/maka/desktop-shell/src/host.rs
+++ b/products/maka/desktop-shell/src/host.rs
@@ -39,10 +39,11 @@ const RECHECK_AFTER: Duration = Duration::from_millis(500);
 const STATUS_LIMIT: Duration = Duration::from_secs(2);
 
 /// The workbench on one Host: its bindings, the engine configuration made
-/// from them, and the Host they reach.
+/// from them, the Root's client directory, and the Host they reach.
 pub struct Connection {
     pub bindings: Bindings,
     pub config: EngineConfig,
+    pub directory: PathBuf,
     pub endpoint: PathBuf,
     pub epoch: Uuid,
 }
@@ -60,11 +61,12 @@ impl Connection {
         let (client, directory) = maka_workbench::host::admit(host, root, "desktop").await?;
         let bindings = Bindings::new(client, runtime.clone());
         let mut config = maka_workbench::configuration(bindings.registry(None), runtime);
-        config.data_dir = Some(directory);
+        config.data_dir = Some(directory.clone());
         config.assets = Some(bindings.assets());
         Ok(Self {
             bindings,
             config,
+            directory,
             endpoint,
             epoch,
         })
diff --git a/products/maka/desktop-shell/src/main.rs b/products/maka/desktop-shell/src/main.rs
index 2a69179e48..76ec2706b1 100644
--- a/products/maka/desktop-shell/src/main.rs
+++ b/products/maka/desktop-shell/src/main.rs
@@ -30,6 +30,8 @@
 #[cfg(any(target_os = "linux", target_os = "macos", windows))]
 mod host;
 #[cfg(any(target_os = "linux", target_os = "macos", windows))]
+mod placement;
+#[cfg(any(target_os = "linux", target_os = "macos", windows))]
 mod window;
 
 use std::{path::PathBuf, process::ExitCode};
diff --git a/products/maka/desktop-shell/src/placement.rs b/products/maka/desktop-shell/src/placement.rs
new file mode 100644
index 0000000000..1bb2db0532
--- /dev/null
+++ b/products/maka/desktop-shell/src/placement.rs
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+//! Where the window was when it closed: its display, whether it filled the
+//! screen, and its bounds. The placement is kept beside the client journal
+//! in the Root's client directory, so the desktop opens where it closed.
+
+use nsh_platform_gpui::gpui::{self, App, Bounds, DisplayId, Pixels, Size, Window, WindowBounds};
+use serde::{Deserialize, Serialize};
+use std::path::{Path, PathBuf};
+use uuid::Uuid;
+
+const FILE: &str = "desktop-window.json";
+/// How much of the window's top edge must stay on its display for the window
+/// to open there again: enough to grab and move it.
+const GRAB: f32 = 64.;
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+struct Saved {
+    /// The display's stable identifier.
+    display: Uuid,
+    state: State,
+    /// The window's bounds; for a maximized or full-screen window, the ones
+    /// it returns to.
+    x: f32,
+    y: f32,
+    width: f32,
+    height: f32,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+enum State {
+    Windowed,
+    Maximized,
+    Fullscreen,
+}
+
+/// Where a saved window opens: on `display`, at `bounds`, or centered there
+/// at `size` when the saved position is no longer on that display.
+#[derive(Debug, PartialEq)]
+struct Spot<T> {
+    display: T,
+    state: State,
+    bounds: Option<Bounds<Pixels>>,
+    size: Size<Pixels>,
+}
+
+/// The window placement saved in a client directory.
+pub struct Placement(PathBuf);
+
+impl Placement {
+    pub fn new(directory: &Path) -> Self {
+        Self(directory.join(FILE))
+    }
+
+    /// The display and bounds the window had, when that display is still
+    /// connected; `None` without a saved placement.
+    pub fn restore(&self, min: Size<Pixels>, cx: &App) -> Option<(DisplayId, WindowBounds)> {
+        let saved = serde_json::from_slice(&std::fs::read(&self.0).ok()?).ok()?;
+        let displays = cx
+            .displays()
+            .into_iter()
+            .filter_map(|display| Some((display.uuid().ok()?, display.id(), display.bounds())));
+        let spot = place(&saved, displays, min)?;
+        let bounds = spot
+            .bounds
+            .unwrap_or_else(|| Bounds::centered(Some(spot.display), spot.size, cx));
+        let bounds = match spot.state {
+            State::Windowed => WindowBounds::Windowed(bounds),
+            State::Maximized => WindowBounds::Maximized(bounds),
+            State::Fullscreen => WindowBounds::Fullscreen(bounds),
+        };
+        Some((spot.display, bounds))
+    }
+
+    /// Records where `window` is. A placement that cannot be saved only
+    /// costs the next window its place, so the error is printed.
+    pub fn save(&self, window: &Window, cx: &App) {
+        let Some(display) = window.display(cx).and_then(|display| display.uuid().ok()) else {
+            return;
+        };
+        let (state, bounds) = match window.window_bounds() {
+            WindowBounds::Windowed(bounds) => (State::Windowed, bounds),
+            WindowBounds::Maximized(bounds) => (State::Maximized, bounds),
+            WindowBounds::Fullscreen(bounds) => (State::Fullscreen, bounds),
+        };
+        let saved = Saved {
+            display,
+            state,
+            x: bounds.origin.x.as_f32(),
+            y: bounds.origin.y.as_f32(),
+            width: bounds.size.width.as_f32(),
+            height: bounds.size.height.as_f32(),
+        };
+        if let Err(error) = write(&self.0, &saved) {
+            eprintln!("maka-desktop-shell: {}: {error}", self.0.display());
+        }
+    }
+}
+
+fn write(path: &Path, saved: &Saved) -> std::io::Result<()> {
+    let partial = path.with_extension("json.partial");
+    std::fs::write(&partial, serde_json::to_vec_pretty(saved)?)?;
+    std::fs::rename(partial, path)
+}
+
+/// Where `saved` opens among `displays` (identifier, id, bounds): on the
+/// display it was on, if that one is connected, at no less than `min` and no
+/// more than the display. It keeps its position while enough of its top edge
+/// stays on the display to grab.
+fn place<T>(
+    saved: &Saved,
+    displays: impl IntoIterator<Item = (Uuid, T, Bounds<Pixels>)>,
+    min: Size<Pixels>,
+) -> Option<Spot<T>> {
+    let (_, display, screen) = displays
+        .into_iter()
+        .find(|(identifier, ..)| *identifier == saved.display)?;
+    let fit = |saved: f32, min: Pixels, screen: Pixels| {
+        gpui::px(saved.max(min.as_f32()).min(screen.as_f32()))
+    };
+    let size = gpui::size(
+        fit(saved.width, min.width, screen.size.width),
+        fit(saved.height, min.height, screen.size.height),
+    );
+    let (left, top) = (screen.origin.x.as_f32(), screen.origin.y.as_f32());
+    let (right, bottom) = (
+        left + screen.size.width.as_f32(),
+        top + screen.size.height.as_f32(),
+    );
+    let grabbed = saved.y >= top
+        && saved.y <= bottom - GRAB
+        && saved.x + size.width.as_f32() >= left + GRAB
+        && saved.x <= right - GRAB;
+    Some(Spot {
+        display,
+        state: saved.state,
+        bounds: grabbed
+            .then(|| Bounds::new(gpui::point(gpui::px(saved.x), gpui::px(saved.y)), size)),
+        size,
+    })
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    fn saved(x: f32, y: f32, width: f32, height: f32) -> Saved {
+        Saved {
+            display: Uuid::from_u128(1),
+            state: State::Windowed,
+            x,
+            y,
+            width,
+            height,
+        }
+    }
+
+    fn screen(x: f32, y: f32, width: f32, height: f32) -> Bounds<Pixels> {
+        Bounds::new(
+            gpui::point(gpui::px(x), gpui::px(y)),
+            gpui::size(gpui::px(width), gpui::px(height)),
+        )
+    }
+
+    const MIN: Size<Pixels> = Size {
+        width: gpui::px(640.),
+        height: gpui::px(480.),
+    };
+
+    #[test]
+    fn a_window_reopens_where_it_was_on_its_display() {
+        let displays = [
+            (Uuid::from_u128(2), 'a', screen(0., 0., 1512., 982.)),
+            (Uuid::from_u128(1), 'b', screen(1512., 0., 2560., 1440.)),
+        ];
+        let spot = place(&saved(1700., 120., 1280., 820.), displays, MIN).unwrap();
+        assert_eq!(spot.display, 'b');
+        assert_eq!(spot.bounds, Some(screen(1700., 120., 1280., 820.)));
+    }
+
+    #[test]
+    fn a_window_whose_display_is_gone_has_no_place() {
+        let displays = [(Uuid::from_u128(2), 'a', screen(0., 0., 1512., 982.))];
+        assert_eq!(place(&saved(10., 10., 800., 600.), displays, MIN), None);
+    }
+
+    #[test]
+    fn a_window_keeps_its_size_between_the_minimum_and_its_display() {
+        let display = [(Uuid::from_u128(1), (), screen(0., 0., 1512., 982.))];
+        let small = place(&saved(10., 10., 100., 100.), display, MIN).unwrap();
+        assert_eq!(small.size, MIN);
+        let large = place(&saved(0., 0., 3000., 2000.), display, MIN).unwrap();
+        assert_eq!(large.size, gpui::size(gpui::px(1512.), gpui::px(982.)));
+    }
+
+    #[test]
+    fn a_window_that_cannot_be_grabbed_is_centered_on_its_display() {
+        let display = [(Uuid::from_u128(1), (), screen(0., 0., 1512., 982.))];
+        for (x, y) in [(1480., 100.), (-1250., 100.), (100., -5.), (100., 960.)] {
+            let spot = place(&saved(x, y, 1280., 820.), display, MIN).unwrap();
+            assert_eq!(spot.bounds, None, "{x}, {y}");
+            assert_eq!(spot.size, gpui::size(gpui::px(1280.), gpui::px(820.)));
+        }
+        let edge = place(&saved(1448., 918., 1280., 820.), display, MIN).unwrap();
+        assert!(edge.bounds.is_some());
+    }
+
+    #[test]
+    fn the_placement_survives_a_round_trip_through_its_file() {
+        let directory = std::env::temp_dir().join(format!("maka-placement-{}", Uuid::new_v4()));
+        std::fs::create_dir(&directory).unwrap();
+        let path = directory.join(FILE);
+        let maximized = Saved {
+            state: State::Maximized,
+            ..saved(-1200., 40., 1024., 700.)
+        };
+        write(&path, &maximized).unwrap();
+        let read: Saved = serde_json::from_slice(&std::fs::read(&path).unwrap()).unwrap();
+        assert_eq!(read, maximized);
+        assert!(!path.with_extension("json.partial").exists());
+        std::fs::remove_dir_all(directory).unwrap();
+    }
+}
diff --git a/products/maka/desktop-shell/src/window.rs b/products/maka/desktop-shell/src/window.rs
index 68190fbe72..2061351253 100644
--- a/products/maka/desktop-shell/src/window.rs
+++ b/products/maka/desktop-shell/src/window.rs
@@ -28,8 +28,13 @@
 //! While the window is open it follows the Root's Host: once the Host its
 //! client reaches stops, it connects to the next one and loads the workbench
 //! again in the same window.
+//!
+//! The window opens where it was when it last closed (see [`Placement`]).
 
-use crate::host::{self, Connection, Root};
+use crate::{
+    host::{self, Connection, Root},
+    placement::Placement,
+};
 use maka_workbench::bindings::Bindings;
 use nsh_engine::{EngineConfig, Limits};
 use nsh_platform_gpui::{
@@ -65,12 +70,13 @@ pub fn run(first: Connection, root: Root, runtime: tokio::runtime::Runtime) -> R
     let Connection {
         bindings,
         config,
+        directory,
         endpoint,
         epoch,
     } = first;
     let handle = runtime.handle().clone();
     let detach = Detach::new(bindings, (endpoint.clone(), epoch), runtime);
-    let mut start = Some((config, endpoint, epoch, root));
+    let mut start = Some((config, Placement::new(&directory), endpoint, epoch, root));
     // Quitting waits for the detach: GPUI's default quits Linux and Windows
     // apps as their last window closes, giving the detach no more than its
     // quit handlers' 200 ms.
@@ -83,34 +89,50 @@ pub fn run(first: Connection, root: Root, runtime: tokio::runtime::Runtime) -> R
             }
         })
         .detach();
-        // Quitting from elsewhere (the Dock, logging out) gets the time GPUI
-        // gives quit handlers.
-        let quitting = detach.clone();
-        cx.on_app_quit(move |cx| quitting.start(cx)).detach();
-        let (config, endpoint, epoch, root) = start.take().expect("the app starts once");
-        match open(config, cx) {
-            Ok(window) => follow(window, (endpoint, epoch), root, handle.clone(), &detach, cx),
+        let (config, placement, endpoint, epoch, root) = start.take().expect("the app starts once");
+        let placement = Rc::new(placement);
+        let window = match open(config, placement.clone(), cx) {
+            Ok(window) => window,
             Err(error) => {
                 eprintln!("maka-desktop-shell: cannot open a window: {error}");
                 std::process::exit(1);
             }
-        }
+        };
+        // Quitting from elsewhere (the Dock, logging out) gets the time GPUI
+        // gives quit handlers; a window still open records its place first.
+        let quitting = detach.clone();
+        cx.on_app_quit(move |cx| {
+            let _ = window.update(cx, |_, window, cx| placement.save(window, cx));
+            quitting.start(cx)
+        })
+        .detach();
+        follow(window, (endpoint, epoch), root, handle.clone(), &detach, cx);
         cx.activate(true);
     });
     Ok(())
 }
 
-fn open(config: EngineConfig, cx: &mut App) -> Result<WindowHandle<AppView>, String> {
-    let bounds = gpui::Bounds::centered(
-        None,
-        gpui::size(gpui::px(SIZE.0 as f32), gpui::px(SIZE.1 as f32)),
-        cx,
-    );
+fn open(
+    config: EngineConfig,
+    placement: Rc<Placement>,
+    cx: &mut App,
+) -> Result<WindowHandle<AppView>, String> {
+    let min = gpui::size(gpui::px(MIN_SIZE.0), gpui::px(MIN_SIZE.1));
+    // Where the window last closed, or centered on the main display.
+    let (display_id, bounds) = match placement.restore(min, cx) {
+        Some((display, bounds)) => (Some(display), bounds),
+        None => {
+            let size = gpui::size(gpui::px(SIZE.0 as f32), gpui::px(SIZE.1 as f32));
+            let bounds = gpui::Bounds::centered(None, size, cx);
+            (None, gpui::WindowBounds::Windowed(bounds))
+        }
+    };
     let options = gpui::WindowOptions {
-        window_bounds: Some(gpui::WindowBounds::Windowed(bounds)),
+        window_bounds: Some(bounds),
+        display_id,
         titlebar: Some(titlebar_options(TITLE)),
         app_id: Some(APP_ID.into()),
-        window_min_size: Some(gpui::size(gpui::px(MIN_SIZE.0), gpui::px(MIN_SIZE.1))),
+        window_min_size: Some(min),
         // Linux: the window manager's decorations until the app hides the
         // title bar.
         window_decorations: Some(gpui::WindowDecorations::Server),
@@ -137,8 +159,12 @@ fn open(config: EngineConfig, cx: &mut App) -> Result<WindowHandle<AppView>, Str
         });
         let app = view.read(cx);
         app.own_window(window, cx);
-        // Quit and close both close the window; the last one closed detaches.
-        app.on_close_request(|_, window, _| window.remove_window());
+        // Quit and close both close the window, which records its place;
+        // the last one closed detaches.
+        app.on_close_request(move |_, window, cx| {
+            placement.save(window, cx);
+            window.remove_window();
+        });
         app.load_with_config(config);
         let focus = view.read(cx).focus_handle(cx);
         window.focus(&focus, cx);
@@ -206,6 +232,7 @@ fn follow(
                 config,
                 endpoint,
                 epoch,
+                ..
             } = connection;
             if let Err(bindings) = detach.put(bindings, (endpoint.clone(), epoch)) {
                 // Quitting began during the attempt: this client detaches


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org