feat(tui): read display.streaming / show_reasoning / show_cost / inline_diffs from config

Extends ConfigDisplayConfig and UiState so the four new display flags
flow from `config.get {key:"full"}` into the nanostore. applyDisplay is
exported to keep the fan-out testable without an Ink harness.

Defaults mirror v1 parity: streaming + inline_diffs default true
(opt-out via `=== false`), show_cost + show_reasoning default false
(opt-in via plain truthy check).
This commit is contained in:
Brooklyn Nicholson
2026-04-18 09:23:29 -05:00
parent 586b2f2089
commit 200c17433c
5 changed files with 85 additions and 2 deletions

View File

@@ -0,0 +1,67 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { $uiState, resetUiState } from '../app/uiStore.js'
import { applyDisplay } from '../app/useConfigSync.js'
describe('applyDisplay', () => {
beforeEach(() => {
resetUiState()
})
it('fans every display flag out to $uiState and the bell callback', () => {
const setBell = vi.fn()
applyDisplay(
{
config: {
display: {
bell_on_complete: true,
details_mode: 'expanded',
inline_diffs: false,
show_cost: true,
show_reasoning: true,
streaming: false,
tui_compact: true,
tui_statusbar: false
}
}
},
setBell
)
const s = $uiState.get()
expect(setBell).toHaveBeenCalledWith(true)
expect(s.compact).toBe(true)
expect(s.detailsMode).toBe('expanded')
expect(s.inlineDiffs).toBe(false)
expect(s.showCost).toBe(true)
expect(s.showReasoning).toBe(true)
expect(s.statusBar).toBe(false)
expect(s.streaming).toBe(false)
})
it('applies v1 parity defaults when display fields are missing', () => {
const setBell = vi.fn()
applyDisplay({ config: { display: {} } }, setBell)
const s = $uiState.get()
expect(setBell).toHaveBeenCalledWith(false)
expect(s.inlineDiffs).toBe(true)
expect(s.showCost).toBe(false)
expect(s.showReasoning).toBe(false)
expect(s.statusBar).toBe(true)
expect(s.streaming).toBe(true)
})
it('treats a null config like an empty display block', () => {
const setBell = vi.fn()
applyDisplay(null, setBell)
const s = $uiState.get()
expect(setBell).toHaveBeenCalledWith(false)
expect(s.inlineDiffs).toBe(true)
expect(s.streaming).toBe(true)
})
})

View File

@@ -78,9 +78,13 @@ export interface UiState {
compact: boolean
detailsMode: DetailsMode
info: null | SessionInfo
inlineDiffs: boolean
showCost: boolean
showReasoning: boolean
sid: null | string
status: string
statusBar: boolean
streaming: boolean
theme: Theme
usage: Usage
}

View File

@@ -11,9 +11,13 @@ const buildUiState = (): UiState => ({
compact: false,
detailsMode: 'collapsed',
info: null,
inlineDiffs: true,
showCost: false,
showReasoning: false,
sid: null,
status: 'summoning hermes…',
statusBar: true,
streaming: true,
theme: DEFAULT_THEME,
usage: ZERO
})

View File

@@ -27,14 +27,18 @@ const quietRpc = async <T extends Record<string, any> = Record<string, any>>(
}
}
const applyDisplay = (cfg: ConfigFullResponse | null, setBell: (v: boolean) => void) => {
export const applyDisplay = (cfg: ConfigFullResponse | null, setBell: (v: boolean) => void) => {
const d = cfg?.config?.display ?? {}
setBell(!!d.bell_on_complete)
patchUiState({
compact: !!d.tui_compact,
detailsMode: resolveDetailsMode(d),
statusBar: d.tui_statusbar !== false
inlineDiffs: d.inline_diffs !== false,
showCost: !!d.show_cost,
showReasoning: !!d.show_reasoning,
statusBar: d.tui_statusbar !== false,
streaming: d.streaming !== false
})
}

View File

@@ -53,6 +53,10 @@ export type CommandDispatchResponse =
export interface ConfigDisplayConfig {
bell_on_complete?: boolean
details_mode?: string
inline_diffs?: boolean
show_cost?: boolean
show_reasoning?: boolean
streaming?: boolean
thinking_mode?: string
tui_compact?: boolean
tui_statusbar?: boolean