Skip to content

feat(tui-theme): write the TUI theme file for every theme and signal its readers - #24

Merged
datapointchris merged 4 commits into
mainfrom
tui-theme-file
Nov 8, 2026
Merged

datapointchris merged 4 commits into
mainfrom
tui-theme-file

Conversation

@datapointchris

@datapointchris datapointchris commented Nov 8, 2026 •

Copy link
Copy Markdown
Owner

Terminal programs built on gotermstyle draw in the 16 ANSI colors, which hold no shade near the background. So a selected row can only be marked by reversing it, which on a dark theme is a bright slab of the foreground color. gotermstyle's tuitheme package reads a theme file of colors and styles keyed by role. This writes that file for every theme, installs it where the reader looks, and signals each running reader to reread it on theme apply.

Written by Claude Code session 4773265d-bd77-424c-b9db-836de857b797.

What to look at

  • 5d25ede — lib/neovim_highlights.py holds the syntax and tree-sitter groups that were Lua text in neovim.py, plus the semantic colors palette.lua maps them through. Check that lua_spec and lua_groups in neovim.py reproduce the old text. lib/generate-all.sh --generators neovim leaves every theme's colorscheme unchanged.
  • lib/generators/tui-theme.py — ui_group is each ui role's fallback chain. group_style follows a link and turns undercurl into underline. syntax_group skips a capture whose link leaves the shared table, which today is only @string.special.path linking Directory. The reader falls back along the dots for it.
  • lib/lib.sh — apply_tui_theme puts each theme beside current.json in ~/.config/tui-theme/ rather than in a themes/ subdirectory, because install_themed_artifact links within one directory. It ignores a relative XDG_CONFIG_HOME, as gotermstyle's reader does, so the two always name one file. reload_tui_theme signals TUI_THEME_READERS by name. _signal_reload now passes the name to its off-Linux check, which _go_binary_on_path needs.
  • reload_apps calls reload_${app//-/_}, so the app named tui-theme reaches reload_tui_theme.

How it was verified

  • bats tests/ passes. The new tests are the TUI theme lands at the path gotermstyle reads with none given and every TUI theme writes the roles its schema declares.
  • The schema test fetches the schema at each file's $schema URL, and skips until that URL resolves. With a stub curl serving gotermstyle's branch schema, it passes. It fails when the schema alone gains ui.cursor or a base syntax role.
  • With XDG_CONFIG_HOME=rel, apply_tui_theme links ~/.config/tui-theme/current.json and creates no rel/ directory.
  • lib/generate-all.sh --generators neovim ran 20 jobs with 0 failed, and git status showed no theme file changed.
  • All 20 themes/*/tui-theme.json validate with check-jsonschema against gotermstyle's tuitheme/schema/v1.json.
  • On this box, apply_tui_theme charcoal-ember alone installed ~/.config/tui-theme/charcoal-ember.json with current.json linking to it. A scratch Go program on gotermstyle's branch painted ui.selection as \x1b[0;38;2;197;200;198;48;2;55;59;65m. Then apply_tui_theme kanagawa and _signal_reload tuitheme-demo _go_binary_on_path ran, and the same running program painted \x1b[0;38;2;200;192;147;48;2;45;79;103m with no restart. The link was set back to charcoal-ember.
  • grep -qa 'Go buildinf:' matched two of the readers' binaries and gofumpt, and did not match ls, bash or btop.

What changes

  • Every theme carries tui-theme.json, and theme current lists it as tui-theme.
  • theme apply installs it on every platform, as ~/.config/tui-theme/<id>.json with ~/.config/tui-theme/current.json linking to the applied one.
  • theme apply sends SIGUSR2 to each running process named in TUI_THEME_READERS. On Linux only a process whose SigCgt mask catches SIGUSR2 gets it, as for btop. Elsewhere only a name whose binary on PATH is a Go binary gets it.
  • A reader built before gotermstyle's tuitheme catches the signal and ignores it, so nothing visible happens until a reader adopts the package.
  • The file's $schema URL resolves once gotermstyle's tuitheme lands on its main branch. The file works without it, and an editor's validation waits for it.

Decisions, and what they rejected

  • ui.match is a foreground color, extended.ui_match falling back to base0A. A filter match drawn as a foreground still reads on the selected row.
  • ui.muted falls back to base04, the color neovim.py already uses for fg_dim. Only 4 of the 20 themes set extended.fg_dim.
  • ui.panel is written as {"bg": …}, because a bare color in the format is a foreground.
  • The base syntax.parameter and syntax.punctuation read @variable.parameter and @punctuation.delimiter. Neovim has no bare @parameter or @punctuation.
  • Smallest change that would work — reload_tui_theme passing true as the off-Linux check. On macOS a non-Go process sharing a reader's name would die of the SIGUSR2 it does not catch.

What this does not do

  • No reader colors anything from the file yet. The selected row and tab line move to it in the readers' own repos.
  • It writes no reader's config. None needs a line, because current.json is gotermstyle's default path. A reader's TUI_THEME_FILE points it at another file instead.

The syntax and tree-sitter highlight groups were Lua text inside neovim.py,
so a second generator wanting the same styles could only copy them, and the
copies would drift. They now live in lib/neovim_highlights.py as Python
data, with the semantic colors palette.lua maps them through, and neovim.py
writes the same Lua from it. Every theme's generated colorscheme is
byte-identical.

The table sits in lib/ because every script in lib/generators/ is read as a
generator. Importing it from there writes __pycache__/, now ignored.

Agent-Name: claude-code-unattended
Agent-Session: 4773265d-bd77-424c-b9db-836de857b797
Agent-Run: 01a1b9b4-580a-78e4-993a-0b0cbf56760f
Worklog: 2555.1
…its readers

Terminal programs on gotermstyle draw in the 16 ANSI colors, which hold no
shade near the background, so a selected row can only be reversed into a
slab. gotermstyle's tuitheme reads a theme file of colors and styles keyed
by role. This writes that file for every theme, installs it where the
reader looks, and asks each running reader to reread it on apply.

tui-theme.py writes tui-theme.json. ui comes from theme.yml with its
fallbacks resolved: the selection is special.selection_fg on
extended.ui_selection or special.selection_bg, and the accent is
extended.ui_accent or base0D. markdown and syntax resolve the shared
Neovim highlight table, so a note or snippet in a terminal program wears
the styles it has in Neovim. Every key the format names is written.

apply_tui_theme installs it as ~/.config/tui-theme/<id>.json with
current.json linking to it, the path tuitheme reads with none given. The
directory holds only theme files, so the themes sit beside the link.
reload_tui_theme sends SIGUSR2 to each reader by name. Off Linux it first
checks the binary on PATH carries Go's build-info marker, because the Go
runtime ignores an unrequested SIGUSR2 and another program may die of it.
reload_apps maps a hyphen in an app's name to its reload function's
underscore.

Agent-Name: claude-code-unattended
Agent-Session: 4773265d-bd77-424c-b9db-836de857b797
Agent-Run: 01a1b9b4-580a-78e4-993a-0b0cbf56760f
Worklog: 2555.1
…s relative

gotermstyle's reader ignores a relative XDG_CONFIG_HOME, as the XDG spec
asks, and falls back to ~/.config. The install used it raw, so the two
named different files and every reader stayed on its built-in colors.

Agent-Name: claude-code-unattended
Agent-Session: 4773265d-bd77-424c-b9db-836de857b797
Agent-Run: 01a1b9cf-f0a1-76ce-a000-cdf27a041ee5
Worklog: 2555.1
…es name

tui-theme.py types the schema's ui, markdown and base syntax keys out
again, and nothing held the copy level. A role the reader's schema gains
and the generator misses would draw in the 16-color default in every
theme with both suites green. The test fetches the schema at each file's
$schema URL, and skips when it cannot reach it.

Agent-Name: claude-code-unattended
Agent-Session: 4773265d-bd77-424c-b9db-836de857b797
Agent-Run: 01a1b9cf-f0a1-76ce-a000-cdf27a041ee5
Worklog: 2555.1
@datapointchris
datapointchris merged commit c28db3e into main Nov 8, 2026
1 check passed
@datapointchris
datapointchris deleted the tui-theme-file branch November 8, 2026 06:52
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