Compare commits

...
Author SHA1 Message Date
KRATOS a1bc1c4af2 fix(tui): stop the permission card clashing on cool themes
The focused permission prompt was the only prompt card in the TUI that used
brand/warning colours for its interior, and both read badly on cool themes such
as dracula:

1. The selected option ("Yes, proceed") was filled with zeroTheme.badge — the
   brand chip meant for short labels (" 0 ", " ASK ", " SPEC REVIEW "), a
   full-brightness accent fill. Every other selectable list in the TUI (model
   picker, provider list, / palette) highlights its selected row with onSel/selBg
   instead. The permission card was the outlier, painting a saturated accent slab
   across the row and skipping the card tint every other line composed onto.

2. The card BODY was tinted with permBg, an amber-family wash intended to signal
   a warning surface. It works on the warm default palette but renders as a
   brown-yellow slab over cool themes, and it made the permission card the only
   prompt whose body carries a fill — ask_user, spec review and plan all use a
   transparent body (lipgloss.NewStyle()).

Fix both to match the rest of the TUI: the selected row uses onSel(ink) like
every other picker, and the body drops its permBg fill for a transparent one.
Warning identity is unchanged — the amber PERMISSION badge and the amber-mixed
border still mark it as a permission gate; only the clashing interior fills go.

TestFocusedPermissionSelectedRowUsesSelectionTintNotBrandChip asserts against
palette entries (not hardcoded hex, so it survives a theme retune): the selected
row carries selBg and not the brand accent, the body carries no permBg wash, and
the PERMISSION badge keeps its amber fill so the card still reads as a warning.
2026-07-20 21:27:15 +05:30
2 changed files with 74 additions and 3 deletions

No files matched your search

+56
View File
@@ -2,6 +2,7 @@ package tui
import (
"context"
"fmt"
"strings"
"testing"
@@ -376,3 +377,58 @@ func TestShiftDownComposerGuard(t *testing.T) {
t.Fatalf("Shift+Down with non-empty composer scrolled offset to %d, want 3 (unchanged)", got)
}
}
// The highlighted permission option must use the selected-row tint, not the
// brand chip. zeroTheme.badge is the accent-filled chip for short labels
// (" 0 ", " ASK ", " SPEC REVIEW "); using it for a whole row painted a
// full-brightness accent slab across a card whose palette is deliberately amber
// (warning), and skipped the card tint every other line composes onto. selBg is
// the tint tuned for a highlighted row against a panel, and onSel is what every
// other selectable list in the TUI uses.
func TestFocusedPermissionSelectedRowUsesSelectionTintNotBrandChip(t *testing.T) {
request := agent.PermissionRequest{ToolName: "exec_command", SideEffect: "shell"}
card, _ := renderFocusedPermissionPrompt(request, 0, 70)
var selected string
for _, line := range strings.Split(card, "\n") {
if strings.Contains(ansiPattern.ReplaceAllString(line, ""), "▸ ") {
selected = line
break
}
}
if selected == "" {
t.Fatal("no highlighted option row rendered")
}
accentBg := backgroundCode(darkPalette.accent)
selBg := backgroundCode(darkPalette.selBg)
if strings.Contains(selected, accentBg) {
t.Errorf("selected row is filled with the brand accent %s (zeroTheme.badge); want the selection tint:\n%q", darkPalette.accent, selected)
}
if !strings.Contains(selected, selBg) {
t.Errorf("selected row should carry the selection tint %s:\n%q", darkPalette.selBg, selected)
}
// The PERMISSION chip keeps its amber fill — the card must still read as a
// warning surface, so this fix must not flatten it.
if !strings.Contains(card, backgroundCode(darkPalette.amber)) {
t.Errorf("PERMISSION badge lost its amber fill:\n%q", card)
}
// The card BODY carries no warm permBg wash any more: it matches the other
// prompt cards (ask_user, spec) whose bodies are transparent. Warning identity
// comes from the amber badge + border, not a full-body tint that clashes on
// cool themes.
if strings.Contains(card, backgroundCode(darkPalette.permBg)) {
t.Errorf("permission card body still tinted with permBg %s; want a transparent body:\n%q", darkPalette.permBg, card)
}
}
// backgroundCode renders the SGR truecolor background sequence for a #rrggbb
// palette entry, so assertions compare against the palette rather than
// hardcoded numbers that drift when a theme is retuned.
func backgroundCode(hex string) string {
var r, g, b int
fmt.Sscanf(hex, "#%02x%02x%02x", &r, &g, &b)
return fmt.Sprintf("48;2;%d;%d;%d", r, g, b)
}
+18 -3
View File
@@ -1112,7 +1112,14 @@ func renderFocusedPermissionPrompt(request agent.PermissionRequest, cursor int,
if name == "" {
name = "tool"
}
fill := zeroTheme.onPerm
// The card body carries no background fill, matching every other prompt card
// (ask_user, spec review, plan) — see the lipgloss.NewStyle() fills below. The
// permission card used to tint its whole body with permBg, an amber-family
// wash that reads as a warm slab on cool themes (e.g. a brown-yellow box over
// dracula's purples) and made it the one outlier. The amber PERMISSION badge
// and the amber-mixed border still carry the "this is a permission gate"
// signal; the body no longer clashes with the surrounding theme.
fill := func(style lipgloss.Style) lipgloss.Style { return style }
top := zeroTheme.permBadge.Render(" PERMISSION ")
@@ -1147,8 +1154,16 @@ func renderFocusedPermissionPrompt(request agent.PermissionRequest, cursor int,
hotkey := fill(zeroTheme.faint).Render(" [" + option.hotkey + "]")
optionLabel := permissionOptionLabel(option, request)
if index == cursor {
// onSel, not badge. zeroTheme.badge is the brand chip (" 0 ", " ASK ",
// " SPEC REVIEW ") — a full-brightness accent fill meant for short
// labels. Using it for a selected ROW painted a bright accent slab
// across the permission card, fighting the card's amber warning palette
// and ignoring the card tint every other line composes onto. selBg is
// the tint tuned for exactly this job ("separates from the panel while
// ink label contrast stays ~9.4:1"), and onSel is what every other
// selectable list in the TUI uses for its highlighted row.
marker := fill(zeroTheme.accent).Render("▸ ")
label := zeroTheme.badge.Render(" " + optionLabel + " ")
label := zeroTheme.onSel(zeroTheme.ink).Bold(true).Render(" " + optionLabel + " ")
lines = append(lines, marker+label+hotkey)
} else {
label := fill(zeroTheme.ink).Render(optionLabel)
@@ -1163,7 +1178,7 @@ func renderFocusedPermissionPrompt(request agent.PermissionRequest, cursor int,
}
lines = append(lines, fill(zeroTheme.faint).Render(footer))
return styledBlockFill(width, lines, zeroTheme.permBorder, zeroTheme.permBg), offsets
return styledBlockFill(width, lines, zeroTheme.permBorder, lipgloss.NewStyle()), offsets
}
func permissionScopeLine(request agent.PermissionRequest, scope string) string {