theme-aware auto-generated caption colors

This commit is contained in:
booleanmaybe 2026-04-14 00:08:24 -04:00
parent 335743b874
commit 404b4be3be
12 changed files with 223 additions and 57 deletions

View file

@ -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"
```

View file

@ -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

View file

@ -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
```

View file

@ -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))
}
}
}

View file

@ -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

View file

@ -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:

View file

@ -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
```

View file

@ -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
},
}
}

View file

@ -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 {

View file

@ -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

View file

@ -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

View file

@ -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)