diff --git a/.doc/doki/doc/config.md b/.doc/doki/doc/config.md index 685bf4c..a6c2bac 100644 --- a/.doc/doki/doc/config.md +++ b/.doc/doki/doc/config.md @@ -61,7 +61,7 @@ Search order: user config dir (base) → `.doc/workflow.yaml` (project) → cwd **Statuses** — last file with a `statuses:` section wins (complete replacement). A project that defines its own statuses fully replaces the user-level defaults. **Views (plugins)** — merged by name across files. The user config is the base; project and cwd files override individual fields: -- Non-empty fields in the override replace the base (description, key, colors, view mode) +- Non-empty fields in the override replace the base (description, key, view mode) - Non-empty arrays in the override replace the entire base array (lanes, actions) - Empty/zero fields in the override are ignored — the base value is kept - Views that only exist in the override are appended @@ -149,8 +149,6 @@ statuses: views: - name: Kanban description: "Move tiki to new status, search, create or delete" - foreground: "#87ceeb" - background: "#25496a" key: "F1" lanes: - name: Ready @@ -167,8 +165,6 @@ views: action: update where id = id() set status="done" - name: Backlog description: "Tasks waiting to be picked up, sorted by priority" - foreground: "#5fff87" - background: "#0b3d2e" key: "F3" lanes: - name: Backlog @@ -180,8 +176,6 @@ views: action: update where id = id() set status="ready" - name: Recent description: "Tasks changed in the last 24 hours, most recent first" - foreground: "#f4d6a6" - background: "#5a3d1b" key: Ctrl-R lanes: - name: Recent @@ -189,8 +183,6 @@ views: filter: select where now() - updatedAt < 24hour order by updatedAt desc - name: Roadmap description: "Epics organized by Now, Next, and Later horizons" - foreground: "#e2e8f0" - background: "#2a5f5a" key: "F4" lanes: - name: Now @@ -214,15 +206,11 @@ views: type: doki fetcher: internal text: "Help" - foreground: "#bcbcbc" - background: "#003399" key: "?" - name: Docs description: "Project notes and documentation files" type: doki fetcher: file url: "index.md" - foreground: "#ff9966" - background: "#2b3a42" key: "F2" ``` \ No newline at end of file diff --git a/.doc/doki/doc/customization.md b/.doc/doki/doc/customization.md index 20e7bc0..233e047 100644 --- a/.doc/doki/doc/customization.md +++ b/.doc/doki/doc/customization.md @@ -71,8 +71,6 @@ how Backlog is defined: views: - name: Backlog description: "Tasks waiting to be picked up, sorted by priority" - foreground: "#5fff87" - background: "#0b3d2e" key: "F3" lanes: - name: Backlog @@ -86,7 +84,7 @@ views: that translates to - show all tikis in the status `backlog`, sort by priority and then by ID arranged visually in 4 columns in a single lane. The `actions` section defines a keyboard shortcut `b` that moves the selected tiki to the board by setting its status to `ready` -You define the name, description, caption colors, hotkey, and `ruki` expressions for filtering and actions. The `description` is displayed in the header when the view is active. Save this into a `workflow.yaml` file in the config directory +You define the name, description, hotkey, and `ruki` expressions for filtering and actions. The `description` is displayed in the header when the view is active. Save this into a `workflow.yaml` file in the config directory Likewise the documentation is just a plugin: @@ -97,8 +95,6 @@ views: type: doki fetcher: file url: "index.md" - foreground: "#ff9966" - background: "#2b3a42" key: "F2" ``` @@ -115,8 +111,6 @@ definition that roughly mimics the board: ```yaml name: Custom -foreground: "#5fff87" -background: "#005f00" key: "F4" lanes: - name: Ready diff --git a/.doc/doki/index.md b/.doc/doki/index.md index b42a944..f7aa3a3 100644 --- a/.doc/doki/index.md +++ b/.doc/doki/index.md @@ -46,8 +46,6 @@ Just configuring multiple plugins. Create a file like `brainstorm.yaml`: ```text name: Brainstorm type: doki - foreground: "##ffff99" - background: "#996600" key: "F6" url: new-doc-root.md ``` diff --git a/config/caption_colors_test.go b/config/caption_colors_test.go new file mode 100644 index 0000000..c26a936 --- /dev/null +++ b/config/caption_colors_test.go @@ -0,0 +1,48 @@ +package config + +import "testing" + +func TestCaptionColorForIndex_Valid(t *testing.T) { + cc := ColorsFromPalette(DarkPalette()) + for i := 0; i < 6; i++ { + pair := cc.CaptionColorForIndex(i) + if pair.Foreground.IsDefault() { + t.Errorf("index %d: foreground is default", i) + } + if pair.Background.IsDefault() { + t.Errorf("index %d: background is default", i) + } + } +} + +func TestCaptionColorForIndex_Wraps(t *testing.T) { + cc := ColorsFromPalette(DarkPalette()) + first := cc.CaptionColorForIndex(0) + wrapped := cc.CaptionColorForIndex(6) + if first.Foreground.Hex() != wrapped.Foreground.Hex() { + t.Errorf("expected index 6 to wrap to index 0: got fg %s vs %s", wrapped.Foreground.Hex(), first.Foreground.Hex()) + } + if first.Background.Hex() != wrapped.Background.Hex() { + t.Errorf("expected index 6 to wrap to index 0: got bg %s vs %s", wrapped.Background.Hex(), first.Background.Hex()) + } +} + +func TestCaptionColorForIndex_Negative(t *testing.T) { + cc := ColorsFromPalette(DarkPalette()) + pair := cc.CaptionColorForIndex(-1) + if !pair.Foreground.IsDefault() { + t.Errorf("expected default foreground for negative index, got %s", pair.Foreground.Hex()) + } + if !pair.Background.IsDefault() { + t.Errorf("expected default background for negative index, got %s", pair.Background.Hex()) + } +} + +func TestAllThemesHaveCaptionColors(t *testing.T) { + for name, info := range themeRegistry { + p := info.Palette() + if len(p.CaptionColors) < 6 { + t.Errorf("theme %q: has %d caption colors, want at least 6", name, len(p.CaptionColors)) + } + } +} diff --git a/config/colors.go b/config/colors.go index 0a4d2d8..91ed124 100644 --- a/config/colors.go +++ b/config/colors.go @@ -8,6 +8,12 @@ type Gradient struct { End [3]int // R, G, B (0-255) } +// CaptionColorPair holds the foreground and background colors for a plugin caption row. +type CaptionColorPair struct { + Foreground Color + Background Color +} + // ColorConfig holds all color and style definitions per view type ColorConfig struct { // Caption colors @@ -87,6 +93,9 @@ type ColorConfig struct { HeaderActionViewKeyColor Color HeaderActionViewLabelColor Color + // Plugin caption colors (auto-generated per theme) + CaptionColors []CaptionColorPair + // Plugin-specific colors DepsEditorBackground Color // muted slate for dependency editor caption @@ -152,6 +161,9 @@ type Palette struct { StatuslineText Color // statusline primary text StatuslineAccent Color // statusline accent background StatuslineOk Color // statusline info/success foreground + + // Plugin caption colors (6 curated fg/bg pairs per theme) + CaptionColors []CaptionColorPair } // darkenRGB returns a darkened version of an RGB triple. ratio 0 = no change, 1 = black. @@ -277,9 +289,21 @@ func ColorsFromPalette(p Palette) *ColorConfig { StatuslineErrorFg: p.HighlightColor, StatuslineErrorBg: p.StatuslineMidBg, StatuslineFillBg: p.StatuslineMidBg, + + // Plugin caption colors + CaptionColors: p.CaptionColors, } } +// CaptionColorForIndex returns the caption color pair for a plugin at the given config index. +// Wraps modulo slice length. Returns zero-value for negative index or empty slice. +func (cc *ColorConfig) CaptionColorForIndex(index int) CaptionColorPair { + if index < 0 || len(cc.CaptionColors) == 0 { + return CaptionColorPair{} + } + return cc.CaptionColors[index%len(cc.CaptionColors)] +} + // Global color config instance var globalColors *ColorConfig var colorsInitialized bool diff --git a/config/default_workflow.yaml b/config/default_workflow.yaml index 596692a..353d662 100644 --- a/config/default_workflow.yaml +++ b/config/default_workflow.yaml @@ -24,8 +24,6 @@ views: - name: Kanban description: "Move tiki to new status, search, create or delete" default: true - foreground: "#87ceeb" - background: "#25496a" key: "F1" lanes: - name: Ready @@ -42,8 +40,6 @@ views: action: update where id = id() set status="done" - name: Backlog description: "Tasks waiting to be picked up, sorted by priority" - foreground: "#5fff87" - background: "#0b3d2e" key: "F3" lanes: - name: Backlog @@ -55,8 +51,6 @@ views: action: update where id = id() set status="ready" - name: Recent description: "Tasks changed in the last 24 hours, most recent first" - foreground: "#f4d6a6" - background: "#5a3d1b" key: Ctrl-R lanes: - name: Recent @@ -64,8 +58,6 @@ views: filter: select where now() - updatedAt < 24hour order by updatedAt desc - name: Roadmap description: "Epics organized by Now, Next, and Later horizons" - foreground: "#e2e8f0" - background: "#2a5f5a" key: "F4" lanes: - name: Now @@ -89,16 +81,12 @@ views: type: doki fetcher: internal text: "Help" - foreground: "#bcbcbc" - background: "#003399" key: "?" - name: Docs description: "Project notes and documentation files" type: doki fetcher: file url: "index.md" - foreground: "#ff9966" - background: "#2b3a42" key: "F2" triggers: diff --git a/config/index.md b/config/index.md index 5b0ed37..c0b4de8 100644 --- a/config/index.md +++ b/config/index.md @@ -48,8 +48,6 @@ Just configuring multiple plugins. Create a file like `brainstorm.yaml`: ```text name: Brainstorm type: doki - foreground: "##ffff99" - background: "#996600" key: "F6" url: new-doc-root.md ``` diff --git a/config/palettes.go b/config/palettes.go index f9e4e7d..005448b 100644 --- a/config/palettes.go +++ b/config/palettes.go @@ -44,6 +44,15 @@ func DarkPalette() Palette { StatuslineText: NewColorHex("#d8dee9"), StatuslineAccent: NewColorHex("#5e81ac"), StatuslineOk: NewColorHex("#a3be8c"), + + CaptionColors: []CaptionColorPair{ + {Foreground: NewColorHex("#87ceeb"), Background: NewColorHex("#25496a")}, // steel-blue (Kanban signature) + {Foreground: NewColorHex("#8cd98c"), Background: NewColorHex("#003300")}, // green + {Foreground: NewColorHex("#ffd78c"), Background: NewColorHex("#4d3200")}, // orange + {Foreground: NewColorHex("#a9f1ea"), Background: NewColorHex("#13433e")}, // teal + {Foreground: NewColorHex("#b7bcc7"), Background: NewColorHex("#1d2027")}, // blue-gray + {Foreground: NewColorHex("#b0c4d4"), Background: NewColorHex("#1e2d3a")}, // slate blue + }, } } @@ -84,6 +93,15 @@ func LightPalette() Palette { StatuslineText: NewColorHex("#2e3440"), StatuslineAccent: NewColorHex("#5e81ac"), StatuslineOk: NewColorHex("#4c7a5a"), + + CaptionColors: []CaptionColorPair{ + {Foreground: NewColorHex("#e0f0ff"), Background: NewColorHex("#3a6a90")}, // steel-blue (Kanban signature) + {Foreground: NewColorHex("#d2ded6"), Background: NewColorHex("#467153")}, // green (StatuslineOk) + {Foreground: NewColorHex("#edd6bf"), Background: NewColorHex("#a45200")}, // orange (InfoLabelColor) + {Foreground: NewColorHex("#d2d3da"), Background: NewColorHex("#5a5e80")}, // indigo (ValueColor) + {Foreground: NewColorHex("#c7e7e3"), Background: NewColorHex("#1a8174")}, // teal (LogoDotColor) + {Foreground: NewColorHex("#dfdfdf"), Background: NewColorHex("#616161")}, // gray (MutedColor) + }, } } @@ -125,6 +143,15 @@ func DraculaPalette() Palette { StatuslineText: NewColorHex("#f8f8f2"), StatuslineAccent: NewColorHex("#bd93f9"), StatuslineOk: NewColorHex("#50fa7b"), + + CaptionColors: []CaptionColorPair{ + {Foreground: NewColorHex("#cbf5fe"), Background: NewColorHex("#2a464c")}, // cyan + {Foreground: NewColorHex("#b0fdc4"), Background: NewColorHex("#184b25")}, // green + {Foreground: NewColorHex("#ffdfbd"), Background: NewColorHex("#4d3720")}, // orange + {Foreground: NewColorHex("#f9fdcb"), Background: NewColorHex("#484b2a")}, // yellow + {Foreground: NewColorHex("#b8c0d6"), Background: NewColorHex("#1d2231")}, // comment + {Foreground: NewColorHex("#ffc3e5"), Background: NewColorHex("#4d243b")}, // pink + }, } } @@ -166,6 +193,15 @@ func TokyoNightPalette() Palette { StatuslineText: NewColorHex("#c0caf5"), StatuslineAccent: NewColorHex("#7aa2f7"), StatuslineOk: NewColorHex("#9ece6a"), + + CaptionColors: []CaptionColorPair{ + {Foreground: NewColorHex("#c5e9ff"), Background: NewColorHex("#263e4d")}, // sky-blue + {Foreground: NewColorHex("#d3e9bc"), Background: NewColorHex("#2f3e20")}, // green + {Foreground: NewColorHex("#ffd3b9"), Background: NewColorHex("#4d2f1e")}, // orange + {Foreground: NewColorHex("#efdbba"), Background: NewColorHex("#44341f")}, // yellow + {Foreground: NewColorHex("#b3b7ca"), Background: NewColorHex("#1a1d29")}, // comment + {Foreground: NewColorHex("#fbc2cc"), Background: NewColorHex("#4a232a")}, // red + }, } } @@ -207,6 +243,15 @@ func GruvboxDarkPalette() Palette { StatuslineText: NewColorHex("#ebdbb2"), StatuslineAccent: NewColorHex("#689d6a"), // dark aqua StatuslineOk: NewColorHex("#b8bb26"), + + CaptionColors: []CaptionColorPair{ + {Foreground: NewColorHex("#c7d7d1"), Background: NewColorHex("#27322e")}, // aqua-blue + {Foreground: NewColorHex("#dfe09d"), Background: NewColorHex("#37380b")}, // green + {Foreground: NewColorHex("#ffc698"), Background: NewColorHex("#4c2608")}, // orange + {Foreground: NewColorHex("#fcdfaa"), Background: NewColorHex("#4b390e")}, // yellow + {Foreground: NewColorHex("#bab6b2"), Background: NewColorHex("#1f1c19")}, // gray + {Foreground: NewColorHex("#fd9a90"), Background: NewColorHex("#4b1610")}, // red + }, } } @@ -248,6 +293,15 @@ func CatppuccinMochaPalette() Palette { StatuslineText: NewColorHex("#cdd6f4"), StatuslineAccent: NewColorHex("#89b4fa"), StatuslineOk: NewColorHex("#a6e3a1"), + + CaptionColors: []CaptionColorPair{ + {Foreground: NewColorHex("#c9dcfd"), Background: NewColorHex("#293648")}, // blue + {Foreground: NewColorHex("#d7f2d5"), Background: NewColorHex("#324430")}, // green + {Foreground: NewColorHex("#fdddc9"), Background: NewColorHex("#4b3629")}, // peach + {Foreground: NewColorHex("#fcf0d9"), Background: NewColorHex("#4b4435")}, // yellow + {Foreground: NewColorHex("#f9eaea"), Background: NewColorHex("#483d3d")}, // flamingo + {Foreground: NewColorHex("#cff2ec"), Background: NewColorHex("#2c4440")}, // teal + }, } } @@ -289,6 +343,15 @@ func SolarizedDarkPalette() Palette { StatuslineText: NewColorHex("#839496"), StatuslineAccent: NewColorHex("#268bd2"), StatuslineOk: NewColorHex("#859900"), + + CaptionColors: []CaptionColorPair{ + {Foreground: NewColorHex("#9dcbeb"), Background: NewColorHex("#0b2a3f")}, // blue + {Foreground: NewColorHex("#c8d18c"), Background: NewColorHex("#282e00")}, // green + {Foreground: NewColorHex("#e8ae96"), Background: NewColorHex("#3d1707")}, // orange + {Foreground: NewColorHex("#9fd5d1"), Background: NewColorHex("#0d302e")}, // cyan + {Foreground: NewColorHex("#b4bec1"), Background: NewColorHex("#1a2123")}, // base01 + {Foreground: NewColorHex("#ec908f"), Background: NewColorHex("#420f0e")}, // red + }, } } @@ -330,6 +393,15 @@ func NordPalette() Palette { StatuslineText: NewColorHex("#d8dee9"), // nord4 StatuslineAccent: NewColorHex("#5e81ac"), // nord10 StatuslineOk: NewColorHex("#a3be8c"), + + CaptionColors: []CaptionColorPair{ + {Foreground: NewColorHex("#c9e3ea"), Background: NewColorHex("#293a3e")}, // frost-cyan + {Foreground: NewColorHex("#d6e2cb"), Background: NewColorHex("#31392a")}, // green + {Foreground: NewColorHex("#eac9bf"), Background: NewColorHex("#3e2922")}, // orange + {Foreground: NewColorHex("#f4e3c3"), Background: NewColorHex("#473d2a")}, // yellow + {Foreground: NewColorHex("#aeb3bc"), Background: NewColorHex("#171a20")}, // nord3 + {Foreground: NewColorHex("#dca8ac"), Background: NewColorHex("#391d20")}, // red + }, } } @@ -371,6 +443,15 @@ func MonokaiPalette() Palette { StatuslineText: NewColorHex("#f8f8f2"), StatuslineAccent: NewColorHex("#66d9ef"), StatuslineOk: NewColorHex("#a6e22e"), + + CaptionColors: []CaptionColorPair{ + {Foreground: NewColorHex("#baeef8"), Background: NewColorHex("#1f4148")}, // cyan + {Foreground: NewColorHex("#d7f2a1"), Background: NewColorHex("#32440e")}, // green + {Foreground: NewColorHex("#fed09a"), Background: NewColorHex("#4c2d09")}, // orange + {Foreground: NewColorHex("#f2eebc"), Background: NewColorHex("#454223")}, // yellow + {Foreground: NewColorHex("#c1bfb7"), Background: NewColorHex("#23221c")}, // comment + {Foreground: NewColorHex("#e08ea5"), Background: NewColorHex("#4b0c22")}, // pink-red + }, } } @@ -412,6 +493,15 @@ func OneDarkPalette() Palette { StatuslineText: NewColorHex("#abb2bf"), StatuslineAccent: NewColorHex("#61afef"), StatuslineOk: NewColorHex("#98c379"), + + CaptionColors: []CaptionColorPair{ + {Foreground: NewColorHex("#b8dbf8"), Background: NewColorHex("#1d3548")}, // blue + {Foreground: NewColorHex("#d1e4c3"), Background: NewColorHex("#2e3b24")}, // green + {Foreground: NewColorHex("#ead2ba"), Background: NewColorHex("#3f2e1f")}, // orange + {Foreground: NewColorHex("#f1deba"), Background: NewColorHex("#453a25")}, // yellow + {Foreground: NewColorHex("#b6b9bf"), Background: NewColorHex("#1c1e22")}, // comment + {Foreground: NewColorHex("#eeb2b6"), Background: NewColorHex("#432123")}, // red + }, } } @@ -455,6 +545,15 @@ func CatppuccinLattePalette() Palette { StatuslineText: NewColorHex("#4c4f69"), StatuslineAccent: NewColorHex("#1e66f5"), StatuslineOk: NewColorHex("#40a02b"), + + CaptionColors: []CaptionColorPair{ + {Foreground: NewColorHex("#c7d9fd"), Background: NewColorHex("#1e66f5")}, // blue (ValueColor) + {Foreground: NewColorHex("#f7e3c7"), Background: NewColorHex("#8d5a12")}, // yellow (HighlightColor) + {Foreground: NewColorHex("#ffd8c2"), Background: NewColorHex("#b54708")}, // peach (InfoLabelColor) + {Foreground: NewColorHex("#dedfe4"), Background: NewColorHex("#5e606f")}, // overlay0 (MutedColor) + {Foreground: NewColorHex("#c5e4e6"), Background: NewColorHex("#148187")}, // teal (LogoDotColor) + {Foreground: NewColorHex("#c0e9f9"), Background: NewColorHex("#0381b3")}, // sky (DeepSkyBlue) + }, } } @@ -496,6 +595,15 @@ func SolarizedLightPalette() Palette { StatuslineText: NewColorHex("#657b83"), StatuslineAccent: NewColorHex("#268bd2"), StatuslineOk: NewColorHex("#859900"), + + CaptionColors: []CaptionColorPair{ + {Foreground: NewColorHex("#c9e2f4"), Background: NewColorHex("#2073ae")}, // blue (ValueColor) + {Foreground: NewColorHex("#ede2bf"), Background: NewColorHex("#826300")}, // yellow (HighlightColor) + {Foreground: NewColorHex("#f2d2c5"), Background: NewColorHex("#b74414")}, // orange (InfoLabelColor) + {Foreground: NewColorHex("#d5dbdd"), Background: NewColorHex("#52666d")}, // base01 (SoftTextColor) + {Foreground: NewColorHex("#cae8e5"), Background: NewColorHex("#217d76")}, // cyan (LogoDotColor) + {Foreground: NewColorHex("#e1e6bf"), Background: NewColorHex("#637200")}, // green (AccentColor) + }, } } @@ -537,6 +645,15 @@ func GruvboxLightPalette() Palette { StatuslineText: NewColorHex("#3c3836"), StatuslineAccent: NewColorHex("#427b58"), StatuslineOk: NewColorHex("#79740e"), + + CaptionColors: []CaptionColorPair{ + {Foreground: NewColorHex("#e2d6c3"), Background: NewColorHex("#8b5b0f")}, // amber/ochre + {Foreground: NewColorHex("#dedcc3"), Background: NewColorHex("#6f6a0d")}, // green + {Foreground: NewColorHex("#ebcec0"), Background: NewColorHex("#c44103")}, // orange + {Foreground: NewColorHex("#d0ded5"), Background: NewColorHex("#3f7554")}, // aqua + {Foreground: NewColorHex("#dedbd8"), Background: NewColorHex("#6a5f55")}, // gray + {Foreground: NewColorHex("#e2d7d2"), Background: NewColorHex("#8b5e4b")}, // warm brown + }, } } @@ -578,5 +695,14 @@ func GithubLightPalette() Palette { StatuslineText: NewColorHex("#1f2328"), StatuslineAccent: NewColorHex("#0969da"), StatuslineOk: NewColorHex("#116329"), + + CaptionColors: []CaptionColorPair{ + {Foreground: NewColorHex("#c2daf6"), Background: NewColorHex("#0a72ed")}, // blue + {Foreground: NewColorHex("#c4d8ca"), Background: NewColorHex("#188d3b")}, // green + {Foreground: NewColorHex("#e5cdbf"), Background: NewColorHex("#ba4600")}, // orange + {Foreground: NewColorHex("#e6d9bf"), Background: NewColorHex("#9a6700")}, // amber + {Foreground: NewColorHex("#d9dbdd"), Background: NewColorHex("#5b626a")}, // muted + {Foreground: NewColorHex("#e6d2d2"), Background: NewColorHex("#9b4a4a")}, // muted red + }, } } diff --git a/plugin/parser.go b/plugin/parser.go index 4993395..f9e7e0d 100644 --- a/plugin/parser.go +++ b/plugin/parser.go @@ -6,7 +6,6 @@ import ( "unicode" "unicode/utf8" - "github.com/gdamore/tcell/v2" "gopkg.in/yaml.v3" "github.com/boolean-maybe/tiki/config" @@ -20,9 +19,9 @@ func parsePluginConfig(cfg pluginFileConfig, source string, schema ruki.Schema) } // Common fields - // Use ColorDefault as sentinel so views can detect "not specified" and use theme-appropriate colors - fg := config.NewColor(parseColor(cfg.Foreground, tcell.ColorDefault)) - bg := config.NewColor(parseColor(cfg.Background, tcell.ColorDefault)) + // caption colors are now auto-generated per theme; YAML fg/bg fields are silently ignored + fg := config.DefaultColor() + bg := config.DefaultColor() key, r, mod, err := parseKey(cfg.Key) if err != nil { diff --git a/view/doki_plugin_view.go b/view/doki_plugin_view.go index a20db29..90aeb86 100644 --- a/view/doki_plugin_view.go +++ b/view/doki_plugin_view.go @@ -56,12 +56,16 @@ func NewDokiView( } func (dv *DokiView) build() { - // title bar with gradient background using plugin color - textColor := config.DefaultColor() - if !dv.pluginDef.Foreground.IsDefault() { - textColor = dv.pluginDef.Foreground + // title bar with gradient background using theme-derived caption colors + colors := config.GetColors() + pair := colors.CaptionColorForIndex(dv.pluginDef.ConfigIndex) + bgColor := pair.Background + textColor := pair.Foreground + if dv.pluginDef.ConfigIndex < 0 { + bgColor = dv.pluginDef.Background + textColor = config.DefaultColor() } - dv.titleBar = NewGradientCaptionRow([]string{dv.pluginDef.Name}, nil, dv.pluginDef.Background, textColor) + dv.titleBar = NewGradientCaptionRow([]string{dv.pluginDef.Name}, nil, bgColor, textColor) // Fetch initial content and create NavigableMarkdown with appropriate provider var content string diff --git a/view/help/custom.md b/view/help/custom.md index 3e0d3c5..c37984c 100644 --- a/view/help/custom.md +++ b/view/help/custom.md @@ -51,8 +51,6 @@ how Backlog is defined: ```yaml views: - name: Backlog - foreground: "#5fff87" - background: "#0b3d2e" key: "F3" lanes: - name: Backlog @@ -66,7 +64,7 @@ views: that translates to - show all tikis in the status `backlog`, sort by priority and then by ID arranged visually in 4 columns in a single lane. The `actions` section defines a keyboard shortcut `b` that moves the selected tiki to the board by setting its status to `ready` -You define the name, caption colors, hotkey, and `ruki` expressions for filtering and actions. Save this into a `workflow.yaml` file in the config directory +You define the name, description, hotkey, and `ruki` expressions for filtering and actions. Save this into a `workflow.yaml` file in the config directory Likewise the documentation is just a plugin: @@ -76,8 +74,6 @@ views: type: doki fetcher: file url: "index.md" - foreground: "#ff9966" - background: "#2b3a42" key: "F2" ``` @@ -94,8 +90,6 @@ definition that roughly mimics the board: ```yaml name: Custom -foreground: "#5fff87" -background: "#005f00" key: "F4" lanes: - name: Ready diff --git a/view/tiki_plugin_view.go b/view/tiki_plugin_view.go index 211d35f..f0bbbe3 100644 --- a/view/tiki_plugin_view.go +++ b/view/tiki_plugin_view.go @@ -57,10 +57,15 @@ func NewPluginView( } func (pv *PluginView) build() { - // title bar with gradient background using plugin color - textColor := config.DefaultColor() - if !pv.pluginDef.Foreground.IsDefault() { - textColor = pv.pluginDef.Foreground + // title bar with gradient background using theme-derived caption colors + colors := config.GetColors() + pair := colors.CaptionColorForIndex(pv.pluginDef.ConfigIndex) + bgColor := pair.Background + textColor := pair.Foreground + if pv.pluginDef.ConfigIndex < 0 { + // code-only plugin (e.g. deps editor) — use explicit Background + bgColor = pv.pluginDef.Background + textColor = config.DefaultColor() } laneNames := make([]string, len(pv.pluginDef.Lanes)) for i, lane := range pv.pluginDef.Lanes { @@ -70,7 +75,7 @@ func (pv *PluginView) build() { for i := range pv.pluginDef.Lanes { laneWidths[i] = pv.pluginConfig.GetWidthForLane(i) } - pv.titleBar = NewGradientCaptionRow(laneNames, laneWidths, pv.pluginDef.Background, textColor) + pv.titleBar = NewGradientCaptionRow(laneNames, laneWidths, bgColor, textColor) // lanes container (rows) pv.lanes = tview.NewFlex().SetDirection(tview.FlexColumn)