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:
Brooklyn Nicholson 2026-04-22 13:41:01 -05:00
parent 7027ce42ef
commit d55a17bd82
8 changed files with 118 additions and 25 deletions

View file

@ -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}`))
}
},