refactor: consolidate theme files and remove manual schema (#1478)

- Delete theme-schema.json (335 lines): redundant with the TypeBox
  schema already defined in theme.ts, only referenced via $schema URLs
  in the JSON files for editor autocomplete.
- Delete dark.json (85 lines) and light.json (84 lines): move built-in
  theme definitions into themes.ts as TypeScript objects, eliminating
  runtime filesystem reads and the getThemesDir() dependency.
- Export ThemeJson type from theme.ts so themes.ts can reference it.
- Net reduction: ~319 lines removed.
This commit is contained in:
Juan Francisco Lebrero 2026-03-19 18:35:56 -03:00 committed by GitHub
parent aa85e99dc0
commit 988ef61488
5 changed files with 203 additions and 522 deletions

View file

@ -1,85 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
"name": "dark",
"vars": {
"cyan": "#00d7ff",
"blue": "#5f87ff",
"green": "#b5bd68",
"red": "#cc6666",
"yellow": "#ffff00",
"gray": "#808080",
"dimGray": "#666666",
"darkGray": "#505050",
"accent": "#8abeb7",
"selectedBg": "#3a3a4a",
"userMsgBg": "#343541",
"toolPendingBg": "#282832",
"toolSuccessBg": "#283228",
"toolErrorBg": "#3c2828",
"customMsgBg": "#2d2838"
},
"colors": {
"accent": "accent",
"border": "blue",
"borderAccent": "cyan",
"borderMuted": "darkGray",
"success": "green",
"error": "red",
"warning": "yellow",
"muted": "gray",
"dim": "dimGray",
"text": "",
"thinkingText": "gray",
"selectedBg": "selectedBg",
"userMessageBg": "userMsgBg",
"userMessageText": "",
"customMessageBg": "customMsgBg",
"customMessageText": "",
"customMessageLabel": "#9575cd",
"toolPendingBg": "toolPendingBg",
"toolSuccessBg": "toolSuccessBg",
"toolErrorBg": "toolErrorBg",
"toolTitle": "",
"toolOutput": "gray",
"mdHeading": "#f0c674",
"mdLink": "#81a2be",
"mdLinkUrl": "dimGray",
"mdCode": "accent",
"mdCodeBlock": "green",
"mdCodeBlockBorder": "gray",
"mdQuote": "gray",
"mdQuoteBorder": "gray",
"mdHr": "gray",
"mdListBullet": "accent",
"toolDiffAdded": "green",
"toolDiffRemoved": "red",
"toolDiffContext": "gray",
"syntaxComment": "#6A9955",
"syntaxKeyword": "#569CD6",
"syntaxFunction": "#DCDCAA",
"syntaxVariable": "#9CDCFE",
"syntaxString": "#CE9178",
"syntaxNumber": "#B5CEA8",
"syntaxType": "#4EC9B0",
"syntaxOperator": "#D4D4D4",
"syntaxPunctuation": "#D4D4D4",
"thinkingOff": "darkGray",
"thinkingMinimal": "#6e6e6e",
"thinkingLow": "#5f87af",
"thinkingMedium": "#81a2be",
"thinkingHigh": "#b294bb",
"thinkingXhigh": "#d183e8",
"bashMode": "green"
},
"export": {
"pageBg": "#18181e",
"cardBg": "#1e1e24",
"infoBg": "#3c3728"
}
}

View file

@ -1,84 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
"name": "light",
"vars": {
"teal": "#5a8080",
"blue": "#547da7",
"green": "#588458",
"red": "#aa5555",
"yellow": "#9a7326",
"mediumGray": "#6c6c6c",
"dimGray": "#767676",
"lightGray": "#b0b0b0",
"selectedBg": "#d0d0e0",
"userMsgBg": "#e8e8e8",
"toolPendingBg": "#e8e8f0",
"toolSuccessBg": "#e8f0e8",
"toolErrorBg": "#f0e8e8",
"customMsgBg": "#ede7f6"
},
"colors": {
"accent": "teal",
"border": "blue",
"borderAccent": "teal",
"borderMuted": "lightGray",
"success": "green",
"error": "red",
"warning": "yellow",
"muted": "mediumGray",
"dim": "dimGray",
"text": "",
"thinkingText": "mediumGray",
"selectedBg": "selectedBg",
"userMessageBg": "userMsgBg",
"userMessageText": "",
"customMessageBg": "customMsgBg",
"customMessageText": "",
"customMessageLabel": "#7e57c2",
"toolPendingBg": "toolPendingBg",
"toolSuccessBg": "toolSuccessBg",
"toolErrorBg": "toolErrorBg",
"toolTitle": "",
"toolOutput": "mediumGray",
"mdHeading": "yellow",
"mdLink": "blue",
"mdLinkUrl": "dimGray",
"mdCode": "teal",
"mdCodeBlock": "green",
"mdCodeBlockBorder": "mediumGray",
"mdQuote": "mediumGray",
"mdQuoteBorder": "mediumGray",
"mdHr": "mediumGray",
"mdListBullet": "green",
"toolDiffAdded": "green",
"toolDiffRemoved": "red",
"toolDiffContext": "mediumGray",
"syntaxComment": "#008000",
"syntaxKeyword": "#0000FF",
"syntaxFunction": "#795E26",
"syntaxVariable": "#001080",
"syntaxString": "#A31515",
"syntaxNumber": "#098658",
"syntaxType": "#267F99",
"syntaxOperator": "#000000",
"syntaxPunctuation": "#000000",
"thinkingOff": "lightGray",
"thinkingMinimal": "#767676",
"thinkingLow": "blue",
"thinkingMedium": "teal",
"thinkingHigh": "#875f87",
"thinkingXhigh": "#8b008b",
"bashMode": "green"
},
"export": {
"pageBg": "#f8f8f8",
"cardBg": "#ffffff",
"infoBg": "#fffae6"
}
}

View file

@ -1,335 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Pi Coding Agent Theme",
"description": "Theme schema for Pi coding agent",
"type": "object",
"required": ["name", "colors"],
"properties": {
"$schema": {
"type": "string",
"description": "JSON schema reference"
},
"name": {
"type": "string",
"description": "Theme name"
},
"vars": {
"type": "object",
"description": "Reusable color variables",
"additionalProperties": {
"oneOf": [
{
"type": "string",
"description": "Hex color (#RRGGBB), variable reference, or empty string for terminal default"
},
{
"type": "integer",
"minimum": 0,
"maximum": 255,
"description": "256-color palette index (0-255)"
}
]
}
},
"colors": {
"type": "object",
"description": "Theme color definitions (all required)",
"required": [
"accent",
"border",
"borderAccent",
"borderMuted",
"success",
"error",
"warning",
"muted",
"dim",
"text",
"thinkingText",
"selectedBg",
"userMessageBg",
"userMessageText",
"customMessageBg",
"customMessageText",
"customMessageLabel",
"toolPendingBg",
"toolSuccessBg",
"toolErrorBg",
"toolTitle",
"toolOutput",
"mdHeading",
"mdLink",
"mdLinkUrl",
"mdCode",
"mdCodeBlock",
"mdCodeBlockBorder",
"mdQuote",
"mdQuoteBorder",
"mdHr",
"mdListBullet",
"toolDiffAdded",
"toolDiffRemoved",
"toolDiffContext",
"syntaxComment",
"syntaxKeyword",
"syntaxFunction",
"syntaxVariable",
"syntaxString",
"syntaxNumber",
"syntaxType",
"syntaxOperator",
"syntaxPunctuation",
"thinkingOff",
"thinkingMinimal",
"thinkingLow",
"thinkingMedium",
"thinkingHigh",
"thinkingXhigh",
"bashMode"
],
"properties": {
"accent": {
"$ref": "#/$defs/colorValue",
"description": "Primary accent color (logo, selected items, cursor)"
},
"border": {
"$ref": "#/$defs/colorValue",
"description": "Normal borders"
},
"borderAccent": {
"$ref": "#/$defs/colorValue",
"description": "Highlighted borders"
},
"borderMuted": {
"$ref": "#/$defs/colorValue",
"description": "Subtle borders"
},
"success": {
"$ref": "#/$defs/colorValue",
"description": "Success states"
},
"error": {
"$ref": "#/$defs/colorValue",
"description": "Error states"
},
"warning": {
"$ref": "#/$defs/colorValue",
"description": "Warning states"
},
"muted": {
"$ref": "#/$defs/colorValue",
"description": "Secondary/dimmed text"
},
"dim": {
"$ref": "#/$defs/colorValue",
"description": "Very dimmed text (more subtle than muted)"
},
"text": {
"$ref": "#/$defs/colorValue",
"description": "Default text color (usually empty string)"
},
"thinkingText": {
"$ref": "#/$defs/colorValue",
"description": "Thinking block text color"
},
"selectedBg": {
"$ref": "#/$defs/colorValue",
"description": "Selected item background"
},
"userMessageBg": {
"$ref": "#/$defs/colorValue",
"description": "User message background"
},
"userMessageText": {
"$ref": "#/$defs/colorValue",
"description": "User message text color"
},
"customMessageBg": {
"$ref": "#/$defs/colorValue",
"description": "Custom message background (hook-injected messages)"
},
"customMessageText": {
"$ref": "#/$defs/colorValue",
"description": "Custom message text color"
},
"customMessageLabel": {
"$ref": "#/$defs/colorValue",
"description": "Custom message type label color"
},
"toolPendingBg": {
"$ref": "#/$defs/colorValue",
"description": "Tool execution box (pending state)"
},
"toolSuccessBg": {
"$ref": "#/$defs/colorValue",
"description": "Tool execution box (success state)"
},
"toolErrorBg": {
"$ref": "#/$defs/colorValue",
"description": "Tool execution box (error state)"
},
"toolTitle": {
"$ref": "#/$defs/colorValue",
"description": "Tool execution box title color"
},
"toolOutput": {
"$ref": "#/$defs/colorValue",
"description": "Tool execution box output text color"
},
"mdHeading": {
"$ref": "#/$defs/colorValue",
"description": "Markdown heading text"
},
"mdLink": {
"$ref": "#/$defs/colorValue",
"description": "Markdown link text"
},
"mdLinkUrl": {
"$ref": "#/$defs/colorValue",
"description": "Markdown link URL"
},
"mdCode": {
"$ref": "#/$defs/colorValue",
"description": "Markdown inline code"
},
"mdCodeBlock": {
"$ref": "#/$defs/colorValue",
"description": "Markdown code block content"
},
"mdCodeBlockBorder": {
"$ref": "#/$defs/colorValue",
"description": "Markdown code block fences"
},
"mdQuote": {
"$ref": "#/$defs/colorValue",
"description": "Markdown blockquote text"
},
"mdQuoteBorder": {
"$ref": "#/$defs/colorValue",
"description": "Markdown blockquote border"
},
"mdHr": {
"$ref": "#/$defs/colorValue",
"description": "Markdown horizontal rule"
},
"mdListBullet": {
"$ref": "#/$defs/colorValue",
"description": "Markdown list bullets/numbers"
},
"toolDiffAdded": {
"$ref": "#/$defs/colorValue",
"description": "Added lines in tool diffs"
},
"toolDiffRemoved": {
"$ref": "#/$defs/colorValue",
"description": "Removed lines in tool diffs"
},
"toolDiffContext": {
"$ref": "#/$defs/colorValue",
"description": "Context lines in tool diffs"
},
"syntaxComment": {
"$ref": "#/$defs/colorValue",
"description": "Syntax highlighting: comments"
},
"syntaxKeyword": {
"$ref": "#/$defs/colorValue",
"description": "Syntax highlighting: keywords"
},
"syntaxFunction": {
"$ref": "#/$defs/colorValue",
"description": "Syntax highlighting: function names"
},
"syntaxVariable": {
"$ref": "#/$defs/colorValue",
"description": "Syntax highlighting: variable names"
},
"syntaxString": {
"$ref": "#/$defs/colorValue",
"description": "Syntax highlighting: string literals"
},
"syntaxNumber": {
"$ref": "#/$defs/colorValue",
"description": "Syntax highlighting: number literals"
},
"syntaxType": {
"$ref": "#/$defs/colorValue",
"description": "Syntax highlighting: type names"
},
"syntaxOperator": {
"$ref": "#/$defs/colorValue",
"description": "Syntax highlighting: operators"
},
"syntaxPunctuation": {
"$ref": "#/$defs/colorValue",
"description": "Syntax highlighting: punctuation"
},
"thinkingOff": {
"$ref": "#/$defs/colorValue",
"description": "Thinking level border: off"
},
"thinkingMinimal": {
"$ref": "#/$defs/colorValue",
"description": "Thinking level border: minimal"
},
"thinkingLow": {
"$ref": "#/$defs/colorValue",
"description": "Thinking level border: low"
},
"thinkingMedium": {
"$ref": "#/$defs/colorValue",
"description": "Thinking level border: medium"
},
"thinkingHigh": {
"$ref": "#/$defs/colorValue",
"description": "Thinking level border: high"
},
"thinkingXhigh": {
"$ref": "#/$defs/colorValue",
"description": "Thinking level border: xhigh (OpenAI codex-max only)"
},
"bashMode": {
"$ref": "#/$defs/colorValue",
"description": "Editor border color in bash mode"
}
},
"additionalProperties": false
},
"export": {
"type": "object",
"description": "Optional colors for HTML export (defaults derived from userMessageBg if not specified)",
"properties": {
"pageBg": {
"$ref": "#/$defs/colorValue",
"description": "Page background color"
},
"cardBg": {
"$ref": "#/$defs/colorValue",
"description": "Card/container background color"
},
"infoBg": {
"$ref": "#/$defs/colorValue",
"description": "Info sections background (system prompt, notices)"
}
},
"additionalProperties": false
}
},
"additionalProperties": false,
"$defs": {
"colorValue": {
"oneOf": [
{
"type": "string",
"description": "Hex color (#RRGGBB), variable reference, or empty string for terminal default"
},
{
"type": "integer",
"minimum": 0,
"maximum": 255,
"description": "256-color palette index (0-255)"
}
]
}
}
}

View file

@ -9,7 +9,8 @@ import {
supportsLanguage,
type HighlightColors,
} from "@gsd/native";
import { getCustomThemesDir, getThemesDir } from "../../../config.js";
import { getCustomThemesDir } from "../../../config.js";
import { builtinThemes } from "./themes.js";
// Issue #453: native preview highlighting can wedge the entire interactive
// session after a successful file tool. Keep the safer plain-text path as the
@ -100,7 +101,7 @@ const ThemeJsonSchema = Type.Object({
),
});
type ThemeJson = Static<typeof ThemeJsonSchema>;
export type ThemeJson = Static<typeof ThemeJsonSchema>;
const validateThemeJson = TypeCompiler.Compile(ThemeJsonSchema);
@ -450,19 +451,8 @@ export class Theme {
// Theme Loading
// ============================================================================
let BUILTIN_THEMES: Record<string, ThemeJson> | undefined;
function getBuiltinThemes(): Record<string, ThemeJson> {
if (!BUILTIN_THEMES) {
const themesDir = getThemesDir();
const darkPath = path.join(themesDir, "dark.json");
const lightPath = path.join(themesDir, "light.json");
BUILTIN_THEMES = {
dark: JSON.parse(fs.readFileSync(darkPath, "utf-8")) as ThemeJson,
light: JSON.parse(fs.readFileSync(lightPath, "utf-8")) as ThemeJson,
};
}
return BUILTIN_THEMES;
return builtinThemes;
}
export function getAvailableThemes(): string[] {
@ -488,13 +478,12 @@ export interface ThemeInfo {
}
export function getAvailableThemesWithPaths(): ThemeInfo[] {
const themesDir = getThemesDir();
const customThemesDir = getCustomThemesDir();
const result: ThemeInfo[] = [];
// Built-in themes
// Built-in themes (embedded in code, no file path)
for (const name of Object.keys(getBuiltinThemes())) {
result.push({ name, path: path.join(themesDir, `${name}.json`) });
result.push({ name, path: undefined });
}
// Custom themes
@ -539,7 +528,7 @@ function parseThemeJson(label: string, json: unknown): ThemeJson {
errorMessage += "\nMissing required color tokens:\n";
errorMessage += missingColors.map((c) => ` - ${c}`).join("\n");
errorMessage += '\n\nPlease add these colors to your theme\'s "colors" object.';
errorMessage += "\nSee the built-in themes (dark.json, light.json) for reference values.";
errorMessage += "\nSee the built-in dark/light themes for reference values.";
}
if (otherErrors.length > 0) {
errorMessage += `\n\nOther errors:\n${otherErrors.join("\n")}`;

View file

@ -0,0 +1,196 @@
/**
* Built-in theme definitions.
*
* Each theme is a self-contained record of color values. Variable references
* (e.g. "accent") are resolved against the `vars` map at load time by the
* theme engine in theme.ts.
*
* To add a new built-in theme, add an entry to `builtinThemes` below.
*/
// Re-use the ThemeJson type from the schema defined in theme.ts.
// We import only the type to avoid circular runtime dependencies.
import type { ThemeJson } from "./theme.js";
// ---------------------------------------------------------------------------
// Dark theme
// ---------------------------------------------------------------------------
const dark: ThemeJson = {
name: "dark",
vars: {
cyan: "#00d7ff",
blue: "#5f87ff",
green: "#b5bd68",
red: "#cc6666",
yellow: "#ffff00",
gray: "#808080",
dimGray: "#666666",
darkGray: "#505050",
accent: "#8abeb7",
selectedBg: "#3a3a4a",
userMsgBg: "#343541",
toolPendingBg: "#282832",
toolSuccessBg: "#283228",
toolErrorBg: "#3c2828",
customMsgBg: "#2d2838",
},
colors: {
accent: "accent",
border: "blue",
borderAccent: "cyan",
borderMuted: "darkGray",
success: "green",
error: "red",
warning: "yellow",
muted: "gray",
dim: "dimGray",
text: "",
thinkingText: "gray",
selectedBg: "selectedBg",
userMessageBg: "userMsgBg",
userMessageText: "",
customMessageBg: "customMsgBg",
customMessageText: "",
customMessageLabel: "#9575cd",
toolPendingBg: "toolPendingBg",
toolSuccessBg: "toolSuccessBg",
toolErrorBg: "toolErrorBg",
toolTitle: "",
toolOutput: "gray",
mdHeading: "#f0c674",
mdLink: "#81a2be",
mdLinkUrl: "dimGray",
mdCode: "accent",
mdCodeBlock: "green",
mdCodeBlockBorder: "gray",
mdQuote: "gray",
mdQuoteBorder: "gray",
mdHr: "gray",
mdListBullet: "accent",
toolDiffAdded: "green",
toolDiffRemoved: "red",
toolDiffContext: "gray",
syntaxComment: "#6A9955",
syntaxKeyword: "#569CD6",
syntaxFunction: "#DCDCAA",
syntaxVariable: "#9CDCFE",
syntaxString: "#CE9178",
syntaxNumber: "#B5CEA8",
syntaxType: "#4EC9B0",
syntaxOperator: "#D4D4D4",
syntaxPunctuation: "#D4D4D4",
thinkingOff: "darkGray",
thinkingMinimal: "#6e6e6e",
thinkingLow: "#5f87af",
thinkingMedium: "#81a2be",
thinkingHigh: "#b294bb",
thinkingXhigh: "#d183e8",
bashMode: "green",
},
export: {
pageBg: "#18181e",
cardBg: "#1e1e24",
infoBg: "#3c3728",
},
};
// ---------------------------------------------------------------------------
// Light theme
// ---------------------------------------------------------------------------
const light: ThemeJson = {
name: "light",
vars: {
teal: "#5a8080",
blue: "#547da7",
green: "#588458",
red: "#aa5555",
yellow: "#9a7326",
mediumGray: "#6c6c6c",
dimGray: "#767676",
lightGray: "#b0b0b0",
selectedBg: "#d0d0e0",
userMsgBg: "#e8e8e8",
toolPendingBg: "#e8e8f0",
toolSuccessBg: "#e8f0e8",
toolErrorBg: "#f0e8e8",
customMsgBg: "#ede7f6",
},
colors: {
accent: "teal",
border: "blue",
borderAccent: "teal",
borderMuted: "lightGray",
success: "green",
error: "red",
warning: "yellow",
muted: "mediumGray",
dim: "dimGray",
text: "",
thinkingText: "mediumGray",
selectedBg: "selectedBg",
userMessageBg: "userMsgBg",
userMessageText: "",
customMessageBg: "customMsgBg",
customMessageText: "",
customMessageLabel: "#7e57c2",
toolPendingBg: "toolPendingBg",
toolSuccessBg: "toolSuccessBg",
toolErrorBg: "toolErrorBg",
toolTitle: "",
toolOutput: "mediumGray",
mdHeading: "yellow",
mdLink: "blue",
mdLinkUrl: "dimGray",
mdCode: "teal",
mdCodeBlock: "green",
mdCodeBlockBorder: "mediumGray",
mdQuote: "mediumGray",
mdQuoteBorder: "mediumGray",
mdHr: "mediumGray",
mdListBullet: "green",
toolDiffAdded: "green",
toolDiffRemoved: "red",
toolDiffContext: "mediumGray",
syntaxComment: "#008000",
syntaxKeyword: "#0000FF",
syntaxFunction: "#795E26",
syntaxVariable: "#001080",
syntaxString: "#A31515",
syntaxNumber: "#098658",
syntaxType: "#267F99",
syntaxOperator: "#000000",
syntaxPunctuation: "#000000",
thinkingOff: "lightGray",
thinkingMinimal: "#767676",
thinkingLow: "blue",
thinkingMedium: "teal",
thinkingHigh: "#875f87",
thinkingXhigh: "#8b008b",
bashMode: "green",
},
export: {
pageBg: "#f8f8f8",
cardBg: "#ffffff",
infoBg: "#fffae6",
},
};
// ---------------------------------------------------------------------------
// Export
// ---------------------------------------------------------------------------
export const builtinThemes: Record<string, ThemeJson> = { dark, light };