refactor(tui): statusbar as 4-mode position (on|off|bottom|top)
Default is back to 'on' (inline, above the input) — bottom was too far from the input and felt disconnected. Users who want it pinned can opt in explicitly. - UiState.statusBar: boolean → 'on' | 'off' | 'bottom' | 'top' - /statusbar [on|off|bottom|top|toggle]; no-arg still binary-toggles between off and on (preserves muscle memory) - appLayout renders StatusRulePane in three slots (inline inside ComposerPane for 'on', above transcript row for 'top', after ComposerPane for 'bottom'); only the slot matching ui.statusBar actually mounts - drop the input's marginBottom when 'bottom' so the rule sits tight against the input instead of floating a row below - useConfigSync.normalizeStatusBar coerces legacy bool (true→on, false→off) and unknown shapes to 'on' for forward-compat reads - tui_gateway: split compact from statusbar config handlers; persist string enum with _coerce_statusbar helper for legacy bool configs
This commit is contained in:
@@ -27,6 +27,8 @@ export interface StateSetter<T> {
|
||||
(value: SetStateAction<T>): void
|
||||
}
|
||||
|
||||
export type StatusBarMode = 'bottom' | 'off' | 'on' | 'top'
|
||||
|
||||
export interface SelectionApi {
|
||||
clearSelection: () => void
|
||||
copySelection: () => string
|
||||
@@ -89,7 +91,7 @@ export interface UiState {
|
||||
showReasoning: boolean
|
||||
sid: null | string
|
||||
status: string
|
||||
statusBar: boolean
|
||||
statusBar: StatusBarMode
|
||||
streaming: boolean
|
||||
theme: Theme
|
||||
usage: Usage
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
SessionSteerResponse,
|
||||
SessionUndoResponse
|
||||
} from '../../../gatewayTypes.js'
|
||||
import type { StatusBarMode } from '../../interfaces.js'
|
||||
import { writeOsc52Clipboard } from '../../../lib/osc52.js'
|
||||
import { configureDetectedTerminalKeybindings, configureTerminalKeybindings } from '../../../lib/terminalSetup.js'
|
||||
import type { DetailsMode, Msg, PanelSection } from '../../../types.js'
|
||||
@@ -305,19 +306,30 @@ export const coreCommands: SlashCommand[] = [
|
||||
|
||||
{
|
||||
aliases: ['sb'],
|
||||
help: 'toggle status bar',
|
||||
help: 'status bar position (on|off|bottom|top)',
|
||||
name: 'statusbar',
|
||||
run: (arg, ctx) => {
|
||||
const next = flagFromArg(arg, ctx.ui.statusBar)
|
||||
const mode = arg.trim().toLowerCase()
|
||||
const current = ctx.ui.statusBar
|
||||
// No-arg / `toggle` flips visibility while preserving the last
|
||||
// explicit position: off → on (inline default), any-visible → off.
|
||||
const next: null | StatusBarMode =
|
||||
mode === '' || mode === 'toggle'
|
||||
? current === 'off'
|
||||
? 'on'
|
||||
: 'off'
|
||||
: mode === 'on' || mode === 'off' || mode === 'bottom' || mode === 'top'
|
||||
? mode
|
||||
: null
|
||||
|
||||
if (next === null) {
|
||||
return ctx.transcript.sys('usage: /statusbar [on|off|toggle]')
|
||||
return ctx.transcript.sys('usage: /statusbar [on|off|bottom|top|toggle]')
|
||||
}
|
||||
|
||||
patchUiState({ statusBar: next })
|
||||
ctx.gateway.rpc<ConfigSetResponse>('config.set', { key: 'statusbar', value: next ? 'on' : 'off' }).catch(() => {})
|
||||
ctx.gateway.rpc<ConfigSetResponse>('config.set', { key: 'statusbar', value: next }).catch(() => {})
|
||||
|
||||
queueMicrotask(() => ctx.transcript.sys(`status bar ${next ? 'on' : 'off'}`))
|
||||
queueMicrotask(() => ctx.transcript.sys(`status bar ${next}`))
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ const buildUiState = (): UiState => ({
|
||||
showReasoning: false,
|
||||
sid: null,
|
||||
status: 'summoning hermes…',
|
||||
statusBar: true,
|
||||
statusBar: 'on',
|
||||
streaming: true,
|
||||
theme: DEFAULT_THEME,
|
||||
usage: ZERO
|
||||
|
||||
@@ -10,9 +10,23 @@ import type {
|
||||
} from '../gatewayTypes.js'
|
||||
import { asRpcResult } from '../lib/rpc.js'
|
||||
|
||||
import type { StatusBarMode } from './interfaces.js'
|
||||
import { turnController } from './turnController.js'
|
||||
import { patchUiState } from './uiStore.js'
|
||||
|
||||
const STATUSBAR_MODES = new Set<StatusBarMode>(['bottom', 'off', 'on', 'top'])
|
||||
|
||||
// Legacy configs stored tui_statusbar as a bool; new configs write a string
|
||||
// ('on' | 'off' | 'bottom' | 'top'). Coerce both shapes so existing users
|
||||
// keep their preference without manual migration.
|
||||
export const normalizeStatusBar = (raw: unknown): StatusBarMode => {
|
||||
if (raw === false) return 'off'
|
||||
if (raw === true || raw == null) return 'on'
|
||||
if (typeof raw === 'string' && STATUSBAR_MODES.has(raw as StatusBarMode)) return raw as StatusBarMode
|
||||
|
||||
return 'on'
|
||||
}
|
||||
|
||||
const MTIME_POLL_MS = 5000
|
||||
|
||||
const quietRpc = async <T extends Record<string, any> = Record<string, any>>(
|
||||
@@ -37,7 +51,7 @@ export const applyDisplay = (cfg: ConfigFullResponse | null, setBell: (v: boolea
|
||||
inlineDiffs: d.inline_diffs !== false,
|
||||
showCost: !!d.show_cost,
|
||||
showReasoning: !!d.show_reasoning,
|
||||
statusBar: d.tui_statusbar !== false,
|
||||
statusBar: normalizeStatusBar(d.tui_statusbar),
|
||||
streaming: d.streaming !== false
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user