Skip to content

feat(ui): resolve terminal capabilities lazily and per-client (#101) - #102

Merged
ezynda3 merged 2 commits into
masterfrom
feat/101-lazy-terminal-caps
Jul 30, 2026
Merged

feat(ui): resolve terminal capabilities lazily and per-client (#101)#102
ezynda3 merged 2 commits into
masterfrom
feat/101-lazy-terminal-caps

Conversation

@ezynda3

@ezynda3 ezynda3 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Description

Replaces the process-global, import-time terminal probes in internal/ui/style with lazily-resolved, per-client-overridable capabilities. Previously the package ran a blocking OSC background-color query and a color-profile lookup at package-init time, against whatever os.Stdin/os.Stdout the process happened to hold — which cannot represent more than one terminal and fires as an import side effect for any consumer of the package (including headless frontends).

Terminal background and color profile are now held behind a mutex-guarded cache resolved lazily on first use. A frontend can call SetTerminalCapabilities(darkBackground, colorProfile) to supply the terminal it actually serves, or ResolveTerminalCapabilities() to probe eagerly. The interactive path calls the latter in cmd/root.go just before the TUI takes over stdin, preserving the old "detect before render" timing and keeping the synchronous OSC query off the render path.

DefaultTheme() calls AdaptiveColor, so theme derivation is now deferred until first use as well; a lazily-derived theme is rebuilt when capabilities change, while a theme set explicitly via SetTheme is preserved untouched.

This is the first, independently-shippable step of #101 (decoupling session/terminal state from the TUI layer). It is a pure refactor with no intended user-visible change. It does not close the issue on its own — the remaining steps (typed event union, TreeManager value methods, session file I/O into internal/app) land as separate PRs, and the final one will carry Fixes #101.

Refs #101

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Refactor (no functional change, code restructuring)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update

Checklist

  • My code follows the style guidelines of this project (go vet, golangci-lint run, gofmt)
  • I have performed a self-review of my own code
  • I have added tests that prove my change is effective
  • New and existing unit tests pass locally (go test -race ./internal/ui/...)
  • I manually verified the interactive TUI (startup splash, /theme, theme switch) in tmux

Additional Information

Files changed:

  • internal/ui/style/enhanced.go — replaced isDarkBg / termColorProfile package vars with a lazily-resolved capability holder; added SetTerminalCapabilities and ResolveTerminalCapabilities; made currentTheme lazy and rebuildable on capability change.
  • internal/ui/style/themes.go — swapped the two isDarkBg reads for isDarkBackground().
  • internal/ui/exports.go — re-exported the two new entry points from internal/ui.
  • cmd/root.go — resolve capabilities before tea.NewProgram.
  • internal/ui/style/logo_test.go — migrated three tests off the removed isDarkBg var onto SetTerminalCapabilities.
  • internal/ui/style/terminal_caps_test.go (new) — covers override, derived-theme rebuild, and explicit-theme preservation.

Backward compatibility: No public API removed. The TUI resolves capabilities against the same fds at an equivalent (pre-TUI) point, so interactive behaviour is unchanged. The new seam is what future headless frontends will call instead of relying on the import-time probe.

Summary by CodeRabbit

  • New Features
    • Added the ability to set and resolve terminal styling capabilities programmatically, including light/dark detection and terminal color-profile handling.
  • Bug Fixes
    • Improved terminal capability resolution during interactive startup to prevent visual/theme inconsistencies.
    • Updated theme-derived colors so they automatically refresh when terminal capabilities change, while keeping explicitly selected themes unchanged.
    • Refined smooth-animation and logo shine behavior to follow the resolved terminal capabilities.
  • Tests
    • Added/updated unit tests covering terminal capability switching and theme refresh behavior.

- Replace the package-init probes in internal/ui/style (isDarkBg,
  termColorProfile) with a mutex-guarded, lazily-resolved capability
  holder so the blocking OSC background query no longer fires at import
  time against arbitrary process fds
- Add SetTerminalCapabilities for frontends to supply the terminal they
  actually serve, and ResolveTerminalCapabilities to probe eagerly
- Defer DefaultTheme derivation until first use; rebuild a derived theme
  on a capability change while preserving an explicitly set theme
- Resolve capabilities in cmd/root.go before the TUI takes over stdin to
  keep the query off the render path
- Re-export the new entry points from internal/ui and cover the override,
  derived-theme rebuild, and explicit-theme preservation with tests

First step of decoupling session/terminal state from the TUI layer.
@mark-iii-labs-huly

Copy link
Copy Markdown

Connected to Huly®: KIT-103

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Terminal capability detection now resolves lazily or synchronously before interactive TUI startup. Styling APIs are re-exported, derived themes and caches respond to capability changes, and tests cover terminal switching, theme preservation, and logo colors.

Changes

Terminal capability and theme handling

Layer / File(s) Summary
Lazy capability resolution
internal/ui/style/enhanced.go
Terminal capabilities use synchronized lazy resolution with explicit override and eager-resolution APIs. Animation, adaptive colors, and dark-background checks use the resolved state.
Theme derivation and cache invalidation
internal/ui/style/enhanced.go, internal/ui/style/themes.go
Themes derive lazily from terminal capabilities; explicit themes are preserved, while derived theme and rendering caches are invalidated when capabilities change.
Public wiring and validation
internal/ui/exports.go, cmd/root.go, internal/ui/style/*_test.go
Capability APIs are re-exported, interactive mode resolves capabilities before Bubble Tea starts, and tests cover capability changes, theme behavior, and logo rendering.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • mark3labs/kit#97: Both changes update terminal-aware theme and adaptive color behavior in internal/ui/style/enhanced.go.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 84.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: lazy, per-client terminal capability resolution in the UI layer.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/101-lazy-terminal-caps

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
cmd/root.go (1)

1763-1768: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Use Bubble Tea’s background query instead of probing os.Stdin/os.Stdout directly.

ResolveTerminalCapabilities() runs lipgloss.HasDarkBackground(os.Stdin, os.Stdout), which is the standalone/background-independent path. For a Bubble Tea application, request the client terminal background via tea.RequestBackgroundColor in Init, handle tea.BackgroundColorMsg in Update, then apply that value in style.SetTerminalCapabilities. This keeps background detection in lockstep with the TUI and avoids using local server fds, which would matter for Wish/SSH sessions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/root.go` around lines 1763 - 1768, Replace the pre-TUI
ResolveTerminalCapabilities call with Bubble Tea’s request flow: have the model
return tea.RequestBackgroundColor from Init, handle tea.BackgroundColorMsg in
Update, and pass the received color to style.SetTerminalCapabilities. Remove the
direct os.Stdin/os.Stdout probing path used by ResolveTerminalCapabilities while
preserving configured theme values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/ui/style/enhanced.go`:
- Around line 67-90: Synchronize all shared theme state to preserve the
goroutine-safety guarantee: protect currentTheme, themeExplicitlySet,
themeGeneration, and the typography/style/syntax cache fields consistently
across SetTerminalCapabilities, GetTheme, SetTheme, invalidateThemeCaches,
GetCachedStyles, and SyntaxStyle. Use the same mutex or a dedicated theme mutex
for every read and write, including cache invalidation, while preserving
explicit-theme behavior.

---

Nitpick comments:
In `@cmd/root.go`:
- Around line 1763-1768: Replace the pre-TUI ResolveTerminalCapabilities call
with Bubble Tea’s request flow: have the model return tea.RequestBackgroundColor
from Init, handle tea.BackgroundColorMsg in Update, and pass the received color
to style.SetTerminalCapabilities. Remove the direct os.Stdin/os.Stdout probing
path used by ResolveTerminalCapabilities while preserving configured theme
values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 511528f4-5ded-40ac-a424-c9a278277d8d

📥 Commits

Reviewing files that changed from the base of the PR and between a01fe35 and 7402e72.

📒 Files selected for processing (6)
  • cmd/root.go
  • internal/ui/exports.go
  • internal/ui/style/enhanced.go
  • internal/ui/style/logo_test.go
  • internal/ui/style/terminal_caps_test.go
  • internal/ui/style/themes.go

Comment thread internal/ui/style/enhanced.go
)

Address CodeRabbit review on PR #102.

- enhanced.go: SetTerminalCapabilities claimed "safe to call from any
  goroutine" but mutated currentTheme/themeExplicitlySet and rebuilt the
  theme-derived caches (generation counter, typography/style/syntax) with
  no lock, while the render loop reads them unsynchronized. Narrow the
  contract to match SetTheme (call on the UI goroutine); the capability
  swap itself stays mutex-guarded. Documented the shared UI-goroutine
  discipline on currentTheme/themeExplicitlySet (🔴 critical, valid).
- Skipped: cmd/root.go nitpick to wire detection through
  tea.RequestBackgroundColor. Out of scope for this step — the PR
  preserves the pre-existing os.Stdin/os.Stdout probe for behaviour
  parity, and SetTerminalCapabilities is the seam a future
  Init/Update-based (Wish/SSH-aware) wiring will use (🔵 trivial).
@ezynda3

ezynda3 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the CodeRabbit review in 36262fe:

  • 🔴 SetTerminalCapabilities goroutine-safety (enhanced.go) — fixed. The doc claimed "safe to call from any goroutine" but the function mutated currentTheme/themeExplicitlySet and rebuilt the theme-derived caches unsynchronized while the render loop reads them. Narrowed the contract to match SetTheme (call on the UI goroutine); the capability swap stays mutex-guarded. This keeps the per-frame GetTheme/GetCachedStyles/SyntaxStyle paths lock-free rather than adding a RWMutex to the render hot path.

  • 🔵 tea.RequestBackgroundColor wiring (cmd/root.go) — intentionally deferred. This PR is step (d) of feat: decouple session state from the TUI layer (AppController/TreeManager coupling) #101 and preserves the pre-existing os.Stdin/os.Stdout probe for behaviour parity (same detection as the former package-init var, just resolved before program.Run()). SetTerminalCapabilities is precisely the seam a future Init/Update-based, Wish/SSH-aware wiring will call, so this is groundwork for that follow-up rather than something to rewire here.

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