44 "fmt"
55 "os"
66 "strings"
7- "sync"
87 "time"
98
109 tea "github.com/charmbracelet/bubbletea"
@@ -84,12 +83,10 @@ type cleanModel struct {
8483 activePhase int
8584 msgCh <- chan cleanMsg
8685 spinnerTick int
87- drainedCh chan struct {}
88- drainOnce sync.Once
8986 width int
9087}
9188
92- func newCleanModel (specs []cleanPhaseSpec , msgCh <- chan cleanMsg , drainedCh chan struct {} ) * cleanModel {
89+ func newCleanModel (specs []cleanPhaseSpec , msgCh <- chan cleanMsg ) * cleanModel {
9390 phases := make ([]cleanPhaseModel , len (specs ))
9491 idx := make (map [string ]int , len (specs ))
9592 for i , spec := range specs {
@@ -106,7 +103,6 @@ func newCleanModel(specs []cleanPhaseSpec, msgCh <-chan cleanMsg, drainedCh chan
106103 phases : phases ,
107104 phaseIndex : idx ,
108105 msgCh : msgCh ,
109- drainedCh : drainedCh ,
110106 }
111107}
112108
@@ -121,7 +117,6 @@ func (m *cleanModel) listen() tea.Cmd {
121117 return func () tea.Msg {
122118 msg , ok := <- m .msgCh
123119 if ! ok {
124- m .drainOnce .Do (func () { close (m .drainedCh ) })
125120 return cleanDoneMsg {}
126121 }
127122 return msg
@@ -330,27 +325,25 @@ type cleanView struct {
330325 program * tea.Program
331326 msgCh chan cleanMsg
332327 doneCh chan error
333- drainedCh chan struct {}
334328 finalModel * cleanModel
335329 // shared
336330 mode output.OutputMode
337331}
338332
339333// newCleanView creates a cleanView for the given phases. In TUI mode the
340- // BubbleTea program is started immediately. In plain mode no program is started.
334+ // BubbleTea program is started immediately in alt-screen mode so the initial
335+ // render never pollutes the terminal scrollback. In plain mode no program is started.
341336func newCleanView (mode output.OutputMode , specs []cleanPhaseSpec ) * cleanView {
342337 v := & cleanView {mode : mode }
343338 if mode == output .ModePlain {
344339 return v
345340 }
346341 msgCh := make (chan cleanMsg , 100 )
347- drainedCh := make (chan struct {})
348- m := newCleanModel (specs , msgCh , drainedCh )
349- p := tea .NewProgram (m )
342+ m := newCleanModel (specs , msgCh )
343+ p := tea .NewProgram (m , tea .WithAltScreen ())
350344 v .program = p
351345 v .msgCh = msgCh
352346 v .doneCh = make (chan error , 1 )
353- v .drainedCh = drainedCh
354347 go func () {
355348 fm , err := p .Run ()
356349 if cm , ok := fm .(* cleanModel ); ok {
@@ -409,16 +402,15 @@ func (v *cleanView) AdvancePhase() {
409402 }
410403}
411404
412- // Wait closes the message channel, waits for the final message to be rendered,
413- // quits the BubbleTea program, then prints the final frame so it remains visible.
405+ // Wait closes the message channel, blocks until the BubbleTea program exits,
406+ // then prints the final frame to the primary screen. Alt-screen mode clears
407+ // the TUI on exit, so we must reprint to leave the results visible.
414408// In plain mode it is a no-op.
415409func (v * cleanView ) Wait () {
416410 if v .mode == output .ModePlain {
417411 return
418412 }
419413 close (v .msgCh )
420- <- v .drainedCh // last message consumed → final frame rendered
421- v .program .Quit () // safe to quit now
422414 <- v .doneCh
423415 if v .finalModel != nil {
424416 fmt .Print (v .finalModel .View ())
0 commit comments