feat(tui): subscribe to daemon event stream for instant updates#593
feat(tui): subscribe to daemon event stream for instant updates#593wesm merged 14 commits intoroborev-dev:mainfrom
Conversation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Subscribe to daemon NDJSON event stream for instant refresh when reviews are closed externally. Falls back to existing polling. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Close old sseCh on reconnect to let orphaned waitForSSE exit cleanly - Reset backoff after successful connection (track connected state) - Use atomic.Int32 for thread-safe test counter - Remove unused eventWritten channel from test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- waitForSSE now selects on both sseCh and sseStop, so closing sseStop cleanly unblocks waiters without closing the data channel (which would race with the producer goroutine) - Run() uses the final model from p.Run() for cleanup, avoiding double-close when reconnect already closed the original sseStop Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- MEDIUM: handleSSEEventMsg now sets ssePendingRefresh instead of dropping events when loadingJobs/loadingMore is true. The flag is consumed in handleJobsMsg/handleJobsErrMsg to trigger a follow-up fetch, preventing stale data. - LOW: sseReadLoop validates resp.StatusCode before decoding, treating non-200 responses as reconnectable errors instead of decoding junk. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TUI now refreshes instantly when new reviews are queued, not just when they start/complete. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Now that the TUI subscribes to daemon SSE events for real-time updates (enqueue, start, complete, fail, cancel, close), the aggressive 2s polling during active jobs is unnecessary. Replace the adaptive 2s/10s intervals with a single 15s fallback that catches anything SSE misses (connection drops, reconnect gaps, un-broadcast state changes). The displayTick (1s) for cosmetic repaints (elapsed counters, flash expiry) is unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…event type - MEDIUM: consumeSSEPendingRefresh now also called from handlePaginationErrMsg so events during pagination aren't dropped - LOW: consumeSSEPendingRefresh triggers full refresh set (fetchJobs + fetchStatus + fetchFixJobs) instead of just fetchJobs - LOW: handleCloseReview broadcasts review.reopened when Closed=false, reserving review.closed for actual close operations Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…chine - Add TestHandleCloseReview_BroadcastsReopenEvent verifying Closed=false emits review.reopened - Add TestSSEPendingRefreshStateMachine covering: flag set during loadingJobs, drained on jobs completion; flag set during loadingMore, drained on pagination; flag not set when no fetch in flight Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Backoff growth now uses min(backoff*2, maxBackoff) to prevent overshooting the 30s cap (was growing 16s -> 32s) - Replace t.Fatal with require.FailNow in select timeout branches per project test conventions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…change The SSE goroutine could be stuck in exponential backoff after a same-address daemon restart, leaving the TUI without real-time updates even though polling had already reconnected. Now handleReconnectMsg restarts the SSE subscription on any successful reconnect regardless of whether the endpoint address changed. LOW finding about newModel starting a goroutine as a constructor side-effect is noted but intentionally not changed — all test callers use withExternalIODisabled() which skips the goroutine, and moving startup to Init would require a larger lifecycle refactor. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
roborev: Combined Review (
|
…ical ref on enqueue review.closed/review.reopened events were broadcast with only JobID, causing repo-filtered SSE subscribers to never receive them. Load job metadata via GetJobByID and populate Repo, RepoName, SHA, Agent. job.enqueued event used the request's original gitRef (possibly a symbolic ref like HEAD) instead of the stored job.GitRef (resolved SHA), causing inconsistent ref values across the job lifecycle event stream. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Set m.loadingJobs = true in handleSSEEventMsg before dispatching fetchJobs, so subsequent SSE events are properly deferred instead of triggering duplicate overlapping refreshes - Fix TestHandleEnqueue_BroadcastsEvent path assertion to handle macOS symlink resolution (/var -> /private/var) and Windows path normalization (short names, forward slashes) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
roborev: Combined Review (
|
roborev: Combined Review (
|
|
This is a pre-existing pattern, not a regression from this PR. Preventing stale status/fix-jobs responses from overwriting fresher data would be a good improvement but would need to touch the existing polling infrastructure too. Filed #596 for follow-up. |
|
Merging this! I have a fix coming for TUI rendering that I spotted when trying this out |
Summary
/api/stream/events) via a background goroutine, triggering immediate data refreshes instead of waiting for the 2-10s poll cyclereview.closed,review.reopened, andjob.enqueuedevents from the corresponding API handlers — previously only worker lifecycle events (review.started/completed/failed/canceled) were broadcast🤖 Generated with Claude Code