Documentation
¶
Overview ¶
Package domain holds prv's pure types and derivations. It does no I/O.
Index ¶
- Constants
- func Bucket(pr PR) int
- func Clean(s string) string
- func InWindow(pr PR, now time.Time, closedDays int) bool
- func ServerWait(now time.Time, retryAfter time.Duration, reset time.Time) time.Duration
- func Sort(prs []PR)
- func SortChecks(checks []Check)
- func ValidRef(r Ref) bool
- type CI
- type Check
- type CheckCounts
- type CheckState
- type Delay
- type Detail
- type FailingCheck
- type Merge
- type Outcome
- type PR
- type RateLimit
- type Ref
- type RefError
- type RequestInput
- type Review
- type ReviewInput
- type Reviewer
- type ReviewerKind
- type ReviewerState
- type Snapshot
- type State
- type StateSet
- type Unavail
- type UnavailKind
Constants ¶
const ( // MinServerWait and MaxServerWait bound every delay that came from the // server (Retry-After, reset time), so a hostile or broken value can // neither spin the loop nor park it for days. MinServerWait = time.Second MaxServerWait = 65 * time.Minute // MinFailureBase is the first retry delay after a failure when the // configured interval is shorter than this. MinFailureBase = 30 * time.Second // MaxFailureWait caps the exponential backoff (a longer configured // interval raises the cap to that interval). MaxFailureWait = 5 * time.Minute // RateLimitFloor is the least wait after a rate-limited failure that // carries no timing information. RateLimitFloor = time.Minute // ReservePerCost is how many refreshes' worth of budget prv keeps unused: // it pauses when Remaining <= ReservePerCost * max(Cost, 1). ReservePerCost = 10 )
Tunables of the refresh policy. They are judgement calls, not facts about the GitHub API: a wrong value costs latency, never correctness.
const ( ReasonOtherHost = "only github.com PRs are supported" ReasonIssue = "that is an issue, not a PR" ReasonNotRef = "not a PR reference" )
The reasons ParseRef gives (D-04).
const AllStates = StateSet(1<<StateOpen | 1<<StateMerged | 1<<StateClosed)
AllStates shows open, merged and closed PRs.
const DefaultClosedDays = 14
DefaultClosedDays is the default closed/merged window, in days.
const MaxPRNumber = 2147483647
MaxPRNumber is the largest GraphQL Int, the largest PR number prv accepts.
Variables ¶
This section is empty.
Functions ¶
func Bucket ¶
Bucket returns the attention bucket of a PR (D-01); a lower number needs attention sooner:
0 unavailable: a saved PR GitHub could not return (D-11) 1 open, failing CI 2 open, changes requested 3 open, approved and CI passing (ready to merge) 4 open, CI running 5 every other open PR (this includes approved with no checks or an unknown CI state: "no checks" never counts as passing) 6 open draft 7 merged 8 closed 9 unknown state
Lifecycle state is checked before the draft flag (D-04 as clarified on 2026-10-12): only an open draft sorts in bucket 6; a merged or closed draft sorts with merged or closed PRs.
func Clean ¶
Clean makes a remote string safe to print in a terminal. It strips ANSI escape sequences, turns tabs into spaces, and drops C0/C1 control characters and the bidi override and isolate characters (U+202A..U+202E, U+2066..U+2069). Everything else, including ZWJ emoji sequences, passes through.
func InWindow ¶
InWindow reports whether a PR belongs in the list. Open and unknown-state PRs are always kept, and so is every saved (Manual) PR whatever its state or close date: the window only decides which discovery PRs are kept (WATCH-07, D-13). A merged or closed PR that is not saved is kept only when it closed at or after now minus closedDays days (the boundary is inclusive); one with no close time cannot be placed in the window and is dropped.
func ServerWait ¶
ServerWait is the pause after a rate-limited response, built from that response's own timing. It uses the order and bounds of NextDelay: a positive retryAfter first, then a reset time after now plus one second, each kept within [MinServerWait, MaxServerWait]. Without usable timing it returns RateLimitFloor, so the result is always within [MinServerWait, MaxServerWait].
func Sort ¶
func Sort(prs []PR)
Sort orders PRs in place for display: by attention bucket (see Bucket), then by creation time newest first, then repository ascending, then PR number ascending as an integer (D-02). The last-update time is deliberately not a key: comments and pushes would swap rows between refreshes. Equal keys keep their input order. Unavailable rows (bucket 0) have no creation time: they go by ref, the repository compared without letter case, then the number (D-11).
func SortChecks ¶
func SortChecks(checks []Check)
SortChecks sorts checks in place into the detail view order (D-10): failing, cancelled, running, passing, skipped or neutral, unknown; within a class by lower-cased name, then workflow, then name. The same checks in any input order give the same output.
Types ¶
type CI ¶
type CI int
CI is the check status shown for a pull request. The zero value is the non-favourable CINone ("no checks").
func ClassifyRollup ¶
ClassifyRollup maps statusCheckRollup.state to a CI value. nil (no checks) is CINone, never passing, and an unrecognised value is CIUnknown.
The rollup precedence is GitHub's own: a rollup with a CANCELLED run or a FAILURE beside running checks is FAILURE (01-SPIKE.md, extra finding 3). prv shows it without recomputing it (D-14). "Cancelled is never red" applies to per-check display in Phase 3, not to this list badge.
type Check ¶
type Check struct {
Name string // check-run name, or the status context name
Workflow string // workflow name, else the check suite's app name, else ""
Source string // detail view only: the workflow file's base name, else the app name; "" for a status
Context bool // true for a commit StatusContext
State CheckState
Word string // lowercase display word, e.g. "failure", "timed out"
SuiteCreatedAt time.Time // when the check suite was created (orders re-runs)
ID int64 // check-run databaseId; zero for a status context
}
Check is one check run or commit status of a pull request, already mapped and sanitised.
func LatestChecks ¶
LatestChecks keeps one check per (Context, Workflow, Name): the one from the newest check suite (SuiteCreatedAt, then the larger ID). A job that failed and then passed in a newer run is therefore not failing (D-03). On a full tie the earlier check in the input stays. The output keeps the input order of each key's first appearance; the input is not modified.
type CheckCounts ¶
type CheckCounts struct {
Failing, Cancelled, Running, Passing, Neutral, Other int
}
CheckCounts is how many checks are in each display class. Neutral holds skipped, neutral and stale checks. Other holds the checks of class CheckOther (unknown), which are never counted as skipped.
func CountChecks ¶
func CountChecks(checks []Check) CheckCounts
CountChecks counts checks by class. Callers pass the de-duplicated checks (LatestChecks), so a superseded re-run is never counted (D-03).
type CheckState ¶
type CheckState int
CheckState is the display class of one check (a check run or a commit status). The zero value is CheckOther: an unrecognised value is never failing and never passing.
const ( CheckOther CheckState = iota CheckFailing CheckCancelled CheckRunning CheckPassing CheckNeutral )
func ClassifyCheck ¶
func ClassifyCheck(typename, status, conclusion, state string) (CheckState, string)
ClassifyCheck maps GitHub's check values to a CheckState and a display word. typename is "StatusContext" for a commit status (which reads state); any other typename is a check run (which reads status, and conclusion once it is COMPLETED). The mapping is total: any value it does not know is (CheckOther, "unknown"), never failing and never passing.
func (CheckState) Glyph ¶
func (s CheckState) Glyph() string
Glyph returns the one-cell glyph of the state.
type Delay ¶
type Delay struct {
Wait time.Duration // time until the next refresh
PausedUntil time.Time // when set, no request may go out before this; zero means no pause
}
Delay is the decision of NextDelay.
func NextDelay ¶
func NextDelay(now time.Time, interval time.Duration, failures int, rl RateLimit, last Outcome) Delay
NextDelay decides how long to wait before the next refresh. It is pure: the current time is the now parameter. failures counts consecutive failures, including the one in last. rl is the last known rate limit.
Order: a Retry-After wins; then a known reset time; then a bounded exponential. On success the reserve pause and pacing apply.
type Detail ¶
type Detail struct {
Checks []Check
Reviewers []Reviewer
Counts CheckCounts
Truncated bool
Partial bool
Read int
Total int
}
Detail is the full detail of one pull request.
Checks are de-duplicated (LatestChecks) and sorted (SortChecks). Counts are always CountChecks(Checks), the de-duplicated checks, never GitHub's own per-state counts, which include superseded runs (D-03).
Truncated means that not every check run was read: paging stopped at the page cap or at an empty cursor, a later page failed, or fewer runs were read than GitHub reports. Read is the number of check runs read and Total the number GitHub reports, so the view can say "read N of M" (D-03, D-08).
Partial means that GitHub returned a GraphQL error next to the data of some page, so a check, a reviewer or a review request may be missing. Partial says nothing about the run count (G-03-6, WR-05).
type FailingCheck ¶
type FailingCheck struct {
Known bool
Name string
Cancelled bool // the named check is cancelled, and nothing else fails
More int // how many other checks fail
}
FailingCheck is the failing check named in a PR's CI cell (LIST-07). The zero value means "no name: show the plain badge".
func SummarizeFailing ¶
func SummarizeFailing(latest []Check, complete bool) FailingCheck
SummarizeFailing picks the check a failing row names from the de-duplicated checks (LatestChecks). complete is false when the contexts page was truncated: then no name is known, because a re-run on the missing page may have fixed it.
A failing check is named before any cancelled one (D-04): the name is the failing check that sorts first by lower-cased name, and More counts the other failing checks. Cancelled checks are never added to that count, so the row agrees with a detail summary that shows failing and cancelled separately. When no check fails, the first cancelled check is named with Cancelled set, and More counts the other cancelled checks. Counts come from the de-duplicated checks, never from GitHub's per-state counts (D-02). No failing or cancelled check gives the zero value.
type Merge ¶
type Merge int
Merge is the merge-readiness shown for an open pull request. The zero value is MergeNone, a blank cell: merged, closed and unknown-state PRs.
func DeriveMerge ¶
DeriveMerge returns the merge-readiness of a pull request from its state, draft flag and GitHub's mergeStateStatus and mergeable values.
State and draft are decided first (RESEARCH F4): a merged PR reports UNKNOWN, a closed PR keeps a stale BLOCKED or DIRTY, and a draft reports BLOCKED or DIRTY because the enum has no DRAFT value. Only then is the status read. CLEAN, HAS_HOOKS and UNSTABLE are mergeable (D-07: the CI column already shows a failing check). UNKNOWN is common and transient (F5); it is a dash, except that mergeable CONFLICTING means conflicts. An empty or unrecognised status is a dash, never mergeable, so a PR cannot look more ready than GitHub's data supports.
type Outcome ¶
type Outcome struct {
Failed bool
RateLimited bool // the failure was a rate limit
RetryAfter time.Duration // from a Retry-After header; zero when absent
ResetAt time.Time // reset time from the error; zero when absent
}
Outcome describes the last fetch to NextDelay.
type PR ¶
type PR struct {
ID string
Ref Ref
Title string
URL string
Author string
IsDraft bool
State State
Review Review
CI CI
// Failing is the failing check named in the CI cell (LIST-07); the zero
// value shows the plain badge.
Failing FailingCheck
// Merge is the merge-readiness (LIST-06); the zero value is a blank cell.
Merge Merge
// Reviewers are the people and teams on the PR in D-06 order (LIST-04).
Reviewers []Reviewer
// Auto is true when discovery found the PR (the viewer authored it or is
// assigned to it). Manual is true when watchlist.json holds it. A PR can be
// both (WATCH-03).
Auto, Manual bool
// Watched is the saved entry's ref, set when Manual. It can differ from Ref
// after a repository rename; removal goes by it.
Watched Ref
// the PR loaded.
Unavail Unavail
CreatedAt time.Time
UpdatedAt time.Time
ClosedAt time.Time
}
PR is one watched pull request, already mapped and sanitised.
func Combine ¶
Combine merges the discovery results with the looked-up saved PRs into a new slice: auto in order, then each looked PR in order. A looked PR whose non-empty ID equals the ID of a PR already kept is folded into that PR, which becomes Manual with the looked PR's Watched ref, and is not appended: a renamed repository is one row (WATCH-03). A looked PR with an empty ID (an unavailable one) is always appended. Neither input is changed.
func Reconcile ¶
Reconcile applies the D-12 rule to the rows of a successful refresh and returns the rows to show and the new strike counts. It is pure: it returns a new slice and a new map and changes neither input.
A row of next that is unavailable with an "other" error (not NOT_FOUND, not SSO) is a strike for its saved ref (Watched.Key()). On the first strike a PR that prev shows as loaded keeps its last data, so one bad answer does not flicker the row; on the second strike in a row, or when there is no last data (the first launch), it stays unavailable. NOT_FOUND and SSO are real answers and show at once. A row that loads adds no key, so its count starts over, and a key whose row is gone is dropped. The rows are sorted again when a row was replaced by its last data, because that row's bucket changed; otherwise the order of next is kept as it is.
A refresh that fails as a whole (network, 5xx, 401, rate limit) never reaches Reconcile: the caller keeps the rows and the counts as they are. Reconcile never touches the saved list: a PR that stays unavailable stays saved.
func Visible ¶
Visible returns the PRs the filters let through, in input order, as a new slice; prs is not changed. A PR passes when its state is in show and, if number is not empty, its PR number starts with the digits in number (so "12" keeps #12, #120 and #1234, never #312). A PR in an unknown state is never hidden by show (same rule as InWindow): it must not vanish silently. The same goes for an unavailable row, whose state is not known (D-13). The number filter still applies to both.
type Ref ¶
Ref identifies a pull request by repository and number.
func MarkWatched ¶
MarkWatched sets Manual and Watched on every PR in prs whose Ref has the same Key as a saved ref, and returns the saved refs that no PR matched, in input order and without repeats (compared by Key). prs is changed in place, as Sort does. A saved PR that discovery already returned costs no Lookup.
func ParseRef ¶
ParseRef reads one pasted PR reference: a github.com PR URL (scheme and www. optional; /files, /checks, /commits or /commits/SHA, a query or a fragment may follow the number) or OWNER/REPO#N. Only the owner, repository and number are kept; the owner and repository keep the letter case that was typed. Every failure is a *RefError whose Reason says why (D-04).
type RefError ¶
type RefError struct{ Reason string }
RefError is a pasted PR reference that ParseRef refused. Reason is the text shown in the add prompt.
type RequestInput ¶
type RequestInput struct{ Typename, Name string }
RequestInput is one review request. Typename is "" when GitHub returned the requested reviewer as null.
type Review ¶
type Review int
Review is the review status shown for a pull request. The zero value is the non-favourable ReviewNone.
func DeriveReview ¶
DeriveReview returns the review status shown for a pull request.
GitHub's reviewDecision wins when it is set. It is null on repositories without a required-review rule even when approvals exist, so a null (or unknown) decision falls through to the opinionated review states: any CHANGES_REQUESTED wins over approvals whatever the order, else any APPROVED gives approved, else none. Derivation never yields ReviewRequired, so a null decision is never shown as "review required".
type ReviewInput ¶
type ReviewInput struct{ Login, State string }
ReviewInput is one review. Strings are already cleaned by the github seam. Login is "" when the review's author is null.
type Reviewer ¶
type Reviewer struct {
Name string
Kind ReviewerKind
State ReviewerState
}
Reviewer is one reviewer of a pull request.
func MergeReviewers ¶
func MergeReviewers(opinionated []ReviewInput, requested []RequestInput, latest []ReviewInput) []Reviewer
MergeReviewers merges review requests and reviews into the reviewers of one pull request, in display order.
Every request is a pending reviewer. A reviewer who reviewed and was then re-requested stays in opinionated, so pending wins (RESEARCH F7); logins are compared case-insensitively and the requested spelling is kept. A request whose reviewer is null is a hidden pending reviewer and is never dropped. A review whose author is null is skipped. opinionated holds only approvals and change requests (list rows); latest adds commented and dismissed reviewers for the detail view and never overrides a reviewer already present.
type ReviewerKind ¶
type ReviewerKind int
ReviewerKind says what a Reviewer names.
const ( ReviewerPerson ReviewerKind = iota // a user, bot or mannequin login ReviewerTeam // a team request with a visible slug // ReviewerHidden is a request whose reviewer GitHub returned as null, // normally a team the viewer cannot see (RESEARCH F6). ReviewerHidden )
type ReviewerState ¶
type ReviewerState int
ReviewerState is one reviewer's state on a pull request. The zero value is ReviewerPending, the least favourable known state.
const ( ReviewerPending ReviewerState = iota ReviewerChangesRequested ReviewerApproved ReviewerCommented // detail view only ReviewerDismissed // detail view only )
func (ReviewerState) Glyph ¶
func (s ReviewerState) Glyph() string
Glyph returns the one-cell glyph of the state.
func (ReviewerState) Word ¶
func (s ReviewerState) Word() string
Word returns the display word of the state.
type Snapshot ¶
type Snapshot struct {
Viewer string
PRs []PR
Truncated bool
Warnings []string
RateLimit RateLimit
}
Snapshot is the result of one fetch.
type State ¶
type State int
State is the lifecycle state of a pull request.
The zero value is StateOpen. An unrecognised remote value is StateUnknown.
func ParseState ¶
ParseState maps GitHub's PullRequestState enum to a State. The mapping is total: any other value is StateUnknown, never StateOpen.
type StateSet ¶
type StateSet uint8
StateSet is a set of the lifecycle states the user wants to see (D-10). The unknown state has no bit: a PR in an unknown state is never filtered out.
type Unavail ¶
type Unavail struct {
}
Unavail describes a saved PR that could not be loaded. Reason is the short text for rows and the add prompt (D-10); Help is the longer text for the detail view.
func LoadFailed ¶
LoadFailed is the Unavail of a PR that failed to load for another reason.
func NotFound ¶
func NotFound() Unavail
NotFound is the Unavail of a PR that GitHub reports as not found.
func SSORequired ¶
SSORequired is the Unavail of a PR in an organization that requires SSO authorization for the token (D-10). org must be the owner of a ref that passed ValidRef (letters, digits and hyphens), never text taken from a message.
type UnavailKind ¶
type UnavailKind int
UnavailKind says why a saved PR could not be loaded.
const ( )
UnavailNone is the zero value: the PR loaded.