From 2e034c4347df41d7a65f580bf93a83a2990d3042 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Fri, 3 Apr 2026 16:23:07 +0200 Subject: [PATCH] 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. --- internal/tui/app.go | 40 +++++++++++++++++++++++++++++++++++++--- internal/tui/theme.go | 30 ++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/internal/tui/app.go b/internal/tui/app.go index 5c2185e..c68b94e 100644 --- a/internal/tui/app.go +++ b/internal/tui/app.go @@ -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() } diff --git a/internal/tui/theme.go b/internal/tui/theme.go index 6552d5e..3a7f055 100644 --- a/internal/tui/theme.go +++ b/internal/tui/theme.go @@ -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) )