diff --git a/internal/tui/model.go b/internal/tui/model.go index a92312c..630a32a 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -176,7 +176,12 @@ func (m Model) Update(message tea.Msg) (tea.Model, tea.Cmd) { } if progress.closed { // A stale Application generation closes without claiming an - // authoritative completion snapshot. + // authoritative completion snapshot. Reconcile with the Application + // so this Model does not strand rows in its superseded Checking frame. + formerCatalog := newTargetCatalog(m.snapshot, m.showUnavailable, m.selectedTargetID) + formerIndex := formerCatalog.index(m.selectedTargetID) + m.snapshot = m.application.Snapshot() + m.selectedTargetID = newTargetCatalog(m.snapshot, m.showUnavailable, m.selectedTargetID).retain(m.selectedTargetID, formerIndex) m.refreshing = false m.detectionUpdates = nil return m, m.deferredQuit() diff --git a/internal/tui/progressive_detection_test.go b/internal/tui/progressive_detection_test.go index cecd950..3dec929 100644 --- a/internal/tui/progressive_detection_test.go +++ b/internal/tui/progressive_detection_test.go @@ -99,6 +99,28 @@ func TestCompleteModelStreamsStartupAndRefreshByTargetWithoutMovingSelection(t * } } +func TestCompleteModelReconcilesAuthoritativeSnapshotWhenDetectionGenerationClosesStale(t *testing.T) { + startup := make(chan app.TargetDetectionUpdate) + module := &progressiveDetectionModule{ + snapshot: app.Snapshot{Targets: []app.TargetSnapshot{{ + ID: "shell", Description: target.Description{Name: "Shell", Summary: "Shell description"}, + }}}, + streams: []chan app.TargetDetectionUpdate{startup}, + } + model := resizedModel(t, module, themeModeNoColor, 80, 24) + assertUsableFrame(t, model.View(), 80, 24, "> … Checking Shell") + + // A newer Application generation has already established the authoritative + // result, so the stale stream closes without publishing a completion update. + _ = module.detectionUpdate("shell", app.TargetReady, true, target.Enabled, "shell enabled", nil) + close(startup) + model, next := applyDetectionCommand(t, model, model.Init()) + if next != nil || model.refreshing { + t.Fatalf("stale closure left detection active: next=%v refreshing=%v", next != nil, model.refreshing) + } + assertUsableFrame(t, model.View(), 80, 24, "> ● Enabled Shell", "Evidence: shell enabled") +} + func (m *progressiveDetectionModule) detectionUpdate(id string, phase app.TargetDetectionPhase, available bool, state target.State, evidence string, err error) app.TargetDetectionUpdate { for i := range m.snapshot.Targets { if m.snapshot.Targets[i].ID != id {