inspect: auto-exit the TUI when the run finishes (no Enter to start the judge) · Entire

inspect: auto-exit the TUI when the run finishes (no Enter to start the judge)

1ecb920→main·

The TUI dashboard blocked on an explicit dismissal key (q/Esc/Enter) after inspectors finished, and only then did the judge (SynthesisSink) run — so the user had to press Enter just to start the final consolidation. Make the model exit on runFinishedMsg instead of waiting: the judge and the narrative dump (which write to stdout after the alt-screen tears down) now run automatically. Ctrl+C during the run is unchanged. Adds a test asserting runFinishedMsg returns tea.Quit.

Sessions

7c0a8804b13eView transcript

Changes

3

176 unmodified lines

177
178
179
180
180
181
182
183
184
185
186
187
188

176 unmodified lines

m.rows[i].runEnd = now
        }
        return m, nil
        // Exit as soon as the run completes — do NOT wait for a keypress. The
        // judge (SynthesisSink) and the narrative dump run after the TUI tears
        // down (they write to stdout, which can't happen over the alt-screen), so
        // blocking here would force the user to press Enter just to start the
        // final consolidation. The dump renders the same per-agent outcome below.
        return m, tea.Quit

case tickMsg:
        if m.finished {

Mcmd/entire/cli/review/tui_model.go+6/-1

1203 unmodified lines

1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223

1203 unmodified lines

}
    }
}

func TestTUIModel_AutoExitsOnRunFinished(t *testing.T) {
    t.Parallel()
    m := newTestModel([]string{"agent-a"}, func() {})
    updated, cmd := m.Update(runFinishedMsg{summary: reviewtypes.RunSummary{}})
    if !mustModel(t, updated).finished {
                        t.Fatal("model should be finished after runFinishedMsg")
    }
    // The TUI must exit on its own when the run finishes (no keypress) so the
    // judge (SynthesisSink) and the narrative dump run automatically.
    if cmd == nil {
            t.Fatal("runFinishedMsg should return a quit command (auto-exit), got nil")
    }
    if _, ok := cmd().(tea.QuitMsg); !ok {
            t.Fatalf("runFinishedMsg command = %T, want tea.QuitMsg", cmd())
    }
}

Mcmd/entire/cli/review/tui_model_test.go+17

145 unmodified lines

146
147
148
149
150
151
152
153
154
149
150
151
152
153
156
157
158
154
155
156
157
158
159
160

145 unmodified lines

}

// RunFinished (Sink interface): mark the run complete and send the final
// summary message. The TUI shows the dashboard one more frame with the
// terminal statuses, then waits for the user to dismiss it. After a run
// completes, dismissal requires an explicit exit key (q/Esc/Enter/Ctrl+C);
// Ctrl+O still drills into agent buffers for post-mortem inspection. Other
// keys are no-ops so users can navigate the completed run without
// accidentally dismissing.
// summary message. On completion the TUI exits on its own — it does NOT wait
// for a keypress — so the judge (SynthesisSink) and the narrative dump run
// automatically instead of forcing the user to press Enter to start the final
// consolidation.
//
// IMPORTANT: RunFinished blocks until the user dismisses so that post-run
// sinks (e.g. DumpSink) render their narrative AFTER the TUI has exited
// and the terminal is back in normal mode.
// RunFinished still blocks until the Bubble Tea program has exited so that
// post-run sinks (DumpSink, SynthesisSink) render AFTER the TUI has torn down
// the alt-screen and the terminal is back in normal mode; that block now ends
// as soon as the run finishes rather than on an explicit dismissal key.
func (s *TUISink) RunFinished(summary reviewtypes.RunSummary) {
    s.mu.Lock()
    if s.finished {