From d8afafd22b081523b3a46930d664289670e8b401 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Wed, 29 Apr 2026 15:23:14 -0500 Subject: [PATCH 1/3] fix(tui): hide reasoning panels immediately Make /reasoning hide update the thinking section visibility so existing and live reasoning blocks disappear without waiting for config sync. --- tests/test_tui_gateway_server.py | 12 ++++++ tui_gateway/server.py | 2 + .../src/__tests__/createSlashHandler.test.ts | 39 +++++++++++++++++++ ui-tui/src/app/slash/commands/session.ts | 24 +++++++++++- ui-tui/src/app/useMainApp.ts | 3 +- 5 files changed, 78 insertions(+), 2 deletions(-) diff --git a/tests/test_tui_gateway_server.py b/tests/test_tui_gateway_server.py index dacc55df5..2e385eec9 100644 --- a/tests/test_tui_gateway_server.py +++ b/tests/test_tui_gateway_server.py @@ -1066,6 +1066,18 @@ def test_config_set_reasoning_updates_live_session_and_agent(tmp_path, monkeypat ) assert resp_show["result"]["value"] == "show" assert server._sessions["sid"]["show_reasoning"] is True + assert server._load_cfg()["display"]["sections"]["thinking"] == "expanded" + + resp_hide = server.handle_request( + { + "id": "3", + "method": "config.set", + "params": {"session_id": "sid", "key": "reasoning", "value": "hide"}, + } + ) + assert resp_hide["result"]["value"] == "hide" + assert server._sessions["sid"]["show_reasoning"] is False + assert server._load_cfg()["display"]["sections"]["thinking"] == "hidden" def test_config_set_verbose_updates_session_mode_and_agent(tmp_path, monkeypatch): diff --git a/tui_gateway/server.py b/tui_gateway/server.py index fee8e9550..50e607414 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -3106,11 +3106,13 @@ def _(rid, params: dict) -> dict: arg = str(value or "").strip().lower() if arg in ("show", "on"): _write_config_key("display.show_reasoning", True) + _write_config_key("display.sections.thinking", "expanded") if session: session["show_reasoning"] = True return _ok(rid, {"key": key, "value": "show"}) if arg in ("hide", "off"): _write_config_key("display.show_reasoning", False) + _write_config_key("display.sections.thinking", "hidden") if session: session["show_reasoning"] = False return _ok(rid, {"key": key, "value": "hide"}) diff --git a/ui-tui/src/__tests__/createSlashHandler.test.ts b/ui-tui/src/__tests__/createSlashHandler.test.ts index 3ec340b8a..56128e388 100644 --- a/ui-tui/src/__tests__/createSlashHandler.test.ts +++ b/ui-tui/src/__tests__/createSlashHandler.test.ts @@ -76,6 +76,45 @@ describe('createSlashHandler', () => { }) }) + it('applies /reasoning hide to the thinking section immediately', async () => { + patchUiState({ sections: { thinking: 'expanded' }, showReasoning: true, sid: 'sid-abc' }) + const ctx = buildCtx({ + gateway: { + ...buildGateway(), + rpc: vi.fn(() => Promise.resolve({ value: 'hide' })) + } + }) + + expect(createSlashHandler(ctx)('/reasoning hide')).toBe(true) + + await vi.waitFor(() => { + expect(getUiState().showReasoning).toBe(false) + expect(getUiState().sections.thinking).toBe('hidden') + }) + expect(ctx.gateway.rpc).toHaveBeenCalledWith('config.set', { + key: 'reasoning', + session_id: 'sid-abc', + value: 'hide' + }) + }) + + it('applies /reasoning show to the thinking section immediately', async () => { + patchUiState({ sections: { thinking: 'hidden' }, showReasoning: false, sid: 'sid-abc' }) + const ctx = buildCtx({ + gateway: { + ...buildGateway(), + rpc: vi.fn(() => Promise.resolve({ value: 'show' })) + } + }) + + expect(createSlashHandler(ctx)('/reasoning show')).toBe(true) + + await vi.waitFor(() => { + expect(getUiState().showReasoning).toBe(true) + expect(getUiState().sections.thinking).toBe('expanded') + }) + }) + it('opens the skills hub locally for bare /skills', () => { const ctx = buildCtx() diff --git a/ui-tui/src/app/slash/commands/session.ts b/ui-tui/src/app/slash/commands/session.ts index ecd1b7866..cfe84f942 100644 --- a/ui-tui/src/app/slash/commands/session.ts +++ b/ui-tui/src/app/slash/commands/session.ts @@ -332,7 +332,29 @@ export const sessionCommands: SlashCommand[] = [ ctx.gateway .rpc('config.set', { key: 'reasoning', session_id: ctx.sid, value: arg }) - .then(ctx.guarded(r => r.value && ctx.transcript.sys(`reasoning: ${r.value}`))) + .then( + ctx.guarded(r => { + if (!r.value) { + return + } + + if (r.value === 'hide') { + patchUiState(state => ({ + ...state, + sections: { ...state.sections, thinking: 'hidden' }, + showReasoning: false + })) + } else if (r.value === 'show') { + patchUiState(state => ({ + ...state, + sections: { ...state.sections, thinking: 'expanded' }, + showReasoning: true + })) + } + + ctx.transcript.sys(`reasoning: ${r.value}`) + }) + ) } }, diff --git a/ui-tui/src/app/useMainApp.ts b/ui-tui/src/app/useMainApp.ts index 70dc96fec..7a4633a32 100644 --- a/ui-tui/src/app/useMainApp.ts +++ b/ui-tui/src/app/useMainApp.ts @@ -711,6 +711,7 @@ export function useMainApp(gw: GatewayClient) { const anyPanelVisible = SECTION_NAMES.some( s => sectionMode(s, ui.detailsMode, ui.sections, ui.detailsModeCommandOverride) !== 'hidden' ) + const thinkingPanelVisible = sectionMode('thinking', ui.detailsMode, ui.sections, ui.detailsModeCommandOverride) !== 'hidden' const showProgressArea = useTurnSelector(state => anyPanelVisible @@ -723,7 +724,7 @@ export function useMainApp(gw: GatewayClient) { state.tools.length || state.todos.length || state.turnTrail.length || - hasReasoning || + (thinkingPanelVisible && hasReasoning) || state.activity.length ) : state.activity.some(item => item.tone !== 'info') From f7abcb4f018839177fb2c8e96353a5cf5d0ccffb Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Wed, 29 Apr 2026 15:50:02 -0500 Subject: [PATCH 2/3] fix(tui): ignore hidden reasoning stream segments Only keep the live progress area mounted for stream segments that can render under the current detail section visibility. --- ui-tui/src/app/useMainApp.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ui-tui/src/app/useMainApp.ts b/ui-tui/src/app/useMainApp.ts index 7a4633a32..39b7c652a 100644 --- a/ui-tui/src/app/useMainApp.ts +++ b/ui-tui/src/app/useMainApp.ts @@ -712,6 +712,7 @@ export function useMainApp(gw: GatewayClient) { s => sectionMode(s, ui.detailsMode, ui.sections, ui.detailsModeCommandOverride) !== 'hidden' ) const thinkingPanelVisible = sectionMode('thinking', ui.detailsMode, ui.sections, ui.detailsModeCommandOverride) !== 'hidden' + const toolsPanelVisible = sectionMode('tools', ui.detailsMode, ui.sections, ui.detailsModeCommandOverride) !== 'hidden' const showProgressArea = useTurnSelector(state => anyPanelVisible @@ -719,7 +720,16 @@ export function useMainApp(gw: GatewayClient) { ui.busy || state.outcome || state.streamPendingTools.length || - state.streamSegments.length || + state.streamSegments.some(segment => { + const thinking = Boolean(segment.thinking?.trim()) + const tools = Boolean(segment.tools?.length) + + if (segment.kind === 'trail' && !segment.text) { + return (thinkingPanelVisible && thinking) || (toolsPanelVisible && tools) + } + + return Boolean(segment.text?.trim()) || (thinkingPanelVisible && thinking) || (toolsPanelVisible && tools) + }) || state.subagents.length || state.tools.length || state.todos.length || From 7d96a5ab6ea10bd67c440cae632d97ec31e61780 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Wed, 29 Apr 2026 16:03:45 -0500 Subject: [PATCH 3/3] fix(tui): refine reasoning visibility updates Save reasoning display changes atomically and keep trail segments visible when Activity can render them. --- tui_gateway/server.py | 28 ++++++++++++++++++++++++---- ui-tui/src/app/useMainApp.ts | 13 +++++++++---- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/tui_gateway/server.py b/tui_gateway/server.py index 50e607414..24f6baf71 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -3105,14 +3105,34 @@ def _(rid, params: dict) -> dict: arg = str(value or "").strip().lower() if arg in ("show", "on"): - _write_config_key("display.show_reasoning", True) - _write_config_key("display.sections.thinking", "expanded") + cfg = _load_cfg() + display = cfg.get("display") if isinstance(cfg.get("display"), dict) else {} + sections = ( + display.get("sections") + if isinstance(display.get("sections"), dict) + else {} + ) + display["show_reasoning"] = True + sections["thinking"] = "expanded" + display["sections"] = sections + cfg["display"] = display + _save_cfg(cfg) if session: session["show_reasoning"] = True return _ok(rid, {"key": key, "value": "show"}) if arg in ("hide", "off"): - _write_config_key("display.show_reasoning", False) - _write_config_key("display.sections.thinking", "hidden") + cfg = _load_cfg() + display = cfg.get("display") if isinstance(cfg.get("display"), dict) else {} + sections = ( + display.get("sections") + if isinstance(display.get("sections"), dict) + else {} + ) + display["show_reasoning"] = False + sections["thinking"] = "hidden" + display["sections"] = sections + cfg["display"] = display + _save_cfg(cfg) if session: session["show_reasoning"] = False return _ok(rid, {"key": key, "value": "hide"}) diff --git a/ui-tui/src/app/useMainApp.ts b/ui-tui/src/app/useMainApp.ts index 39b7c652a..9ec18337b 100644 --- a/ui-tui/src/app/useMainApp.ts +++ b/ui-tui/src/app/useMainApp.ts @@ -713,6 +713,7 @@ export function useMainApp(gw: GatewayClient) { ) const thinkingPanelVisible = sectionMode('thinking', ui.detailsMode, ui.sections, ui.detailsModeCommandOverride) !== 'hidden' const toolsPanelVisible = sectionMode('tools', ui.detailsMode, ui.sections, ui.detailsModeCommandOverride) !== 'hidden' + const activityPanelVisible = sectionMode('activity', ui.detailsMode, ui.sections, ui.detailsModeCommandOverride) !== 'hidden' const showProgressArea = useTurnSelector(state => anyPanelVisible @@ -721,14 +722,18 @@ export function useMainApp(gw: GatewayClient) { state.outcome || state.streamPendingTools.length || state.streamSegments.some(segment => { - const thinking = Boolean(segment.thinking?.trim()) - const tools = Boolean(segment.tools?.length) + const hasThinking = Boolean(segment.thinking?.trim()) + const hasTrailTools = Boolean(segment.tools?.length) if (segment.kind === 'trail' && !segment.text) { - return (thinkingPanelVisible && thinking) || (toolsPanelVisible && tools) + return (thinkingPanelVisible && hasThinking) || ((toolsPanelVisible || activityPanelVisible) && hasTrailTools) } - return Boolean(segment.text?.trim()) || (thinkingPanelVisible && thinking) || (toolsPanelVisible && tools) + return ( + Boolean(segment.text?.trim()) || + (thinkingPanelVisible && hasThinking) || + ((toolsPanelVisible || activityPanelVisible) && hasTrailTools) + ) }) || state.subagents.length || state.tools.length ||