feat(renderer): add Mode 2031 dark/light theme detection#657
Merged
kommander merged 2 commits intoanomalyco:mainfrom Feb 10, 2026
Merged
feat(renderer): add Mode 2031 dark/light theme detection#657kommander merged 2 commits intoanomalyco:mainfrom
kommander merged 2 commits intoanomalyco:mainfrom
Conversation
Add support for the Mode 2031 terminal protocol for automatic dark/light
theme detection. This allows applications to reactively respond to OS
appearance changes in supported terminals (Ghostty 1.0+, kitty 0.38.1+,
Contour 0.4+, VTE 0.82+).
Zig (terminal.zig):
- Add setColorSchemeUpdates() helper matching setBracketedPaste/setFocusTracking pattern
- Use setColorSchemeUpdates() in resetState() instead of inline write + state flip
- Enable color scheme updates in enableDetectedFeatures() with initial query
- Recognize both 2031;1$y (mode active) and 2031;2$y (mode recognized)
in processCapabilityResponse(), matching how focus_tracking/sync/bracketed_paste
already handle both DECRPM response codes
TypeScript (renderer.ts):
- Add themeModeHandler parsing CSI ? 997 ; {1|2} n responses
- Emit theme_mode event with dedup guard (only when mode changes)
- Expose themeMode getter returning ThemeMode | null
Types (types.ts):
- Export ThemeMode type
- Add theme_mode to RendererEvents interface
@opentui/core
@opentui/react
@opentui/solid
@opentui/core-darwin-arm64
@opentui/core-darwin-x64
@opentui/core-linux-arm64
@opentui/core-linux-x64
@opentui/core-win32-arm64
@opentui/core-win32-x64
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
pr-657.MP4
Summary
Adds support for the Mode 2031 terminal protocol, enabling automatic dark/light theme detection and live reactivity to OS appearance changes.
Supersedes #654 (closed) — this is a clean rewrite addressing the review feedback to move setup/shutdown into the Zig native layer.
What this does
CSI ? 2031 h) duringenableDetectedFeatures()in terminal.zigCSI ? 996 n) on startupCSI ? 997 ; 1 n(dark) /CSI ? 997 ; 2 n(light) responses via a newthemeModeHandlerin the input handler pipelinetheme_modeevent on the renderer when the mode changesthemeModegetter returningThemeMode | nullCSI ? 2031 l) on destroy viaresetState()ThemeModetype fromtypes.tsand addstheme_modetoRendererEventsSupported terminals
Unsupported terminals silently ignore the escape sequences — no fallback needed.
Design decisions
Each decision follows existing patterns in the codebase and/or established implementations in other terminal applications:
enableDetectedFeatures()/resetState()setFocusTracking) and bracketed paste (setBracketedPaste) are toggled — review feedback from #654setColorSchemeUpdates()helpersetBracketedPaste,setFocusTracking,setModifyOtherKeyspattern exactlyif (!self.state.color_scheme_updates)guardself.caps.bracketed_paste = trueincheckEnvironmentOverrides). Mode 2031 is safe to send unconditionally — unsupported terminals ignore it2031;1$yand2031;2$y1= mode set (active),2= mode recognizedfocus_tracking(1004),sync(2026), andbracketed_paste(2004) all match both response codes inprocessCapabilityResponse()CSI ? 996 nfocusHandler, before key handlercapabilityHandler,focusHandler)nullThemeModetype intypes.tsCursorStyle,WidthMethodUsage
Changes
packages/core/src/zig/terminal.zig—setColorSchemeUpdates()helper, enable inenableDetectedFeatures(), use inresetState(), broaden DECRPM detectionpackages/core/src/renderer.ts—_themeModeproperty,themeModeHandler,themeModegetterpackages/core/src/types.ts—ThemeModetype,theme_modeevent inRendererEventsReferences