Skip to content

CPLAT-10756: fix refs preview stuck on Resolving… (read refs from source of truth)#62

Open
gavin-jeong wants to merge 3 commits into
masterfrom
CPLAT-10756-refs-dispatch-on-entry
Open

CPLAT-10756: fix refs preview stuck on Resolving… (read refs from source of truth)#62
gavin-jeong wants to merge 3 commits into
masterfrom
CPLAT-10756-refs-dispatch-on-entry

Conversation

@gavin-jeong

@gavin-jeong gavin-jeong commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

JIRA: https://sendbird.atlassian.net/browse/CPLAT-10756

Problem

The References preview kept getting stuck on "Resolving…" no matter where the extract/resolve was dispatched from. Five prior fixes (#57/#58/#60/#61 + dispatch on entry) moved the dispatch around because they read the symptom as a missing dispatch.

The real root cause (state ownership)

The dispatch was fine all along — a.sessions was being updated. The render path was reading the wrong copy.

State was duplicated, 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 rendered 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 saw len(refs)==0 && HasRefs && !RefsResolved forever → "Resolving…", while refsInFlight stayed armed and blocked any 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 — which is also wiped by a full rescan.

Fix

  1. Read from the source of truth. updateSessionRefsPreview now re-reads the authoritative session from a.sessions (new sessionByIDFromStore) instead of trusting the passed-in list copy. Covers the async-handler, navigation, and resize paths in one place.
  2. Survive rescans. 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.

The earlier commit on this branch (dispatch ref extract on entry, keep View pure) is kept — dispatching from a real Update path and keeping View pure are still correct — but they were necessary, not sufficient.

Async open-PR/Jira badges (follow-up commit)

Previously the open-PR/Jira badge only appeared after opening a session's References preview, since status resolved on demand for the open preview only. Sessions you never previewed showed no badge even with 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 into the list-widget row copy so OpenRefCounts — and thus the badge — updates live, without a full rebuildSessionList (which 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.

Testing

  • TestRefsExtractedReflectedInPreview — fails before the read-from-store fix (renders "Resolving…"), passes after.
  • TestRefsSurviveRescan, TestCarryOverRefStateReresolvesOnTranscriptGrowth, TestCarryOverRefStateKeepsMidFlight.
  • TestSetRefsPreviewModeDispatchesExtract (from the prior commit) still green.
  • TestResolveVisibleRefsCmdOnlyVisibleUnresolved — async badge fill-in is scoped to the visible page and deduped (regression guard against the CPLAT-10756: resolve ref status only for open preview (fix References freeze) #60 fleet sweep).
  • TestSyncSessionRefsToListUpdatesBadge — badge count reflects resolved state without a full rebuild.
  • go build, go vet, full go test ./internal/tui/ ./internal/session/ incl. -race all green.

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.

@jinsekim jinsekim left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@jinsekim jinsekim added the auto-review/approved Auto-approved by the Slack auto-reviewer bot label Jul 8, 2026

@jinsekim jinsekim left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

…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.
@gavin-jeong gavin-jeong changed the title CPLAT-10756: dispatch ref extract on entry, keep View pure (fix stuck/lag/CPU) CPLAT-10756: fix refs preview stuck on Resolving… (read refs from source of truth) Jul 8, 2026
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-review/approved Auto-approved by the Slack auto-reviewer bot

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants