co-mono/packages/coding-agent/docs/themes.md
Mario Zechner bc670bc63c Fix lockstep versioning and improve documentation
- Sync all packages to version 0.7.7
- Rewrite sync-versions.js to handle ALL inter-package dependencies automatically
- Fix web-ui dependency on pi-ai (was 0.6.0, now 0.7.7)
- Move agent fix changelog entry to coding-agent CHANGELOG
- Remove redundant agent CHANGELOG.md
- Improve README.md with clearer lockstep versioning docs
- Add /changelog command to display full changelog in TUI (newest last)
- Fix changelog description (not a scrollable viewer, just displays in chat)
- Update CHANGELOG for 0.7.7 release
2025-11-13 23:37:43 +01:00

9.2 KiB
Raw Blame History

Theme System Analysis

Problem Statement

Issue #7: In terminals with light backgrounds, some outputs use dark colors that are hard to read. We need a theme system that allows users to choose between light and dark themes.

Current Color Usage Analysis

Color Usage Statistics

Total chalk color calls: 132 across 14 files

Most frequent colors:

  • chalk.dim (48 occurrences) - Used for secondary text
  • chalk.gray (28 occurrences) - Used for borders, metadata, dimmed content
  • chalk.bold (20 occurrences) - Used for emphasis
  • chalk.blue (12 occurrences) - Used for selections, borders, links
  • chalk.cyan (9 occurrences) - Used for primary UI elements (logo, list bullets, code)
  • chalk.red (7 occurrences) - Used for errors, stderr output
  • chalk.green (6 occurrences) - Used for success, stdout output
  • chalk.yellow (3 occurrences) - Used for headings in markdown
  • chalk.bgRgb (6 occurrences) - Used for custom backgrounds in Text/Markdown

Files Using Colors

coding-agent Package

  1. main.ts - CLI output messages
  2. tui/assistant-message.ts - Thinking text (gray italic), errors (red), aborted (red)
  3. tui/dynamic-border.ts - Configurable border color (default blue)
  4. tui/footer.ts - Stats and pwd (gray)
  5. tui/model-selector.ts - Borders (blue), selection arrow (blue), provider badge (gray), checkmark (green)
  6. tui/session-selector.ts - Border (blue), selection cursor (blue), metadata (dim)
  7. tui/thinking-selector.ts - Border (blue)
  8. tui/tool-execution.ts - stdout (green), stderr (red), dim lines (dim), line numbers
  9. tui/tui-renderer.ts - Logo (bold cyan), instructions (dim/gray)

tui Package

  1. components/editor.ts - Horizontal border (gray)
  2. components/loader.ts - Spinner (cyan), message (dim)
  3. components/markdown.ts - Complex color system:
    • H1 headings: bold.underline.yellow
    • H2 headings: bold.yellow
    • H3+ headings: bold
    • Code blocks: gray (delimiters), dim (indent), green (code)
    • List bullets: cyan
    • Blockquotes: gray (pipe), italic (text)
    • Horizontal rules: gray
    • Inline code: gray (backticks), cyan (code)
    • Links: underline.blue (text), gray (URL)
    • Strikethrough: strikethrough
    • Tables: bold (headers)
  4. components/select-list.ts - No matches (gray), selection arrow (blue), selected item (blue), description (gray)
  5. components/text.ts - Custom bgRgb support

Color System Architecture

Current Implementation

  • Colors are hardcoded using chalk directly
  • No centralized theme management
  • No way to switch themes at runtime
  • Some components accept color parameters (e.g., DynamicBorder, Text, Markdown)

Markdown Component Color System

The Markdown component has a Color type enum:

type Color = "black" | "red" | "green" | "yellow" | "blue" | "magenta" | "cyan" | "white" | "gray" | 
             "bgBlack" | "bgRed" | "bgGreen" | "bgYellow" | "bgBlue" | "bgMagenta" | "bgCyan" | "bgWhite" | "bgGray"

It accepts optional bgColor and fgColor parameters, plus customBgRgb.

Proposed Solution

Theme Structure

Create a centralized theme system with semantic color names:

interface Theme {
  name: string;
  
  // UI Chrome
  border: ChalkFunction;
  selection: ChalkFunction;
  selectionText: ChalkFunction;
  
  // Text hierarchy
  primary: ChalkFunction;
  secondary: ChalkFunction;
  dim: ChalkFunction;
  
  // Semantic colors
  error: ChalkFunction;
  success: ChalkFunction;
  warning: ChalkFunction;
  info: ChalkFunction;
  
  // Code/output
  code: ChalkFunction;
  codeDelimiter: ChalkFunction;
  stdout: ChalkFunction;
  stderr: ChalkFunction;
  
  // Markdown specific
  heading1: ChalkFunction;
  heading2: ChalkFunction;
  heading3: ChalkFunction;
  link: ChalkFunction;
  linkUrl: ChalkFunction;
  listBullet: ChalkFunction;
  blockquote: ChalkFunction;
  blockquotePipe: ChalkFunction;
  inlineCode: ChalkFunction;
  inlineCodeDelimiter: ChalkFunction;
  
  // Backgrounds (optional, for components like Text/Markdown)
  backgroundRgb?: { r: number; g: number; b: number };
}

type ChalkFunction = (str: string) => string;

Built-in Themes

Dark Theme (current default)

const darkTheme: Theme = {
  name: "dark",
  border: chalk.blue,
  selection: chalk.blue,
  selectionText: chalk.blue,
  primary: (s) => s, // no color
  secondary: chalk.gray,
  dim: chalk.dim,
  error: chalk.red,
  success: chalk.green,
  warning: chalk.yellow,
  info: chalk.cyan,
  code: chalk.green,
  codeDelimiter: chalk.gray,
  stdout: chalk.green,
  stderr: chalk.red,
  heading1: chalk.bold.underline.yellow,
  heading2: chalk.bold.yellow,
  heading3: chalk.bold,
  link: chalk.underline.blue,
  linkUrl: chalk.gray,
  listBullet: chalk.cyan,
  blockquote: chalk.italic,
  blockquotePipe: chalk.gray,
  inlineCode: chalk.cyan,
  inlineCodeDelimiter: chalk.gray,
};

Light Theme

const lightTheme: Theme = {
  name: "light",
  border: chalk.blue,
  selection: chalk.blue,
  selectionText: chalk.blue.bold,
  primary: (s) => s,
  secondary: chalk.gray,
  dim: chalk.gray, // Don't use chalk.dim on light backgrounds
  error: chalk.red.bold,
  success: chalk.green.bold,
  warning: chalk.yellow.bold,
  info: chalk.cyan.bold,
  code: chalk.green.bold,
  codeDelimiter: chalk.gray,
  stdout: chalk.green.bold,
  stderr: chalk.red.bold,
  heading1: chalk.bold.underline.blue,
  heading2: chalk.bold.blue,
  heading3: chalk.bold,
  link: chalk.underline.blue,
  linkUrl: chalk.blue,
  listBullet: chalk.blue.bold,
  blockquote: chalk.italic,
  blockquotePipe: chalk.gray,
  inlineCode: chalk.blue.bold,
  inlineCodeDelimiter: chalk.gray,
};

Implementation Plan

1. Create Theme Module

Location: packages/tui/src/theme.ts

export interface Theme { ... }
export const darkTheme: Theme = { ... };
export const lightTheme: Theme = { ... };
export const themes = { dark: darkTheme, light: lightTheme };

let currentTheme: Theme = darkTheme;

export function setTheme(theme: Theme): void {
  currentTheme = theme;
}

export function getTheme(): Theme {
  return currentTheme;
}

2. Update Settings Manager

Location: packages/coding-agent/src/settings-manager.ts

Add theme field to Settings interface:

export interface Settings {
  lastChangelogVersion?: string;
  theme?: "dark" | "light";
}

3. Create Theme Selector Component

Location: packages/coding-agent/src/tui/theme-selector.ts

Similar to ModelSelector and ThinkingSelector, create a TUI component for selecting themes.

4. Refactor Color Usage

Replace all hardcoded chalk.* calls with theme.*:

Example - Before:

lines.push(chalk.blue("─".repeat(width)));
const cursor = chalk.blue(" ");

Example - After:

const theme = getTheme();
lines.push(theme.border("─".repeat(width)));
const cursor = theme.selection(" ");

5. Update Components

High Priority (User-facing content issues)
  1. markdown.ts - Update all color calls to use theme
  2. tool-execution.ts - stdout/stderr colors
  3. assistant-message.ts - Error messages
  4. tui-renderer.ts - Logo and instructions
  5. footer.ts - Stats display
Medium Priority (UI chrome)
  1. dynamic-border.ts - Accept theme parameter
  2. model-selector.ts - Selection colors
  3. session-selector.ts - Selection colors
  4. thinking-selector.ts - Border colors
  5. select-list.ts - Selection colors
  6. loader.ts - Spinner color
  7. editor.ts - Border color
Low Priority (CLI output)
  1. main.ts - CLI messages

6. Add Theme Slash Command

Location: packages/coding-agent/src/tui/tui-renderer.ts

Add /theme command similar to /model and /thinking.

7. Initialize Theme on Startup

Location: packages/coding-agent/src/main.ts

// Load theme from settings
const settingsManager = new SettingsManager();
const themeName = settingsManager.getTheme() || "dark";
const theme = themes[themeName] || darkTheme;
setTheme(theme);

Migration Strategy

  1. Phase 1: Create theme infrastructure (theme.ts, types, built-in themes)
  2. Phase 2: Update TUI package components (markdown, text, loader, editor, select-list)
  3. Phase 3: Update coding-agent TUI components (all tui/*.ts files)
  4. Phase 4: Add theme selector and persistence
  5. Phase 5: Update CLI output in main.ts (optional, low priority)

Testing Plan

  1. Test both themes in terminals with light backgrounds
  2. Test both themes in terminals with dark backgrounds
  3. Verify theme switching works at runtime via /theme
  4. Verify theme persists across sessions via settings.json
  5. Test all components for readability in both themes

Open Questions

  1. Should we support custom user themes loaded from a JSON file?
  2. Should we auto-detect terminal background color and choose theme automatically?
  3. Should theme apply to background colors used in Text/Markdown components?
  4. Do we need more than two themes initially?

Breaking Changes

None - the default theme will remain "dark" matching current behavior.

Performance Considerations

  • Theme getter is called frequently (on every render)
  • Should be a simple variable access, not a function call chain
  • Consider caching theme functions if performance becomes an issue