Files
Ed Zynda 78570d4188 remove dead code identified by audit
Removes ~600 lines of unreferenced code surfaced by deadcode + manual
audit (none of it reachable from production code paths or test setup):

- internal/models/pool.go: ProviderPool was never wired into kitsetup
  or the agent; the global pool singleton had zero callers.
- internal/ui/debug_logger.go: CLIDebugLogger was unreachable; debug
  routing goes through internal/tools/buffered_logger.go instead.
- internal/ui/tool_approval_input.go: tea.Model never instantiated;
  approvals are handled inline in model.go.
- internal/ui/cli.go: DisplayAssistantMessage / DisplayCancellation /
  GetDebugLogger had zero callers (the *WithModel variant is what
  event_handler.go uses).
- internal/ui/style/enhanced.go: Style{Card,Header,Subheader,Muted,
  Success,Error,Warning,Info} + Create{Separator,ProgressBar} — none
  used. CreateBadge stays (used by model.go).
- internal/ui/style/themes.go: RefreshThemeRegistry — never called.
- internal/ui/block_renderer.go: With{FullWidth,MarginTop,Padding{Left,
  Right},Background,Foreground,Width} — option helpers nobody calls.
- internal/ui/render/blocks.go: UserBlock, ToolBlock — replaced by
  inline rendering elsewhere; the test for UserBlock was rewritten to
  directly exercise HighlightFileTokens (which is what the test really
  cared about).
- internal/ui/commands/commands.go: GetAllCommandNames — no callers.
- internal/ui/message_items.go: NewTextMessageItem,
  NewSystemMessageItem + the entire SystemMessageItem type — model.go
  uses NewStyledMessageItem instead.
- internal/prompts/loader.go: Deduplicate — the loader does dedup
  internally; standalone helper was unused.
- internal/models/cache_options.go: mergeProviderOptions + its
  test-only consumer.
- internal/extensions/installer.go: Installer.GetInstalledPackages —
  intended for a 'kit ext list' command that was never built.
- internal/extensions/manifest.go: saveManifestToScope,
  saveManifestToPath, GetGlobalManifest, GetProjectManifest,
  addEntryToManifest, removeEntryFromManifest — package-level
  duplicates of *Installer methods. Tests rewritten to exercise the
  live Installer methods instead, which fixes a latent path-resolution
  inconsistency between manifestPathForScope and Installer.manifestPath
  (the former hard-coded paths, the latter respects projectGitRoot).
- internal/extensions/subagent.go: SpawnSubagent + helpers
  (generateSubagentID, findKitBinary, subagentJSONOutput). The
  subprocess-spawn implementation is unreachable; production code
  routes through kit.Kit.Subagent (in-process). Types
  (SubagentConfig/Result/Handle/etc.) and the SubagentHandle methods
  remain because they are exposed to extensions via Yaegi symbols and
  the Context.SpawnSubagent field.
- cmd/root.go: LoadConfigWithEnvSubstitution — one-line wrapper around
  kit.LoadConfigWithEnvSubstitution with zero callers.

go test -race ./... passes.
2026-05-07 12:20:08 +03:00

291 lines
11 KiB
Go

package style
import (
"fmt"
"image/color"
"os"
"strings"
"charm.land/lipgloss/v2"
)
// Enhanced styling utilities and theme definitions
// isDarkBg caches the terminal background detection result at package init.
var isDarkBg = lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
// AdaptiveColor picks between a light-mode and dark-mode hex color string
// based on the detected terminal background. This replaces the old
// lipgloss.AdaptiveColor{Light: ..., Dark: ...} pattern from v1.
func AdaptiveColor(light, dark string) color.Color {
if isDarkBg {
return lipgloss.Color(dark)
}
return lipgloss.Color(light)
}
// Global theme instance
var currentTheme = DefaultTheme()
// GetTheme returns the currently active UI theme. The theme controls all color
// and styling decisions throughout the application's interface.
func GetTheme() Theme {
return currentTheme
}
// SetTheme updates the global UI theme, affecting all subsequent rendering
// operations. This allows runtime theme switching for different visual preferences.
// It also invalidates the markdownTypographyCache so the next call to
// GetMarkdownTypography picks up the new theme.
func SetTheme(theme Theme) {
currentTheme = theme
markdownTypographyCache = nil // invalidate cached renderer; colors may have changed
styleCache = nil // invalidate cached styles; colors may have changed
}
// CachedStyles holds pre-built lipgloss styles that are reused across
// render frames. Invalidated by SetTheme, lazily rebuilt on next access.
// Only accessed from BubbleTea's single-threaded Update/View cycle.
type CachedStyles struct {
// render/blocks.go
FileTokenAccent lipgloss.Style // Foreground(Accent).Bold(true)
Muted lipgloss.Style // Foreground(Muted)
VeryMuted lipgloss.Style // Foreground(VeryMuted)
Accent lipgloss.Style // Foreground(Accent)
MarginBottom1 lipgloss.Style // MarginBottom(1)
// stream.go - spinner phases
SpinnerBright lipgloss.Style // Foreground(Primary)
SpinnerMed lipgloss.Style // Foreground(Muted)
SpinnerDim lipgloss.Style // Foreground(VeryMuted)
SpinnerOff lipgloss.Style // Foreground(MutedBorder)
// message_items.go - bash output
BashHeader lipgloss.Style // Foreground(Muted).Italic(true)
BashStderr lipgloss.Style // Foreground(Error)
// render/blocks.go - tool block
ToolSuccess lipgloss.Style // Foreground(Success)
ToolError lipgloss.Style // Foreground(Error)
ToolInfo lipgloss.Style // Foreground(Info).Bold(true)
ToolMuted lipgloss.Style // Foreground(Muted)
// common
ErrorFg lipgloss.Style // Foreground(Error)
TextBold lipgloss.Style // Foreground(Text).Bold(true)
}
var styleCache *CachedStyles
// GetCachedStyles returns the pre-built style cache, creating it lazily
// from the current theme. Invalidated by SetTheme.
func GetCachedStyles() *CachedStyles {
if styleCache != nil {
return styleCache
}
theme := GetTheme()
styleCache = &CachedStyles{
FileTokenAccent: lipgloss.NewStyle().Foreground(theme.Accent).Bold(true),
Muted: lipgloss.NewStyle().Foreground(theme.Muted),
VeryMuted: lipgloss.NewStyle().Foreground(theme.VeryMuted),
Accent: lipgloss.NewStyle().Foreground(theme.Accent),
MarginBottom1: lipgloss.NewStyle().MarginBottom(1),
SpinnerBright: lipgloss.NewStyle().Foreground(theme.Primary),
SpinnerMed: lipgloss.NewStyle().Foreground(theme.Muted),
SpinnerDim: lipgloss.NewStyle().Foreground(theme.VeryMuted),
SpinnerOff: lipgloss.NewStyle().Foreground(theme.MutedBorder),
BashHeader: lipgloss.NewStyle().Foreground(theme.Muted).Italic(true),
BashStderr: lipgloss.NewStyle().Foreground(theme.Error),
ToolSuccess: lipgloss.NewStyle().Foreground(theme.Success),
ToolError: lipgloss.NewStyle().Foreground(theme.Error),
ToolInfo: lipgloss.NewStyle().Foreground(theme.Info).Bold(true),
ToolMuted: lipgloss.NewStyle().Foreground(theme.Muted),
ErrorFg: lipgloss.NewStyle().Foreground(theme.Error),
TextBold: lipgloss.NewStyle().Foreground(theme.Text).Bold(true),
}
return styleCache
}
// MarkdownThemeColors defines colors for markdown rendering and syntax highlighting.
type MarkdownThemeColors struct {
Text color.Color
Muted color.Color
Heading color.Color
Emph color.Color
Strong color.Color
Link color.Color
Code color.Color
Error color.Color
Keyword color.Color
String color.Color
Number color.Color
Comment color.Color
}
// Theme defines a comprehensive color scheme for the application's UI, supporting
// both light and dark terminal modes through adaptive colors. Inspired by the
// Knight Rider KITT aesthetic — scanner reds, amber dashboard glows, and dark
// cockpit tones.
type Theme struct {
Primary color.Color
Secondary color.Color
Success color.Color
Warning color.Color
Error color.Color
Info color.Color
Text color.Color
Muted color.Color
VeryMuted color.Color
Background color.Color
Border color.Color
MutedBorder color.Color
System color.Color
Tool color.Color
Accent color.Color
Highlight color.Color
// Diff block backgrounds
DiffInsertBg color.Color // Green-tinted bg for added lines
DiffDeleteBg color.Color // Red-tinted bg for removed lines
DiffEqualBg color.Color // Neutral bg for context lines
DiffMissingBg color.Color // Empty-cell bg when sides are uneven
// Code/output block backgrounds
CodeBg color.Color // Background for code blocks (Read tool)
GutterBg color.Color // Line-number gutter background
WriteBg color.Color // Green-tinted bg for Write tool content
// Markdown rendering and syntax highlighting colors
Markdown MarkdownThemeColors
}
// DefaultTheme creates and returns the default KIT theme inspired by the
// Knight Rider KITT aesthetic — scanner reds, amber dashboard glows, and a
// dark cockpit. No blues or bright greens; everything stays in the warm
// red/amber/gray family of KITT's instrument panel.
func DefaultTheme() Theme {
return Theme{
Primary: AdaptiveColor("#CC1100", "#FF2200"), // KITT scanner red
Secondary: AdaptiveColor("#CC6600", "#FF8800"), // Amber dashboard glow
Success: AdaptiveColor("#998800", "#CCAA00"), // Warm gold — system OK
Warning: AdaptiveColor("#CC8800", "#FFB800"), // Amber caution light
Error: AdaptiveColor("#CC0000", "#FF3333"), // Alert red
Info: AdaptiveColor("#BB6600", "#DD8833"), // Warm amber readout
Text: AdaptiveColor("#1A1A1A", "#E0E0E0"), // Console text
Muted: AdaptiveColor("#707070", "#808080"), // Dimmed readout
VeryMuted: AdaptiveColor("#A0A0A0", "#505050"), // Inactive element
Background: AdaptiveColor("#F0F0F0", "#0D0D0D"), // Cockpit interior
Border: AdaptiveColor("#B0B0B0", "#3A3A3A"), // Panel edge
MutedBorder: AdaptiveColor("#D0D0D0", "#222222"), // Subtle divider
System: AdaptiveColor("#CC6600", "#FF8800"), // Amber system status
Tool: AdaptiveColor("#CC6600", "#FF8800"), // Amber instrument
Accent: AdaptiveColor("#DD2222", "#FF4444"), // Secondary scanner glow
Highlight: AdaptiveColor("#FFF0F0", "#1A1010"), // Red-tinted mantle
// Diff backgrounds
DiffInsertBg: AdaptiveColor("#F0E8D0", "#2A2410"), // Warm amber tint (added)
DiffDeleteBg: AdaptiveColor("#F5D5D5", "#2E1A1A"), // Red tint (removed)
DiffEqualBg: AdaptiveColor("#E8E8E8", "#161616"), // Neutral
DiffMissingBg: AdaptiveColor("#E0E0E0", "#111111"), // Darker neutral
// Code & output backgrounds
CodeBg: AdaptiveColor("#E8E8E8", "#161616"), // Matches DiffEqualBg
GutterBg: AdaptiveColor("#E0E0E0", "#111111"), // Slightly darker
WriteBg: AdaptiveColor("#F0E8D0", "#2A2410"), // Warm amber tint
// Markdown & syntax highlighting — all warm tones
Markdown: MarkdownThemeColors{
Text: AdaptiveColor("#1A1A1A", "#E0E0E0"), // Console text
Muted: AdaptiveColor("#707070", "#808080"), // Dimmed readout
Heading: AdaptiveColor("#CC1100", "#FF4444"), // Scanner red accent
Emph: AdaptiveColor("#CC8800", "#FFB800"), // Amber emphasis
Strong: AdaptiveColor("#1A1A1A", "#E0E0E0"), // Bright text
Link: AdaptiveColor("#CC4400", "#FF7744"), // Warm orange link
Code: AdaptiveColor("#333333", "#CCCCCC"), // Inline code
Error: AdaptiveColor("#CC0000", "#FF3333"), // Alert red
Keyword: AdaptiveColor("#CC3300", "#FF6644"), // Orange-red keyword
String: AdaptiveColor("#BB7700", "#DDAA33"), // Amber string
Number: AdaptiveColor("#CC8800", "#FFB800"), // Amber number
Comment: AdaptiveColor("#909090", "#606060"), // Dark gray comment
},
}
}
// IsDarkBackground returns the cached terminal background detection result.
func IsDarkBackground() bool {
return isDarkBg
}
// CreateBadge generates a styled badge or label with inverted colors (text on
// colored background) for highlighting important tags, statuses, or categories.
func CreateBadge(text string, c color.Color) string {
return lipgloss.NewStyle().
Foreground(AdaptiveColor("#FFFFFF", "#000000")).
Background(c).
Padding(0, 1).
Bold(true).
Render(text)
}
// interpolateColor blends between two colors based on position (0.0 to 1.0)
// using linear RGB channel interpolation.
func interpolateColor(a, b color.Color, pos float64) color.Color {
r1, g1, b1, _ := a.RGBA()
r2, g2, b2, _ := b.RGBA()
r := uint8(float64(r1>>8)*(1-pos) + float64(r2>>8)*pos)
g := uint8(float64(g1>>8)*(1-pos) + float64(g2>>8)*pos)
bl := uint8(float64(b1>>8)*(1-pos) + float64(b2>>8)*pos)
return lipgloss.Color(fmt.Sprintf("#%02x%02x%02x", r, g, bl))
}
// ApplyGradient applies a color gradient from colorA to colorB across the text.
// Uses ~8 color stops for performance rather than per-character coloring.
func ApplyGradient(text string, colorA, colorB color.Color) string {
runes := []rune(text)
if len(runes) == 0 {
return text
}
const maxStops = 8
segmentSize := max(len(runes)/maxStops, 1)
var result strings.Builder
for i := 0; i < len(runes); i += segmentSize {
end := min(i+segmentSize, len(runes))
pos := float64(i) / float64(len(runes))
c := interpolateColor(colorA, colorB, pos)
style := lipgloss.NewStyle().Foreground(c)
result.WriteString(style.Render(string(runes[i:end])))
}
return result.String()
}
// KitBanner returns the KIT ASCII art title with KITT scanner lights,
// rendered with a KITT red gradient.
func KitBanner() string {
kittDark := lipgloss.Color("#8B0000")
kittBright := lipgloss.Color("#FF2200")
lines := []string{
" ██╗ ██╗ ██╗ ████████╗",
" ██║ ██╔╝ ██║ ╚══██╔══╝",
" █████╔╝ ██║ ██║",
" ██╔═██╗ ██║ ██║",
" ██║ ██╗ ██║ ██║",
" ╚═╝ ╚═╝ ╚═╝ ╚═╝",
"░░ ░░ ░░ ▒▒ ▒▒ ▓▓ ▓▓ ████ ▓▓ ▓▓ ▒▒ ▒▒ ░░ ░░ ░░",
}
var result strings.Builder
for i, line := range lines {
if i > 0 {
result.WriteString("\n")
}
result.WriteString(ApplyGradient(line, kittDark, kittBright))
}
return result.String()
}