mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
refactor(tui): /clean pass on blitz closeout — trim comments, flatten logic
- normalizeStatusBar collapses to one ternary expression
- /statusbar slash hoists the toggle value and flattens the branch tree
- shift-tab yolo comment reduced to one line
- cursorLayout/offsetFromPosition lose paragraph-length comments
- appLayout collapses the three {!overlay.agents && …} into one fragment
- StatusRule drops redundant flexShrink={0} (Yoga default)
- server.py uses a walrus + frozenset and trims the compat helper
Net -43 LoC. 237 vitest + 46 pytest green, layouts unchanged.
This commit is contained in:
parent
1e8cfa9092
commit
48f2ac3352
8 changed files with 40 additions and 83 deletions
|
|
@ -455,21 +455,14 @@ def _write_config_key(key_path: str, value):
|
|||
_save_cfg(cfg)
|
||||
|
||||
|
||||
# Legacy configs stored display.tui_statusbar as a bool; a short-lived
|
||||
# intermediate wrote 'on'. Both forms map to 'top' — the inline position
|
||||
# above the input where the bar originally lived — so users don't need to
|
||||
# migrate by hand.
|
||||
_STATUSBAR_MODES = frozenset({"off", "top", "bottom"})
|
||||
|
||||
|
||||
def _coerce_statusbar(raw) -> str:
|
||||
if raw is True:
|
||||
return "top"
|
||||
if raw is False:
|
||||
return "off"
|
||||
if isinstance(raw, str):
|
||||
s = raw.strip().lower()
|
||||
if s == "on":
|
||||
return "top"
|
||||
if s in {"off", "top", "bottom"}:
|
||||
return s
|
||||
if isinstance(raw, str) and (s := raw.strip().lower()) in _STATUSBAR_MODES:
|
||||
return s
|
||||
return "top"
|
||||
|
||||
|
||||
|
|
@ -2535,17 +2528,18 @@ def _(rid, params: dict) -> dict:
|
|||
|
||||
if key == "statusbar":
|
||||
raw = str(value or "").strip().lower()
|
||||
cfg0 = _load_cfg()
|
||||
d0 = cfg0.get("display") if isinstance(cfg0.get("display"), dict) else {}
|
||||
d0 = _load_cfg().get("display") or {}
|
||||
current = _coerce_statusbar(d0.get("tui_statusbar", "top"))
|
||||
|
||||
if raw in ("", "toggle"):
|
||||
nv = "top" if current == "off" else "off"
|
||||
elif raw == "on":
|
||||
nv = "top"
|
||||
elif raw in ("off", "top", "bottom"):
|
||||
elif raw in _STATUSBAR_MODES:
|
||||
nv = raw
|
||||
else:
|
||||
return _err(rid, 4002, f"unknown statusbar value: {value}")
|
||||
|
||||
_write_config_key("display.tui_statusbar", nv)
|
||||
return _ok(rid, {"key": key, "value": nv})
|
||||
|
||||
|
|
|
|||
|
|
@ -50,17 +50,8 @@ export default function wrapText(text: string, maxWidth: number, wrapType: Style
|
|||
})
|
||||
}
|
||||
|
||||
// Char-granularity wrap: break at exact column boundaries regardless of
|
||||
// whitespace. Used for text inputs where the cursor position must track
|
||||
// the wrap boundary deterministically — word-wrap's whitespace-preferring
|
||||
// reshuffle causes visible cursor flicker as each keystroke can push a
|
||||
// word across a line break.
|
||||
if (wrapType === 'wrap-char') {
|
||||
return wrapAnsi(text, maxWidth, {
|
||||
trim: false,
|
||||
hard: true,
|
||||
wordWrap: false
|
||||
})
|
||||
return wrapAnsi(text, maxWidth, { trim: false, hard: true, wordWrap: false })
|
||||
}
|
||||
|
||||
if (wrapType === 'wrap-trim') {
|
||||
|
|
|
|||
|
|
@ -310,23 +310,18 @@ export const coreCommands: SlashCommand[] = [
|
|||
name: 'statusbar',
|
||||
run: (arg, ctx) => {
|
||||
const mode = arg.trim().toLowerCase()
|
||||
const current = ctx.ui.statusBar
|
||||
const toggle: StatusBarMode = ctx.ui.statusBar === 'off' ? 'top' : 'off'
|
||||
|
||||
// 'on' is a legacy alias for 'top' — the inline position above the
|
||||
// input where the bar originally lived. No-arg / `toggle` flips
|
||||
// visibility and defaults to 'top' when reappearing.
|
||||
const next: null | StatusBarMode =
|
||||
mode === '' || mode === 'toggle'
|
||||
? current === 'off'
|
||||
? 'top'
|
||||
: 'off'
|
||||
!mode || mode === 'toggle'
|
||||
? toggle
|
||||
: mode === 'on' || mode === 'top'
|
||||
? 'top'
|
||||
: mode === 'off' || mode === 'bottom'
|
||||
? mode
|
||||
: null
|
||||
|
||||
if (next === null) {
|
||||
if (!next) {
|
||||
return ctx.transcript.sys('usage: /statusbar [on|off|top|bottom|toggle]')
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,17 +16,12 @@ import { patchUiState } from './uiStore.js'
|
|||
|
||||
const STATUSBAR_MODES = new Set<StatusBarMode>(['bottom', 'off', 'top'])
|
||||
|
||||
// Legacy configs stored tui_statusbar as a bool; the short-lived 4-mode
|
||||
// variant wrote 'on'. Both map to 'top' (inline above the input) — the
|
||||
// original feature's default — so users keep their preference without
|
||||
// manual migration.
|
||||
export const normalizeStatusBar = (raw: unknown): StatusBarMode => {
|
||||
if (raw === false) return 'off'
|
||||
if (raw === true || raw == null || raw === 'on') return 'top'
|
||||
if (typeof raw === 'string' && STATUSBAR_MODES.has(raw as StatusBarMode)) return raw as StatusBarMode
|
||||
|
||||
return 'top'
|
||||
}
|
||||
export const normalizeStatusBar = (raw: unknown): StatusBarMode =>
|
||||
raw === false
|
||||
? 'off'
|
||||
: typeof raw === 'string' && STATUSBAR_MODES.has(raw as StatusBarMode)
|
||||
? (raw as StatusBarMode)
|
||||
: 'top'
|
||||
|
||||
const MTIME_POLL_MS = 5000
|
||||
|
||||
|
|
|
|||
|
|
@ -378,10 +378,7 @@ export function useInputHandlers(ctx: InputHandlerContext): InputHandlerResult {
|
|||
return cActions.openEditor()
|
||||
}
|
||||
|
||||
// Shift-Tab toggles per-session yolo without submitting a turn — mirrors
|
||||
// Claude Code's in-place dangerously-approve toggle. Slash /yolo keeps
|
||||
// working for discoverability; this just skips the inference round-trip
|
||||
// when you only want to flip the flag mid-flow (blitz #5 sub-item 11).
|
||||
// shift-tab flips yolo without spending a turn (claude-code parity)
|
||||
if (key.shift && key.tab && !cState.completions.length) {
|
||||
return void gateway
|
||||
.rpc<ConfigSetResponse>('config.set', { key: 'yolo', session_id: live.sid })
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ export function StatusRule({
|
|||
const leftWidth = Math.max(12, cols - cwdLabel.length - 3)
|
||||
|
||||
return (
|
||||
<Box flexShrink={0} height={1}>
|
||||
<Box height={1}>
|
||||
<Box flexShrink={1} width={leftWidth}>
|
||||
<Text color={t.color.bronze} wrap="truncate-end">
|
||||
{'─ '}
|
||||
|
|
|
|||
|
|
@ -216,13 +216,7 @@ const ComposerPane = memo(function ComposerPane({
|
|||
</Box>
|
||||
|
||||
<Box flexGrow={1} position="relative">
|
||||
{/*
|
||||
Subtract the NoSelect paddingX={1} (2 cols total) and the
|
||||
prompt-glyph column (pw) so cursorLayout agrees with the
|
||||
width wrap-ansi actually uses at render time. Off-by-one/
|
||||
two here manifests as the final letter flickering
|
||||
in/out when a sentence crosses the wrap boundary.
|
||||
*/}
|
||||
{/* subtract NoSelect paddingX={1} (2 cols) + pw so wrap-ansi and cursorLayout agree */}
|
||||
<TextInput
|
||||
columns={Math.max(20, composer.cols - pw - 2)}
|
||||
onChange={composer.updateInput}
|
||||
|
|
@ -271,12 +265,8 @@ const StatusRulePane = memo(function StatusRulePane({
|
|||
return null
|
||||
}
|
||||
|
||||
// 'top' sits inline above the input; give it one row of breathing
|
||||
// space above so the transcript (or queue) doesn't butt directly
|
||||
// against the status row. 'bottom' lives at the last row of the
|
||||
// viewport so it needs no margin.
|
||||
return (
|
||||
<Box flexShrink={0} marginTop={at === 'top' ? 1 : 0}>
|
||||
<Box marginTop={at === 'top' ? 1 : 0}>
|
||||
<StatusRule
|
||||
bgCount={ui.bgTasks.size}
|
||||
busy={ui.busy}
|
||||
|
|
@ -318,18 +308,20 @@ export const AppLayout = memo(function AppLayout({
|
|||
</Box>
|
||||
|
||||
{!overlay.agents && (
|
||||
<PromptZone
|
||||
cols={composer.cols}
|
||||
onApprovalChoice={actions.answerApproval}
|
||||
onClarifyAnswer={actions.answerClarify}
|
||||
onSecretSubmit={actions.answerSecret}
|
||||
onSudoSubmit={actions.answerSudo}
|
||||
/>
|
||||
<>
|
||||
<PromptZone
|
||||
cols={composer.cols}
|
||||
onApprovalChoice={actions.answerApproval}
|
||||
onClarifyAnswer={actions.answerClarify}
|
||||
onSecretSubmit={actions.answerSecret}
|
||||
onSudoSubmit={actions.answerSudo}
|
||||
/>
|
||||
|
||||
<ComposerPane actions={actions} composer={composer} status={status} />
|
||||
|
||||
<StatusRulePane at="bottom" composer={composer} status={status} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{!overlay.agents && <ComposerPane actions={actions} composer={composer} status={status} />}
|
||||
|
||||
{!overlay.agents && <StatusRulePane at="bottom" composer={composer} status={status} />}
|
||||
</Box>
|
||||
</AlternateScreen>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -167,11 +167,8 @@ export function lineNav(s: string, p: number, dir: -1 | 1): null | number {
|
|||
return snapPos(s, Math.min(nextBreak + 1 + col, lineEnd))
|
||||
}
|
||||
|
||||
// Cursor layout mirrors `wrap-ansi(text, cols, { wordWrap: false, hard: true })`
|
||||
// which is what `<Text wrap="wrap-char">` ends up feeding to the renderer.
|
||||
// Char-granularity wrap keeps wrap boundaries deterministic as the user
|
||||
// types — word-wrap's whitespace-preferring reshuffle causes the cursor
|
||||
// to flicker as each keystroke moves a word across a line break (blitz #9).
|
||||
// mirrors wrap-ansi(..., { wordWrap: false, hard: true }) so the declared
|
||||
// cursor lines up with what <Text wrap="wrap-char"> actually renders
|
||||
export function cursorLayout(value: string, cursor: number, cols: number) {
|
||||
const pos = Math.max(0, Math.min(cursor, value.length))
|
||||
const w = Math.max(1, cols)
|
||||
|
|
@ -205,11 +202,7 @@ export function cursorLayout(value: string, cursor: number, cols: number) {
|
|||
col += sw
|
||||
}
|
||||
|
||||
// The cursor renders as an inverted cell AFTER the character at `pos`
|
||||
// (or as a standalone trailing cell when `pos === value.length`). If
|
||||
// col has reached the wrap column, that cell overflows to the next row
|
||||
// — match wrap-ansi's behavior so the declared cursor doesn't sit past
|
||||
// the visual edge.
|
||||
// trailing cursor-cell overflows to the next row at the wrap column
|
||||
if (col >= w) {
|
||||
line++
|
||||
col = 0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue