Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions packages/core/src/theme/themeRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
export interface ColorPalette {
primary: string;
secondary: string;
background: string;
text: string;
border?: string;
accent?: string;
}

export interface ThemeDefinition {
name: string;
palette: ColorPalette;
}
Comment on lines +1 to +13

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Add color-mode metadata and fallback resolution.

ColorPalette stores every color as an undifferentiated string. The manager cannot resolve RGB values to 256-color or ANSI 16-color values when terminal support is limited.

Use a discriminated color value type and add a capability-aware resolver. Test RGB, indexed, and ANSI fallback behavior.

Based on PR objectives, the registry must support 16-color ANSI, 256-color, TrueColor, and non-TrueColor fallback behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/theme/themeRegistry.ts` around lines 1 - 13, Replace the
undifferentiated string fields in ColorPalette with a discriminated color value
type carrying RGB, indexed, or ANSI metadata, and add a capability-aware
resolver in the theme registry. Resolve colors appropriately for TrueColor,
256-color, and 16-color ANSI terminals, with non-TrueColor fallbacks preserving
usable output. Add tests covering RGB, indexed, ANSI, and each fallback path.


/**
* ThemeRegistryManager - Extensible Color Palette & Dynamic Theme Registry Subsystem (#3334).
*/
export class ThemeRegistryManager {
private themes: Map<string, ThemeDefinition> = new Map();
private activeThemeName: string = 'default';

constructor() {
this.registerTheme({
name: 'default',
palette: {
primary: '#3b82f6',
secondary: '#64748b',
background: '#0f172a',
text: '#f8fafc',
border: '#334155',
accent: '#eab308',
},
});
}
Comment on lines +22 to +34

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Register the required built-in presets.

The constructor registers only default. It does not register the required Dracula, Nord, Monokai, and Cyberpunk presets. listThemes() and setActiveTheme() cannot use those themes until a caller adds them manually.

Register the four presets during initialization and add behavior tests for each preset.

Based on PR objectives, these four presets are required defaults.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/theme/themeRegistry.ts` around lines 22 - 34, Update the
ThemeRegistry constructor to register the required built-in Dracula, Nord,
Monokai, and Cyberpunk presets alongside default, using each preset’s defined
name and palette. Add behavior tests confirming each preset is available through
listThemes() and can be selected with setActiveTheme().


registerTheme(theme: ThemeDefinition): void {
if (!theme || !theme.name) return;
this.themes.set(theme.name, theme);
}

setActiveTheme(name: string): boolean {
if (this.themes.has(name)) {
this.activeThemeName = name;
return true;
}
return false;
}

getActiveTheme(): ThemeDefinition {
return this.themes.get(this.activeThemeName) || Array.from(this.themes.values())[0];
}

extendPalette(themeName: string, customPalette: Partial<ColorPalette>): void {
const existing = this.themes.get(themeName);
if (existing) {
existing.palette = {
...existing.palette,
...customPalette,
};
Comment on lines +36 to +59

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate runtime theme data before storage or merge.

registerTheme() checks only theme.name. A parsed external JSON theme can omit required palette fields or provide non-string values. extendPalette() can also overwrite a required color with an invalid value. The registry then returns data that violates ThemeDefinition.

Validate required palette keys and color value formats before this.themes.set() and before merging customPalette. Reject invalid input with a result that callers can handle.

Based on PR objectives, registerTheme() must support external JSON themes through a JSON theme schema.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/theme/themeRegistry.ts` around lines 36 - 59, The
ThemeRegistry methods must validate runtime theme data against the JSON theme
schema before storage or mutation. Update registerTheme to reject themes lacking
required palette keys or containing non-string/invalid color values, and update
extendPalette to validate every customPalette entry before merging so existing
valid data is preserved on failure; return a caller-handleable success/failure
result from both operations and keep this.themes.set and palette updates limited
to valid input.

}
}

listThemes(): string[] {
return Array.from(this.themes.keys());
}
}
38 changes: 38 additions & 0 deletions packages/core/test/themeRegistry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { describe, it, expect, beforeEach } from 'bun:test';
import { ThemeRegistryManager } from '../src/theme/themeRegistry';

describe('ThemeRegistryManager Unit Tests', () => {
let registry: ThemeRegistryManager;

beforeEach(() => {
registry = new ThemeRegistryManager();
});

it('should initialize with default theme', () => {
const active = registry.getActiveTheme();
expect(active.name).toBe('default');
expect(active.palette.primary).toBe('#3b82f6');
});

it('should register and switch to custom themes dynamically', () => {
registry.registerTheme({
name: 'dracula',
palette: {
primary: '#bd93f9',
secondary: '#6272a4',
background: '#282a36',
text: '#f8f8f2',
},
});

expect(registry.listThemes()).toContain('dracula');
const switched = registry.setActiveTheme('dracula');
expect(switched).toBe(true);
expect(registry.getActiveTheme().palette.primary).toBe('#bd93f9');
});

it('should allow extending existing palette colors', () => {
registry.extendPalette('default', { primary: '#ff0000' });
expect(registry.getActiveTheme().palette.primary).toBe('#ff0000');
});
});
Loading