From fe7007409fafa4fae7d31b4ac402ebaad394c592 Mon Sep 17 00:00:00 2001 From: Allan Thraen Date: Wed, 30 Sep 2026 13:07:33 +0200 Subject: [PATCH 1/2] feat(sessions): show a bulk restart running, and let it be stopped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Restart all…" on a large fleet is a minute-scale operation — restarts are strictly sequential and each Claude session waits for its old process to exit — and until now nothing on screen said so and nothing could stop it. On a 55-session setup that is roughly 3½–7 minutes during which the only exit is closing the app. Three parts: **A rail, not the shutdown board.** RestartRail (2px, docked under RestoreRail) plus a RestartPill counter in the toolbar's right stack, driven by SetRestartProgress. ShutdownOverlay only works because OnClosing collapses TerminalGrid first — WebView2 is an HwndHost and composites over any WPF overlay — and collapsing the grid here would black out every live pane for the whole run. A bulk restart doesn't block the user, so it gets the quiet rail; shutdown does, so it keeps the board. Hidden for a single target, so the toolbar ↻ never flashes one. The restart pair is separate from the restore pair rather than a shared rail: the OnLoaded restore loop leaves the UI responsive between awaits, so the quick menu's "Restart all…" is clickable mid-restore and the two loops can genuinely overlap. Peach (#fab387, the shutdown board's "closing…" colour) keeps the two counters apart when both are up. **Stop means "after the current one".** The ⏹ sets _restartCancelRequested, read at the TOP of each iteration. It cannot abandon the session already mid-restart: its VM is out of _vm.Sessions and its PTY is down by then, so returning early would strand it as dormant — the failure path, not a stop. Targets not yet reached keep their live terminals untouched. **The confirmation quotes a duration.** Services/BulkRestartEstimate is WPF-free so the wording is unit-testable at all (the ShellIntegrationPayload / SessionConfigEditor precedent). It costs a Claude target at 4–8s (exit wait 2.3–4.7s + stagger + ~1.4s relaunch) and a plain shell at 1–2s, reports a range rather than false precision, and switches to minutes only once the optimistic bound passes 60s. At 10+ targets it also says the restarts are sequential and names how many are Claude — that count is what makes the estimate look earned rather than arbitrary. Verified: 0-warning build, 609/609 unit tests (12 new). The chrome was rendered against a live --clean instance and sampled by pixel: below the toolbar border at #313244, the restore rail reads #89B4FA and the restart rail #FAB387, each filling to exactly its k/N proportion, with both pills side by side in the toolbar. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01NDsEfog5kVkT5NmX1Ya5be --- CLAUDE.md | 8 + src/CodeShellManager/MainWindow.xaml | 43 ++++++ src/CodeShellManager/MainWindow.xaml.cs | 116 +++++++++++++- .../Services/BulkRestartEstimate.cs | 110 +++++++++++++ .../BulkRestartEstimateTests.cs | 146 ++++++++++++++++++ 5 files changed, 418 insertions(+), 5 deletions(-) create mode 100644 src/CodeShellManager/Services/BulkRestartEstimate.cs create mode 100644 tests/CodeShellManager.Tests/BulkRestartEstimateTests.cs diff --git a/CLAUDE.md b/CLAUDE.md index cda4cd7..7857e07 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -409,6 +409,14 @@ Confirmation is driven by the `confirmHeadline` parameter rather than a count te `RestartSessionsAsync` runs its targets **strictly sequentially** — `RestartSessionAsync` waits for each Claude process to exit before starting its replacement, and awaiting those in parallel would put several `claude.exe` instances back on the shared config file at once, which is the race the wait exists to prevent. A `_restartInProgress` flag drops (rather than queues) an overlapping invocation for the same reason — see the guard note above. That sequencing is also why the bulk entry points confirm: restarting a fleet of Claude sessions is a minute-scale operation, not an instant one. Each target is re-resolved per iteration, since every restart replaces the `SessionViewModel` for that id and an earlier one in the loop may have failed into dormant. +**The rail, and why it is not the shutdown board.** A bulk restart drives `RestartRail` (2px, docked under `RestoreRail`) plus a `RestartPill` counter in the toolbar's right stack, via `SetRestartProgress`. That is the *restore* pattern, deliberately, and the choice is forced: `ShutdownOverlay` only works because `OnClosing` collapses `TerminalGrid` before showing it — WebView2 is an `HwndHost` and composites over any WPF overlay — and collapsing the grid here would black out every live pane for the whole run. A bulk restart does not block the user, so it gets the quiet rail; shutdown does, so it gets the board. Both are hidden for a single target: the toolbar ↻ restarts in seconds and a rail that appears and vanishes is noise. + +The restart pair is **separate from the restore pair rather than a shared rail**. The `OnLoaded` restore loop leaves the UI responsive between awaits, so the sidebar quick menu is clickable while a restore is still running and the two loops can genuinely overlap; one shared rail would need a precedence rule to no benefit. Peach (`#fab387`, the shutdown board's "closing…" colour) rather than the restore blue, so the two counters stay apart when both are up. + +**Stop means "after the current one".** The `⏹` on the pill sets `_restartCancelRequested`, read at the **top** of each loop iteration. It cannot abandon the session already mid-restart: by that point the VM is out of `_vm.Sessions` and the PTY is down, so returning early would strand it as dormant — the failure path, not a stop. Targets not yet reached keep their live terminals untouched, and a toast reports how many were skipped. The flag clears in the same `finally` as `_restartInProgress`. + +**The confirmation quotes a duration**, built by the WPF-free `Services/BulkRestartEstimate` (tested in `BulkRestartEstimateTests`) so the wording is unit-testable at all. It costs a Claude target at 4–8s (exit wait 2.3–4.7s + stagger + ~1.4s relaunch) and a plain shell at 1–2s, reports a range rather than false precision, and switches to minutes only once the *optimistic* bound passes 60s. At or above `ScaleWarningThreshold` (10 targets) it also says the restarts are sequential and names how many are Claude — that count is what makes the estimate look earned rather than arbitrary. `MessageBox` cannot emphasise anything, so scale is carried by the words; making the warning *look* like one would mean a themed window instead. + ## Editing a Session's Configuration Any existing session can be reconfigured through the **same form used to create one** — diff --git a/src/CodeShellManager/MainWindow.xaml b/src/CodeShellManager/MainWindow.xaml index 8ef7615..fd9a306 100644 --- a/src/CodeShellManager/MainWindow.xaml +++ b/src/CodeShellManager/MainWindow.xaml @@ -113,6 +113,33 @@ + + + +