@@ -28,6 +28,7 @@ import (
2828 "github.com/docker/cagent/pkg/tui/page/chat"
2929 "github.com/docker/cagent/pkg/tui/service"
3030 "github.com/docker/cagent/pkg/tui/styles"
31+ "github.com/docker/cagent/pkg/tui/subscription"
3132)
3233
3334// appModel represents the main application model
@@ -48,9 +49,10 @@ type appModel struct {
4849
4950 transcriber * transcribe.Transcriber
5051
51- themeWatcher * styles.ThemeWatcher
52- themeWatcherEventCh chan string // Channel for theme file change events (carries themeRef)
53- themeListenerStarted bool // Guard to prevent multiple listeners
52+ // External event subscriptions (Elm Architecture pattern)
53+ themeWatcher * styles.ThemeWatcher
54+ themeSubscription * subscription.ChannelSubscription [string ] // Listens for theme file changes
55+ themeSubStarted bool // Guard against multiple subscriptions
5456
5557 // keyboardEnhancements stores the last keyboard enhancements message from the terminal.
5658 // This is reapplied to new chat/editor instances when sessions are switched.
@@ -119,21 +121,24 @@ func DefaultKeyMap() KeyMap {
119121func New (ctx context.Context , a * app.App ) tea.Model {
120122 sessionState := service .NewSessionState (a .Session ())
121123
122- // Create a channel for theme file change events (carries themeRef)
124+ // Create a channel for theme file change events
123125 themeEventCh := make (chan string , 1 )
124126
125127 t := & appModel {
126- keyMap : DefaultKeyMap (),
127- dialog : dialog .New (),
128- notification : notification .New (),
129- completions : completion .New (),
130- application : a ,
131- sessionState : sessionState ,
132- transcriber : transcribe .New (os .Getenv ("OPENAI_API_KEY" )), // TODO(dga): should use envProvider
133- themeWatcherEventCh : themeEventCh ,
128+ keyMap : DefaultKeyMap (),
129+ dialog : dialog .New (),
130+ notification : notification .New (),
131+ completions : completion .New (),
132+ application : a ,
133+ sessionState : sessionState ,
134+ transcriber : transcribe .New (os .Getenv ("OPENAI_API_KEY" )), // TODO(dga): should use envProvider
135+ // Set up theme subscription using the subscription package
136+ themeSubscription : subscription .NewChannelSubscription (themeEventCh , func (themeRef string ) tea.Msg {
137+ return messages.ThemeFileChangedMsg {ThemeRef : themeRef }
138+ }),
134139 }
135140
136- // Create theme watcher with callback that sends themeRef to channel
141+ // Create theme watcher with callback that sends to the subscription channel
137142 t .themeWatcher = styles .NewThemeWatcher (func (themeRef string ) {
138143 // Non-blocking send to the event channel
139144 select {
@@ -170,27 +175,15 @@ func (a *appModel) Init() tea.Cmd {
170175 a .application .SendFirstMessage (),
171176 }
172177
173- // Start theme file listener only once (guard against Init being called multiple times)
174- if ! a .themeListenerStarted {
175- a .themeListenerStarted = true
176- cmds = append (cmds , a .listenForThemeFileChanges ())
178+ // Start theme subscription only once (guard against Init being called multiple times)
179+ if ! a .themeSubStarted {
180+ a .themeSubStarted = true
181+ cmds = append (cmds , a .themeSubscription . Listen ())
177182 }
178183
179184 return tea .Sequence (cmds ... )
180185}
181186
182- // listenForThemeFileChanges returns a command that listens for theme file change events
183- // and sends them as messages to the TUI.
184- func (a * appModel ) listenForThemeFileChanges () tea.Cmd {
185- return func () tea.Msg {
186- themeRef , ok := <- a .themeWatcherEventCh
187- if ! ok {
188- return nil // Channel closed
189- }
190- return messages.ThemeFileChangedMsg {ThemeRef : themeRef }
191- }
192- }
193-
194187// Help returns help information
195188func (a * appModel ) Help () help.KeyMap {
196189 return core .NewSimpleHelp (a .Bindings ())
@@ -422,14 +415,14 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
422415 if err != nil {
423416 // Failed to load - show error but keep current theme
424417 return a , tea .Batch (
425- a .listenForThemeFileChanges (),
418+ a .themeSubscription . Listen (), // Re-subscribe to continue listening
426419 notification .ErrorCmd (fmt .Sprintf ("Failed to hot-reload theme: %v" , err )),
427420 )
428421 }
429422 styles .ApplyTheme (theme )
430423 // Continue listening for more changes and emit ThemeChangedMsg for cache invalidation
431424 return a , tea .Batch (
432- a .listenForThemeFileChanges (),
425+ a .themeSubscription . Listen (), // Re-subscribe to continue listening
433426 notification .SuccessCmd ("Theme hot-reloaded" ),
434427 core .CmdHandler (messages.ThemeChangedMsg {}),
435428 )
0 commit comments