Skip to content

Make TUI themes data: JSON theme files with user and project discovery#374

Merged
alejandro-ao merged 8 commits into
huggingface:mainfrom
rian-dolphin:feat/custom-tui-themes
Jul 18, 2026
Merged

Make TUI themes data: JSON theme files with user and project discovery#374
alejandro-ao merged 8 commits into
huggingface:mainfrom
rian-dolphin:feat/custom-tui-themes

Conversation

@rian-dolphin

@rian-dolphin rian-dolphin commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Themes were hardcoded Python literals behind a closed Literal type — adding one meant editing config.py. Following Pi, themes are now data: JSON files with a vars palette, color tokens, and per-role styles.

I.e. you can change to your own theme without editing any python.

The built-in themes now ship as packaged JSON through the same parser, and custom themes are discovered at startup from ~/.tau/themes/ and <project>/.tau/themes/, appearing in /theme, completions, the picker, and Textual's native theme menu.

Example of a custom theme can be seen here. Just put it in ~/.tau/themes/catppuccin-mocha.json and then it will be available to choose.

Screenshot 2026-07-16 at 12 29 27

Notes

A broken theme can never break tau: invalid files are skipped with a startup notice listing every problem at once, and a missing configured theme falls back to tau-dark without overwriting the setting. Colors previously hardcoded in the transcript renderer (tool accents, success/error) became theme tokens; built-ins render identically. The format is documented in the website's new Themes guide.

Hot reload and extension-contributed themes are left as follow-ups.

@rian-dolphin
rian-dolphin force-pushed the feat/custom-tui-themes branch from b3a27e2 to c47988f Compare July 16, 2026 11:38
Themes become data, following Pi's design. The three built-in themes ship
as packaged JSON parsed by the same validator as user themes; custom
themes are discovered at startup from ~/.tau/themes and <project>/.tau/themes
(project wins on name collisions) and appear in /theme, completions, the
picker, and Textual's native theme menu. Invalid files are skipped with a
startup notice listing every problem at once, and a configured theme that
cannot be found falls back to tau-dark without overwriting the setting.

The theme format mirrors TuiTheme: name, optional dark (defaulting from
background luminance) and syntax_theme, a vars block for palette colors
referenced by token inside style strings, colors, and per-role
border/body styles. Colors previously hardcoded in the transcript
renderer (tool result accents and their background, success/error) are
now theme tokens; built-ins render identically except tau-light's failed
tool border, which now uses its error token.
@rian-dolphin
rian-dolphin force-pushed the feat/custom-tui-themes branch from c47988f to c70d4fa Compare July 16, 2026 11:46
@rian-dolphin
rian-dolphin marked this pull request as ready for review July 16, 2026 11:46

@alejandro-ao alejandro-ao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for making themes data-driven and adding user/project discovery. The implementation and test coverage are generally strong, but there is one startup-safety issue to address before merging.

The theme parser validates palette colors with Rich's Color.parse (src/tau_coding/tui/themes/__init__.py:231-234), while those colors are later passed to Textual's Theme and CSS variables. Rich accepts formats that Textual rejects, such as color(123), bright_red, grey50, and default. As a result, a custom theme can pass validation but crash the TUI when selected—or at startup if it is configured—instead of being skipped with a diagnostic.

Please validate fields consumed by Textual with Textual's color parser, while retaining Rich validation for Rich-only role/style fields. A regression test using a Rich-valid/Textual-invalid color should verify that such a theme is rejected during discovery.

The branch also currently conflicts with the latest main in src/tau_coding/tui/app.py, so it will need to be updated before merging.

# Conflicts:
#	src/tau_coding/tui/app.py
#	src/tau_coding/tui/widgets.py
Rich's Color.parse accepts syntax Textual rejects (bright_red, grey50,
color(1), default), so a custom theme could pass discovery validation and
then crash the TUI at startup or on selection. Colors that reach Textual
(CSS variables, Theme slots, role borders via styles.border_left, and the
foreground/background of role bodies via styles.color/background) now
have to parse under both libraries. tool_success_text/tool_error_text and
the completion_* style strings are only ever rendered through Rich and
keep Rich-only validation.
@rian-dolphin

rian-dolphin commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, addressed those and got it up to date with main. Summary below!

Colors consumed by Textual are now validated with Textual's parser in addition to Rich's. That covers the CSS variables and Theme() slots, and also two paths beyond the palette fields: role borders (which feed styles.border_left) and the foreground/background of role body styles (which feed styles.color/styles.background via _split_rich_style_colors). Shared fields render through both libraries, so they validate against both rather than swapping Rich validation for Textual's — Textual-only syntax like ansi_red or alpha hex is rejected there too. Only tool_success_text/tool_error_text and the completion_* style strings keep Rich-only validation, since Textual never sees them; any future field defaults to the strict dual check, so a missed opt-out rejects a theme with a diagnostic instead of crashing the TUI.

Regression tests cover the requested case — a Rich-valid/Textual-invalid color (bright_red) skipped during discovery with a diagnostic — plus parse-level rejection of color(123)/grey50/default on palette fields, role borders, role bodies, and via vars, and the mirror direction for Textual-only syntax. The themes guide now notes that colors must use formats both libraries accept. All three built-in themes pass the stricter validation unchanged.

alejandro-ao
alejandro-ao previously approved these changes Jul 18, 2026

@alejandro-ao alejandro-ao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thank you, @rian-dolphin — both requested changes are addressed, and this is in great shape.

@alejandro-ao

alejandro-ao commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

adding hand-written thank you for all your help. merging. you rock! 🤘

# Conflicts:
#	src/tau_coding/tui/config.py
#	website/content/guides/tui.md
@alejandro-ao

Copy link
Copy Markdown
Collaborator

Updated this branch with the latest main and preserved the current built-in theme behavior while resolving the resulting regressions:

  • kept Tau Dark's aqua accent (#a7f3f0) for the global accent, Markdown headings, and bullets
  • aligned each built-in theme's completion_selected and completion_selected_description foreground with its highlight_text
  • restored each user role body's background to that theme's prompt_background

Local validation on the integrated branch:

  • uv sync --dev --locked
  • uv run pytest — 1037 passed
  • uv run ruff check .
  • uv run ruff format --check .
  • uv run mypy
  • hugo --minify
  • npx --yes pagefind@latest --site public

All checks passed. The Pagefind command was run against the public npm registry because this environment's configured Hugging Face npm mirror timed out.

@alejandro-ao alejandro-ao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-approving after the latest main integration and built-in theme compatibility fixes. Local validation and GitHub CI pass.

@alejandro-ao
alejandro-ao merged commit ed0dde0 into huggingface:main Jul 18, 2026
2 checks passed
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.

2 participants