CPLAT-10756: fix References preview stuck on "Resolving…"#62
Merged
Conversation
JIRA: https://sendbird.atlassian.net/browse/CPLAT-10756 The References preview still had three symptoms — stuck on "Resolving…", lag, and CPU spikes. Root cause was structural: the extract/resolve was never dispatched from a real Update path, so it leaned on the View render path (which discards cmds) plus a tick flush. - setSessPreviewMode returned no cmd. Entering References mode therefore dispatched nothing; the extract only ran when the View render path happened to call updateSessionRefsPreview (cmd discarded) and handleTick later flushed a pending slot. That added up-to-3s latency and, if navigation intervened, left the pane stuck forever. Fixed: setSessPreviewMode now returns updateSessionPreview()'s cmd, and every caller dispatches it. - The View render path (renderSessionSplit) called updateSessionPreview for refs mode every frame, mutating refsInFlight as a side effect while discarding the returned extract cmd — the exact shape that stranded a session on "Resolving…". Fixed: exclude refs (like live) from the View path; it is initialized from Update paths only, and re-rendered on resize via resizeAll (which can dispatch). - updateSessionRefsPreview allocated a fresh viewport.New on every call. Fixed: only reallocate when the pane dimensions actually change. Removes the sessRefsPending map + handleTick flush crutch (no longer needed now that entry/navigation/resize all dispatch the cmd directly). Replaces the pending-flush test with TestSetRefsPreviewModeDispatchesExtract, which asserts entering refs mode returns a cmd that extracts the selected session.
Kairo-Kim
approved these changes
Jul 8, 2026
mitrilmad
approved these changes
Jul 8, 2026
…root cause) JIRA: https://sendbird.atlassian.net/browse/CPLAT-10756 The References preview kept getting stuck on "Resolving…" no matter where the extract/resolve was dispatched from. Five prior fixes moved the dispatch around (#57/#58/#60/#61 + the on-entry commit) because they read the symptom as a missing dispatch. The dispatch was fine — a.sessions WAS being updated. The render path was reading the wrong copy. Root cause is duplicated state with the reader pointed at the stale half: - refsExtractedMsg / refStatusMsg update a.sessions[i].Refs (the source of truth) but do NOT rebuild the session list. - updateSessionRefsPreview renders from selectedSession(), which returns the list widget's sessionItem.sess — a copy snapshotted at the last rebuildSessionList. That copy's Refs/RefsResolved never update, so the preview sees len(refs)==0 && HasRefs && !RefsResolved forever → "Resolving…", while refsInFlight stays armed and blocks re-extract. Every other preview mode (memory/tasks/workflows) stores results in an App-level cache keyed by session ID, so they were immune; refs alone stored state inside the session slice. Fixes: - updateSessionRefsPreview now re-reads the authoritative session from a.sessions (new sessionByIDFromStore) instead of trusting the passed-in list copy. Covers both the async-handler and navigation paths. - carryOverRefState preserves resolved/mid-flight Refs across a full rescan (manual refresh, new-session autorefresh), which replaced a.sessions with a freshly-scanned slice that has HasRefs but empty Refs — blanking resolved refs and, mid-resolve, stranding on a bogus "No resolvable references". Transcript-grew sessions keep cached refs but clear RefsResolved for one re-resolve; in-flight sessions are left for their pass to finish. Tests: TestRefsExtractedReflectedInPreview (fails before the read-from-store fix), TestRefsSurviveRescan, TestCarryOverRefStateReresolvesOnTranscriptGrowth, TestCarryOverRefStateKeepsMidFlight.
JIRA: https://sendbird.atlassian.net/browse/CPLAT-10756 Previously the open-PR/Jira badge only appeared after the user opened a session's References preview, because ref status was resolved on demand for the open preview only. Sessions you never previewed showed no badge even when they had open PRs. Now badges fill in asynchronously for the sessions currently ON SCREEN: - resolveVisibleRefsCmd extracts refs for the visible page slice (Paginator.GetSliceBounds) whose links aren't resolved yet, dispatched from handleTick and the navigation debounce. refsInFlight dedups so a row visible across many ticks is worked once. - syncSessionRefsToList copies resolved state from a.sessions (source of truth) into the list-widget row copy so OpenRefCounts — and thus the badge — updates live, without a full rebuildSessionList that would reset scroll/cursor. - refStatusMsg now calls syncSessionRefsToList so the badge fills in whether or not the References preview is open. Deliberately scoped to the visible page, NOT the fleet. A tick-driven sweep across every HasRefs session is exactly what pegged CPU for minutes (each `gh pr view` ~1.6s, hundreds of them) and was removed in #60. Bounding work to what the user can see keeps the fan-out tiny: extract is a ~10ms offline scan and the follow-on status resolve is already capped at 4 concurrent. Tests: TestResolveVisibleRefsCmdOnlyVisibleUnresolved (scope + dedup), TestSyncSessionRefsToListUpdatesBadge (badge reflects resolved state).
…e-2 wipe JIRA: https://sendbird.atlassian.net/browse/CPLAT-10756 The References preview kept sticking on "Resolving PR/Jira status…" with no open-PR/Jira badge for the current-window live session, despite the earlier read-from-store and carry-over fixes. Two more root causes, both reproduced by new tests before fixing: 1. projectCentric group mode (the DEFAULT) never dispatched the extract. In projectCentric mode a project's session renders as a projectItem head row, not a sessionItem. updateSessionPreview took the selectedProject() branch, called updateProjectPreview (project summary — no refs), and returned nil. updateSessionRefsPreview was never reached, so the extract cmd was never dispatched and the preview stuck on "Resolving…" forever. The prior fixes all exercised the flat-mode sessionItem path and missed this. Fix: in refs mode, route a projectItem through the session refs path using its representative session (pi.sessions[0]). 2. phase-2 full scan wiped refs extracted during phase-1. sessionsScannedMsg replaced a.sessions with a freshly-scanned slice (HasRefs=true, empty Refs) without carrying over ref state, blanking refs the user already extracted and reverting to "Resolving…". Fix: call carryOverRefState before the swap (same treatment doRefresh already had). Also hardened the refsInFlight dedup guard: it was armed before checking whether extractSessionRefsCmd actually returned a cmd. A session with no FilePath yields nil, so the guard latched with nothing to clear it — a permanent "Resolving…" with no retry. Now armed only when a cmd is dispatched, in both updateSessionRefsPreview and resolveVisibleRefsCmd. And extended syncSessionRefsToList to update projectItem rows (re-summing openPRs) so the open-PR badge fills in on the project head row too. Tests (refs_stuck_test.go): projectCentric dispatch + badge, phase-2 survival, live full-flow, refsInFlight leak guard.
JIRA: https://sendbird.atlassian.net/browse/CPLAT-10756 This is the actual root cause behind the persistent "Resolving PR/Jira status…" the user kept hitting, which the earlier projectCentric/phase-2/leak fixes did not touch: the "refs" preview mode is PERSISTED across runs (preview_mode: refs in prefs). On startup NewApp restores sessPreviewMode=refs + sessSplit.Show=true directly, NOT through setSessPreviewMode — the only path that dispatches the offline extract. The first WindowSizeMsg then hits resizeAll's startup branch (sessionList.Width()==0), which dispatched the restored preview's async cmd for LIVE mode only. Refs mode fell through, so the extract was never dispatched: View() deliberately skips refs mode (it can't dispatch a cmd), and no tick path re-initializes a preview that was never opened via setSessPreviewMode. Result: refs stayed empty and the pane sat on "Resolving…" indefinitely. Fix: in the startup-restore branch, dispatch updateSessionPreview for refs mode too (not just live). Reproduced by refs_startup_test.go in both flat and the default projectCentric group modes before fixing. Also make newTestApp hermetic: a persisted "refs" mode now arms refsInFlight at startup, so clear refsInFlight + sessSplit.Show in the test harness, otherwise ref tests inherit in-flight state from the developer's local prefs and see a nil extract cmd.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
JIRA: https://sendbird.atlassian.net/browse/CPLAT-10756
Problem
The References preview kept getting stuck on "Resolving PR/Jira status…" (empty pane, no open-PR/Jira badge) for the current-window live session, no matter where the extract/resolve was dispatched from. This took several passes: the symptom (empty "Resolving…" placeholder) is reachable from any path where the extracted refs never land in
a.sessions, so each pass fixed a real path but not the one the user was actually hitting.The screen state that pins the diagnosis:
renderSessionRefsshows the placeholder only whenlen(Refs)==0 && HasRefs && !RefsResolved. If extract had landed (even unresolved) you'd see ref lines. So a persistent empty placeholder always means the extract result never reacheda.sessions— either it was never dispatched, or it was dispatched and then wiped.Root causes (fixed in order; #4 was the actual user-facing one)
1. Read from the wrong copy (
8c5bd3d).refsExtractedMsg/refStatusMsgupdatea.sessions[i].Refs(source of truth) but don't rebuild the list.updateSessionRefsPreviewrendered fromselectedSession(), which returns the list widget'ssessionItem.sess— a stale snapshot from the lastrebuildSessionList. Fix: re-read the authoritative session viasessionByIDFromStore.2. projectCentric routing (
a8a4f47).In the default
groupProjectCentricmode a session renders as a projectItem head row.updateSessionPreviewtook theselectedProject()branch →updateProjectPreview(project summary, no refs) →return nil, soupdateSessionRefsPreviewwas never reached and the extract was never dispatched. Fix: in refs mode, route a projectItem through the session refs path using its representative session.3. phase-2 scan wipe + refsInFlight leak (
a8a4f47).sessionsScannedMsg(phase-2 full scan) replaceda.sessionswithoutcarryOverRefState, blanking already-extracted refs. Separately,refsInFlightwas armed before checkingextractSessionRefsCmdreturned non-nil (empty FilePath → nil cmd → guard latched forever with nothing to clear it). Fix: carry over ref state before the swap; arm the guard only when a cmd is actually dispatched.4. THE REAL ONE — startup restore (
dc918b9).preview_mode: refsis persisted across runs. On startupNewApprestoressessPreviewMode=refs+sessSplit.Show=truedirectly, bypassingsetSessPreviewMode— the only path that dispatches the offline extract. The firstWindowSizeMsgthen hitsresizeAll's startup branch (sessionList.Width()==0), which dispatched the restored preview's async cmd for LIVE mode only. Refs fell through,View()deliberately skips refs mode (it can't dispatch a cmd), and no tick re-initializes a preview that was never opened via the setter. So the extract was never dispatched and the pane sat on "Resolving…" indefinitely. Fix: dispatchupdateSessionPreviewfor refs mode too in that startup branch.Async open-PR/Jira badges (
efc2f22)Previously the open-PR/Jira badge only appeared after opening a session's References preview. Now badges fill in asynchronously for the sessions currently on screen:
resolveVisibleRefsCmdextracts refs for the visible page slice whose links aren't resolved yet, dispatched fromhandleTickand the navigation debounce;refsInFlightdedups.syncSessionRefsToListcopies resolved state froma.sessionsinto the list-widget row (now handles projectItem rows too, re-summingopenPRs) so the badge updates live without a fullrebuildSessionList.Deliberately scoped to the visible page, NOT the fleet — a tick-driven sweep across every
HasRefssession is what pegged CPU for minutes (eachgh pr view~1.6s, hundreds of them) and was removed in #60.Testing
refs_startup_test.go— reproduces the real startup-restore bug (flat + projectCentric); fails beforedc918b9, passes after.refs_stuck_test.go— projectCentric dispatch + badge, phase-2 survival, live full-flow, refsInFlight leak guard.TestRefsExtractedReflectedInPreview— fails before the read-from-store fix, passes after.TestRefsSurviveRescan,TestCarryOverRefState*,TestSetRefsPreviewModeDispatchesExtract,TestResolveVisibleRefsCmdOnlyVisibleUnresolved,TestSyncSessionRefsToListUpdatesBadge.newTestAppmade hermetic w.r.t. the dev's persistedpreview_mode: refs(clearsrefsInFlight+sessSplit.Show).go build,go vet, fullgo test ./internal/tui/ ./internal/session/incl.-raceall green.