Skip to content

ui: detect the terminal background and pick a light or dark palette - #115

Merged
hbrooks merged 1 commit into
mainfrom
light-terminal-theme
Aug 20, 2026
Merged

ui: detect the terminal background and pick a light or dark palette#115
hbrooks merged 1 commit into
mainfrom
light-terminal-theme

Conversation

@hbrooks

@hbrooks hbrooks commented Aug 20, 2026

Copy link
Copy Markdown
Member

Important

Detects the terminal's background color at startup and applies either a light or dark color palette accordingly. Uses OSC 11 (the standard terminal background query), falls back to the COLORFGBG environment variable, and defaults to dark mode if neither method works.

  • Adds src/lib/terminalBackground.ts with detection logic — queries the terminal and interprets its response or environment hints to determine if a light or dark palette should render.
  • Extends src/lib/theme.ts with lightPalette and darkPalette constants; the live theme object is now mutated in place by applyThemeMode() so existing call sites automatically follow the detected mode without code changes.
  • The detection runs once at each UI entry point (connect.ts and launch.tsx), BEFORE the first Ink frame renders, so no palette switching or re-renders are needed.
  • Includes unit tests for the OSC 11 parser, COLORFGBG parser, and theme application.

This description was created by Ellipsis for 473813c. It will automatically update as commits are pushed.

@hbrooks
hbrooks merged commit 83c75f2 into main Aug 20, 2026
1 check passed

@ellipsis-dev ellipsis-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Changes requested ❌ — 2 issues

Reviewed 473813c in 12 minutes, 7 seconds.
  • Reviewed 1 commit with 278 lines of code in 5 files
  • Ran 1 review agent producing 2 comments where 2 were posted
  • This pipeline runs no gatekeeper, so findings are posted as written.
  • View full details on ellipsis.dev

This review was created by Ellipsis. You can tag @ellipsis in this pull request.

Comment on lines +53 to +73
const finish = (mode: ThemeMode | null): void => {
if (settled) return
settled = true
clearTimeout(timer)
stdin.off('data', onData)
stdin.setRawMode(wasRaw)
// Ink attaches its own stdin handling after this; leave the stream
// paused so a bare detection doesn't hold the process open.
stdin.pause()
resolve(mode)
}
const onData = (chunk: Buffer): void => {
buffer += chunk.toString('latin1')
const mode = modeFromOscReply(buffer)
if (mode) finish(mode)
else if (buffer.length > 64) finish(null)
}
const timer = setTimeout(() => finish(null), timeoutMs)
stdin.setRawMode(true)
stdin.resume()
stdin.on('data', onData)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

finish() detaches the reader and pauses stdin without draining it, so any part of the OSC 11 reply that has not arrived yet is buffered and replayed into Ink as typed text.

The regex matches before the reply's BEL/ST terminator, and the 200ms timeout can fire before the reply arrives at all. I ran queryOsc11 against a fake TTY: with the reply split so the terminator lands in a second chunk, stdin still holds "\x1b\" after resolve; with the reply arriving at 260ms, stdin holds the entire "\x1b]11;rgb:fdfd/f6f6/e3e3\x1b\". Ink resumes that same stream, and its parser turns those bytes into keypresses with ctrl/meta/escape all false ("\x1b]", "11;rgb:fdfd/f6f6/e3e3", "\x1b\"), which ConnectApp.tsx:1467 feeds to insertAtCursor — the composer opens pre-filled with raw ESC bytes and hex garbage, and enter POSTs it as a message. Requiring the terminator in OSC_REPLY ((?:\x07|\x1b\\)) fixes the split-chunk half deterministically; the late-reply half needs the stdin reader to stay attached and discarding until just before render, or a \x1b[6n sent after the OSC query as an ordering barrier so a terminal that will not answer OSC 11 is detected by its CPR reply instead of by a timeout that races the answer.

Comment thread src/lib/theme.ts
foreground: '#1c1b17',
muted: '#706f66',
cursor: '#175173',
success: '#10b981',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Light-mode success green is 2.5:1 on white — below the 3:1 floor every other value in this palette clears; darken it.

Contrast against #ffffff: cursor 8.5, foreground 17.2, syntaxString 7.6, muted 5.1, active 4.9, error 4.8, success 2.5. It is used as a foreground, not a fill — the completed-step gutter (transcriptRows.ts:675) and the closed-session dot in sessions.ts:71, which additionally applies dim on top.

Suggested change
success: '#10b981',
success: '#047857',

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