fix(tui): approval nav lockups, shell non-diff preview, hint dedup

Three QA-found issues in the approval gate:

- displayState gated the in-session/approval surfaces on hasApproval
  before sessionEntered, so moving the list cursor onto a session with a
  pending gate auto-opened it, and `l` back-to-list couldn't escape
  (the gate re-popped). Require sessionEntered first.
- The band ran every preview through the two-column diff renderer, so a
  shell tool's argv JSON rendered as an identical-column "diff". Detect
  real unified diffs (isUnifiedDiff); render other previews as plain
  text, and drop the ^x fullscreen hint when there's nothing to expand.
- The footer duplicated the band's approve/reject/steer/diff keys. It now
  shows navigation (l back / e events / q quit) instead.
This commit is contained in:
2026-06-03 01:16:04 +04:00
parent 3600ec6897
commit b56f0e88ca
5 changed files with 100 additions and 26 deletions
+9 -13
View File
@@ -210,25 +210,21 @@ func NewModel(client *ws.Client) Model {
}
}
// displayState derives the active screen, matching the Kotlin extension.
// displayState derives the active screen. A session must be *entered*
// (sessionEntered) before its in-session or approval surfaces show — otherwise
// merely moving the list cursor onto a session with a pending gate would yank
// you into the approval, and `l` back-to-list couldn't escape it.
func (m Model) displayState() DisplayState {
if m.selectedID == "" {
if m.selectedID == "" || !m.sessionEntered {
return StateIdle
}
hasApproval := false
if m.approvalDismissed {
return StateInSession
}
if s := m.session(m.selectedID); s != nil && s.Pending != nil {
hasApproval = true
}
switch {
case m.approvalDismissed:
return StateInSession
case hasApproval:
return StateApproval
case m.sessionEntered:
return StateInSession
default:
return StateIdle
}
return StateInSession
}
func (m Model) session(id string) *Session {