- 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
9.2 KiB
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 textchalk.gray(28 occurrences) - Used for borders, metadata, dimmed contentchalk.bold(20 occurrences) - Used for emphasischalk.blue(12 occurrences) - Used for selections, borders, linkschalk.cyan(9 occurrences) - Used for primary UI elements (logo, list bullets, code)chalk.red(7 occurrences) - Used for errors, stderr outputchalk.green(6 occurrences) - Used for success, stdout outputchalk.yellow(3 occurrences) - Used for headings in markdownchalk.bgRgb(6 occurrences) - Used for custom backgrounds in Text/Markdown
Files Using Colors
coding-agent Package
- main.ts - CLI output messages
- tui/assistant-message.ts - Thinking text (gray italic), errors (red), aborted (red)
- tui/dynamic-border.ts - Configurable border color (default blue)
- tui/footer.ts - Stats and pwd (gray)
- tui/model-selector.ts - Borders (blue), selection arrow (blue), provider badge (gray), checkmark (green)
- tui/session-selector.ts - Border (blue), selection cursor (blue), metadata (dim)
- tui/thinking-selector.ts - Border (blue)
- tui/tool-execution.ts - stdout (green), stderr (red), dim lines (dim), line numbers
- tui/tui-renderer.ts - Logo (bold cyan), instructions (dim/gray)
tui Package
- components/editor.ts - Horizontal border (gray)
- components/loader.ts - Spinner (cyan), message (dim)
- 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)
- components/select-list.ts - No matches (gray), selection arrow (blue), selected item (blue), description (gray)
- components/text.ts - Custom bgRgb support
Color System Architecture
Current Implementation
- Colors are hardcoded using
chalkdirectly - 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)
- markdown.ts - Update all color calls to use theme
- tool-execution.ts - stdout/stderr colors
- assistant-message.ts - Error messages
- tui-renderer.ts - Logo and instructions
- footer.ts - Stats display
Medium Priority (UI chrome)
- dynamic-border.ts - Accept theme parameter
- model-selector.ts - Selection colors
- session-selector.ts - Selection colors
- thinking-selector.ts - Border colors
- select-list.ts - Selection colors
- loader.ts - Spinner color
- editor.ts - Border color
Low Priority (CLI output)
- 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
- Phase 1: Create theme infrastructure (theme.ts, types, built-in themes)
- Phase 2: Update TUI package components (markdown, text, loader, editor, select-list)
- Phase 3: Update coding-agent TUI components (all tui/*.ts files)
- Phase 4: Add theme selector and persistence
- Phase 5: Update CLI output in main.ts (optional, low priority)
Testing Plan
- Test both themes in terminals with light backgrounds
- Test both themes in terminals with dark backgrounds
- Verify theme switching works at runtime via
/theme - Verify theme persists across sessions via settings.json
- Test all components for readability in both themes
Open Questions
- Should we support custom user themes loaded from a JSON file?
- Should we auto-detect terminal background color and choose theme automatically?
- Should theme apply to background colors used in Text/Markdown components?
- 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