[PR #13233] feat(tui): add Mode 2031 theme detection with tri-state appearance mode #14572

Closed
opened 2026-02-16 18:19:21 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/anomalyco/opencode/pull/13233

State: closed
Merged: No


What does this PR do?

Closes #13232

OpenTUI v0.1.78 added Mode 2031 dark/light theme detection (opentui#657). OpenCode is already on v0.1.79 (#13036) which includes these APIs, but doesn't use them. This PR integrates renderer.themeMode / renderer.on("theme_mode", ...) into OpenCode's theme system.

Three changes:

  1. Tri-state appearance mode (auto/dark/light) — replaces the binary toggle. auto follows OS via Mode 2031, falling back to OSC 11 luminance on unsupported terminals. dark/light are manual overrides.
  2. Per-mode theme config"theme": { "light": "github", "dark": "catppuccin" } alongside existing "theme": "opencode" (backward compatible).
  3. Appearance selector in the theme dialog — ←/→ arrow keys cycle modes, clickable, reverts on cancel.

Mode 2031 is the same approach used by Neovim and Helix. Event-driven, zero overhead. Unsupported terminals silently ignore the sequences — no degradation.

Why Mode 2031 over the approaches in existing open PRs:

Approach PR Problem
Poll terminal bg every 5s #7182, #7162 Wastes CPU, delayed detection, races with palette queries
execSync("defaults read") / gsettings every 3s #9719 Platform-specific, breaks in SSH/containers/tmux, spawns child processes
Mode 2031 (this PR) Terminal-native, event-driven, zero overhead

Edge cases handled:

  • Unsupported terminals: detectedMode stays at initial OSC 11 value. No degradation.
  • Custom themes without variants: activeThemeMode() falls back to "dark". Existing custom themes work identically.
  • Cancel in dialog: both theme and mode revert to initial values.
  • Event cleanup: onCleanup(() => renderer.off("theme_mode", ...)) prevents leaks.

How did you verify your code works?

  • bun run typecheck passes (12/12 packages, zero errors)
  • Tested on Ghostty (Mode 2031 supported): toggling macOS appearance instantly switches theme variants
  • Tested on Terminal.app (Mode 2031 unsupported): falls back to existing behavior, manual mode selector works, no errors
  • Backward compatible: "theme": "opencode" works, "theme": { "light": "github", "dark": "catppuccin" } works
  • All 31 dual-variant built-in themes resolve correctly in both modes
  • aura and ayu (single-palette) unaffected by mode changes

Demo

https://github.com/user-attachments/assets/f754518f-ddac-4683-a57f-ba7132203ac7

**Original Pull Request:** https://github.com/anomalyco/opencode/pull/13233 **State:** closed **Merged:** No --- ### What does this PR do? Closes #13232 OpenTUI v0.1.78 added Mode 2031 dark/light theme detection ([opentui#657](https://github.com/anomalyco/opentui/pull/657)). OpenCode is already on v0.1.79 ([#13036](https://github.com/anomalyco/opencode/pull/13036)) which includes these APIs, but doesn't use them. This PR integrates `renderer.themeMode` / `renderer.on("theme_mode", ...)` into OpenCode's theme system. Three changes: 1. **Tri-state appearance mode** (`auto`/`dark`/`light`) — replaces the binary toggle. `auto` follows OS via Mode 2031, falling back to OSC 11 luminance on unsupported terminals. `dark`/`light` are manual overrides. 2. **Per-mode theme config** — `"theme": { "light": "github", "dark": "catppuccin" }` alongside existing `"theme": "opencode"` (backward compatible). 3. **Appearance selector** in the theme dialog — ←/→ arrow keys cycle modes, clickable, reverts on cancel. Mode 2031 is the same approach used by [Neovim](https://github.com/neovim/neovim/pull/31350) and [Helix](https://github.com/helix-editor/helix/pull/14356). Event-driven, zero overhead. Unsupported terminals silently ignore the sequences — no degradation. **Why Mode 2031 over the approaches in existing open PRs:** | Approach | PR | Problem | |----------|----|---------| | Poll terminal bg every 5s | [#7182](https://github.com/anomalyco/opencode/pull/7182), [#7162](https://github.com/anomalyco/opencode/pull/7162) | Wastes CPU, delayed detection, races with palette queries | | `execSync("defaults read")` / `gsettings` every 3s | [#9719](https://github.com/anomalyco/opencode/pull/9719) | Platform-specific, breaks in SSH/containers/tmux, spawns child processes | | **Mode 2031** (this PR) | — | Terminal-native, event-driven, zero overhead | **Edge cases handled:** - Unsupported terminals: `detectedMode` stays at initial OSC 11 value. No degradation. - Custom themes without variants: `activeThemeMode()` falls back to `"dark"`. Existing custom themes work identically. - Cancel in dialog: both theme and mode revert to initial values. - Event cleanup: `onCleanup(() => renderer.off("theme_mode", ...))` prevents leaks. ### How did you verify your code works? - `bun run typecheck` passes (12/12 packages, zero errors) - Tested on Ghostty (Mode 2031 supported): toggling macOS appearance instantly switches theme variants - Tested on Terminal.app (Mode 2031 unsupported): falls back to existing behavior, manual mode selector works, no errors - Backward compatible: `"theme": "opencode"` works, `"theme": { "light": "github", "dark": "catppuccin" }` works - All 31 dual-variant built-in themes resolve correctly in both modes - `aura` and `ayu` (single-palette) unaffected by mode changes ### Demo https://github.com/user-attachments/assets/f754518f-ddac-4683-a57f-ba7132203ac7
yindo added the pull-request label 2026-02-16 18:19:21 -05:00
yindo closed this issue 2026-02-16 18:19:21 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#14572