Skip to content

feat: custom color themes#484

Open
liruifengv wants to merge 8 commits into
mainfrom
custom-theme
Open

feat: custom color themes#484
liruifengv wants to merge 8 commits into
mainfrom
custom-theme

Conversation

@liruifengv
Copy link
Copy Markdown
Collaborator

Summary

Adds user-customizable color themes to the TUI, with the supporting theme-system work.

  • Theme system — colors resolve through a global currentTheme singleton, so switching themes recolors everything live, including already-rendered transcript.
  • Token cleanup — tidy the ColorPalette: drop the unused roleTool token, fold exact-alias tokens into their canonical sibling (roleAssistanttext, roleThinking/statustextDim), inline hex directly into darkColors/lightColors, and document what each token controls. No visual change to the built-in dark/light themes — the merges are exact aliases.
  • Custom themes — define a palette as JSON in ~/.kimi-code/themes/<name>.json. Unspecified tokens fall back to the dark palette; invalid hex and missing files degrade gracefully. The /theme picker re-scans the directory on open (no restart), and tui.toml's theme accepts a custom theme name.
  • Docs — new "Custom Themes" reference page (en + zh) with the full token table, linked from the tui.toml theme field.
  • Built-in /custom-theme skill — guides the model (or a manual /custom-theme run) to author a theme JSON: it asks light/dark + style + any specific colors first, then writes the file, validates the hex, and explains how to apply it. Users can also just ask Kimi to set one up.

Release

@moonshot-ai/kimi-code: minor (changeset included).

Test plan

  • agent-core skill tests + kimi-code theme/footer/component tests pass; typecheck and lint clean.
  • Built-in dark/light render identically (token merges are exact aliases); a custom theme loads with partial override + dark fallback; /theme lists newly added files without a restart.

Document the custom theme file location, the color token reference, selecting a theme via /theme and tui.toml, and fallback behavior. Link it from the customization sidebar and the tui.toml theme field.
Guides the model (or a manual /custom-theme run) to author a theme JSON in ~/.kimi-code/themes/: docs token reference, deliberate color choices, hex validation, and how to apply via /theme or /reload-tui. Note in the write-tui skill to keep the token set in sync across colors.ts, the schema, the docs, and this skill. Enrich the custom-theme changeset to cover all three usage paths.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 5, 2026

🦋 Changeset detected

Latest commit: deb5595

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

# Conflicts:
#	apps/kimi-code/test/tui/components/panels/help-panel.test.ts
#	packages/agent-core/src/skill/builtin/index.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ea333376c0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".


// Theme
applyTheme(theme: Theme, resolved?: ResolvedTheme): void;
applyTheme(theme: ThemeName, resolved?: ResolvedTheme): void;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Await theme application before refreshing auto tracking

When the user switches from a non-auto theme to auto, KimiTUI.applyTheme now returns a promise and only updates appState.theme after loading the palette, but the host contract still exposes it as void. Callers such as /theme and /reload-tui therefore call refreshTerminalThemeTracking() immediately while appState.theme still contains the old non-auto value, so the auto terminal-theme watcher is not installed and the UI stops following terminal background changes until another reload/restart. Please make this return a Promise<void> and await it before refreshing tracking.

Useful? React with 👍 / 👎.

Comment on lines +1599 to +1603
currentTheme.setPalette(palette);
this.updateEditorBorderHighlight();
this.state.footer.setColors(palette);
this.state.todoPanel.setColors(palette);
this.state.ui.requestRender(true);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Invalidate transcript on auto theme updates

When the terminal reports a dark/light change while theme = "auto" is already active, this path updates the global palette but skips the transcript invalidation that applyTheme() performs. Components such as status and markdown messages cache ANSI-colored Text/Markdown children and only rebuild them from invalidate(), so existing transcript entries keep the old colors after an automatic terminal-background change while new renders use the new palette. Add the same transcript invalidation here before requesting the full render.

Useful? React with 👍 / 👎.

Comment on lines +48 to +50
return entries
.filter((e) => e.isFile() && e.name.endsWith('.json'))
.map((e) => e.name.replace(/\.json$/, ''));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate custom themes before listing them

The picker now lists every *.json filename in the themes directory without loading or validating it, and selecting one bypasses the /theme <name> validation path. If a user has a malformed theme file or a JSON file missing the required shape, the picker shows Custom: <name>, saves that name, reports success, and getColorPalette() silently falls back to dark, leaving the user with a persisted theme that does not render. Filter the list through the same custom-theme loader or validate before applying the selected value.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 41ed40b53c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

}
if (errors.length > 0) {
// eslint-disable-next-line no-console
console.warn(`Theme "${name}" has invalid colors:\n${errors.join('\n')}`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid writing theme validation warnings to the raw terminal

When a custom theme has at least one invalid hex value and the user applies it from inside the TUI (for example via /theme <name> or /reload-tui), this console.warn writes directly to the process streams while pi-tui owns the terminal, so the warning text can be injected into/corrupt the rendered screen instead of appearing as a managed status/error message. Please route this validation feedback through the TUI/logging layer or return it to the caller rather than printing from the loader.

Useful? React with 👍 / 👎.

# Conflicts:
#	apps/kimi-code/src/tui/commands/goal.ts
#	apps/kimi-code/src/tui/components/dialogs/goal-queue-manager.ts
#	apps/kimi-code/src/tui/components/dialogs/goal-start-permission-prompt.ts
#	apps/kimi-code/src/tui/components/editor/custom-editor.ts
#	apps/kimi-code/src/tui/components/messages/goal-panel.ts
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 5, 2026

pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@deb5595
npx https://pkg.pr.new/@moonshot-ai/kimi-code@deb5595

commit: deb5595

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4a781c8023

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

'Invalid TUI config in ~/.kimi-code/tui.toml; using defaults.';

export const TuiThemeSchema = z.enum(['dark', 'light', 'auto']);
export const TuiThemeSchema = z.string();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Escape custom theme names before saving

This now accepts any string as a theme name, but renderTuiConfig still writes it as theme = "${config.theme}" without using the existing TOML escaping helper. If a custom theme file is named with a quote, backslash escape, or newline (the picker uses raw *.json filenames and docs say the filename is the theme name), selecting it saves malformed or changed TOML, so the next startup falls back to defaults instead of the chosen theme. Please either validate theme names to a safe filename subset or escape config.theme the same way editorCommand is escaped.

Useful? React with 👍 / 👎.

Comment on lines +156 to +159
const accent = (text: string) => currentTheme.boldFg('primary', text);
const value = (text: string) => currentTheme.fg('text', text);
const muted = (text: string) => currentTheme.fg('textDim', text);
const errorStyle = (text: string) => currentTheme.fg('error', text);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Rebuild precolored report lines on theme changes

These report builders now eagerly embed currentTheme ANSI colors into the lines array, but UsagePanelComponent only stores those strings and its invalidate() remains a no-op. After /usage, /status, /mcp, or /plugins output is in the transcript, switching themes invalidates the transcript but the panel body keeps the old SGR colors while the border/new output uses the new palette. Store the raw report data or recompute the colored lines during invalidation/render.

Useful? React with 👍 / 👎.

Remove unused chalk/currentTheme/ResolvedTheme imports left by the theme refactor; break the theme <-> pi-tui-theme import cycle by dropping the markdown/editor theme getters from the Theme class (consumers call createMarkdownTheme directly); fix unused vars/params, a floating promise, and a redundant union type.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: deb559584f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

const customThemes = listCustomThemesSync();
const options: ChoiceOption[] = [
...THEME_OPTIONS,
...customThemes.map((name) => ({ value: name, label: `Custom: ${name}` })),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Filter reserved names from custom theme picker

If a user adds dark.json, light.json, or auto.json under the themes directory, the picker now shows it as Custom: <name> but passes the raw value through unchanged. applyThemeChoice then treats that value as a built-in theme via isBuiltInTheme, so selecting Custom: dark can only apply the built-in dark theme and the custom file is unreachable despite the docs saying the filename is the theme name. Please either hide/reject reserved filenames or disambiguate custom option values before applying them.

Useful? React with 👍 / 👎.

Comment on lines +56 to +57
override invalidate(): void {
// Rebuild instruction line with fresh theme colours.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Repaint compaction headers during invalidation

When a completed, cancelled, or in-progress compaction card is already in the transcript and the user switches themes, applyTheme() invalidates the transcript but this override only rebuilds the optional instruction line. The headerText still contains the ANSI codes produced by the old palette, so the compaction bullet/title remains in the previous theme while the rest of the UI updates; refresh it with this.headerText.setText(this.buildHeader()) during invalidation.

Useful? React with 👍 / 👎.

Comment on lines +75 to +76
dim(text: string): string {
return chalk.dim(text);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Route dim helper through the palette

With a custom theme that changes textDim, every new call site using currentTheme.dim(...) still renders in the terminal's default foreground with SGR dim instead of the theme's textDim color. This affects many of the newly migrated dim UI elements such as tool metadata, truncation hints, and grouped agent/read details, so custom themes cannot actually control the dim text token documented for those surfaces; make this helper color via the active palette, e.g. the old styles.dim behavior.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant