From a9de5462269d4597b87135ea5e239ad3f188f939 Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Sun, 22 Mar 2026 19:38:38 -0700 Subject: [PATCH 1/2] feat: add 0github diff themes --- README.md | 7 +- .../plans/2026-03-22-0github-theme.md | 61 ++++++ src/command/diff/theme.rs | 178 +++++++++++++++++- 3 files changed, 242 insertions(+), 4 deletions(-) create mode 100644 docs/superpowers/plans/2026-03-22-0github-theme.md diff --git a/README.md b/README.md index 4887ddb4..ddef99b2 100644 --- a/README.md +++ b/README.md @@ -168,14 +168,14 @@ Customize the diff viewer colors with preset themes: ```bash # Using CLI flag -lumen diff --theme dracula +lumen diff --theme 0github-dark # Using environment variable -LUMEN_THEME=catppuccin-mocha lumen diff +LUMEN_THEME=0github-light lumen diff # Or set permanently in config file (~/.config/lumen/lumen.config.json) { - "theme": "dracula" + "theme": "0github-dark" } ``` @@ -183,6 +183,7 @@ LUMEN_THEME=catppuccin-mocha lumen diff | Theme | Value | |-------|-------| | Default (auto-detect) | `dark`, `light` | +| 0github | `0github-dark`, `0github-light` | | Catppuccin | `catppuccin-mocha`, `catppuccin-latte` | | Dracula | `dracula` | | Nord | `nord` | diff --git a/docs/superpowers/plans/2026-03-22-0github-theme.md b/docs/superpowers/plans/2026-03-22-0github-theme.md new file mode 100644 index 00000000..db5a10fd --- /dev/null +++ b/docs/superpowers/plans/2026-03-22-0github-theme.md @@ -0,0 +1,61 @@ +# 0github Theme Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add 0github-inspired diff/syntax highlighting to the `lumen` fork in a way that is testable, selectable, and consistent with the reference palette in `manaflow`. + +**Architecture:** Extend `lumen`'s existing `ThemePreset` system rather than changing the diff renderer. The theme layer already owns syntax colors, diff backgrounds, CLI parsing, and environment/config selection, so a 0github preset can be introduced with narrow changes and verified through unit tests. + +**Tech Stack:** Rust, ratatui, clap, tree-sitter, cargo test + +--- + +### Task 1: Add failing theme tests + +**Files:** +- Modify: `src/command/diff/theme.rs` +- Reference: `/Users/lawrence/fun/manaflow/apps/www/app/globals.css` + +- [ ] **Step 1: Write the failing test** + +Add tests that assert: +- `ThemePreset::from_str("0github-light")` and `ThemePreset::from_str("0github-dark")` parse successfully. +- The resulting theme syntax and diff colors match the 0github palette copied from `manaflow`. + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cargo test theme::tests::test_0github_theme_parsing theme::tests::test_0github_light_palette theme::tests::test_0github_dark_palette` +Expected: FAIL because the new preset does not exist yet. + +### Task 2: Implement 0github presets + +**Files:** +- Modify: `src/command/diff/theme.rs` + +- [ ] **Step 1: Add minimal implementation** + +Add: +- `ThemePreset::ZeroGithubLight` +- `ThemePreset::ZeroGithubDark` +- `FromStr` aliases for `0github-light`, `0github-dark`, and a reasonable shorthand +- `Theme::zero_github_light()` and `Theme::zero_github_dark()` +- `Theme::from_preset(...)` match arms + +- [ ] **Step 2: Run test to verify it passes** + +Run: `cargo test theme::tests::test_0github_theme_parsing theme::tests::test_0github_light_palette theme::tests::test_0github_dark_palette` +Expected: PASS + +### Task 3: Document the preset + +**Files:** +- Modify: `README.md` + +- [ ] **Step 1: Update user-facing docs** + +Add the new theme names to the diff theme examples and available theme table. + +- [ ] **Step 2: Run lightweight verification** + +Run: `cargo test theme::tests::test_0github_theme_parsing theme::tests::test_0github_light_palette theme::tests::test_0github_dark_palette` +Expected: PASS diff --git a/src/command/diff/theme.rs b/src/command/diff/theme.rs index f6111652..5e6e85fa 100644 --- a/src/command/diff/theme.rs +++ b/src/command/diff/theme.rs @@ -14,6 +14,8 @@ pub enum ThemeMode { pub enum ThemePreset { DefaultDark, DefaultLight, + ZeroGithubDark, + ZeroGithubLight, CatppuccinMocha, CatppuccinLatte, Dracula, @@ -32,6 +34,8 @@ impl FromStr for ThemePreset { match s.to_lowercase().replace('_', "-").as_str() { "default-dark" | "dark" => Ok(Self::DefaultDark), "default-light" | "light" => Ok(Self::DefaultLight), + "0github-dark" => Ok(Self::ZeroGithubDark), + "0github-light" => Ok(Self::ZeroGithubLight), "catppuccin-mocha" | "mocha" => Ok(Self::CatppuccinMocha), "catppuccin-latte" | "latte" => Ok(Self::CatppuccinLatte), "dracula" => Ok(Self::Dracula), @@ -42,7 +46,7 @@ impl FromStr for ThemePreset { "solarized-dark" => Ok(Self::SolarizedDark), "solarized-light" => Ok(Self::SolarizedLight), _ => Err(format!( - "Unknown theme '{}'. Valid: default-dark, default-light, catppuccin-mocha, catppuccin-latte, dracula, nord, gruvbox-dark, gruvbox-light, one-dark, solarized-dark, solarized-light", + "Unknown theme '{}'. Valid: default-dark, default-light, 0github-dark, 0github-light, catppuccin-mocha, catppuccin-latte, dracula, nord, gruvbox-dark, gruvbox-light, one-dark, solarized-dark, solarized-light", s )), } @@ -264,6 +268,8 @@ impl Theme { match preset { ThemePreset::DefaultDark => Self::dark(), ThemePreset::DefaultLight => Self::light(), + ThemePreset::ZeroGithubDark => Self::zero_github_dark(), + ThemePreset::ZeroGithubLight => Self::zero_github_light(), ThemePreset::CatppuccinMocha => Self::catppuccin_mocha(), ThemePreset::CatppuccinLatte => Self::catppuccin_latte(), ThemePreset::Dracula => Self::dracula(), @@ -337,6 +343,128 @@ impl Theme { } } + pub fn zero_github_light() -> Self { + Self { + mode: ThemeMode::Light, + syntax: SyntaxColors { + comment: Color::Rgb(108, 120, 137), + keyword: Color::Rgb(104, 39, 196), + string: Color::Rgb(52, 141, 94), + number: Color::Rgb(204, 68, 30), + function: Color::Rgb(24, 128, 180), + function_macro: Color::Rgb(24, 109, 170), + r#type: Color::Rgb(198, 110, 16), + variable_builtin: Color::Rgb(24, 109, 170), + variable_member: Color::Rgb(71, 97, 133), + module: Color::Rgb(198, 110, 16), + operator: Color::Rgb(15, 79, 230), + tag: Color::Rgb(104, 39, 196), + attribute: Color::Rgb(71, 97, 133), + label: Color::Rgb(198, 110, 16), + punctuation: Color::Rgb(73, 78, 90), + default_text: Color::Rgb(38, 38, 38), + }, + diff: DiffColors { + added_bg: Color::Rgb(240, 253, 244), + added_gutter_bg: Color::Rgb(226, 252, 237), + added_gutter_fg: Color::Rgb(21, 128, 61), + deleted_bg: Color::Rgb(255, 255, 255), + deleted_gutter_bg: Color::Rgb(252, 208, 204), + deleted_gutter_fg: Color::Rgb(115, 115, 115), + context_bg: Color::Rgb(255, 255, 255), + empty_placeholder_fg: Color::Rgb(212, 212, 212), + added_word_bg: Color::Rgb(220, 252, 231), + deleted_word_bg: Color::Rgb(254, 226, 226), + }, + ui: UiColors { + border_focused: Color::Rgb(24, 128, 180), + border_unfocused: Color::Rgb(229, 229, 229), + text_primary: Color::Rgb(38, 38, 38), + text_secondary: Color::Rgb(82, 82, 82), + text_muted: Color::Rgb(115, 115, 115), + line_number: Color::Rgb(115, 115, 115), + bg: Color::Rgb(255, 255, 255), + footer_branch_bg: Color::Rgb(245, 245, 245), + footer_branch_fg: Color::Rgb(24, 128, 180), + status_added: Color::Rgb(21, 128, 61), + status_modified: Color::Rgb(198, 110, 16), + status_deleted: Color::Rgb(208, 37, 48), + stats_added: Color::Rgb(21, 128, 61), + stats_removed: Color::Rgb(208, 37, 48), + selection_bg: Color::Rgb(191, 219, 254), + selection_fg: Color::Black, + highlight: Color::Rgb(198, 110, 16), + viewed: Color::Rgb(21, 128, 61), + watching: Color::Rgb(198, 110, 16), + search_match_bg: Color::Rgb(254, 240, 138), + search_match_fg: Color::Black, + search_current_bg: Color::Rgb(245, 158, 11), + search_current_fg: Color::Black, + }, + } + } + + pub fn zero_github_dark() -> Self { + Self { + mode: ThemeMode::Dark, + syntax: SyntaxColors { + comment: Color::Rgb(117, 136, 163), + keyword: Color::Rgb(170, 125, 232), + string: Color::Rgb(83, 198, 136), + number: Color::Rgb(237, 125, 94), + function: Color::Rgb(94, 190, 237), + function_macro: Color::Rgb(71, 167, 235), + r#type: Color::Rgb(245, 156, 61), + variable_builtin: Color::Rgb(71, 167, 235), + variable_member: Color::Rgb(135, 161, 197), + module: Color::Rgb(245, 156, 61), + operator: Color::Rgb(117, 154, 240), + tag: Color::Rgb(170, 125, 232), + attribute: Color::Rgb(135, 161, 197), + label: Color::Rgb(245, 156, 61), + punctuation: Color::Rgb(138, 147, 168), + default_text: Color::Rgb(229, 229, 229), + }, + diff: DiffColors { + added_bg: Color::Rgb(18, 18, 18), + added_gutter_bg: Color::Rgb(34, 197, 94), + added_gutter_fg: Color::Rgb(134, 239, 172), + deleted_bg: Color::Rgb(10, 10, 10), + deleted_gutter_bg: Color::Rgb(248, 81, 73), + deleted_gutter_fg: Color::Rgb(163, 163, 163), + context_bg: Color::Rgb(10, 10, 10), + empty_placeholder_fg: Color::Rgb(64, 64, 64), + added_word_bg: Color::Rgb(20, 83, 45), + deleted_word_bg: Color::Rgb(127, 29, 29), + }, + ui: UiColors { + border_focused: Color::Rgb(94, 190, 237), + border_unfocused: Color::Rgb(64, 64, 64), + text_primary: Color::Rgb(229, 229, 229), + text_secondary: Color::Rgb(212, 212, 212), + text_muted: Color::Rgb(163, 163, 163), + line_number: Color::Rgb(163, 163, 163), + bg: Color::Rgb(10, 10, 10), + footer_branch_bg: Color::Rgb(23, 23, 23), + footer_branch_fg: Color::Rgb(94, 190, 237), + status_added: Color::Rgb(134, 239, 172), + status_modified: Color::Rgb(245, 156, 61), + status_deleted: Color::Rgb(233, 99, 108), + stats_added: Color::Rgb(134, 239, 172), + stats_removed: Color::Rgb(233, 99, 108), + selection_bg: Color::Rgb(71, 167, 235), + selection_fg: Color::Rgb(10, 10, 10), + highlight: Color::Rgb(245, 156, 61), + viewed: Color::Rgb(134, 239, 172), + watching: Color::Rgb(245, 156, 61), + search_match_bg: Color::Rgb(30, 64, 175), + search_match_fg: Color::Rgb(229, 229, 229), + search_current_bg: Color::Rgb(245, 156, 61), + search_current_fg: Color::Rgb(10, 10, 10), + }, + } + } + pub fn catppuccin_latte() -> Self { Self { mode: ThemeMode::Light, @@ -849,3 +977,51 @@ pub fn init(config_theme: Option<&str>) { pub fn get() -> &'static Theme { THEME.get_or_init(|| Theme::from_mode(ThemeMode::detect())) } + +#[cfg(test)] +mod tests { + use super::*; + + fn parsed_theme(name: &str) -> Theme { + let preset = ThemePreset::from_str(name).expect("theme should parse"); + Theme::from_preset(preset) + } + + #[test] + fn test_0github_theme_parsing() { + assert!(ThemePreset::from_str("0github-light").is_ok()); + assert!(ThemePreset::from_str("0github-dark").is_ok()); + } + + #[test] + fn test_0github_light_palette() { + let theme = parsed_theme("0github-light"); + + assert_eq!(theme.mode, ThemeMode::Light); + assert_eq!(theme.syntax.comment, Color::Rgb(108, 120, 137)); + assert_eq!(theme.syntax.keyword, Color::Rgb(104, 39, 196)); + assert_eq!(theme.syntax.string, Color::Rgb(52, 141, 94)); + assert_eq!(theme.syntax.function, Color::Rgb(24, 128, 180)); + assert_eq!(theme.syntax.variable_member, Color::Rgb(71, 97, 133)); + assert_eq!(theme.syntax.attribute, Color::Rgb(71, 97, 133)); + assert_eq!(theme.diff.added_bg, Color::Rgb(240, 253, 244)); + assert_eq!(theme.diff.added_gutter_bg, Color::Rgb(226, 252, 237)); + assert_eq!(theme.diff.deleted_gutter_bg, Color::Rgb(252, 208, 204)); + } + + #[test] + fn test_0github_dark_palette() { + let theme = parsed_theme("0github-dark"); + + assert_eq!(theme.mode, ThemeMode::Dark); + assert_eq!(theme.syntax.comment, Color::Rgb(117, 136, 163)); + assert_eq!(theme.syntax.keyword, Color::Rgb(170, 125, 232)); + assert_eq!(theme.syntax.string, Color::Rgb(83, 198, 136)); + assert_eq!(theme.syntax.function, Color::Rgb(94, 190, 237)); + assert_eq!(theme.syntax.variable_member, Color::Rgb(135, 161, 197)); + assert_eq!(theme.syntax.attribute, Color::Rgb(135, 161, 197)); + assert_eq!(theme.diff.added_gutter_bg, Color::Rgb(34, 197, 94)); + assert_eq!(theme.diff.added_gutter_fg, Color::Rgb(134, 239, 172)); + assert_eq!(theme.diff.deleted_gutter_bg, Color::Rgb(248, 81, 73)); + } +} From 4b97d88928d650b62479db0e56b9e9755a4cab54 Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Tue, 24 Mar 2026 15:29:19 -0700 Subject: [PATCH 2/2] chore: remove internal plan artifact --- .../plans/2026-03-22-0github-theme.md | 61 ------------------- 1 file changed, 61 deletions(-) delete mode 100644 docs/superpowers/plans/2026-03-22-0github-theme.md diff --git a/docs/superpowers/plans/2026-03-22-0github-theme.md b/docs/superpowers/plans/2026-03-22-0github-theme.md deleted file mode 100644 index db5a10fd..00000000 --- a/docs/superpowers/plans/2026-03-22-0github-theme.md +++ /dev/null @@ -1,61 +0,0 @@ -# 0github Theme Implementation Plan - -> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Add 0github-inspired diff/syntax highlighting to the `lumen` fork in a way that is testable, selectable, and consistent with the reference palette in `manaflow`. - -**Architecture:** Extend `lumen`'s existing `ThemePreset` system rather than changing the diff renderer. The theme layer already owns syntax colors, diff backgrounds, CLI parsing, and environment/config selection, so a 0github preset can be introduced with narrow changes and verified through unit tests. - -**Tech Stack:** Rust, ratatui, clap, tree-sitter, cargo test - ---- - -### Task 1: Add failing theme tests - -**Files:** -- Modify: `src/command/diff/theme.rs` -- Reference: `/Users/lawrence/fun/manaflow/apps/www/app/globals.css` - -- [ ] **Step 1: Write the failing test** - -Add tests that assert: -- `ThemePreset::from_str("0github-light")` and `ThemePreset::from_str("0github-dark")` parse successfully. -- The resulting theme syntax and diff colors match the 0github palette copied from `manaflow`. - -- [ ] **Step 2: Run test to verify it fails** - -Run: `cargo test theme::tests::test_0github_theme_parsing theme::tests::test_0github_light_palette theme::tests::test_0github_dark_palette` -Expected: FAIL because the new preset does not exist yet. - -### Task 2: Implement 0github presets - -**Files:** -- Modify: `src/command/diff/theme.rs` - -- [ ] **Step 1: Add minimal implementation** - -Add: -- `ThemePreset::ZeroGithubLight` -- `ThemePreset::ZeroGithubDark` -- `FromStr` aliases for `0github-light`, `0github-dark`, and a reasonable shorthand -- `Theme::zero_github_light()` and `Theme::zero_github_dark()` -- `Theme::from_preset(...)` match arms - -- [ ] **Step 2: Run test to verify it passes** - -Run: `cargo test theme::tests::test_0github_theme_parsing theme::tests::test_0github_light_palette theme::tests::test_0github_dark_palette` -Expected: PASS - -### Task 3: Document the preset - -**Files:** -- Modify: `README.md` - -- [ ] **Step 1: Update user-facing docs** - -Add the new theme names to the diff theme examples and available theme table. - -- [ ] **Step 2: Run lightweight verification** - -Run: `cargo test theme::tests::test_0github_theme_parsing theme::tests::test_0github_light_palette theme::tests::test_0github_dark_palette` -Expected: PASS