fix(tui): approval flow + input ergonomics + selection perf

- tui_gateway: route approvals through gateway callback (HERMES_GATEWAY_SESSION/
  HERMES_EXEC_ASK) so dangerous commands emit approval.request instead of
  silently falling through the CLI input() path and auto-denying
- approval UX: dedicated PromptZone between transcript and composer, safer
  defaults (sel=0, numeric quick-picks, no Esc=deny), activity trail line,
  outcome footer under the cost row
- text input: Ctrl+A select-all, real forward Delete, Ctrl+W always consumed
  (fixes Ctrl+Backspace at cursor 0 inserting literal w)
- hermes-ink selection: swap synchronous onRender() for throttled
  scheduleRender() on drag, and only notify React subscribers on presence
  change — no more per-cell paint/subscribe spam
- useConfigSync: silence config.get polling failures instead of surfacing
  'error: timeout: config.get' in the transcript
This commit is contained in:
Brooklyn Nicholson 2026-04-17 10:37:48 -05:00
parent 0219da9626
commit 5b386ced71
15 changed files with 319 additions and 129 deletions

View file

@ -10,7 +10,7 @@ import type { Theme } from '../theme.js'
import type { DetailsMode } from '../types.js'
import { GoodVibesHeart, StatusRule, StickyPromptTracker, TranscriptScrollbar } from './appChrome.js'
import { AppOverlays } from './appOverlays.js'
import { FloatingOverlays, PromptZone } from './appOverlays.js'
import { Banner, Panel, SessionPanel } from './branding.js'
import { MessageLine } from './messageLine.js'
import { QueuedMessages } from './queuedMessages.js'
@ -37,6 +37,7 @@ const StreamingAssistant = memo(function StreamingAssistant({
activity={progress.activity}
busy={busy}
detailsMode={detailsMode}
outcome={progress.outcome}
reasoning={progress.reasoning}
reasoningActive={progress.reasoningActive}
reasoningStreaming={progress.reasoningStreaming}
@ -179,16 +180,12 @@ const ComposerPane = memo(function ComposerPane({
/>
)}
<AppOverlays
<FloatingOverlays
cols={composer.cols}
compIdx={composer.compIdx}
completions={composer.completions}
onApprovalChoice={actions.answerApproval}
onClarifyAnswer={actions.answerClarify}
onModelSelect={actions.onModelSelect}
onPickerSelect={actions.resumeById}
onSecretSubmit={actions.answerSecret}
onSudoSubmit={actions.answerSudo}
pagerPageSize={composer.pagerPageSize}
/>
</Box>
@ -254,6 +251,14 @@ export const AppLayout = memo(function AppLayout({
<TranscriptPane actions={actions} composer={composer} progress={progress} transcript={transcript} />
</Box>
<PromptZone
cols={composer.cols}
onApprovalChoice={actions.answerApproval}
onClarifyAnswer={actions.answerClarify}
onSecretSubmit={actions.answerSecret}
onSudoSubmit={actions.answerSudo}
/>
<ComposerPane actions={actions} composer={composer} status={status} />
</Box>
</AlternateScreen>