Expand description
GitHub fetch state for the TUI (issue #128, part 6/6 of the
tui::app::App decomposition #102; threading migrated onto the
shared async-task spine in #255).
Owns the slice of App state that tracks issue / PR linking + the
cached results of the gh issue view / gh pr view shell-outs:
link— theBranchLinkresolved for the currently-selected worktree’s branch (the(issue, pr)tuple plus their provenanceLinkSourcemarkers).link_slug— theowner/reposlug parsed from theoriginremote,Nonewhen there is no GitHub remote.issue_cache/pr_cache— per-(target, number) caches keyed by issue / PR number. Each entry is aGitHubFetchState: cold entries are simply absent from the map (treated asIdleby the accessors),Loadingwhile a shell-out is in flight,Loaded(T)on success,Error(msg)on failure. Per-key identity matters: pre-#138 the cache was a single per-target slot, so completingIssue(42)falsely “warmed”Issue(43)(the cache identity ignored the number).
What this module is, post-#255: a result cache + link state.
It deliberately no longer owns the off-thread coalescing / dedupe /
late-result-drop — that machinery is now the generic
super::async_task::TaskRunner spine, shared with the worktree
refresh, so there is one off-thread mechanism instead of two. Pre-
#255 this module also held an inflight: HashSet<FetchKey> that
deduped concurrent fetches and gated the #138 late-drop, but it had
no per-fetch generation: two workers for the same key (the
request → invalidate → request retry path) were indistinguishable,
so a stale worker that reported first could win the slot and a fresh
result be dropped (Codex adversarial-review finding on PR #260). The
spine’s per-key generation counter fixes that race.
The orchestrator pattern (post-#255): App checks
GitHubFetch::is_cached for a terminal hit; on a miss it claims a
generation from the spine ([TaskRunner::request]), marks the cache
GitHubFetchState::Loading via GitHubFetch::mark_loading, and
spawns the gh worker tagged with that generation. The worker posts
a TaskMsg::Github{Issue,Pr} back; the event loop applies it only
when [TaskRunner::complete] confirms the generation is still
authoritative, then stamps the terminal result here via
GitHubFetch::complete_issue / GitHubFetch::complete_pr (pure
cache writes — the drop decision lives on the spine now).
The explicit user-initiated refresh (F key →
App::refresh_github_status) flushes the cache via
GitHubFetch::invalidate before re-requesting — the user just
asked for fresh data, so a HitCache short-circuit there would be a
bug — and the App pairs that flush with a spine
invalidate_matching(is_github) so any in-flight worker’s late
result is dropped (the navigation invariant: cache clear and spine
generation-bump always move together).
Structs§
- GitHub
Fetch - GitHub fetch state slice of the TUI
App(issue #128; threading on the async-task spine since #255).
Enums§
- Fetch
Key - Identity of a GitHub fetch. The
(target, number)tuple is the dedupe key:Issue(42)andPr(42)never collide (they hit differentghsubcommands), andIssue(42)vsIssue(43)are independent fetches against different REST endpoints. - GitHub
Fetch State - State of a background GitHub fetch (issue or PR). Generic over
Tso the same enum drives bothIssueStatusandPrStatus. TheIdlevariant is the cold-cache identity;Loadingflags an inflightghshell-out so the UI can paint a “…loading” badge;Loaded(T)andError(String)are the two terminal outcomes.