Skip to content

Commit a139cbf

Browse files
authored
fix(session): prevent input window from reopening in child sessions (#387)
Pressing <tab> or triggering focus_input in a child session could re-show the input window that was intentionally hidden on session switch. The root cause was that toggle_pane, ui.focus_input, and input_window._show had no awareness of child session state. Add child-session guards at three layers: - input_window._show(): safety net at the lowest level, prevents the window from ever being recreated during a child session - ui.focus_input(): early return so service-layer callers (agent_model, session_runtime) cannot accidentally reveal the input - ui.toggle_pane(): early return so <tab> stays on the output pane
1 parent 99f26ff commit a139cbf

4 files changed

Lines changed: 71 additions & 0 deletions

File tree

lua/opencode/ui/input_window.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,11 @@ end
591591

592592
---Show the input window by recreating it
593593
function M._show()
594+
-- Child sessions must never show the input window
595+
if state.active_session and state.active_session.parentID then
596+
return
597+
end
598+
594599
local windows = state.windows
595600
if not windows or not windows.input_buf or not windows.output_win then
596601
return

lua/opencode/ui/ui.lua

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,10 @@ end
403403

404404
---@param opts? { restore_position?: boolean, start_insert?: boolean }
405405
function M.focus_input(opts)
406+
if state.active_session and state.active_session.parentID then
407+
return
408+
end
409+
406410
opts = opts or {}
407411
local windows = state.windows
408412
if not windows then
@@ -557,6 +561,9 @@ function M.toggle_pane()
557561
if state.windows and current_win == state.windows.input_win then
558562
output_window.focus_output(true)
559563
else
564+
if state.active_session and state.active_session.parentID then
565+
return
566+
end
560567
input_window.focus_input()
561568
end
562569
end

tests/unit/input_window_spec.lua

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,4 +548,30 @@ describe('input_window', function()
548548
assert.is_false(input_window._hidden)
549549
end)
550550
end)
551+
552+
describe('child session input visibility', function()
553+
after_each(function()
554+
state.session.clear_active()
555+
end)
556+
557+
it('_show() is a no-op when active session is a child session', function()
558+
state.session.set_active({ id = 'child1', parentID = 'parent1' })
559+
input_window._hidden = true
560+
561+
input_window._show()
562+
563+
assert.is_true(input_window._hidden)
564+
end)
565+
566+
it('_show() proceeds when active session is a root session', function()
567+
state.session.set_active({ id = 'root1' })
568+
input_window._hidden = true
569+
570+
-- _show will early-return due to missing windows, but it should pass the guard
571+
input_window._show()
572+
573+
-- _hidden remains true because windows are nil, but the parentID guard was passed
574+
assert.is_true(input_window._hidden)
575+
end)
576+
end)
551577
end)

tests/unit/services_session_runtime_spec.lua

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,39 @@ describe('opencode.services.session_runtime', function()
398398
end)
399399
end)
400400

401+
describe('child session UI guards', function()
402+
local input_window = require('opencode.ui.input_window')
403+
404+
after_each(function()
405+
state.session.clear_active()
406+
end)
407+
408+
it('toggle_pane does not show input when in a child session', function()
409+
state.session.set_active({ id = 'child1', parentID = 'parent1' })
410+
stub(input_window, 'focus_input')
411+
412+
-- Simulate being in the output window (not input)
413+
state.ui.set_windows({ input_win = -1, output_win = vim.api.nvim_get_current_win(), input_buf = 1, output_buf = 2 })
414+
415+
ui.toggle_pane()
416+
417+
assert.stub(input_window.focus_input).was_not_called()
418+
input_window.focus_input:revert()
419+
end)
420+
421+
it('focus_input is a no-op when in a child session', function()
422+
state.session.set_active({ id = 'child1', parentID = 'parent1' })
423+
stub(input_window, 'is_hidden').returns(true)
424+
stub(input_window, '_show')
425+
426+
ui.focus_input()
427+
428+
assert.stub(input_window._show).was_not_called()
429+
input_window.is_hidden:revert()
430+
input_window._show:revert()
431+
end)
432+
end)
433+
401434
describe('send_message', function()
402435
it('delegates message-sending coverage to services_messaging_spec', function()
403436
-- This spec focuses on session_runtime responsibilities.

0 commit comments

Comments
 (0)