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.
This commit is contained in:
Brooklyn Nicholson 2026-04-29 15:23:14 -05:00
parent 456955c2e4
commit d8afafd22b
5 changed files with 78 additions and 2 deletions

View file

@ -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):

View file

@ -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"})

View file

@ -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()

View file

@ -332,7 +332,29 @@ export const sessionCommands: SlashCommand[] = [
ctx.gateway
.rpc<ConfigSetResponse>('config.set', { key: 'reasoning', session_id: ctx.sid, value: arg })
.then(ctx.guarded<ConfigSetResponse>(r => r.value && ctx.transcript.sys(`reasoning: ${r.value}`)))
.then(
ctx.guarded<ConfigSetResponse>(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}`)
})
)
}
},

View file

@ -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')