Skip to content

fix(tui): rewire theme watcher lost in tab-view rewrite#3505

Merged
dgageot merged 1 commit into
mainfrom
fix/3501-theme-watcher-rewire
Jul 7, 2026
Merged

fix(tui): rewire theme watcher lost in tab-view rewrite#3505
dgageot merged 1 commit into
mainfrom
fix/3501-theme-watcher-rewire

Conversation

@Sayt-0

@Sayt-0 Sayt-0 commented Jul 7, 2026

Copy link
Copy Markdown
Member

Fixes #3501

Editing ~/.cagent/themes/*.yaml while the TUI runs did nothing until restart, despite the docs promising auto-reload on save.

Root cause

The tab-view rewrite (0069cad) rewrote pkg/tui/tui.go and kept only the ThemeFileChangedMsg handler: nothing constructed styles.ThemeWatcher or emitted the message anymore (NewThemeWatcher had no non-test caller). Confirmed by reproduction: an e2e test editing the active theme file times out waiting for the hot-reload on main, and passes with this change.

Fix

runTUIWrapped / tuitest
  SetProgram(p)
    creates ThemeWatcher, callback: p.Send(ThemeFileChangedMsg)
    watchCurrentTheme()                          initial Watch

ChangeThemeMsg        > handleChangeTheme        > ThemeChangedMsg \
ThemePreviewMsg / ThemeCancelPreviewMsg ..........................> applyThemeChanged()
ThemeFileChangedMsg   > handleThemeFileChanged   > ThemeChangedMsg /   watchCurrentTheme() re-Watch

cleanupManagedResources()
  themeWatcher.Stop()                            both exit paths, once-guarded
Issue acceptance criterion Handled by Status
Editing the active custom theme updates the running TUI within ~1s (debounce), incl. atomic-save editors SetProgram wiring; atomic saves already handled by the watcher (directory watching, rename/basename matching) yes, e2e TestTheme_HotReload
Switching themes re-targets the watcher re-Watch in applyThemeChanged, the single funnel for picker selection, preview, cancel and hot reload yes, unit test
Exiting the TUI stops the watcher (no goroutine/fd leak) Stop() in cleanupManagedResources, reached from both normal exit and external cancellation yes, unit test
Regression test so the next TUI refactor cannot silently drop the wiring e2e/tui/theme_test.go (red/green verified against main) plus pkg/tui/theme_watcher_wiring_test.go yes

Notes

  • themeWatcher sits behind a small themeFileWatcher interface so unit tests can record Watch/Stop calls.
  • Nil watcher (embedders or tests that never call SetProgram) is a no-op; built-in-only refs already no-op inside the watcher.
  • p.Send after program termination is a documented no-op in bubbletea v2, so a late debounce callback cannot block or crash.
  • The e2e test re-edits the file on each poll: the watcher arms asynchronously (ThemeChangedMsg is sequenced after the change notification), so a single write could race fsnotify attachment. The 700ms poll interval exceeds the 500ms debounce so rewrites cannot starve the timer.
  • docs/features/tui/index.md needs no change: its hot-reload claim is accurate again.

Validation

  • go build ./...
  • go test -race ./e2e/tui/ ./pkg/tui/ ./pkg/tui/styles/ ./cmd/root/
  • golangci-lint run pkg/tui/... e2e/tui/... reports 0 issues

Custom theme hot reload stopped working when the tab-view rewrite
dropped the ThemeWatcher wiring: nothing constructed the watcher or
emitted ThemeFileChangedMsg anymore.

Create the watcher in SetProgram (its callback injects the message via
program.Send), point it at the applied theme, re-target it in
applyThemeChanged (single funnel for picker selection, preview, cancel
and hot reload), and stop it in cleanupManagedResources so both exit
paths release the fsnotify handle.

Add an e2e regression test driving the full loop through tuitest plus
unit tests for the watcher lifecycle wiring.

Fixes #3501
@Sayt-0 Sayt-0 requested a review from a team as a code owner July 7, 2026 11:14
@dgageot dgageot merged commit 8e79067 into main Jul 7, 2026
14 checks passed
@dgageot dgageot deleted the fix/3501-theme-watcher-rewire branch July 7, 2026 11:21
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.

TUI: custom theme hot-reload no longer works — ThemeWatcher was unwired in the tab-view rewrite

2 participants