π Problem
runTUI (cmd/ccpulse/main.go:258-384, ~127 lines) wires everything inline β env resolution, devlog, cache open/rebuild, pricing, auto-recost, ingester, watcher, quota seam, TUI model β and then manages goroutine lifecycle by hand:
- Two separate
sync.WaitGroups (bg, bfDone), two cancel funcs
- A five-deep defer stack whose relative order is load-bearing and documented only in a 16-line block comment (
main.go:322-337)
- Adding any new background worker requires re-deriving the ordering contract from prose;
shutdown_test.go exists precisely because this is fragile
Related watcher shape: pkg/watcher/watcher.go:56-81 β Run takes no context.Context (shutdown is solely via Close() closing fsnotify channels), and fsnotify errors are silently dropped (watcher.go:74-79). The watcher callback closes over the root ctx for ProcessFile, but the watcher itself can't be cancelled by that ctx.
The current design is correct (per #52's shutdown-discipline work) β this is a maintainability refactor, not a bug fix.
π οΈ Suggested shape
- An
app struct holding the wired deps with start(ctx) / shutdown() methods, so registration order and teardown order live in code (a slice of stop funcs or errgroup) rather than in a comment
errgroup.WithContext per lifecycle stage (backfill group + long-running group), waited in one deterministic teardown
- Add
ctx to watcher.Run (select on ctx.Done()), giving the fsnotify error stream a propagation path; keep w.Close() as the post-p.Run() trigger
β οΈ Notes
π Problem
runTUI(cmd/ccpulse/main.go:258-384, ~127 lines) wires everything inline β env resolution, devlog, cache open/rebuild, pricing, auto-recost, ingester, watcher, quota seam, TUI model β and then manages goroutine lifecycle by hand:sync.WaitGroups (bg,bfDone), two cancel funcsmain.go:322-337)shutdown_test.goexists precisely because this is fragileRelated watcher shape:
pkg/watcher/watcher.go:56-81βRuntakes nocontext.Context(shutdown is solely viaClose()closing fsnotify channels), and fsnotify errors are silently dropped (watcher.go:74-79). The watcher callback closes over the root ctx forProcessFile, but the watcher itself can't be cancelled by that ctx.The current design is correct (per #52's shutdown-discipline work) β this is a maintainability refactor, not a bug fix.
π οΈ Suggested shape
appstruct holding the wired deps withstart(ctx)/shutdown()methods, so registration order and teardown order live in code (a slice of stop funcs or errgroup) rather than in a commenterrgroup.WithContextper lifecycle stage (backfill group + long-running group), waited in one deterministic teardownctxtowatcher.Run(select onctx.Done()), giving the fsnotify error stream a propagation path; keepw.Close()as the post-p.Run()triggershutdown_test.go+ goleak guards inpkg/tuiandcmd/ccpulse(remember the bubbleteaTick.func1goleak ignore applies to both guards).