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
+28 -12
View File
@@ -58,7 +58,7 @@ func mbg(t Theme, s string, fg lipgloss.Color) string {
func (m Model) approvalBandHeight() int {
rows, rat := 0, 0
if s := m.session(m.selectedID); s != nil && s.Pending != nil {
rows = len(parseUnifiedDiff(s.Pending.Preview))
rows = previewRowCount(s.Pending.Preview)
if n := len(s.Pending.Rationale); n > 0 {
rat = n + 1 // rationale lines + trailing blank
}
@@ -114,8 +114,6 @@ func (m Model) renderApprovalBand(h int) string {
if diffH < 1 {
diffH = 1
}
rows := parseUnifiedDiff(a.Preview)
body := make([]string, 0, innerH)
body = append(body, m.justify(leftHdr, rightHdr, textW, t.P.Bg), "")
for _, r := range a.Rationale {
@@ -125,8 +123,14 @@ func (m Model) renderApprovalBand(h int) string {
if len(a.Rationale) > 0 {
body = append(body, "")
}
var content []string
if isUnifiedDiff(a.Preview) {
content = m.renderSplitDiff(parseUnifiedDiff(a.Preview), textW, diffH, 0)
} else {
content = m.renderPreviewLines(a.Preview, textW, diffH, 0)
}
diffStart := len(body)
body = append(body, m.renderSplitDiff(rows, textW, diffH, 0)...)
body = append(body, content...)
for len(body) < diffStart+diffH {
body = append(body, "")
}
@@ -150,8 +154,11 @@ func (m Model) approvalActions() string {
return lipgloss.NewStyle().Foreground(t.P.Accent).Background(t.P.Bg).Bold(true).Render(k) +
t.span(" "+l, t.P.Faint)
}
parts := []string{hint("a", "approve"), hint("A", "auto"), hint("s", "steer"),
hint("r", "reject"), hint("^x", "fullscreen"), hint("esc", "later")}
parts := []string{hint("a", "approve"), hint("A", "auto"), hint("s", "steer"), hint("r", "reject")}
if s := m.session(m.selectedID); s != nil && s.Pending != nil && isUnifiedDiff(s.Pending.Preview) {
parts = append(parts, hint("^x", "fullscreen"))
}
parts = append(parts, hint("esc", "later"))
return strings.Join(parts, t.span(" ", t.P.Faint))
}
@@ -167,8 +174,7 @@ func (m Model) diffBodyHeight() int {
// diffMaxScroll is the largest scroll offset that still fills the body with
// diff rows, keeping the last page anchored to the bottom of the modal.
func (m Model) diffMaxScroll() int {
rows := parseUnifiedDiff(m.currentDiff())
max := len(rows) - m.diffBodyHeight()
max := previewRowCount(m.currentDiff()) - m.diffBodyHeight()
if max < 0 {
max = 0
}
@@ -180,7 +186,7 @@ func (m Model) diffModal() string {
w := m.modalWidth()
bodyH := m.diffBodyHeight()
diff := m.currentDiff()
rows := parseUnifiedDiff(diff)
isDiff := isUnifiedDiff(diff)
off := m.diffScrollOffset
if off > m.diffMaxScroll() {
@@ -191,12 +197,22 @@ func (m Model) diffModal() string {
}
var b strings.Builder
b.WriteString(m.titleLine("diff"))
title := "preview"
if isDiff {
title = "diff"
}
b.WriteString(m.titleLine(title))
if tgt := diffTarget(diff); tgt != "" {
b.WriteString(mbg(t, " "+tgt, t.P.Dim))
}
b.WriteString(mbg(t, " ("+itoa(len(rows))+" rows)", t.P.Faint) + "\n\n")
for _, ln := range m.renderSplitDiff(rows, w-6, bodyH, off) {
b.WriteString(mbg(t, " ("+itoa(previewRowCount(diff))+" rows)", t.P.Faint) + "\n\n")
var lines []string
if isDiff {
lines = m.renderSplitDiff(parseUnifiedDiff(diff), w-6, bodyH, off)
} else {
lines = m.renderPreviewLines(diff, w-6, bodyH, off)
}
for _, ln := range lines {
b.WriteString(ln + "\n")
}
b.WriteString("\n" + modalHints(t, [][2]string{{"↑↓", "scroll"}, {"^x/esc", "close"}}))