feat: colored permission mode indicators on separator lines

Each permission mode has a distinct color:
- bypass: green, default: blue, plan: teal
- accept_edits: purple, auto: peach, deny: red

Top separator line shows mode label on right side in mode color.
Both separator lines (above/below input) colored to match.
Shift+Tab cycling visually changes the line colors.
This commit is contained in:
2026-04-03 16:23:07 +02:00
parent 603e67a77e
commit 2e034c4347
2 changed files with 63 additions and 7 deletions

View File

@@ -336,7 +336,7 @@ func (m Model) View() tea.View {
status := m.renderStatus()
input := m.renderInput()
sepLine := sLine.Width(m.width).Render(strings.Repeat("─", m.width))
topLine, bottomLine := m.renderSeparators()
// Fixed: status bar + separator + input + separator = bottom area
statusH := lipgloss.Height(status)
@@ -347,9 +347,9 @@ func (m Model) View() tea.View {
v := tea.NewView(lipgloss.JoinVertical(lipgloss.Left,
chat,
sepLine,
topLine,
input,
sepLine,
bottomLine,
status,
))
v.MouseMode = tea.MouseModeCellMotion
@@ -493,6 +493,40 @@ func (m Model) renderMessage(msg chatMessage) []string {
return lines
}
func (m Model) renderSeparators() (string, string) {
// Get mode color
lineColor := cSurface // default dim
modeLabel := ""
if m.config.Permissions != nil {
mode := m.config.Permissions.Mode()
lineColor = ModeColor(mode)
modeLabel = string(mode)
}
lineStyle := lipgloss.NewStyle().Foreground(lineColor)
labelStyle := lipgloss.NewStyle().Foreground(lineColor).Bold(true)
// Top line: ─── with mode label on right ─── bypass ───
label := " " + modeLabel + " "
labelW := lipgloss.Width(labelStyle.Render(label))
lineW := m.width - labelW
if lineW < 4 {
lineW = 4
}
leftW := lineW - 2
rightW := 2
topLine := lineStyle.Render(strings.Repeat("─", leftW)) +
labelStyle.Render(label) +
lineStyle.Render(strings.Repeat("─", rightW))
// Bottom line: plain colored line
bottomLine := lineStyle.Render(strings.Repeat("─", m.width))
return topLine, bottomLine
}
func (m Model) renderInput() string {
return " " + m.input.View()
}

View File

@@ -1,6 +1,11 @@
package tui
import "charm.land/lipgloss/v2"
import (
"image/color"
"charm.land/lipgloss/v2"
"somegit.dev/Owlibou/gnoma/internal/permission"
)
// Color palette — catppuccin mocha inspired
var (
@@ -9,6 +14,8 @@ var (
cGreen = lipgloss.Color("#A6E3A1") // green
cRed = lipgloss.Color("#F38BA8") // red
cYellow = lipgloss.Color("#F9E2AF") // yellow
cPeach = lipgloss.Color("#FAB387") // peach
cTeal = lipgloss.Color("#94E2D5") // teal
cText = lipgloss.Color("#CDD6F4") // text
cSubtext = lipgloss.Color("#A6ADC8") // subtext0
cOverlay = lipgloss.Color("#6C7086") // overlay0
@@ -17,6 +24,24 @@ var (
cMantle = lipgloss.Color("#181825") // mantle
)
// Permission mode colors — each mode has a distinct color
var modeColors = map[permission.Mode]color.Color{
permission.ModeBypass: cGreen, // green = all allowed
permission.ModeDefault: cBlue, // blue = prompting
permission.ModePlan: cTeal, // teal = read-only
permission.ModeAcceptEdits: cPurple, // purple = edits ok
permission.ModeAuto: cPeach, // peach = smart
permission.ModeDeny: cRed, // red = locked down
}
// ModeColor returns the color for a permission mode.
func ModeColor(mode permission.Mode) color.Color {
if c, ok := modeColors[mode]; ok {
return c
}
return cOverlay
}
// Header
var (
sHeaderBrand = lipgloss.NewStyle().
@@ -79,7 +104,4 @@ var (
sStatusIncognito = lipgloss.NewStyle().
Foreground(cYellow)
sLine = lipgloss.NewStyle().
Foreground(cSurface)
)