Parcourir la source

feat(tui): custom themes

adamdottv il y a 8 mois
Parent
commit
4f7e4a9436

+ 12 - 0
packages/tui/internal/app/app.go

@@ -60,6 +60,9 @@ func New(
 	if err != nil {
 		return nil, err
 	}
+	if configResponse.StatusCode() != 200 || configResponse.JSON200 == nil {
+		return nil, fmt.Errorf("failed to get config: %d", configResponse.StatusCode())
+	}
 	configInfo := configResponse.JSON200
 	if configInfo.Keybinds == nil {
 		leader := "ctrl+x"
@@ -85,6 +88,15 @@ func New(
 		appState.Model = strings.Join(splits[1:], "/")
 	}
 
+	// Load themes from all directories
+	if err := theme.LoadThemesFromDirectories(
+		appInfo.Path.Config,
+		appInfo.Path.Root,
+		appInfo.Path.Cwd,
+	); err != nil {
+		slog.Warn("Failed to load themes from directories", "error", err)
+	}
+
 	if appState.Theme != "" {
 		theme.SetTheme(appState.Theme)
 	}

+ 110 - 0
packages/tui/internal/theme/README.md

@@ -0,0 +1,110 @@
+# OpenCode Theme System
+
+OpenCode supports a flexible JSON-based theme system that allows users to create and customize themes easily.
+
+## Theme Loading Hierarchy
+
+Themes are loaded from multiple directories in the following order (later directories override earlier ones):
+
+1. **Built-in themes** - Embedded in the binary
+2. **User config directory** - `~/.config/opencode/themes/*.json` (or `$XDG_CONFIG_HOME/opencode/themes/*.json`)
+3. **Project root directory** - `<project-root>/.opencode/themes/*.json`
+4. **Current working directory** - `./.opencode/themes/*.json`
+
+If multiple directories contain a theme with the same name, the theme from the directory with higher priority will be used.
+
+## Creating a Custom Theme
+
+To create a custom theme, create a JSON file in one of the theme directories:
+
+```bash
+# For user-wide themes
+mkdir -p ~/.config/opencode/themes
+vim ~/.config/opencode/themes/my-theme.json
+
+# For project-specific themes
+mkdir -p .opencode/themes
+vim .opencode/themes/my-theme.json
+```
+
+## Theme JSON Format
+
+Themes use a flexible JSON format with support for:
+
+- **Hex colors**: `"#ffffff"`
+- **ANSI colors**: `3` (0-255)
+- **Color references**: `"primary"` or custom definitions
+- **Dark/light variants**: `{"dark": "#000", "light": "#fff"}`
+
+### Example Theme
+
+```json
+{
+  "$schema": "../theme.schema.json",
+  "defs": {
+    "brandColor": "#ff6600",
+    "darkBg": "#1a1a1a",
+    "lightBg": "#ffffff"
+  },
+  "theme": {
+    "primary": "brandColor",
+    "secondary": {
+      "dark": "#0066ff",
+      "light": "#0044cc"
+    },
+    "accent": 208,
+    "text": {
+      "dark": "#ffffff",
+      "light": "#000000"
+    },
+    "background": {
+      "dark": "darkBg",
+      "light": "lightBg"
+    },
+    "border": {
+      "dark": 8,
+      "light": 7
+    },
+    "borderActive": "primary"
+  }
+}
+```
+
+### Color Definitions
+
+The `defs` section (optional) allows you to define reusable colors that can be referenced in the theme.
+
+### Required Theme Colors
+
+At minimum, a theme must define:
+- `primary`
+- `secondary`
+- `accent`
+- `text`
+- `textMuted`
+- `background`
+
+### All Available Theme Colors
+
+- **Base colors**: `primary`, `secondary`, `accent`
+- **Status colors**: `error`, `warning`, `success`, `info`
+- **Text colors**: `text`, `textMuted`
+- **Background colors**: `background`, `backgroundPanel`, `backgroundElement`
+- **Border colors**: `border`, `borderActive`, `borderSubtle`
+- **Diff colors**: `diffAdded`, `diffRemoved`, `diffContext`, etc.
+- **Markdown colors**: `markdownHeading`, `markdownLink`, `markdownCode`, etc.
+- **Syntax colors**: `syntaxKeyword`, `syntaxFunction`, `syntaxString`, etc.
+
+See the JSON schema file for a complete list of available colors.
+
+## Built-in Themes
+
+OpenCode comes with several built-in themes:
+- `opencode` - Default OpenCode theme
+- `tokyonight` - Tokyo Night theme
+- `everforest` - Everforest theme
+- `ayu` - Ayu dark theme
+
+## Using a Theme
+
+To use a theme, set it in your OpenCode configuration or select it from the theme dialog in the TUI.

+ 0 - 276
packages/tui/internal/theme/ayu.go

@@ -1,276 +0,0 @@
-package theme
-
-import (
-	"github.com/charmbracelet/lipgloss/v2"
-	"github.com/charmbracelet/lipgloss/v2/compat"
-)
-
-// AyuTheme implements the Theme interface with Ayu Dark colors.
-// It provides a modern dark theme inspired by the Ayu color scheme.
-type AyuTheme struct {
-	BaseTheme
-}
-
-// NewAyuTheme creates a new instance of the Ayu Dark theme.
-func NewAyuTheme() *AyuTheme {
-	// Ayu Dark color palette
-	// Base background colors
-	darkBg := "#0B0E14"    // App background
-	darkBgAlt := "#0D1017" // Editor background
-	darkLine := "#11151C"  // UI line separators
-	darkPanel := "#0F131A" // UI panel background
-
-	// Text colors
-	darkFg := "#BFBDB6"      // Primary text
-	darkFgMuted := "#565B66" // Muted text
-	darkGutter := "#6C7380"  // Gutter text
-
-	// Syntax highlighting colors
-	darkTag := "#39BAE6"      // Tags and attributes
-	darkFunc := "#FFB454"     // Functions
-	darkEntity := "#59C2FF"   // Entities and variables
-	darkString := "#AAD94C"   // Strings
-	darkRegexp := "#95E6CB"   // Regular expressions
-	darkMarkup := "#F07178"   // Markup elements
-	darkKeyword := "#FF8F40"  // Keywords
-	darkSpecial := "#E6B673"  // Special characters
-	darkComment := "#ACB6BF"  // Comments
-	darkConstant := "#D2A6FF" // Constants
-	darkOperator := "#F29668" // Operators
-
-	// Version control colors
-	darkAdded := "#7FD962"   // Added lines
-	darkRemoved := "#F26D78" // Removed lines
-
-	// Accent colors
-	darkAccent := "#E6B450" // Primary accent
-	darkError := "#D95757"  // Error color
-
-	// Active state colors
-	darkIndentActive := "#6C7380" // Active indent guides
-
-	theme := &AyuTheme{}
-
-	// Base colors
-	theme.PrimaryColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkEntity),
-		Light: lipgloss.Color(darkEntity),
-	}
-	theme.SecondaryColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkConstant),
-		Light: lipgloss.Color(darkConstant),
-	}
-	theme.AccentColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkAccent),
-		Light: lipgloss.Color(darkAccent),
-	}
-
-	// Status colors
-	theme.ErrorColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkError),
-		Light: lipgloss.Color(darkError),
-	}
-	theme.WarningColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkSpecial),
-		Light: lipgloss.Color(darkSpecial),
-	}
-	theme.SuccessColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkAdded),
-		Light: lipgloss.Color(darkAdded),
-	}
-	theme.InfoColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkTag),
-		Light: lipgloss.Color(darkTag),
-	}
-
-	// Text colors
-	theme.TextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkFg),
-		Light: lipgloss.Color(darkFg),
-	}
-	theme.TextMutedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkFgMuted),
-		Light: lipgloss.Color(darkFgMuted),
-	}
-
-	// Background colors
-	theme.BackgroundColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkBg),
-		Light: lipgloss.Color(darkBg),
-	}
-	theme.BackgroundPanelColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkBgAlt),
-		Light: lipgloss.Color(darkBgAlt),
-	}
-	theme.BackgroundElementColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkPanel),
-		Light: lipgloss.Color(darkPanel),
-	}
-
-	// Border colors
-	theme.BorderColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkGutter),
-		Light: lipgloss.Color(darkGutter),
-	}
-	theme.BorderActiveColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkIndentActive),
-		Light: lipgloss.Color(darkIndentActive),
-	}
-	theme.BorderSubtleColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkLine),
-		Light: lipgloss.Color(darkLine),
-	}
-
-	// Diff view colors
-	theme.DiffAddedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkAdded),
-		Light: lipgloss.Color(darkAdded),
-	}
-	theme.DiffRemovedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkRemoved),
-		Light: lipgloss.Color(darkRemoved),
-	}
-	theme.DiffContextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkFgMuted),
-		Light: lipgloss.Color(darkFgMuted),
-	}
-	theme.DiffHunkHeaderColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkGutter),
-		Light: lipgloss.Color(darkGutter),
-	}
-	theme.DiffHighlightAddedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkAdded),
-		Light: lipgloss.Color(darkAdded),
-	}
-	theme.DiffHighlightRemovedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkRemoved),
-		Light: lipgloss.Color(darkRemoved),
-	}
-	theme.DiffAddedBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#1a2b1a"),
-		Light: lipgloss.Color("#1a2b1a"),
-	}
-	theme.DiffRemovedBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#2b1a1a"),
-		Light: lipgloss.Color("#2b1a1a"),
-	}
-	theme.DiffContextBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkBgAlt),
-		Light: lipgloss.Color(darkBgAlt),
-	}
-	theme.DiffLineNumberColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkGutter),
-		Light: lipgloss.Color(darkGutter),
-	}
-	theme.DiffAddedLineNumberBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#152b15"),
-		Light: lipgloss.Color("#152b15"),
-	}
-	theme.DiffRemovedLineNumberBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#2b1515"),
-		Light: lipgloss.Color("#2b1515"),
-	}
-
-	// Markdown colors
-	theme.MarkdownTextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkFg),
-		Light: lipgloss.Color(darkFg),
-	}
-	theme.MarkdownHeadingColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkFunc),
-		Light: lipgloss.Color(darkFunc),
-	}
-	theme.MarkdownLinkColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkTag),
-		Light: lipgloss.Color(darkTag),
-	}
-	theme.MarkdownLinkTextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkEntity),
-		Light: lipgloss.Color(darkEntity),
-	}
-	theme.MarkdownCodeColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkString),
-		Light: lipgloss.Color(darkString),
-	}
-	theme.MarkdownBlockQuoteColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkSpecial),
-		Light: lipgloss.Color(darkSpecial),
-	}
-	theme.MarkdownEmphColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkKeyword),
-		Light: lipgloss.Color(darkKeyword),
-	}
-	theme.MarkdownStrongColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkMarkup),
-		Light: lipgloss.Color(darkMarkup),
-	}
-	theme.MarkdownHorizontalRuleColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkGutter),
-		Light: lipgloss.Color(darkGutter),
-	}
-	theme.MarkdownListItemColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkOperator),
-		Light: lipgloss.Color(darkOperator),
-	}
-	theme.MarkdownListEnumerationColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkConstant),
-		Light: lipgloss.Color(darkConstant),
-	}
-	theme.MarkdownImageColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkRegexp),
-		Light: lipgloss.Color(darkRegexp),
-	}
-	theme.MarkdownImageTextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkEntity),
-		Light: lipgloss.Color(darkEntity),
-	}
-	theme.MarkdownCodeBlockColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkString),
-		Light: lipgloss.Color(darkString),
-	}
-
-	// Syntax highlighting colors
-	theme.SyntaxCommentColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkComment),
-		Light: lipgloss.Color(darkComment),
-	}
-	theme.SyntaxKeywordColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkKeyword),
-		Light: lipgloss.Color(darkKeyword),
-	}
-	theme.SyntaxFunctionColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkFunc),
-		Light: lipgloss.Color(darkFunc),
-	}
-	theme.SyntaxVariableColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkEntity),
-		Light: lipgloss.Color(darkEntity),
-	}
-	theme.SyntaxStringColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkString),
-		Light: lipgloss.Color(darkString),
-	}
-	theme.SyntaxNumberColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkConstant),
-		Light: lipgloss.Color(darkConstant),
-	}
-	theme.SyntaxTypeColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkSpecial),
-		Light: lipgloss.Color(darkSpecial),
-	}
-	theme.SyntaxOperatorColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkOperator),
-		Light: lipgloss.Color(darkOperator),
-	}
-	theme.SyntaxPunctuationColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkFg),
-		Light: lipgloss.Color(darkFg),
-	}
-
-	return theme
-}
-
-func init() {
-	// Register the Ayu theme with the theme manager
-	RegisterTheme("ayu", NewAyuTheme())
-}

+ 0 - 298
packages/tui/internal/theme/everforest.go

@@ -1,298 +0,0 @@
-package theme
-
-import (
-	"github.com/charmbracelet/lipgloss/v2"
-	"github.com/charmbracelet/lipgloss/v2/compat"
-)
-
-// EverforestTheme implements the Theme interface with Everforest colors.
-// It provides both dark and light variants with Medium (default) contrast.
-type EverforestTheme struct {
-	BaseTheme
-}
-
-// NewEverforestTheme creates a new instance of the Everforest Medium theme.
-func NewEverforestTheme() *EverforestTheme {
-	// Everforest color palette - Medium variant
-	// Official colors from https://github.com/sainnhe/everforest/wiki
-	// Dark mode colors - using Everforest:Dark Medium contrast palette
-	darkStep1 := "#2d353b"  // App background
-	darkStep2 := "#333c43"  // Subtle background
-	darkStep3 := "#343f44"  // UI element background
-	darkStep4 := "#3d484d"  // Hovered UI element background
-	darkStep5 := "#475258"  // Active/Selected UI element background
-	darkStep6 := "#7a8478"  // Subtle borders and separators
-	darkStep7 := "#859289"  // UI element border and focus rings
-	darkStep8 := "#9da9a0"  // Hovered UI element border
-	darkStep9 := "#a7c080"  // Solid backgrounds
-	darkStep10 := "#83c092" // Hovered solid backgrounds
-	darkStep11 := "#7a8478" // Low-contrast text
-	darkStep12 := "#d3c6aa" // High-contrast text
-
-	// Dark mode accent colors
-	darkPrimary := darkStep9   // Primary uses step 9 (green)
-	darkSecondary := "#7fbbb3" // Secondary (blue)
-	darkAccent := "#d699b6"    // Accent (purple)
-	darkRed := "#e67e80"       // Error (red)
-	darkOrange := "#e69875"    // Warning (orange)
-	darkGreen := "#a7c080"     // Success (green)
-	darkCyan := "#83c092"      // Info (aqua)
-	darkYellow := "#dbbc7f"    // Emphasized text
-
-	// Light mode colors for the Everforest:Light Medium contrast palette
-	lightStep1 := "#fdf6e3"  // App background
-	lightStep2 := "#efebd4"  // Subtle background
-	lightStep3 := "#f4f0d9"  // UI element background
-	lightStep4 := "#efebd4"  // Hovered UI element background
-	lightStep5 := "#e6e2cc"  // Active/Selected UI element background
-	lightStep6 := "#a6b0a0"  // Subtle borders and separators
-	lightStep7 := "#939f91"  // UI element border and focus rings
-	lightStep8 := "#829181"  // Hovered UI element border
-	lightStep9 := "#8da101"  // Solid backgrounds
-	lightStep10 := "#35a77c" // Hovered solid backgrounds
-	lightStep11 := "#a6b0a0" // Low-contrast text
-	lightStep12 := "#5c6a72" // High-contrast text
-
-	// Light mode accent colors
-	lightPrimary := lightStep9  // Primary uses step 9 (green)
-	lightSecondary := "#3a94c5" // Secondary blue
-	lightAccent := "#df69ba"    // Accent purple
-	lightRed := "#f85552"       // Error red
-	lightOrange := "#f57d26"    // Warning orange
-	lightGreen := "#8da101"     // Success green
-	lightCyan := "#35a77c"      // Info aqua
-	lightYellow := "#dfa000"    // Emphasized text
-
-	// Unused variables. These could be used for hover states
-	_ = darkStep4
-	_ = darkStep5
-	_ = darkStep10
-	_ = lightStep4
-	_ = lightStep5
-	_ = lightStep10
-
-	theme := &EverforestTheme{}
-
-	// Base colors
-	theme.PrimaryColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkPrimary),
-		Light: lipgloss.Color(lightPrimary),
-	}
-	theme.SecondaryColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkSecondary),
-		Light: lipgloss.Color(lightSecondary),
-	}
-	theme.AccentColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkAccent),
-		Light: lipgloss.Color(lightAccent),
-	}
-
-	// Status colors
-	theme.ErrorColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkRed),
-		Light: lipgloss.Color(lightRed),
-	}
-	theme.WarningColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkOrange),
-		Light: lipgloss.Color(lightOrange),
-	}
-	theme.SuccessColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkGreen),
-		Light: lipgloss.Color(lightGreen),
-	}
-	theme.InfoColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkCyan),
-		Light: lipgloss.Color(lightCyan),
-	}
-
-	// Text colors
-	theme.TextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep12),
-		Light: lipgloss.Color(lightStep12),
-	}
-	theme.TextMutedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep11),
-		Light: lipgloss.Color(lightStep11),
-	}
-
-	// Background colors
-	theme.BackgroundColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep1),
-		Light: lipgloss.Color(lightStep1),
-	}
-	theme.BackgroundPanelColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep2),
-		Light: lipgloss.Color(lightStep2),
-	}
-	theme.BackgroundElementColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep3),
-		Light: lipgloss.Color(lightStep3),
-	}
-
-	// Border colors
-	theme.BorderColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep7),
-		Light: lipgloss.Color(lightStep7),
-	}
-	theme.BorderActiveColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep8),
-		Light: lipgloss.Color(lightStep8),
-	}
-	theme.BorderSubtleColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep6),
-		Light: lipgloss.Color(lightStep6),
-	}
-
-	// Diff view colors
-	theme.DiffAddedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#A7C080"),
-		Light: lipgloss.Color("#8DA101"),
-	}
-	theme.DiffRemovedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#E67E80"),
-		Light: lipgloss.Color("#F85552"),
-	}
-	theme.DiffContextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#7A8478"),
-		Light: lipgloss.Color("#A6B0A0"),
-	}
-	theme.DiffHunkHeaderColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#859289"),
-		Light: lipgloss.Color("#939F91"),
-	}
-	theme.DiffHighlightAddedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#A7C080"),
-		Light: lipgloss.Color("#8DA101"),
-	}
-	theme.DiffHighlightRemovedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#E67E80"),
-		Light: lipgloss.Color("#F85552"),
-	}
-	theme.DiffAddedBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#425047"),
-		Light: lipgloss.Color("#F0F1D2"),
-	}
-	theme.DiffRemovedBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#543A48"),
-		Light: lipgloss.Color("#FBE3DA"),
-	}
-	theme.DiffContextBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep2),
-		Light: lipgloss.Color(lightStep2),
-	}
-	theme.DiffLineNumberColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep3),
-		Light: lipgloss.Color(lightStep3),
-	}
-	theme.DiffAddedLineNumberBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#3A4A3F"),
-		Light: lipgloss.Color("#E8F2D1"),
-	}
-	theme.DiffRemovedLineNumberBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#4A3A40"),
-		Light: lipgloss.Color("#FBDAD2"),
-	}
-
-	// Markdown colors
-	theme.MarkdownTextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep12),
-		Light: lipgloss.Color(lightStep12),
-	}
-	theme.MarkdownHeadingColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkSecondary),
-		Light: lipgloss.Color(lightSecondary),
-	}
-	theme.MarkdownLinkColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkPrimary),
-		Light: lipgloss.Color(lightPrimary),
-	}
-	theme.MarkdownLinkTextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkCyan),
-		Light: lipgloss.Color(lightCyan),
-	}
-	theme.MarkdownCodeColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkGreen),
-		Light: lipgloss.Color(lightGreen),
-	}
-	theme.MarkdownBlockQuoteColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkYellow),
-		Light: lipgloss.Color(lightYellow),
-	}
-	theme.MarkdownEmphColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkYellow),
-		Light: lipgloss.Color(lightYellow),
-	}
-	theme.MarkdownStrongColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkAccent),
-		Light: lipgloss.Color(lightAccent),
-	}
-	theme.MarkdownHorizontalRuleColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep11),
-		Light: lipgloss.Color(lightStep11),
-	}
-	theme.MarkdownListItemColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkPrimary),
-		Light: lipgloss.Color(lightPrimary),
-	}
-	theme.MarkdownListEnumerationColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkCyan),
-		Light: lipgloss.Color(lightCyan),
-	}
-	theme.MarkdownImageColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkPrimary),
-		Light: lipgloss.Color(lightPrimary),
-	}
-	theme.MarkdownImageTextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkCyan),
-		Light: lipgloss.Color(lightCyan),
-	}
-	theme.MarkdownCodeBlockColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep12),
-		Light: lipgloss.Color(lightStep12),
-	}
-
-	// Syntax highlighting colors
-	theme.SyntaxCommentColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep11),
-		Light: lipgloss.Color(lightStep11),
-	}
-	theme.SyntaxKeywordColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkPrimary),
-		Light: lipgloss.Color(lightPrimary),
-	}
-	theme.SyntaxFunctionColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkSecondary),
-		Light: lipgloss.Color(lightSecondary),
-	}
-	theme.SyntaxVariableColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkRed),
-		Light: lipgloss.Color(lightRed),
-	}
-	theme.SyntaxStringColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkGreen),
-		Light: lipgloss.Color(lightGreen),
-	}
-	theme.SyntaxNumberColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkAccent),
-		Light: lipgloss.Color(lightAccent),
-	}
-	theme.SyntaxTypeColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkYellow),
-		Light: lipgloss.Color(lightYellow),
-	}
-	theme.SyntaxOperatorColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkCyan),
-		Light: lipgloss.Color(lightCyan),
-	}
-	theme.SyntaxPunctuationColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep12),
-		Light: lipgloss.Color(lightStep12),
-	}
-
-	return theme
-}
-
-func init() {
-	// Register the Everforest theme with the theme manager
-	RegisterTheme("everforest", NewEverforestTheme())
-}

+ 394 - 0
packages/tui/internal/theme/loader.go

@@ -0,0 +1,394 @@
+package theme
+
+import (
+	"embed"
+	"encoding/json"
+	"fmt"
+	"image/color"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/charmbracelet/lipgloss/v2"
+	"github.com/charmbracelet/lipgloss/v2/compat"
+)
+
+//go:embed themes/*.json
+var themesFS embed.FS
+
+type JSONTheme struct {
+	Defs  map[string]any `json:"defs,omitempty"`
+	Theme map[string]any `json:"theme"`
+}
+
+type LoadedTheme struct {
+	BaseTheme
+	name string
+}
+
+type colorRef struct {
+	value    any
+	resolved bool
+}
+
+func LoadThemesFromJSON() error {
+	entries, err := themesFS.ReadDir("themes")
+	if err != nil {
+		return fmt.Errorf("failed to read themes directory: %w", err)
+	}
+
+	for _, entry := range entries {
+		if !strings.HasSuffix(entry.Name(), ".json") {
+			continue
+		}
+		themeName := strings.TrimSuffix(entry.Name(), ".json")
+		data, err := themesFS.ReadFile(filepath.Join("themes", entry.Name()))
+		if err != nil {
+			return fmt.Errorf("failed to read theme file %s: %w", entry.Name(), err)
+		}
+		theme, err := parseJSONTheme(themeName, data)
+		if err != nil {
+			return fmt.Errorf("failed to parse theme %s: %w", themeName, err)
+		}
+		RegisterTheme(themeName, theme)
+	}
+
+	return nil
+}
+
+// LoadThemesFromDirectories loads themes from user directories in the correct override order.
+// The hierarchy is (from lowest to highest priority):
+// 1. Built-in themes (embedded)
+// 2. USER_CONFIG/opencode/themes/*.json
+// 3. PROJECT_ROOT/.opencode/themes/*.json
+// 4. CWD/.opencode/themes/*.json
+func LoadThemesFromDirectories(userConfig, projectRoot, cwd string) error {
+	if err := LoadThemesFromJSON(); err != nil {
+		return fmt.Errorf("failed to load built-in themes: %w", err)
+	}
+
+	dirs := []string{
+		filepath.Join(userConfig, "themes"),
+		filepath.Join(projectRoot, ".opencode", "themes"),
+	}
+	if cwd != projectRoot {
+		dirs = append(dirs, filepath.Join(cwd, ".opencode", "themes"))
+	}
+
+	for _, dir := range dirs {
+		if err := loadThemesFromDirectory(dir); err != nil {
+			fmt.Printf("Warning: Failed to load themes from %s: %v\n", dir, err)
+		}
+	}
+
+	return nil
+}
+
+func loadThemesFromDirectory(dir string) error {
+	if _, err := os.Stat(dir); os.IsNotExist(err) {
+		return nil // Directory doesn't exist, which is fine
+	}
+
+	entries, err := os.ReadDir(dir)
+	if err != nil {
+		return fmt.Errorf("failed to read directory: %w", err)
+	}
+
+	for _, entry := range entries {
+		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") {
+			continue
+		}
+
+		themeName := strings.TrimSuffix(entry.Name(), ".json")
+		filePath := filepath.Join(dir, entry.Name())
+
+		data, err := os.ReadFile(filePath)
+		if err != nil {
+			fmt.Printf("Warning: Failed to read theme file %s: %v\n", filePath, err)
+			continue
+		}
+
+		theme, err := parseJSONTheme(themeName, data)
+		if err != nil {
+			fmt.Printf("Warning: Failed to parse theme %s: %v\n", filePath, err)
+			continue
+		}
+
+		RegisterTheme(themeName, theme)
+	}
+
+	return nil
+}
+
+func parseJSONTheme(name string, data []byte) (Theme, error) {
+	var jsonTheme JSONTheme
+	if err := json.Unmarshal(data, &jsonTheme); err != nil {
+		return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
+	}
+	theme := &LoadedTheme{
+		name: name,
+	}
+	colorMap := make(map[string]*colorRef)
+	for key, value := range jsonTheme.Defs {
+		colorMap[key] = &colorRef{value: value, resolved: false}
+	}
+	for key, value := range jsonTheme.Theme {
+		colorMap[key] = &colorRef{value: value, resolved: false}
+	}
+	resolver := &colorResolver{
+		colors:  colorMap,
+		visited: make(map[string]bool),
+	}
+	for key, value := range jsonTheme.Theme {
+		resolved, err := resolver.resolveColor(key, value)
+		if err != nil {
+			return nil, fmt.Errorf("failed to resolve color %s: %w", key, err)
+		}
+		adaptiveColor, err := parseResolvedColor(resolved)
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse color %s: %w", key, err)
+		}
+		if err := setThemeColor(theme, key, adaptiveColor); err != nil {
+			return nil, fmt.Errorf("failed to set color %s: %w", key, err)
+		}
+	}
+
+	return theme, nil
+}
+
+type colorResolver struct {
+	colors  map[string]*colorRef
+	visited map[string]bool
+}
+
+func (r *colorResolver) resolveColor(key string, value any) (any, error) {
+	if r.visited[key] {
+		return nil, fmt.Errorf("circular reference detected for color %s", key)
+	}
+	r.visited[key] = true
+	defer func() { r.visited[key] = false }()
+
+	switch v := value.(type) {
+	case string:
+		if strings.HasPrefix(v, "#") {
+			return v, nil
+		}
+		return r.resolveReference(v)
+	case float64:
+		return v, nil
+	case map[string]any:
+		resolved := make(map[string]any)
+
+		if dark, ok := v["dark"]; ok {
+			resolvedDark, err := r.resolveColorValue(dark)
+			if err != nil {
+				return nil, fmt.Errorf("failed to resolve dark variant: %w", err)
+			}
+			resolved["dark"] = resolvedDark
+		}
+
+		if light, ok := v["light"]; ok {
+			resolvedLight, err := r.resolveColorValue(light)
+			if err != nil {
+				return nil, fmt.Errorf("failed to resolve light variant: %w", err)
+			}
+			resolved["light"] = resolvedLight
+		}
+
+		return resolved, nil
+	default:
+		return nil, fmt.Errorf("invalid color value type: %T", value)
+	}
+}
+
+func (r *colorResolver) resolveColorValue(value any) (any, error) {
+	switch v := value.(type) {
+	case string:
+		if strings.HasPrefix(v, "#") {
+			return v, nil
+		}
+		return r.resolveReference(v)
+	case float64:
+		return v, nil
+	default:
+		return nil, fmt.Errorf("invalid color value type: %T", value)
+	}
+}
+
+func (r *colorResolver) resolveReference(ref string) (any, error) {
+	colorRef, exists := r.colors[ref]
+	if !exists {
+		return nil, fmt.Errorf("color reference '%s' not found", ref)
+	}
+
+	if colorRef.resolved {
+		return colorRef.value, nil
+	}
+
+	resolved, err := r.resolveColor(ref, colorRef.value)
+	if err != nil {
+		return nil, err
+	}
+
+	colorRef.value = resolved
+	colorRef.resolved = true
+
+	return resolved, nil
+}
+
+func parseResolvedColor(value any) (compat.AdaptiveColor, error) {
+	switch v := value.(type) {
+	case string:
+		return compat.AdaptiveColor{
+			Dark:  lipgloss.Color(v),
+			Light: lipgloss.Color(v),
+		}, nil
+	case float64:
+		colorStr := fmt.Sprintf("%d", int(v))
+		return compat.AdaptiveColor{
+			Dark:  lipgloss.Color(colorStr),
+			Light: lipgloss.Color(colorStr),
+		}, nil
+	case map[string]any:
+		dark, darkOk := v["dark"]
+		light, lightOk := v["light"]
+
+		if !darkOk || !lightOk {
+			return compat.AdaptiveColor{}, fmt.Errorf("color object must have both 'dark' and 'light' keys")
+		}
+		darkColor, err := parseColorValue(dark)
+		if err != nil {
+			return compat.AdaptiveColor{}, fmt.Errorf("failed to parse dark color: %w", err)
+		}
+		lightColor, err := parseColorValue(light)
+		if err != nil {
+			return compat.AdaptiveColor{}, fmt.Errorf("failed to parse light color: %w", err)
+		}
+		return compat.AdaptiveColor{
+			Dark:  darkColor,
+			Light: lightColor,
+		}, nil
+	default:
+		return compat.AdaptiveColor{}, fmt.Errorf("invalid resolved color type: %T", value)
+	}
+}
+
+func parseColorValue(value any) (color.Color, error) {
+	switch v := value.(type) {
+	case string:
+		return lipgloss.Color(v), nil
+	case float64:
+		return lipgloss.Color(fmt.Sprintf("%d", int(v))), nil
+	default:
+		return nil, fmt.Errorf("invalid color value type: %T", value)
+	}
+}
+
+func setThemeColor(theme *LoadedTheme, key string, color compat.AdaptiveColor) error {
+	switch key {
+	case "primary":
+		theme.PrimaryColor = color
+	case "secondary":
+		theme.SecondaryColor = color
+	case "accent":
+		theme.AccentColor = color
+	case "error":
+		theme.ErrorColor = color
+	case "warning":
+		theme.WarningColor = color
+	case "success":
+		theme.SuccessColor = color
+	case "info":
+		theme.InfoColor = color
+	case "text":
+		theme.TextColor = color
+	case "textMuted":
+		theme.TextMutedColor = color
+	case "background":
+		theme.BackgroundColor = color
+	case "backgroundPanel":
+		theme.BackgroundPanelColor = color
+	case "backgroundElement":
+		theme.BackgroundElementColor = color
+	case "border":
+		theme.BorderColor = color
+	case "borderActive":
+		theme.BorderActiveColor = color
+	case "borderSubtle":
+		theme.BorderSubtleColor = color
+	case "diffAdded":
+		theme.DiffAddedColor = color
+	case "diffRemoved":
+		theme.DiffRemovedColor = color
+	case "diffContext":
+		theme.DiffContextColor = color
+	case "diffHunkHeader":
+		theme.DiffHunkHeaderColor = color
+	case "diffHighlightAdded":
+		theme.DiffHighlightAddedColor = color
+	case "diffHighlightRemoved":
+		theme.DiffHighlightRemovedColor = color
+	case "diffAddedBg":
+		theme.DiffAddedBgColor = color
+	case "diffRemovedBg":
+		theme.DiffRemovedBgColor = color
+	case "diffContextBg":
+		theme.DiffContextBgColor = color
+	case "diffLineNumber":
+		theme.DiffLineNumberColor = color
+	case "diffAddedLineNumberBg":
+		theme.DiffAddedLineNumberBgColor = color
+	case "diffRemovedLineNumberBg":
+		theme.DiffRemovedLineNumberBgColor = color
+	case "markdownText":
+		theme.MarkdownTextColor = color
+	case "markdownHeading":
+		theme.MarkdownHeadingColor = color
+	case "markdownLink":
+		theme.MarkdownLinkColor = color
+	case "markdownLinkText":
+		theme.MarkdownLinkTextColor = color
+	case "markdownCode":
+		theme.MarkdownCodeColor = color
+	case "markdownBlockQuote":
+		theme.MarkdownBlockQuoteColor = color
+	case "markdownEmph":
+		theme.MarkdownEmphColor = color
+	case "markdownStrong":
+		theme.MarkdownStrongColor = color
+	case "markdownHorizontalRule":
+		theme.MarkdownHorizontalRuleColor = color
+	case "markdownListItem":
+		theme.MarkdownListItemColor = color
+	case "markdownListEnumeration":
+		theme.MarkdownListEnumerationColor = color
+	case "markdownImage":
+		theme.MarkdownImageColor = color
+	case "markdownImageText":
+		theme.MarkdownImageTextColor = color
+	case "markdownCodeBlock":
+		theme.MarkdownCodeBlockColor = color
+	case "syntaxComment":
+		theme.SyntaxCommentColor = color
+	case "syntaxKeyword":
+		theme.SyntaxKeywordColor = color
+	case "syntaxFunction":
+		theme.SyntaxFunctionColor = color
+	case "syntaxVariable":
+		theme.SyntaxVariableColor = color
+	case "syntaxString":
+		theme.SyntaxStringColor = color
+	case "syntaxNumber":
+		theme.SyntaxNumberColor = color
+	case "syntaxType":
+		theme.SyntaxTypeColor = color
+	case "syntaxOperator":
+		theme.SyntaxOperatorColor = color
+	case "syntaxPunctuation":
+		theme.SyntaxPunctuationColor = color
+	default:
+		// Ignore unknown keys for forward compatibility
+		return nil
+	}
+	return nil
+}

+ 136 - 0
packages/tui/internal/theme/loader_test.go

@@ -0,0 +1,136 @@
+package theme
+
+import (
+	"os"
+	"path/filepath"
+	"slices"
+	"testing"
+)
+
+func TestLoadThemesFromJSON(t *testing.T) {
+	// Test loading themes
+	err := LoadThemesFromJSON()
+	if err != nil {
+		t.Fatalf("Failed to load themes: %v", err)
+	}
+
+	// Check that themes were loaded
+	themes := AvailableThemes()
+	if len(themes) == 0 {
+		t.Fatal("No themes were loaded")
+	}
+
+	// Check for expected themes
+	expectedThemes := []string{"tokyonight", "opencode", "everforest", "ayu", "example"}
+	for _, expected := range expectedThemes {
+		found := slices.Contains(themes, expected)
+		if !found {
+			t.Errorf("Expected theme %s not found", expected)
+		}
+	}
+
+	// Test getting a specific theme
+	tokyonight := GetTheme("tokyonight")
+	if tokyonight == nil {
+		t.Fatal("Failed to get tokyonight theme")
+	}
+
+	// Test theme colors
+	primary := tokyonight.Primary()
+	if primary.Dark == nil || primary.Light == nil {
+		t.Error("Primary color not properly set")
+	}
+}
+
+func TestColorReferenceResolution(t *testing.T) {
+	// Test the example theme which uses references
+	example := GetTheme("example")
+	if example == nil {
+		t.Fatal("Failed to get example theme")
+	}
+
+	// Check that brandBlue reference was resolved
+	primary := example.Primary()
+	if primary.Dark == nil || primary.Light == nil {
+		t.Error("Primary color (brandBlue reference) not resolved")
+	}
+
+	// Check that nested reference (borderActive -> primary -> brandBlue) works
+	borderActive := example.BorderActive()
+	if borderActive.Dark == nil || borderActive.Light == nil {
+		t.Error("BorderActive color (nested reference) not resolved")
+	}
+}
+
+func TestLoadThemesFromDirectories(t *testing.T) {
+	// Create temporary directories for testing
+	tempDir := t.TempDir()
+
+	userConfig := filepath.Join(tempDir, "config")
+	projectRoot := filepath.Join(tempDir, "project")
+	cwd := filepath.Join(tempDir, "cwd")
+
+	// Create theme directories
+	os.MkdirAll(filepath.Join(userConfig, "opencode", "themes"), 0755)
+	os.MkdirAll(filepath.Join(projectRoot, ".opencode", "themes"), 0755)
+	os.MkdirAll(filepath.Join(cwd, ".opencode", "themes"), 0755)
+
+	// Create test themes with same name to test override behavior
+	testTheme1 := `{
+		"theme": {
+			"primary": "#111111",
+			"secondary": "#222222",
+			"accent": "#333333",
+			"text": "#ffffff",
+			"textMuted": "#cccccc",
+			"background": "#000000"
+		}
+	}`
+
+	testTheme2 := `{
+		"theme": {
+			"primary": "#444444",
+			"secondary": "#555555",
+			"accent": "#666666",
+			"text": "#ffffff",
+			"textMuted": "#cccccc",
+			"background": "#000000"
+		}
+	}`
+
+	testTheme3 := `{
+		"theme": {
+			"primary": "#777777",
+			"secondary": "#888888",
+			"accent": "#999999",
+			"text": "#ffffff",
+			"textMuted": "#cccccc",
+			"background": "#000000"
+		}
+	}`
+
+	// Write themes to different directories
+	os.WriteFile(filepath.Join(userConfig, "opencode", "themes", "override-test.json"), []byte(testTheme1), 0644)
+	os.WriteFile(filepath.Join(projectRoot, ".opencode", "themes", "override-test.json"), []byte(testTheme2), 0644)
+	os.WriteFile(filepath.Join(cwd, ".opencode", "themes", "override-test.json"), []byte(testTheme3), 0644)
+
+	// Load themes
+	err := LoadThemesFromDirectories(userConfig, projectRoot, cwd)
+	if err != nil {
+		t.Fatalf("Failed to load themes from directories: %v", err)
+	}
+
+	// Check that the theme from CWD (highest priority) won
+	overrideTheme := GetTheme("override-test")
+	if overrideTheme == nil {
+		t.Fatal("Failed to get override-test theme")
+	}
+
+	// The primary color should be from testTheme3 (#777777)
+	primary := overrideTheme.Primary()
+	// We can't directly check the color value, but we can verify it was loaded
+	if primary.Dark == nil || primary.Light == nil {
+		t.Error("Override theme not properly loaded")
+	}
+}
+

+ 1 - 132
packages/tui/internal/theme/manager.go

@@ -2,13 +2,11 @@ package theme
 
 import (
 	"fmt"
-	"log/slog"
 	"slices"
 	"strings"
 	"sync"
 
 	"github.com/alecthomas/chroma/v2/styles"
-	// "github.com/alecthomas/chroma/v2/styles"
 )
 
 // Manager handles theme registration, selection, and retrieval.
@@ -25,9 +23,6 @@ var globalManager = &Manager{
 	currentName: "",
 }
 
-// Default theme instance for custom theme defaulting
-var defaultThemeColors = NewOpenCodeTheme()
-
 // RegisterTheme adds a new theme to the registry.
 // If this is the first theme registered, it becomes the default.
 func RegisterTheme(name string, theme Theme) {
@@ -89,6 +84,7 @@ func AvailableThemes() []string {
 		names = append(names, name)
 	}
 	slices.SortFunc(names, func(a, b string) int {
+		// list system theme first
 		if a == "opencode" {
 			return -1
 		} else if b == "opencode" {
@@ -107,130 +103,3 @@ func GetTheme(name string) Theme {
 
 	return globalManager.themes[name]
 }
-
-// LoadCustomTheme creates a new theme instance based on the custom theme colors
-// defined in the configuration. It uses the default OpenCode theme as a base
-// and overrides colors that are specified in the customTheme map.
-func LoadCustomTheme(customTheme map[string]any) (Theme, error) {
-	// Create a new theme based on the default OpenCode theme
-	theme := NewOpenCodeTheme()
-
-	// Process each color in the custom theme map
-	for key, value := range customTheme {
-		adaptiveColor, err := ParseAdaptiveColor(value)
-		if err != nil {
-			slog.Warn("Invalid color definition in custom theme", "key", key, "error", err)
-			continue // Skip this color but continue processing others
-		}
-
-		// Set the color in the theme based on the key
-		switch strings.ToLower(key) {
-		case "primary":
-			theme.PrimaryColor = adaptiveColor
-		case "secondary":
-			theme.SecondaryColor = adaptiveColor
-		case "accent":
-			theme.AccentColor = adaptiveColor
-		case "error":
-			theme.ErrorColor = adaptiveColor
-		case "warning":
-			theme.WarningColor = adaptiveColor
-		case "success":
-			theme.SuccessColor = adaptiveColor
-		case "info":
-			theme.InfoColor = adaptiveColor
-		case "text":
-			theme.TextColor = adaptiveColor
-		case "textmuted":
-			theme.TextMutedColor = adaptiveColor
-		case "background":
-			theme.BackgroundColor = adaptiveColor
-		case "backgroundsubtle":
-			theme.BackgroundPanelColor = adaptiveColor
-		case "backgroundelement":
-			theme.BackgroundElementColor = adaptiveColor
-		case "border":
-			theme.BorderColor = adaptiveColor
-		case "borderactive":
-			theme.BorderActiveColor = adaptiveColor
-		case "bordersubtle":
-			theme.BorderSubtleColor = adaptiveColor
-		case "diffadded":
-			theme.DiffAddedColor = adaptiveColor
-		case "diffremoved":
-			theme.DiffRemovedColor = adaptiveColor
-		case "diffcontext":
-			theme.DiffContextColor = adaptiveColor
-		case "diffhunkheader":
-			theme.DiffHunkHeaderColor = adaptiveColor
-		case "diffhighlightadded":
-			theme.DiffHighlightAddedColor = adaptiveColor
-		case "diffhighlightremoved":
-			theme.DiffHighlightRemovedColor = adaptiveColor
-		case "diffaddedbg":
-			theme.DiffAddedBgColor = adaptiveColor
-		case "diffremovedbg":
-			theme.DiffRemovedBgColor = adaptiveColor
-		case "diffcontextbg":
-			theme.DiffContextBgColor = adaptiveColor
-		case "difflinenumber":
-			theme.DiffLineNumberColor = adaptiveColor
-		case "diffaddedlinenumberbg":
-			theme.DiffAddedLineNumberBgColor = adaptiveColor
-		case "diffremovedlinenumberbg":
-			theme.DiffRemovedLineNumberBgColor = adaptiveColor
-		case "syntaxcomment":
-			theme.SyntaxCommentColor = adaptiveColor
-		case "syntaxkeyword":
-			theme.SyntaxKeywordColor = adaptiveColor
-		case "syntaxfunction":
-			theme.SyntaxFunctionColor = adaptiveColor
-		case "syntaxvariable":
-			theme.SyntaxVariableColor = adaptiveColor
-		case "syntaxstring":
-			theme.SyntaxStringColor = adaptiveColor
-		case "syntaxnumber":
-			theme.SyntaxNumberColor = adaptiveColor
-		case "syntaxtype":
-			theme.SyntaxTypeColor = adaptiveColor
-		case "syntaxoperator":
-			theme.SyntaxOperatorColor = adaptiveColor
-		case "syntaxpunctuation":
-			theme.SyntaxPunctuationColor = adaptiveColor
-		case "markdowntext":
-			theme.MarkdownTextColor = adaptiveColor
-		case "markdownheading":
-			theme.MarkdownHeadingColor = adaptiveColor
-		case "markdownlink":
-			theme.MarkdownLinkColor = adaptiveColor
-		case "markdownlinktext":
-			theme.MarkdownLinkTextColor = adaptiveColor
-		case "markdowncode":
-			theme.MarkdownCodeColor = adaptiveColor
-		case "markdownblockquote":
-			theme.MarkdownBlockQuoteColor = adaptiveColor
-		case "markdownemph":
-			theme.MarkdownEmphColor = adaptiveColor
-		case "markdownstrong":
-			theme.MarkdownStrongColor = adaptiveColor
-		case "markdownhorizontalrule":
-			theme.MarkdownHorizontalRuleColor = adaptiveColor
-		case "markdownlistitem":
-			theme.MarkdownListItemColor = adaptiveColor
-		case "markdownlistitemenum":
-			theme.MarkdownListEnumerationColor = adaptiveColor
-		case "markdownimage":
-			theme.MarkdownImageColor = adaptiveColor
-		case "markdownimagetext":
-			theme.MarkdownImageTextColor = adaptiveColor
-		case "markdowncodeblock":
-			theme.MarkdownCodeBlockColor = adaptiveColor
-		case "markdownlistenumeration":
-			theme.MarkdownListEnumerationColor = adaptiveColor
-		default:
-			slog.Warn("Unknown color key in custom theme", "key", key)
-		}
-	}
-
-	return theme, nil
-}

+ 0 - 297
packages/tui/internal/theme/opencode.go

@@ -1,297 +0,0 @@
-package theme
-
-import (
-	"github.com/charmbracelet/lipgloss/v2"
-	"github.com/charmbracelet/lipgloss/v2/compat"
-)
-
-// OpenCodeTheme implements the Theme interface with OpenCode brand colors.
-// It provides both dark and light variants.
-type OpenCodeTheme struct {
-	BaseTheme
-}
-
-// NewOpenCodeTheme creates a new instance of the OpenCode theme.
-func NewOpenCodeTheme() *OpenCodeTheme {
-	// OpenCode color palette with Radix-inspired scale progression
-	// Dark mode colors - using a neutral gray scale as base
-	darkStep1 := "#0a0a0a"  // App background
-	darkStep2 := "#141414"  // Subtle background
-	darkStep3 := "#1e1e1e"  // UI element background
-	darkStep4 := "#282828"  // Hovered UI element background
-	darkStep5 := "#323232"  // Active/Selected UI element background
-	darkStep6 := "#3c3c3c"  // Subtle borders and separators
-	darkStep7 := "#484848"  // UI element border and focus rings
-	darkStep8 := "#606060"  // Hovered UI element border
-	darkStep9 := "#fab283"  // Solid backgrounds (primary orange/gold)
-	darkStep10 := "#ffc09f" // Hovered solid backgrounds
-	darkStep11 := "#808080" // Low-contrast text (more muted)
-	darkStep12 := "#eeeeee" // High-contrast text
-
-	// Dark mode accent colors
-	darkPrimary := darkStep9   // Primary uses step 9 (solid background)
-	darkSecondary := "#5c9cf5" // Secondary blue
-	darkAccent := "#9d7cd8"    // Accent purple
-	darkRed := "#e06c75"       // Error red
-	darkOrange := "#f5a742"    // Warning orange
-	darkGreen := "#7fd88f"     // Success green
-	darkCyan := "#56b6c2"      // Info cyan
-	darkYellow := "#e5c07b"    // Emphasized text
-
-	// Light mode colors - using a neutral gray scale as base
-	lightStep1 := "#ffffff"  // App background
-	lightStep2 := "#fafafa"  // Subtle background
-	lightStep3 := "#f5f5f5"  // UI element background
-	lightStep4 := "#ebebeb"  // Hovered UI element background
-	lightStep5 := "#e1e1e1"  // Active/Selected UI element background
-	lightStep6 := "#d4d4d4"  // Subtle borders and separators
-	lightStep7 := "#b8b8b8"  // UI element border and focus rings
-	lightStep8 := "#a0a0a0"  // Hovered UI element border
-	lightStep9 := "#3b7dd8"  // Solid backgrounds (primary blue)
-	lightStep10 := "#2968c3" // Hovered solid backgrounds
-	lightStep11 := "#8a8a8a" // Low-contrast text (more muted)
-	lightStep12 := "#1a1a1a" // High-contrast text
-
-	// Light mode accent colors
-	lightPrimary := lightStep9  // Primary uses step 9 (solid background)
-	lightSecondary := "#7b5bb6" // Secondary purple
-	lightAccent := "#d68c27"    // Accent orange/gold
-	lightRed := "#d1383d"       // Error red
-	lightOrange := "#d68c27"    // Warning orange
-	lightGreen := "#3d9a57"     // Success green
-	lightCyan := "#318795"      // Info cyan
-	lightYellow := "#b0851f"    // Emphasized text
-
-	// Unused variables to avoid compiler errors (these could be used for hover states)
-	_ = darkStep4
-	_ = darkStep5
-	_ = darkStep10
-	_ = lightStep4
-	_ = lightStep5
-	_ = lightStep10
-
-	theme := &OpenCodeTheme{}
-
-	// Base colors
-	theme.PrimaryColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkPrimary),
-		Light: lipgloss.Color(lightPrimary),
-	}
-	theme.SecondaryColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkSecondary),
-		Light: lipgloss.Color(lightSecondary),
-	}
-	theme.AccentColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkAccent),
-		Light: lipgloss.Color(lightAccent),
-	}
-
-	// Status colors
-	theme.ErrorColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkRed),
-		Light: lipgloss.Color(lightRed),
-	}
-	theme.WarningColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkOrange),
-		Light: lipgloss.Color(lightOrange),
-	}
-	theme.SuccessColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkGreen),
-		Light: lipgloss.Color(lightGreen),
-	}
-	theme.InfoColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkCyan),
-		Light: lipgloss.Color(lightCyan),
-	}
-
-	// Text colors
-	theme.TextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep12),
-		Light: lipgloss.Color(lightStep12),
-	}
-	theme.TextMutedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep11),
-		Light: lipgloss.Color(lightStep11),
-	}
-
-	// Background colors
-	theme.BackgroundColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep1),
-		Light: lipgloss.Color(lightStep1),
-	}
-	theme.BackgroundPanelColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep2),
-		Light: lipgloss.Color(lightStep2),
-	}
-	theme.BackgroundElementColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep3),
-		Light: lipgloss.Color(lightStep3),
-	}
-
-	// Border colors
-	theme.BorderColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep7),
-		Light: lipgloss.Color(lightStep7),
-	}
-	theme.BorderActiveColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep8),
-		Light: lipgloss.Color(lightStep8),
-	}
-	theme.BorderSubtleColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep6),
-		Light: lipgloss.Color(lightStep6),
-	}
-
-	// Diff view colors
-	theme.DiffAddedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#478247"),
-		Light: lipgloss.Color("#2E7D32"),
-	}
-	theme.DiffRemovedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#7C4444"),
-		Light: lipgloss.Color("#C62828"),
-	}
-	theme.DiffContextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#a0a0a0"),
-		Light: lipgloss.Color("#757575"),
-	}
-	theme.DiffHunkHeaderColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#a0a0a0"),
-		Light: lipgloss.Color("#757575"),
-	}
-	theme.DiffHighlightAddedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#DAFADA"),
-		Light: lipgloss.Color("#A5D6A7"),
-	}
-	theme.DiffHighlightRemovedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#FADADD"),
-		Light: lipgloss.Color("#EF9A9A"),
-	}
-	theme.DiffAddedBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#303A30"),
-		Light: lipgloss.Color("#E8F5E9"),
-	}
-	theme.DiffRemovedBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#3A3030"),
-		Light: lipgloss.Color("#FFEBEE"),
-	}
-	theme.DiffContextBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep2),
-		Light: lipgloss.Color(lightStep2),
-	}
-	theme.DiffLineNumberColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep3),
-		Light: lipgloss.Color(lightStep3),
-	}
-	theme.DiffAddedLineNumberBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#293229"),
-		Light: lipgloss.Color("#C8E6C9"),
-	}
-	theme.DiffRemovedLineNumberBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#332929"),
-		Light: lipgloss.Color("#FFCDD2"),
-	}
-
-	// Markdown colors
-	theme.MarkdownTextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep12),
-		Light: lipgloss.Color(lightStep12),
-	}
-	theme.MarkdownHeadingColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkSecondary),
-		Light: lipgloss.Color(lightSecondary),
-	}
-	theme.MarkdownLinkColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkPrimary),
-		Light: lipgloss.Color(lightPrimary),
-	}
-	theme.MarkdownLinkTextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkCyan),
-		Light: lipgloss.Color(lightCyan),
-	}
-	theme.MarkdownCodeColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkGreen),
-		Light: lipgloss.Color(lightGreen),
-	}
-	theme.MarkdownBlockQuoteColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkYellow),
-		Light: lipgloss.Color(lightYellow),
-	}
-	theme.MarkdownEmphColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkYellow),
-		Light: lipgloss.Color(lightYellow),
-	}
-	theme.MarkdownStrongColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkAccent),
-		Light: lipgloss.Color(lightAccent),
-	}
-	theme.MarkdownHorizontalRuleColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep11),
-		Light: lipgloss.Color(lightStep11),
-	}
-	theme.MarkdownListItemColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkPrimary),
-		Light: lipgloss.Color(lightPrimary),
-	}
-	theme.MarkdownListEnumerationColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkCyan),
-		Light: lipgloss.Color(lightCyan),
-	}
-	theme.MarkdownImageColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkPrimary),
-		Light: lipgloss.Color(lightPrimary),
-	}
-	theme.MarkdownImageTextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkCyan),
-		Light: lipgloss.Color(lightCyan),
-	}
-	theme.MarkdownCodeBlockColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep12),
-		Light: lipgloss.Color(lightStep12),
-	}
-
-	// Syntax highlighting colors
-	theme.SyntaxCommentColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep11),
-		Light: lipgloss.Color(lightStep11),
-	}
-	theme.SyntaxKeywordColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkPrimary),
-		Light: lipgloss.Color(lightPrimary),
-	}
-	theme.SyntaxFunctionColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkPrimary),
-		Light: lipgloss.Color(lightPrimary),
-	}
-	theme.SyntaxVariableColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkRed),
-		Light: lipgloss.Color(lightRed),
-	}
-	theme.SyntaxStringColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkGreen),
-		Light: lipgloss.Color(lightGreen),
-	}
-	theme.SyntaxNumberColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkAccent),
-		Light: lipgloss.Color(lightAccent),
-	}
-	theme.SyntaxTypeColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkYellow),
-		Light: lipgloss.Color(lightYellow),
-	}
-	theme.SyntaxOperatorColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkCyan),
-		Light: lipgloss.Color(lightCyan),
-	}
-	theme.SyntaxPunctuationColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep12),
-		Light: lipgloss.Color(lightStep12),
-	}
-
-	return theme
-}
-
-func init() {
-	// Register the OpenCode theme with the theme manager
-	RegisterTheme("opencode", NewOpenCodeTheme())
-}

+ 0 - 75
packages/tui/internal/theme/theme.go

@@ -1,10 +1,6 @@
 package theme
 
 import (
-	"fmt"
-	"regexp"
-
-	"github.com/charmbracelet/lipgloss/v2"
 	"github.com/charmbracelet/lipgloss/v2/compat"
 )
 
@@ -215,74 +211,3 @@ func (t *BaseTheme) SyntaxNumber() compat.AdaptiveColor      { return t.SyntaxNu
 func (t *BaseTheme) SyntaxType() compat.AdaptiveColor        { return t.SyntaxTypeColor }
 func (t *BaseTheme) SyntaxOperator() compat.AdaptiveColor    { return t.SyntaxOperatorColor }
 func (t *BaseTheme) SyntaxPunctuation() compat.AdaptiveColor { return t.SyntaxPunctuationColor }
-
-// ParseAdaptiveColor parses a color value from the config file into a compat.AdaptiveColor.
-// It accepts either a string (hex color) or a map with "dark" and "light" keys.
-func ParseAdaptiveColor(value any) (compat.AdaptiveColor, error) {
-	// Regular expression to validate hex color format
-	hexColorRegex := regexp.MustCompile(`^#[0-9a-fA-F]{6}$`)
-
-	// Case 1: String value (same color for both dark and light modes)
-	if hexColor, ok := value.(string); ok {
-		if !hexColorRegex.MatchString(hexColor) {
-			return compat.AdaptiveColor{}, fmt.Errorf("invalid hex color format: %s", hexColor)
-		}
-		return compat.AdaptiveColor{
-			Dark:  lipgloss.Color(hexColor),
-			Light: lipgloss.Color(hexColor),
-		}, nil
-	}
-
-	// Case 2: Int value between 0 and 255
-	if numericVal, ok := value.(float64); ok {
-		intVal := int(numericVal)
-		if intVal < 0 || intVal > 255 {
-			return compat.AdaptiveColor{}, fmt.Errorf("invalid int color value (must be between 0 and 255): %d", intVal)
-		}
-		return compat.AdaptiveColor{
-			Dark:  lipgloss.Color(fmt.Sprintf("%d", intVal)),
-			Light: lipgloss.Color(fmt.Sprintf("%d", intVal)),
-		}, nil
-	}
-
-	// Case 3: Map with dark and light keys
-	if colorMap, ok := value.(map[string]any); ok {
-		darkVal, darkOk := colorMap["dark"]
-		lightVal, lightOk := colorMap["light"]
-
-		if !darkOk || !lightOk {
-			return compat.AdaptiveColor{}, fmt.Errorf("color map must contain both 'dark' and 'light' keys")
-		}
-
-		darkHex, darkIsString := darkVal.(string)
-		lightHex, lightIsString := lightVal.(string)
-
-		if !darkIsString || !lightIsString {
-			darkVal, darkIsNumber := darkVal.(float64)
-			lightVal, lightIsNumber := lightVal.(float64)
-
-			if !darkIsNumber || !lightIsNumber {
-				return compat.AdaptiveColor{}, fmt.Errorf("color map values must be strings or ints")
-			}
-
-			darkInt := int(darkVal)
-			lightInt := int(lightVal)
-
-			return compat.AdaptiveColor{
-				Dark:  lipgloss.Color(fmt.Sprintf("%d", darkInt)),
-				Light: lipgloss.Color(fmt.Sprintf("%d", lightInt)),
-			}, nil
-		}
-
-		if !hexColorRegex.MatchString(darkHex) || !hexColorRegex.MatchString(lightHex) {
-			return compat.AdaptiveColor{}, fmt.Errorf("invalid hex color format")
-		}
-
-		return compat.AdaptiveColor{
-			Dark:  lipgloss.Color(darkHex),
-			Light: lipgloss.Color(lightHex),
-		}, nil
-	}
-
-	return compat.AdaptiveColor{}, fmt.Errorf("color must be either a hex string or an object with dark/light keys")
-}

+ 81 - 0
packages/tui/internal/theme/themes/ayu.json

@@ -0,0 +1,81 @@
+{
+  "$schema": "https://opencode.ai/theme.json",
+  "defs": {
+    "darkBg": "#0B0E14",
+    "darkBgAlt": "#0D1017",
+    "darkLine": "#11151C",
+    "darkPanel": "#0F131A",
+    "darkFg": "#BFBDB6",
+    "darkFgMuted": "#565B66",
+    "darkGutter": "#6C7380",
+    "darkTag": "#39BAE6",
+    "darkFunc": "#FFB454",
+    "darkEntity": "#59C2FF",
+    "darkString": "#AAD94C",
+    "darkRegexp": "#95E6CB",
+    "darkMarkup": "#F07178",
+    "darkKeyword": "#FF8F40",
+    "darkSpecial": "#E6B673",
+    "darkComment": "#ACB6BF",
+    "darkConstant": "#D2A6FF",
+    "darkOperator": "#F29668",
+    "darkAdded": "#7FD962",
+    "darkRemoved": "#F26D78",
+    "darkAccent": "#E6B450",
+    "darkError": "#D95757",
+    "darkIndentActive": "#6C7380"
+  },
+  "theme": {
+    "primary": "darkEntity",
+    "secondary": "darkConstant",
+    "accent": "darkAccent",
+    "error": "darkError",
+    "warning": "darkSpecial",
+    "success": "darkAdded",
+    "info": "darkTag",
+    "text": "darkFg",
+    "textMuted": "darkFgMuted",
+    "background": "darkBg",
+    "backgroundPanel": "darkPanel",
+    "backgroundElement": "darkBgAlt",
+    "border": "darkGutter",
+    "borderActive": "darkIndentActive",
+    "borderSubtle": "darkLine",
+    "diffAdded": "darkAdded",
+    "diffRemoved": "darkRemoved",
+    "diffContext": "darkComment",
+    "diffHunkHeader": "darkComment",
+    "diffHighlightAdded": "darkString",
+    "diffHighlightRemoved": "darkMarkup",
+    "diffAddedBg": "#20303b",
+    "diffRemovedBg": "#37222c",
+    "diffContextBg": "darkPanel",
+    "diffLineNumber": "darkGutter",
+    "diffAddedLineNumberBg": "#1b2b34",
+    "diffRemovedLineNumberBg": "#2d1f26",
+    "markdownText": "darkFg",
+    "markdownHeading": "darkConstant",
+    "markdownLink": "darkEntity",
+    "markdownLinkText": "darkTag",
+    "markdownCode": "darkString",
+    "markdownBlockQuote": "darkSpecial",
+    "markdownEmph": "darkSpecial",
+    "markdownStrong": "darkFunc",
+    "markdownHorizontalRule": "darkFgMuted",
+    "markdownListItem": "darkEntity",
+    "markdownListEnumeration": "darkTag",
+    "markdownImage": "darkEntity",
+    "markdownImageText": "darkTag",
+    "markdownCodeBlock": "darkFg",
+    "syntaxComment": "darkComment",
+    "syntaxKeyword": "darkKeyword",
+    "syntaxFunction": "darkFunc",
+    "syntaxVariable": "darkEntity",
+    "syntaxString": "darkString",
+    "syntaxNumber": "darkConstant",
+    "syntaxType": "darkSpecial",
+    "syntaxOperator": "darkOperator",
+    "syntaxPunctuation": "darkFg"
+  }
+}
+

+ 242 - 0
packages/tui/internal/theme/themes/everforest.json

@@ -0,0 +1,242 @@
+{
+  "$schema": "https://opencode.ai/theme.json",
+  "defs": {
+    "darkStep1": "#2d353b",
+    "darkStep2": "#333c43",
+    "darkStep3": "#343f44",
+    "darkStep4": "#3d484d",
+    "darkStep5": "#475258",
+    "darkStep6": "#7a8478",
+    "darkStep7": "#859289",
+    "darkStep8": "#9da9a0",
+    "darkStep9": "#a7c080",
+    "darkStep10": "#83c092",
+    "darkStep11": "#7a8478",
+    "darkStep12": "#d3c6aa",
+    "darkRed": "#e67e80",
+    "darkOrange": "#e69875",
+    "darkGreen": "#a7c080",
+    "darkCyan": "#83c092",
+    "darkYellow": "#dbbc7f",
+    "lightStep1": "#fdf6e3",
+    "lightStep2": "#efebd4",
+    "lightStep3": "#f4f0d9",
+    "lightStep4": "#efebd4",
+    "lightStep5": "#e6e2cc",
+    "lightStep6": "#a6b0a0",
+    "lightStep7": "#939f91",
+    "lightStep8": "#829181",
+    "lightStep9": "#8da101",
+    "lightStep10": "#35a77c",
+    "lightStep11": "#a6b0a0",
+    "lightStep12": "#5c6a72",
+    "lightRed": "#f85552",
+    "lightOrange": "#f57d26",
+    "lightGreen": "#8da101",
+    "lightCyan": "#35a77c",
+    "lightYellow": "#dfa000"
+  },
+  "theme": {
+    "primary": {
+      "dark": "darkStep9",
+      "light": "lightStep9"
+    },
+    "secondary": {
+      "dark": "#7fbbb3",
+      "light": "#3a94c5"
+    },
+    "accent": {
+      "dark": "#d699b6",
+      "light": "#df69ba"
+    },
+    "error": {
+      "dark": "darkRed",
+      "light": "lightRed"
+    },
+    "warning": {
+      "dark": "darkOrange",
+      "light": "lightOrange"
+    },
+    "success": {
+      "dark": "darkGreen",
+      "light": "lightGreen"
+    },
+    "info": {
+      "dark": "darkCyan",
+      "light": "lightCyan"
+    },
+    "text": {
+      "dark": "darkStep12",
+      "light": "lightStep12"
+    },
+    "textMuted": {
+      "dark": "darkStep11",
+      "light": "lightStep11"
+    },
+    "background": {
+      "dark": "darkStep1",
+      "light": "lightStep1"
+    },
+    "backgroundPanel": {
+      "dark": "darkStep2",
+      "light": "lightStep2"
+    },
+    "backgroundElement": {
+      "dark": "darkStep3",
+      "light": "lightStep3"
+    },
+    "border": {
+      "dark": "darkStep7",
+      "light": "lightStep7"
+    },
+    "borderActive": {
+      "dark": "darkStep8",
+      "light": "lightStep8"
+    },
+    "borderSubtle": {
+      "dark": "darkStep6",
+      "light": "lightStep6"
+    },
+    "diffAdded": {
+      "dark": "#4fd6be",
+      "light": "#1e725c"
+    },
+    "diffRemoved": {
+      "dark": "#c53b53",
+      "light": "#c53b53"
+    },
+    "diffContext": {
+      "dark": "#828bb8",
+      "light": "#7086b5"
+    },
+    "diffHunkHeader": {
+      "dark": "#828bb8",
+      "light": "#7086b5"
+    },
+    "diffHighlightAdded": {
+      "dark": "#b8db87",
+      "light": "#4db380"
+    },
+    "diffHighlightRemoved": {
+      "dark": "#e26a75",
+      "light": "#f52a65"
+    },
+    "diffAddedBg": {
+      "dark": "#20303b",
+      "light": "#d5e5d5"
+    },
+    "diffRemovedBg": {
+      "dark": "#37222c",
+      "light": "#f7d8db"
+    },
+    "diffContextBg": {
+      "dark": "darkStep2",
+      "light": "lightStep2"
+    },
+    "diffLineNumber": {
+      "dark": "darkStep3",
+      "light": "lightStep3"
+    },
+    "diffAddedLineNumberBg": {
+      "dark": "#1b2b34",
+      "light": "#c5d5c5"
+    },
+    "diffRemovedLineNumberBg": {
+      "dark": "#2d1f26",
+      "light": "#e7c8cb"
+    },
+    "markdownText": {
+      "dark": "darkStep12",
+      "light": "lightStep12"
+    },
+    "markdownHeading": {
+      "dark": "#d699b6",
+      "light": "#df69ba"
+    },
+    "markdownLink": {
+      "dark": "darkStep9",
+      "light": "lightStep9"
+    },
+    "markdownLinkText": {
+      "dark": "darkCyan",
+      "light": "lightCyan"
+    },
+    "markdownCode": {
+      "dark": "darkGreen",
+      "light": "lightGreen"
+    },
+    "markdownBlockQuote": {
+      "dark": "darkYellow",
+      "light": "lightYellow"
+    },
+    "markdownEmph": {
+      "dark": "darkYellow",
+      "light": "lightYellow"
+    },
+    "markdownStrong": {
+      "dark": "darkOrange",
+      "light": "lightOrange"
+    },
+    "markdownHorizontalRule": {
+      "dark": "darkStep11",
+      "light": "lightStep11"
+    },
+    "markdownListItem": {
+      "dark": "darkStep9",
+      "light": "lightStep9"
+    },
+    "markdownListEnumeration": {
+      "dark": "darkCyan",
+      "light": "lightCyan"
+    },
+    "markdownImage": {
+      "dark": "darkStep9",
+      "light": "lightStep9"
+    },
+    "markdownImageText": {
+      "dark": "darkCyan",
+      "light": "lightCyan"
+    },
+    "markdownCodeBlock": {
+      "dark": "darkStep12",
+      "light": "lightStep12"
+    },
+    "syntaxComment": {
+      "dark": "darkStep11",
+      "light": "lightStep11"
+    },
+    "syntaxKeyword": {
+      "dark": "#d699b6",
+      "light": "#df69ba"
+    },
+    "syntaxFunction": {
+      "dark": "darkStep9",
+      "light": "lightStep9"
+    },
+    "syntaxVariable": {
+      "dark": "darkRed",
+      "light": "lightRed"
+    },
+    "syntaxString": {
+      "dark": "darkGreen",
+      "light": "lightGreen"
+    },
+    "syntaxNumber": {
+      "dark": "darkOrange",
+      "light": "lightOrange"
+    },
+    "syntaxType": {
+      "dark": "darkYellow",
+      "light": "lightYellow"
+    },
+    "syntaxOperator": {
+      "dark": "darkCyan",
+      "light": "lightCyan"
+    },
+    "syntaxPunctuation": {
+      "dark": "darkStep12",
+      "light": "lightStep12"
+    }
+  }
+}
+

+ 246 - 0
packages/tui/internal/theme/themes/opencode.json

@@ -0,0 +1,246 @@
+{
+  "$schema": "https://opencode.ai/theme.json",
+  "defs": {
+    "darkStep1": "#0a0a0a",
+    "darkStep2": "#141414",
+    "darkStep3": "#1e1e1e",
+    "darkStep4": "#282828",
+    "darkStep5": "#323232",
+    "darkStep6": "#3c3c3c",
+    "darkStep7": "#484848",
+    "darkStep8": "#606060",
+    "darkStep9": "#fab283",
+    "darkStep10": "#ffc09f",
+    "darkStep11": "#808080",
+    "darkStep12": "#eeeeee",
+    "darkSecondary": "#5c9cf5",
+    "darkAccent": "#9d7cd8",
+    "darkRed": "#e06c75",
+    "darkOrange": "#f5a742",
+    "darkGreen": "#7fd88f",
+    "darkCyan": "#56b6c2",
+    "darkYellow": "#e5c07b",
+    "lightStep1": "#ffffff",
+    "lightStep2": "#fafafa",
+    "lightStep3": "#f5f5f5",
+    "lightStep4": "#ebebeb",
+    "lightStep5": "#e1e1e1",
+    "lightStep6": "#d4d4d4",
+    "lightStep7": "#b8b8b8",
+    "lightStep8": "#a0a0a0",
+    "lightStep9": "#3b7dd8",
+    "lightStep10": "#2968c3",
+    "lightStep11": "#8a8a8a",
+    "lightStep12": "#1a1a1a",
+    "lightSecondary": "#7b5bb6",
+    "lightAccent": "#d68c27",
+    "lightRed": "#d1383d",
+    "lightOrange": "#d68c27",
+    "lightGreen": "#3d9a57",
+    "lightCyan": "#318795",
+    "lightYellow": "#b0851f"
+  },
+  "theme": {
+    "primary": {
+      "dark": "darkStep9",
+      "light": "lightStep9"
+    },
+    "secondary": {
+      "dark": "darkSecondary",
+      "light": "lightSecondary"
+    },
+    "accent": {
+      "dark": "darkAccent",
+      "light": "lightAccent"
+    },
+    "error": {
+      "dark": "darkRed",
+      "light": "lightRed"
+    },
+    "warning": {
+      "dark": "darkOrange",
+      "light": "lightOrange"
+    },
+    "success": {
+      "dark": "darkGreen",
+      "light": "lightGreen"
+    },
+    "info": {
+      "dark": "darkCyan",
+      "light": "lightCyan"
+    },
+    "text": {
+      "dark": "darkStep12",
+      "light": "lightStep12"
+    },
+    "textMuted": {
+      "dark": "darkStep11",
+      "light": "lightStep11"
+    },
+    "background": {
+      "dark": "darkStep1",
+      "light": "lightStep1"
+    },
+    "backgroundPanel": {
+      "dark": "darkStep2",
+      "light": "lightStep2"
+    },
+    "backgroundElement": {
+      "dark": "darkStep3",
+      "light": "lightStep3"
+    },
+    "border": {
+      "dark": "darkStep7",
+      "light": "lightStep7"
+    },
+    "borderActive": {
+      "dark": "darkStep8",
+      "light": "lightStep8"
+    },
+    "borderSubtle": {
+      "dark": "darkStep6",
+      "light": "lightStep6"
+    },
+    "diffAdded": {
+      "dark": "#4fd6be",
+      "light": "#1e725c"
+    },
+    "diffRemoved": {
+      "dark": "#c53b53",
+      "light": "#c53b53"
+    },
+    "diffContext": {
+      "dark": "#828bb8",
+      "light": "#7086b5"
+    },
+    "diffHunkHeader": {
+      "dark": "#828bb8",
+      "light": "#7086b5"
+    },
+    "diffHighlightAdded": {
+      "dark": "#b8db87",
+      "light": "#4db380"
+    },
+    "diffHighlightRemoved": {
+      "dark": "#e26a75",
+      "light": "#f52a65"
+    },
+    "diffAddedBg": {
+      "dark": "#20303b",
+      "light": "#d5e5d5"
+    },
+    "diffRemovedBg": {
+      "dark": "#37222c",
+      "light": "#f7d8db"
+    },
+    "diffContextBg": {
+      "dark": "darkStep2",
+      "light": "lightStep2"
+    },
+    "diffLineNumber": {
+      "dark": "darkStep3",
+      "light": "lightStep3"
+    },
+    "diffAddedLineNumberBg": {
+      "dark": "#1b2b34",
+      "light": "#c5d5c5"
+    },
+    "diffRemovedLineNumberBg": {
+      "dark": "#2d1f26",
+      "light": "#e7c8cb"
+    },
+    "markdownText": {
+      "dark": "darkStep12",
+      "light": "lightStep12"
+    },
+    "markdownHeading": {
+      "dark": "darkAccent",
+      "light": "lightAccent"
+    },
+    "markdownLink": {
+      "dark": "darkStep9",
+      "light": "lightStep9"
+    },
+    "markdownLinkText": {
+      "dark": "darkCyan",
+      "light": "lightCyan"
+    },
+    "markdownCode": {
+      "dark": "darkGreen",
+      "light": "lightGreen"
+    },
+    "markdownBlockQuote": {
+      "dark": "darkYellow",
+      "light": "lightYellow"
+    },
+    "markdownEmph": {
+      "dark": "darkYellow",
+      "light": "lightYellow"
+    },
+    "markdownStrong": {
+      "dark": "darkOrange",
+      "light": "lightOrange"
+    },
+    "markdownHorizontalRule": {
+      "dark": "darkStep11",
+      "light": "lightStep11"
+    },
+    "markdownListItem": {
+      "dark": "darkStep9",
+      "light": "lightStep9"
+    },
+    "markdownListEnumeration": {
+      "dark": "darkCyan",
+      "light": "lightCyan"
+    },
+    "markdownImage": {
+      "dark": "darkStep9",
+      "light": "lightStep9"
+    },
+    "markdownImageText": {
+      "dark": "darkCyan",
+      "light": "lightCyan"
+    },
+    "markdownCodeBlock": {
+      "dark": "darkStep12",
+      "light": "lightStep12"
+    },
+    "syntaxComment": {
+      "dark": "darkStep11",
+      "light": "lightStep11"
+    },
+    "syntaxKeyword": {
+      "dark": "darkAccent",
+      "light": "lightAccent"
+    },
+    "syntaxFunction": {
+      "dark": "darkStep9",
+      "light": "lightStep9"
+    },
+    "syntaxVariable": {
+      "dark": "darkRed",
+      "light": "lightRed"
+    },
+    "syntaxString": {
+      "dark": "darkGreen",
+      "light": "lightGreen"
+    },
+    "syntaxNumber": {
+      "dark": "darkOrange",
+      "light": "lightOrange"
+    },
+    "syntaxType": {
+      "dark": "darkYellow",
+      "light": "lightYellow"
+    },
+    "syntaxOperator": {
+      "dark": "darkCyan",
+      "light": "lightCyan"
+    },
+    "syntaxPunctuation": {
+      "dark": "darkStep12",
+      "light": "lightStep12"
+    }
+  }
+}
+

+ 244 - 0
packages/tui/internal/theme/themes/tokyonight.json

@@ -0,0 +1,244 @@
+{
+  "$schema": "https://opencode.ai/theme.json",
+  "defs": {
+    "darkStep1": "#1a1b26",
+    "darkStep2": "#1e2030",
+    "darkStep3": "#222436",
+    "darkStep4": "#292e42",
+    "darkStep5": "#3b4261",
+    "darkStep6": "#545c7e",
+    "darkStep7": "#737aa2",
+    "darkStep8": "#9099b2",
+    "darkStep9": "#82aaff",
+    "darkStep10": "#89b4fa",
+    "darkStep11": "#828bb8",
+    "darkStep12": "#c8d3f5",
+    "darkRed": "#ff757f",
+    "darkOrange": "#ff966c",
+    "darkYellow": "#ffc777",
+    "darkGreen": "#c3e88d",
+    "darkCyan": "#86e1fc",
+    "darkPurple": "#c099ff",
+    "lightStep1": "#e1e2e7",
+    "lightStep2": "#d5d6db",
+    "lightStep3": "#c8c9ce",
+    "lightStep4": "#b9bac1",
+    "lightStep5": "#a8aecb",
+    "lightStep6": "#9699a8",
+    "lightStep7": "#737a8c",
+    "lightStep8": "#5a607d",
+    "lightStep9": "#2e7de9",
+    "lightStep10": "#1a6ce7",
+    "lightStep11": "#8990a3",
+    "lightStep12": "#3760bf",
+    "lightRed": "#f52a65",
+    "lightOrange": "#b15c00",
+    "lightYellow": "#8c6c3e",
+    "lightGreen": "#587539",
+    "lightCyan": "#007197",
+    "lightPurple": "#9854f1"
+  },
+  "theme": {
+    "primary": {
+      "dark": "darkStep9",
+      "light": "lightStep9"
+    },
+    "secondary": {
+      "dark": "darkPurple",
+      "light": "lightPurple"
+    },
+    "accent": {
+      "dark": "darkOrange",
+      "light": "lightOrange"
+    },
+    "error": {
+      "dark": "darkRed",
+      "light": "lightRed"
+    },
+    "warning": {
+      "dark": "darkOrange",
+      "light": "lightOrange"
+    },
+    "success": {
+      "dark": "darkGreen",
+      "light": "lightGreen"
+    },
+    "info": {
+      "dark": "darkStep9",
+      "light": "lightStep9"
+    },
+    "text": {
+      "dark": "darkStep12",
+      "light": "lightStep12"
+    },
+    "textMuted": {
+      "dark": "darkStep11",
+      "light": "lightStep11"
+    },
+    "background": {
+      "dark": "darkStep1",
+      "light": "lightStep1"
+    },
+    "backgroundPanel": {
+      "dark": "darkStep2",
+      "light": "lightStep2"
+    },
+    "backgroundElement": {
+      "dark": "darkStep3",
+      "light": "lightStep3"
+    },
+    "border": {
+      "dark": "darkStep7",
+      "light": "lightStep7"
+    },
+    "borderActive": {
+      "dark": "darkStep8",
+      "light": "lightStep8"
+    },
+    "borderSubtle": {
+      "dark": "darkStep6",
+      "light": "lightStep6"
+    },
+    "diffAdded": {
+      "dark": "#4fd6be",
+      "light": "#1e725c"
+    },
+    "diffRemoved": {
+      "dark": "#c53b53",
+      "light": "#c53b53"
+    },
+    "diffContext": {
+      "dark": "#828bb8",
+      "light": "#7086b5"
+    },
+    "diffHunkHeader": {
+      "dark": "#828bb8",
+      "light": "#7086b5"
+    },
+    "diffHighlightAdded": {
+      "dark": "#b8db87",
+      "light": "#4db380"
+    },
+    "diffHighlightRemoved": {
+      "dark": "#e26a75",
+      "light": "#f52a65"
+    },
+    "diffAddedBg": {
+      "dark": "#20303b",
+      "light": "#d5e5d5"
+    },
+    "diffRemovedBg": {
+      "dark": "#37222c",
+      "light": "#f7d8db"
+    },
+    "diffContextBg": {
+      "dark": "darkStep2",
+      "light": "lightStep2"
+    },
+    "diffLineNumber": {
+      "dark": "darkStep3",
+      "light": "lightStep3"
+    },
+    "diffAddedLineNumberBg": {
+      "dark": "#1b2b34",
+      "light": "#c5d5c5"
+    },
+    "diffRemovedLineNumberBg": {
+      "dark": "#2d1f26",
+      "light": "#e7c8cb"
+    },
+    "markdownText": {
+      "dark": "darkStep12",
+      "light": "lightStep12"
+    },
+    "markdownHeading": {
+      "dark": "darkPurple",
+      "light": "lightPurple"
+    },
+    "markdownLink": {
+      "dark": "darkStep9",
+      "light": "lightStep9"
+    },
+    "markdownLinkText": {
+      "dark": "darkCyan",
+      "light": "lightCyan"
+    },
+    "markdownCode": {
+      "dark": "darkGreen",
+      "light": "lightGreen"
+    },
+    "markdownBlockQuote": {
+      "dark": "darkYellow",
+      "light": "lightYellow"
+    },
+    "markdownEmph": {
+      "dark": "darkYellow",
+      "light": "lightYellow"
+    },
+    "markdownStrong": {
+      "dark": "darkOrange",
+      "light": "lightOrange"
+    },
+    "markdownHorizontalRule": {
+      "dark": "darkStep11",
+      "light": "lightStep11"
+    },
+    "markdownListItem": {
+      "dark": "darkStep9",
+      "light": "lightStep9"
+    },
+    "markdownListEnumeration": {
+      "dark": "darkCyan",
+      "light": "lightCyan"
+    },
+    "markdownImage": {
+      "dark": "darkStep9",
+      "light": "lightStep9"
+    },
+    "markdownImageText": {
+      "dark": "darkCyan",
+      "light": "lightCyan"
+    },
+    "markdownCodeBlock": {
+      "dark": "darkStep12",
+      "light": "lightStep12"
+    },
+    "syntaxComment": {
+      "dark": "darkStep11",
+      "light": "lightStep11"
+    },
+    "syntaxKeyword": {
+      "dark": "darkPurple",
+      "light": "lightPurple"
+    },
+    "syntaxFunction": {
+      "dark": "darkStep9",
+      "light": "lightStep9"
+    },
+    "syntaxVariable": {
+      "dark": "darkRed",
+      "light": "lightRed"
+    },
+    "syntaxString": {
+      "dark": "darkGreen",
+      "light": "lightGreen"
+    },
+    "syntaxNumber": {
+      "dark": "darkOrange",
+      "light": "lightOrange"
+    },
+    "syntaxType": {
+      "dark": "darkYellow",
+      "light": "lightYellow"
+    },
+    "syntaxOperator": {
+      "dark": "darkCyan",
+      "light": "lightCyan"
+    },
+    "syntaxPunctuation": {
+      "dark": "darkStep12",
+      "light": "lightStep12"
+    }
+  }
+}
+

+ 0 - 295
packages/tui/internal/theme/tokyonight.go

@@ -1,295 +0,0 @@
-package theme
-
-import (
-	"github.com/charmbracelet/lipgloss/v2"
-	"github.com/charmbracelet/lipgloss/v2/compat"
-)
-
-// TokyoNightTheme implements the Theme interface with Tokyo Night colors.
-// It provides both dark and light variants.
-type TokyoNightTheme struct {
-	BaseTheme
-}
-
-// NewTokyoNightTheme creates a new instance of the Tokyo Night theme.
-func NewTokyoNightTheme() *TokyoNightTheme {
-	// Tokyo Night color palette with Radix-inspired scale progression
-	// Dark mode colors - Tokyo Night Moon variant
-	darkStep1 := "#1a1b26"  // App background (bg)
-	darkStep2 := "#1e2030"  // Subtle background (bg_dark)
-	darkStep3 := "#222436"  // UI element background (bg_highlight)
-	darkStep4 := "#292e42"  // Hovered UI element background
-	darkStep5 := "#3b4261"  // Active/Selected UI element background (bg_visual)
-	darkStep6 := "#545c7e"  // Subtle borders and separators (dark3)
-	darkStep7 := "#737aa2"  // UI element border and focus rings (dark5)
-	darkStep8 := "#9099b2"  // Hovered UI element border
-	darkStep9 := "#82aaff"  // Solid backgrounds (blue)
-	darkStep10 := "#89b4fa" // Hovered solid backgrounds
-	darkStep11 := "#828bb8" // Low-contrast text (using fg_dark for better contrast)
-	darkStep12 := "#c8d3f5" // High-contrast text (fg)
-
-	// Dark mode accent colors
-	darkRed := "#ff757f"
-	darkOrange := "#ff966c"
-	darkYellow := "#ffc777"
-	darkGreen := "#c3e88d"
-	darkCyan := "#86e1fc"
-	darkBlue := darkStep9 // Using step 9 for primary
-	darkPurple := "#c099ff"
-
-	// Light mode colors - Tokyo Night Day variant
-	lightStep1 := "#e1e2e7"  // App background
-	lightStep2 := "#d5d6db"  // Subtle background
-	lightStep3 := "#c8c9ce"  // UI element background
-	lightStep4 := "#b9bac1"  // Hovered UI element background
-	lightStep5 := "#a8aecb"  // Active/Selected UI element background
-	lightStep6 := "#9699a8"  // Subtle borders and separators
-	lightStep7 := "#737a8c"  // UI element border and focus rings
-	lightStep8 := "#5a607d"  // Hovered UI element border
-	lightStep9 := "#2e7de9"  // Solid backgrounds (blue)
-	lightStep10 := "#1a6ce7" // Hovered solid backgrounds
-	lightStep11 := "#8990a3" // Low-contrast text (more muted)
-	lightStep12 := "#3760bf" // High-contrast text
-
-	// Light mode accent colors
-	lightRed := "#f52a65"
-	lightOrange := "#b15c00"
-	lightYellow := "#8c6c3e"
-	lightGreen := "#587539"
-	lightCyan := "#007197"
-	lightBlue := lightStep9 // Using step 9 for primary
-	lightPurple := "#9854f1"
-
-	// Unused variables to avoid compiler errors (these could be used for hover states)
-	_ = darkStep4
-	_ = darkStep5
-	_ = darkStep10
-	_ = lightStep4
-	_ = lightStep5
-	_ = lightStep10
-
-	theme := &TokyoNightTheme{}
-
-	// Base colors
-	theme.PrimaryColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkBlue),
-		Light: lipgloss.Color(lightBlue),
-	}
-	theme.SecondaryColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkPurple),
-		Light: lipgloss.Color(lightPurple),
-	}
-	theme.AccentColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkOrange),
-		Light: lipgloss.Color(lightOrange),
-	}
-
-	// Status colors
-	theme.ErrorColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkRed),
-		Light: lipgloss.Color(lightRed),
-	}
-	theme.WarningColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkOrange),
-		Light: lipgloss.Color(lightOrange),
-	}
-	theme.SuccessColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkGreen),
-		Light: lipgloss.Color(lightGreen),
-	}
-	theme.InfoColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkBlue),
-		Light: lipgloss.Color(lightBlue),
-	}
-
-	// Text colors
-	theme.TextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep12),
-		Light: lipgloss.Color(lightStep12),
-	}
-	theme.TextMutedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep11),
-		Light: lipgloss.Color(lightStep11),
-	}
-
-	// Background colors
-	theme.BackgroundColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep1),
-		Light: lipgloss.Color(lightStep1),
-	}
-	theme.BackgroundPanelColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep2),
-		Light: lipgloss.Color(lightStep2),
-	}
-	theme.BackgroundElementColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep3),
-		Light: lipgloss.Color(lightStep3),
-	}
-
-	// Border colors
-	theme.BorderColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep7),
-		Light: lipgloss.Color(lightStep7),
-	}
-	theme.BorderActiveColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep8),
-		Light: lipgloss.Color(lightStep8),
-	}
-	theme.BorderSubtleColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep6),
-		Light: lipgloss.Color(lightStep6),
-	}
-
-	// Diff view colors
-	theme.DiffAddedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#4fd6be"), // teal from palette
-		Light: lipgloss.Color("#1e725c"),
-	}
-	theme.DiffRemovedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#c53b53"), // red1 from palette
-		Light: lipgloss.Color("#c53b53"),
-	}
-	theme.DiffContextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#828bb8"), // fg_dark from palette
-		Light: lipgloss.Color("#7086b5"),
-	}
-	theme.DiffHunkHeaderColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#828bb8"), // fg_dark from palette
-		Light: lipgloss.Color("#7086b5"),
-	}
-	theme.DiffHighlightAddedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#b8db87"), // git.add from palette
-		Light: lipgloss.Color("#4db380"),
-	}
-	theme.DiffHighlightRemovedColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#e26a75"), // git.delete from palette
-		Light: lipgloss.Color("#f52a65"),
-	}
-	theme.DiffAddedBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#20303b"),
-		Light: lipgloss.Color("#d5e5d5"),
-	}
-	theme.DiffRemovedBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#37222c"),
-		Light: lipgloss.Color("#f7d8db"),
-	}
-	theme.DiffContextBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep2),
-		Light: lipgloss.Color(lightStep2),
-	}
-	theme.DiffLineNumberColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep3), // dark3 from palette
-		Light: lipgloss.Color(lightStep3),
-	}
-	theme.DiffAddedLineNumberBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#1b2b34"),
-		Light: lipgloss.Color("#c5d5c5"),
-	}
-	theme.DiffRemovedLineNumberBgColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color("#2d1f26"),
-		Light: lipgloss.Color("#e7c8cb"),
-	}
-
-	// Markdown colors
-	theme.MarkdownTextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep12),
-		Light: lipgloss.Color(lightStep12),
-	}
-	theme.MarkdownHeadingColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkPurple),
-		Light: lipgloss.Color(lightPurple),
-	}
-	theme.MarkdownLinkColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkBlue),
-		Light: lipgloss.Color(lightBlue),
-	}
-	theme.MarkdownLinkTextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkCyan),
-		Light: lipgloss.Color(lightCyan),
-	}
-	theme.MarkdownCodeColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkGreen),
-		Light: lipgloss.Color(lightGreen),
-	}
-	theme.MarkdownBlockQuoteColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkYellow),
-		Light: lipgloss.Color(lightYellow),
-	}
-	theme.MarkdownEmphColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkYellow),
-		Light: lipgloss.Color(lightYellow),
-	}
-	theme.MarkdownStrongColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkOrange),
-		Light: lipgloss.Color(lightOrange),
-	}
-	theme.MarkdownHorizontalRuleColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep11),
-		Light: lipgloss.Color(lightStep11),
-	}
-	theme.MarkdownListItemColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkBlue),
-		Light: lipgloss.Color(lightBlue),
-	}
-	theme.MarkdownListEnumerationColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkCyan),
-		Light: lipgloss.Color(lightCyan),
-	}
-	theme.MarkdownImageColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkBlue),
-		Light: lipgloss.Color(lightBlue),
-	}
-	theme.MarkdownImageTextColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkCyan),
-		Light: lipgloss.Color(lightCyan),
-	}
-	theme.MarkdownCodeBlockColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep12),
-		Light: lipgloss.Color(lightStep12),
-	}
-
-	// Syntax highlighting colors
-	theme.SyntaxCommentColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep11),
-		Light: lipgloss.Color(lightStep11),
-	}
-	theme.SyntaxKeywordColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkPurple),
-		Light: lipgloss.Color(lightPurple),
-	}
-	theme.SyntaxFunctionColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkBlue),
-		Light: lipgloss.Color(lightBlue),
-	}
-	theme.SyntaxVariableColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkRed),
-		Light: lipgloss.Color(lightRed),
-	}
-	theme.SyntaxStringColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkGreen),
-		Light: lipgloss.Color(lightGreen),
-	}
-	theme.SyntaxNumberColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkOrange),
-		Light: lipgloss.Color(lightOrange),
-	}
-	theme.SyntaxTypeColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkYellow),
-		Light: lipgloss.Color(lightYellow),
-	}
-	theme.SyntaxOperatorColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkCyan),
-		Light: lipgloss.Color(lightCyan),
-	}
-	theme.SyntaxPunctuationColor = compat.AdaptiveColor{
-		Dark:  lipgloss.Color(darkStep12),
-		Light: lipgloss.Color(lightStep12),
-	}
-
-	return theme
-}
-
-func init() {
-	// Register the Tokyo Night theme with the theme manager
-	RegisterTheme("tokyonight", NewTokyoNightTheme())
-}

+ 1 - 1
packages/tui/internal/tui/tui.go

@@ -346,7 +346,7 @@ func (a appModel) View() string {
 		layoutView,
 		a.status.View(),
 	}
-	appView := lipgloss.JoinVertical(lipgloss.Top, components...)
+	appView := strings.Join(components, "\n")
 
 	if a.modal != nil {
 		appView = a.modal.Render(appView)