Summary
OpenCode TUI's Adaptive Theme always starts in dark mode regardless of the terminal's actual background color. The terminal background query (OSC 11) works correctly and returns a valid response, but the luminance calculation in mode() incorrectly assumes the RGBA channels are in the 0..255 range when they are in fact normalized to 0..1. As a result, even a pure white background produces a luminance of ~0.0039, which never crosses the 0.5 threshold, so the theme is always resolved as "dark".
Environment
- OpenCode version: 1.14.20 (also reproduces on the latest dev branch)
- First reported in: issues related to v1.2.27
- OS: Ubuntu 24.04 (OS-independent; reproduces on any environment where the terminal supports OSC 11 responses)
- Terminals: Windows Terminal, kitty, Ghostty, and other terminals that respond to OSC 11
- Runtime: Bun-based TUI
Details
Expected Behavior
- When the terminal emulator's background is light → OpenCode TUI should start with the light theme.
- When the terminal emulator's background is dark → OpenCode TUI should start with the dark theme.
Actual Behavior
- TUI always starts in dark mode, even when the terminal background is clearly light.
- Manually toggling via /theme switches between light and dark correctly, but the initial adaptive detection is always biased toward dark.
- Even after the user unlocks the theme, runtime adaptive events (THEME_MODE) are not reflected correctly because the underlying mode detection is stuck on dark due to the luminance miscalculation.
Root Cause
Location
packages/opencode/src/cli/cmd/tui/util/terminal.ts
Offending code
function mode(background: RGBA | null): "dark" | "light" {
if (!background) return "dark"
const luminance = (0.299 * background.r + 0.587 * background.g + 0.114 * background.b) / 255
return luminance > 0.5 ? "light" : "dark"
}
Why this is wrong
In this codebase, the .r, .g, and .b channels of an RGBA object are normalized floats in the range 0..1, not 0..255. Other theme-related code in the same repository (context/theme.tsx) treats them this way explicitly:
// RGBA stores floats in range 0-1, convert to 0-255
const bgR = bg.r * 255
const bgG = bg.g * 255
const bgB = bg.b * 255
const luminance = 0.299 * bgR + 0.587 * bgG + 0.114 * bgB
Here the channels are explicitly multiplied by 255 before computing luminance.
In contrast, terminal.ts's mode() function takes the already-normalized 0..1 values and divides them by 255 again. Consequences:
- A white background
(1, 1, 1) yields a luminance of ~0.0039 instead of ~1.0.
- The
0.5 threshold is essentially unreachable, so the result is always "dark" for any non-black background.
Numerical proof
| Background |
r, g, b (normalized) |
Current luminance |
Expected luminance |
Result |
| White |
1.0, 1.0, 1.0 |
0.0039 |
1.0 |
dark (wrong) |
| Light gray |
0.8, 0.8, 0.8 |
0.0031 |
0.8 |
dark (wrong) |
| Mid gray |
0.5, 0.5, 0.5 |
0.0020 |
0.5 |
dark (wrong) |
| Black |
0.0, 0.0, 0.0 |
0.0 |
0.0 |
dark (correct, but coincidental) |
Real-world OSC 11 response
Response observed from kitty when querying OSC 11:
b'\x1b]11;rgb:f4f4/f4f4/f4f4\x1b\\'
This is a very light background (~0.957 normalized), but the current code computes luminance ≈ 0.00375 → "dark".
History
The bug has existed for a while and was preserved across refactors:
Both commits contain the same erroneous / 255 expression.
Proposed Fix
Before
function mode(background: RGBA | null): "dark" | "light" {
if (!background) return "dark"
const luminance = (0.299 * background.r + 0.587 * background.g + 0.114 * background.b) / 255
return luminance > 0.5 ? "light" : "dark"
}
After
function mode(background: RGBA | null): "dark" | "light" {
if (!background) return "dark"
const luminance = 0.299 * background.r + 0.587 * background.g + 0.114 * background.b
return luminance > 0.5 ? "light" : "dark"
}
Impact
- Fix effect:
getTerminalBackgroundColor() now returns the correct "dark" / "light" based on the actual terminal background.
- TUI propagation: The correct initial mode is propagated along
app.tsx → ThemeProvider → resolveTheme(), so the adaptive theme works as intended at startup.
- Side effects: None — this is purely a fix to a single arithmetic expression; no behavioral change elsewhere.
- non-TTY / timeout fallback: Still returns
"dark" as before (intended behavior preserved).
Related Issues
Verification
Plugins
No response
OpenCode version
1.14.20
Steps to reproduce
- Use a terminal emulator configured with a light background color.
- Run the TUI: opencode
- Observe that the TUI starts in dark mode.
Screenshot and/or share link
Theme
Terminal Theme: Light
Before Fix
After Fix
Theme
Before Fix
After Fix
Operating System
Ubuntu 24.04
Terminal
Kitty, Warp
Summary
OpenCode TUI's Adaptive Theme always starts in dark mode regardless of the terminal's actual background color. The terminal background query (OSC 11) works correctly and returns a valid response, but the luminance calculation in mode() incorrectly assumes the RGBA channels are in the 0..255 range when they are in fact normalized to 0..1. As a result, even a pure white background produces a luminance of ~0.0039, which never crosses the 0.5 threshold, so the theme is always resolved as "dark".
Environment
Details
Expected Behavior
Actual Behavior
Root Cause
Location
Offending code
Why this is wrong
In this codebase, the
.r,.g, and.bchannels of anRGBAobject are normalized floats in the range0..1, not0..255. Other theme-related code in the same repository (context/theme.tsx) treats them this way explicitly:Here the channels are explicitly multiplied by
255before computing luminance.In contrast,
terminal.ts'smode()function takes the already-normalized0..1values and divides them by 255 again. Consequences:(1, 1, 1)yields a luminance of~0.0039instead of~1.0.0.5threshold is essentially unreachable, so the result is always"dark"for any non-black background.Numerical proof
Real-world OSC 11 response
Response observed from kitty when querying OSC 11:
This is a very light background (
~0.957normalized), but the current code computesluminance ≈ 0.00375→"dark".History
The bug has existed for a while and was preserved across refactors:
e95474df0— "fix: revert parts of a824064 which caused system theme regression (fix: revert parts of a824064c4 which caused system theme regression #23714)"3eb6508a6— "refactor: share TUI terminal background detection (refactor: share TUI terminal background detection #22297)"Both commits contain the same erroneous
/ 255expression.Proposed Fix
Before
After
Impact
getTerminalBackgroundColor()now returns the correct"dark"/"light"based on the actual terminal background.app.tsx→ThemeProvider→resolveTheme(), so the adaptive theme works as intended at startup."dark"as before (intended behavior preserved).Related Issues
darkandlightentries happen to be identical.Verification
RGBAusage across the codebase is consistent with0..1normalized floats.bun typecheckpasses inpackages/opencode.bun run script/build.ts --single.--version.Plugins
No response
OpenCode version
1.14.20
Steps to reproduce
Screenshot and/or share link
Theme
Terminal Theme: Light
Before Fix
After Fix
Theme
Before Fix
After Fix
Operating System
Ubuntu 24.04
Terminal
Kitty, Warp