Skip to main content

Module github_fetch

Module github_fetch 

Source
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 — the BranchLink resolved for the currently-selected worktree’s branch (the (issue, pr) tuple plus their provenance LinkSource markers).
  • link_slug — the owner/repo slug parsed from the origin remote, None when there is no GitHub remote.
  • issue_cache / pr_cache — per-(target, number) caches keyed by issue / PR number. Each entry is a GitHubFetchState: cold entries are simply absent from the map (treated as Idle by the accessors), Loading while 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 completing Issue(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§

GitHubFetch
GitHub fetch state slice of the TUI App (issue #128; threading on the async-task spine since #255).

Enums§

FetchKey
Identity of a GitHub fetch. The (target, number) tuple is the dedupe key: Issue(42) and Pr(42) never collide (they hit different gh subcommands), and Issue(42) vs Issue(43) are independent fetches against different REST endpoints.
GitHubFetchState
State of a background GitHub fetch (issue or PR). Generic over T so the same enum drives both IssueStatus and PrStatus. The Idle variant is the cold-cache identity; Loading flags an inflight gh shell-out so the UI can paint a “…loading” badge; Loaded(T) and Error(String) are the two terminal outcomes.