From 6a4f1a1185dd4a7d316376491ff814eea83b9cc0 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Thu, 14 May 2026 00:19:44 -0500 Subject: fix(ai-vterm): force buffer swap after F9 toggle-off in lone window The original fix (9600611) set a flag at toggle-off and let the next toggle-on detect it. The flag mechanism is right, but the toggle-off itself wasn't observable when bury-buffer couldn't switch the lone window onto a different buffer -- `bury-buffer' falls back to `switch-to-prev-buffer', which no-ops when the window's prev-buffer history contains only the agent itself (common right after a `C-x 1' that cleared the surrounding windows' histories). Without an observable swap, the second F9 found the agent still displayed and routed back through toggle-off, looping the user with no visible effect. Dispatcher now explicitly forces the window onto another buffer (`(other-buffer agent-buf t)`) when the lone window is still showing the agent after `bury-buffer'. The round-trip test now exercises the real `bury-buffer' path instead of simulating it; a new test asserts the lone window's buffer is non-agent after toggle-off. --- modules/ai-vterm.el | 14 ++++- tests/test-ai-vterm--single-window-toggle.el | 76 +++++++++++++++++----------- 2 files changed, 59 insertions(+), 31 deletions(-) diff --git a/modules/ai-vterm.el b/modules/ai-vterm.el index 1eae19f3..975374f6 100644 --- a/modules/ai-vterm.el +++ b/modules/ai-vterm.el @@ -710,7 +710,19 @@ AI-vterm buffers without touching the project list." (cond ((one-window-p) (setq cj/--ai-vterm-last-was-bury t) - (bury-buffer (window-buffer win))) + (bury-buffer (window-buffer win)) + ;; `bury-buffer' calls `switch-to-prev-buffer' to swap the + ;; lone window onto another buffer, but that no-ops when the + ;; window's `window-prev-buffers' list only contains the + ;; agent itself (common right after a `C-x 1' that cleared + ;; the other windows' histories). Without an observable swap + ;; the toggle-off appears to do nothing -- a subsequent F9 + ;; finds the agent still displayed and just buries again. + ;; Force the switch when bury's own swap didn't take. + (when (and (window-live-p win) + (cj/--ai-vterm-buffer-p (window-buffer win))) + (with-selected-window win + (switch-to-buffer (other-buffer (window-buffer win) t))))) (t (setq cj/--ai-vterm-last-was-bury nil) (delete-window win))) diff --git a/tests/test-ai-vterm--single-window-toggle.el b/tests/test-ai-vterm--single-window-toggle.el index 85108a01..50f1504a 100644 --- a/tests/test-ai-vterm--single-window-toggle.el +++ b/tests/test-ai-vterm--single-window-toggle.el @@ -24,49 +24,65 @@ ;;; Normal Cases (ert-deftest test-ai-vterm--single-window-toggle-normal-roundtrip-preserves-fullscreen () - "Normal: agent in the only window, simulated bury, F9 (on) -> still single window with agent. + "Normal: agent in the only window, F9 (off), F9 (on) -> still single window with agent. -Reproduces Craig's report. Before the fix the toggle-on path fell -through to `display-buffer-in-direction', which split the lone window -into two and left the agent in a side panel. - -The bury step is simulated (set flag + swap window buffer to a non- -agent buffer) because batch-mode `bury-buffer' won't switch the -displayed buffer on a window with empty prev-buffers; the toggle-off -branch's *logic* is covered by the flag-set-on-bury test." +Reproduces Craig's report. Before the original fix the toggle-on path +fell through to `display-buffer-in-direction', which split the lone +window into two and left the agent in a side panel. Before the +follow-up fix the toggle-off path could no-op entirely when +`bury-buffer' couldn't find a buffer to switch to, so the user saw +\"F9 does nothing\". The dispatcher now forces the swap to a non- +agent buffer after bury so the toggle-off is observable in real and +batch use both." (cj/test--kill-agent-buffers) (let ((agent-name "agent [single-window-roundtrip]") - (other-name "*test-sw-roundtrip-other*") (cj/--ai-vterm-last-was-bury nil) (cj/--ai-vterm-last-direction nil) (cj/--ai-vterm-last-size nil)) (unwind-protect (save-window-excursion (delete-other-windows) - (let ((agent-buf (get-buffer-create agent-name)) - (other-buf (get-buffer-create other-name))) + (let ((agent-buf (get-buffer-create agent-name))) (set-window-buffer (selected-window) agent-buf) (should (one-window-p)) - ;; Simulate the toggle-off bury path: capture state, set the - ;; bury flag, and put a non-agent buffer in the window where - ;; the real bury would have left one. This isolates the - ;; toggle-on behaviour without depending on batch-mode - ;; `bury-buffer' (which is unreliable with empty prev-buffers). - (cj/--ai-vterm-capture-state (selected-window)) - (setq cj/--ai-vterm-last-was-bury t) - (set-window-buffer (selected-window) other-buf) - (should (one-window-p)) - (should-not (cj/--ai-vterm-displayed-agent-window)) - ;; Toggle on -- should restore agent in the same lone window. + (let ((display-buffer-alist (cj/--ai-vterm-display-rule-list))) + ;; Toggle off -- the dispatcher's force-swap should put the + ;; window on a non-agent buffer. + (cj/ai-vterm) + (should (one-window-p)) + (should-not (cj/--ai-vterm-displayed-agent-window)) + (should (eq cj/--ai-vterm-last-was-bury t)) + ;; Toggle on -- should restore agent in the same lone window. + (cj/ai-vterm) + (should (one-window-p)) + (let ((win (cj/--ai-vterm-displayed-agent-window))) + (should (windowp win)) + (should (eq (window-buffer win) agent-buf))) + ;; Flag consumed by the display-saved action. + (should-not cj/--ai-vterm-last-was-bury)))) + (cj/test--kill-agent-buffers)))) + +(ert-deftest test-ai-vterm--single-window-toggle-off-swaps-window-buffer () + "Normal: toggle-off in single-window state forces the window onto a non- +agent buffer when `bury-buffer' itself didn't swap. + +Catches the regression Craig reported after the original fix shipped: +F9 in a lone-window agent did nothing visible. The fix layer here +ensures the displayed buffer changes -- so the next F9 sees an empty +agent-window state and can route through the display-saved path." + (cj/test--kill-agent-buffers) + (let ((agent-name "agent [bury-swap-observable]") + (cj/--ai-vterm-last-was-bury nil)) + (unwind-protect + (save-window-excursion + (delete-other-windows) + (let* ((agent-buf (get-buffer-create agent-name)) + (win (selected-window))) + (set-window-buffer win agent-buf) (let ((display-buffer-alist (cj/--ai-vterm-display-rule-list))) (cj/ai-vterm)) - (should (one-window-p)) - (let ((win (cj/--ai-vterm-displayed-agent-window))) - (should (windowp win)) - (should (eq (window-buffer win) agent-buf))) - ;; Flag must be consumed by the display-saved action. - (should-not cj/--ai-vterm-last-was-bury))) - (when (get-buffer other-name) (kill-buffer other-name)) + (should (window-live-p win)) + (should-not (cj/--ai-vterm-buffer-p (window-buffer win))))) (cj/test--kill-agent-buffers)))) (ert-deftest test-ai-vterm--single-window-toggle-normal-flag-set-on-bury () -- cgit v1.2.3