feat(tui): bare /details opens a keyboard selector
DetailsDialog is a centered SelectList over the five transcript-detail states (three tool-card phases, reasoning shown/hidden); it preselects the current phase, marks both current values, applies on Enter, and cancels on Esc/Ctrl+C. Width is the new detailsDialogWidth config key. The argument grammar is unchanged and shares the same setters.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
terminal 100x40 buffer=normal length=40 base=0 viewport=0
|
||||
lifecycle started=1 stopped=0 progress=inactive
|
||||
title "DSH snapshot"
|
||||
cursor hidden column=7 viewportRow=19 bufferRow=19
|
||||
cursor hidden column=7 viewportRow=17 bufferRow=17
|
||||
buffer
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-magenta bold
|
||||
@@ -29,17 +29,14 @@ buffer
|
||||
14| "Tool cards hidden. "
|
||||
style 0-17 dim
|
||||
15| <blank>
|
||||
16| "Tool and context cards hidden; reasoning blocks hidden. "
|
||||
style 0-54 dim
|
||||
17| <blank>
|
||||
18| "/workspace/project (tui-staging) deepseek-v4-flash ↑0 ↓0 0% context"
|
||||
16| "/workspace/project (tui-staging) deepseek-v4-flash ↑0 ↓0 0% context"
|
||||
style 0-17 fg=bright-magenta bold
|
||||
style 18-31 dim
|
||||
style 34-50 dim
|
||||
style 53-57 dim
|
||||
style 60-69 dim
|
||||
19| " dsh > "
|
||||
17| " dsh > "
|
||||
style 1-3 fg=bright-magenta bold
|
||||
style 5-6 dim
|
||||
style 7-7 inverse
|
||||
20-39| <blank>
|
||||
18-39| <blank>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
terminal 100x40 buffer=normal length=40 base=0 viewport=0
|
||||
lifecycle started=1 stopped=0 progress=inactive
|
||||
title "DSH snapshot"
|
||||
cursor hidden column=0 viewportRow=39 bufferRow=39
|
||||
buffer
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-magenta bold
|
||||
style 10-16 bold
|
||||
1| " Snapshot agent ready."
|
||||
style 1-21 dim
|
||||
2| " main-session"
|
||||
style 1-12 dim
|
||||
3| <blank>
|
||||
4| "Assistant "
|
||||
style 0-8 fg=bright-magenta bold underline
|
||||
5| "Running the check now. "
|
||||
6| "Model wait 0.0s "
|
||||
style 0-14 dim
|
||||
7| <blank>
|
||||
8| "You "
|
||||
style 0-2 fg=bright-magenta bold underline
|
||||
9| "Inspect the renderer. "
|
||||
10| "Model wait 0.0s · Completed 2026-07-30 18:00:00 "
|
||||
style 0-46 dim
|
||||
11| <blank>
|
||||
12| "Reasoning blocks hidden. "
|
||||
style 0-23 dim
|
||||
13| <blank>
|
||||
14| "Tool cards hidden. "
|
||||
style 0-17 dim
|
||||
15| " ╭ Transcript details ──────────────────────────────────────────────────╮ "
|
||||
style 14-85 fg=bright-magenta
|
||||
16| "/workspace/pro│ Tool cards · collapsed head/tail preview │ "
|
||||
style 0-13 fg=bright-magenta bold
|
||||
style 14-14 fg=bright-magenta
|
||||
style 40-66 dim
|
||||
style 85-85 fg=bright-magenta
|
||||
17| " dsh > │ Tool cards · expanded full bodies │ "
|
||||
style 1-3 fg=bright-magenta bold
|
||||
style 5-6 dim
|
||||
style 7-7 inverse
|
||||
style 14-14 fg=bright-magenta
|
||||
style 39-60 dim
|
||||
style 85-85 fg=bright-magenta
|
||||
18| " │ → Tool cards · hidden conversation only — current │ "
|
||||
style 14-14 fg=bright-magenta
|
||||
style 16-76 fg=bright-magenta inverse
|
||||
style 85-85 fg=bright-magenta
|
||||
19| " │ Reasoning · shown show reasoning blocks │ "
|
||||
style 14-14 fg=bright-magenta
|
||||
style 35-70 dim
|
||||
style 85-85 fg=bright-magenta
|
||||
20| " │ Reasoning · hidden omit reasoning blocks — current │ "
|
||||
style 14-14 fg=bright-magenta
|
||||
style 36-80 dim
|
||||
style 85-85 fg=bright-magenta
|
||||
21| " │ │ "
|
||||
style 14-14 fg=bright-magenta
|
||||
style 85-85 fg=bright-magenta
|
||||
22| " │ ↑/↓ move • Enter apply • Esc cancel │ "
|
||||
style 14-14 fg=bright-magenta
|
||||
style 16-50 dim
|
||||
style 85-85 fg=bright-magenta
|
||||
23| " ╰──────────────────────────────────────────────────────────────────────╯ "
|
||||
style 14-85 fg=bright-magenta
|
||||
24-39| <blank>
|
||||
@@ -29,10 +29,10 @@ buffer
|
||||
12| " "
|
||||
13| "/clear — Clear the transcript view (session history is unchanged) "
|
||||
style 0-64 dim
|
||||
14| "/details [collapsed|expanded|hidden] [reasoning [on|off]] — Show or set tool-card visibility"
|
||||
14| "/details [collapsed|expanded|hidden] [reasoning [on|off]] — Select tool-card visibility and "
|
||||
style 0-91 dim
|
||||
15| "and reasoning display "
|
||||
style 0-20 dim
|
||||
15| "reasoning display "
|
||||
style 0-16 dim
|
||||
16| "/exit — Exit after the active turn reaches idle "
|
||||
style 0-46 dim
|
||||
17| "/help — Show keyboard shortcuts and commands "
|
||||
|
||||
@@ -29,10 +29,10 @@ buffer
|
||||
12| " "
|
||||
13| "/clear — Clear the transcript view (session history is unchanged) "
|
||||
style 0-64 dim
|
||||
14| "/details [collapsed|expanded|hidden] [reasoning [on|off]] — Show or set tool-card visibility"
|
||||
14| "/details [collapsed|expanded|hidden] [reasoning [on|off]] — Select tool-card visibility and "
|
||||
style 0-91 dim
|
||||
15| "and reasoning display "
|
||||
style 0-20 dim
|
||||
15| "reasoning display "
|
||||
style 0-16 dim
|
||||
16| "/exit — Exit after the active turn reaches idle "
|
||||
style 0-46 dim
|
||||
17| "/help — Show keyboard shortcuts and commands "
|
||||
|
||||
@@ -46,6 +46,7 @@ const CHECKPOINTS = [
|
||||
'advanced-cards-expanded',
|
||||
'tool-cards-hidden-folded',
|
||||
'details-command',
|
||||
'details-selector',
|
||||
'untrusted-controls',
|
||||
'question-dialog',
|
||||
'question-dialog-single-option',
|
||||
@@ -661,11 +662,14 @@ describe('TUI terminal-state snapshots', () => {
|
||||
harness.terminal.send('/details hidden reasoning off')
|
||||
harness.terminal.send('\r')
|
||||
})
|
||||
await checkpoint('details-command', harness.terminal, { includeScrollback: true })
|
||||
// Bare /details opens the selector, preselecting and marking the current
|
||||
// hidden/reasoning-off state.
|
||||
await renderAfter(harness, () => {
|
||||
harness.terminal.send('/details')
|
||||
harness.terminal.send('\r')
|
||||
})
|
||||
await checkpoint('details-command', harness.terminal, { includeScrollback: true })
|
||||
await checkpoint('details-selector', harness.terminal, { includeScrollback: true })
|
||||
nowSpy.mockRestore()
|
||||
await disposeSnapshot(harness)
|
||||
})
|
||||
|
||||
@@ -186,6 +186,7 @@ describe('TUI config', () => {
|
||||
questionDialogMaxHeight: 20,
|
||||
modelDialogWidth: 76,
|
||||
modelDialogMaxHeight: 20,
|
||||
detailsDialogWidth: 72,
|
||||
fileSearchMaxResults: 20,
|
||||
fileSearchMaxEntries: 10_000,
|
||||
fileSearchExcludedDirectories: ['.git', 'node_modules'],
|
||||
@@ -210,6 +211,7 @@ describe('TUI config', () => {
|
||||
questionDialogMaxHeight: 14,
|
||||
modelDialogWidth: 64,
|
||||
modelDialogMaxHeight: 16,
|
||||
detailsDialogWidth: 44,
|
||||
fileSearchMaxResults: 7,
|
||||
fileSearchMaxEntries: 123,
|
||||
fileSearchExcludedDirectories: ['.git', 'generated'],
|
||||
@@ -226,6 +228,7 @@ describe('TUI config', () => {
|
||||
questionDialogMaxHeight: 14,
|
||||
modelDialogWidth: 64,
|
||||
modelDialogMaxHeight: 16,
|
||||
detailsDialogWidth: 44,
|
||||
fileSearchMaxResults: 7,
|
||||
fileSearchMaxEntries: 123,
|
||||
fileSearchExcludedDirectories: ['.git', 'generated'],
|
||||
@@ -2510,7 +2513,7 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
await dispose(result)
|
||||
})
|
||||
|
||||
it('/details reports and sets card visibility and reasoning display', async () => {
|
||||
it('/details sets card visibility and reasoning display from arguments', async () => {
|
||||
const result = await setup()
|
||||
const run = async (line: string): Promise<void> => {
|
||||
result.terminal.send(line)
|
||||
@@ -2518,9 +2521,6 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
await tick()
|
||||
}
|
||||
|
||||
await run('/details')
|
||||
expect(result.terminal.output).toContain('Tool and context cards collapsed; reasoning blocks shown.')
|
||||
|
||||
await run('/details hidden')
|
||||
expect(result.terminal.output).toContain('Tool cards hidden.')
|
||||
|
||||
@@ -2531,11 +2531,12 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
await run('/details reasoning on')
|
||||
expect(result.terminal.output).toContain('Reasoning blocks shown.')
|
||||
|
||||
// Bare `reasoning` toggles: shown -> hidden, confirmed by the status line.
|
||||
// Bare `reasoning` toggles: shown -> hidden.
|
||||
const toggleOutput = result.terminal.output.length
|
||||
await run('/details reasoning')
|
||||
expect(result.terminal.output.slice(toggleOutput)).toContain('Reasoning blocks hidden.')
|
||||
await run('/details collapsed')
|
||||
await run('/details')
|
||||
expect(result.terminal.output).toContain('Tool and context cards collapsed; reasoning blocks hidden.')
|
||||
expect(result.terminal.output.slice(toggleOutput)).toContain('Tool and context cards collapsed.')
|
||||
|
||||
await run('/details bogus')
|
||||
expect(result.terminal.output).toContain('Unknown /details argument "bogus"')
|
||||
@@ -2543,6 +2544,59 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
await dispose(result)
|
||||
})
|
||||
|
||||
it('bare /details opens the transcript-details selector and applies the confirmed state', async () => {
|
||||
const result = await setup()
|
||||
const open = async (): Promise<number> => {
|
||||
const from = result.terminal.output.length
|
||||
result.terminal.send('/details')
|
||||
result.terminal.send('\r')
|
||||
await vi.waitFor(() => { expect(result.terminal.output.slice(from)).toContain('Transcript details') })
|
||||
return from
|
||||
}
|
||||
|
||||
await open()
|
||||
expect(result.terminal.output).toContain('Tool cards · collapsed')
|
||||
expect(result.terminal.output).toContain('head/tail preview — current')
|
||||
expect(result.terminal.output).toContain('show reasoning blocks — current')
|
||||
|
||||
// A second /details while the selector is open replaces the overlay
|
||||
// instead of stacking a second one behind it.
|
||||
await result.ctx.commands.execute(result.agent, '/details', new AbortController().signal)
|
||||
await tick()
|
||||
|
||||
// Esc cancels without touching the state.
|
||||
const cancelOutput = result.terminal.output.length
|
||||
result.terminal.send('\x1b')
|
||||
await tick()
|
||||
expect(result.terminal.output.slice(cancelOutput)).not.toContain('Tool and context cards')
|
||||
|
||||
// Enter on the next visibility row applies it and closes.
|
||||
await open()
|
||||
result.terminal.send('\x1b[B')
|
||||
result.terminal.send('\r')
|
||||
await tick()
|
||||
expect(result.terminal.output).toContain('Tool and context cards expanded.')
|
||||
|
||||
// The reopened selector preselects the current phase and marks it.
|
||||
const reopened = await open()
|
||||
expect(result.terminal.output.slice(reopened)).toContain('full bodies — current')
|
||||
result.terminal.send('\x1b[B')
|
||||
result.terminal.send('\x1b[B')
|
||||
result.terminal.send('\x1b[B')
|
||||
result.terminal.send('\r')
|
||||
await tick()
|
||||
expect(result.terminal.output).toContain('Reasoning blocks hidden.')
|
||||
|
||||
// Ctrl+C also cancels.
|
||||
const ctrlCOutput = result.terminal.output.length
|
||||
await open()
|
||||
result.terminal.send('\x03')
|
||||
await tick()
|
||||
expect(result.terminal.output.slice(ctrlCOutput)).not.toContain('Reasoning blocks shown.')
|
||||
|
||||
await dispose(result)
|
||||
})
|
||||
|
||||
it('sends, steers, handles commands, global keys, and disposed-agent input', async () => {
|
||||
const result = await setup()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user