[comment audit] Complete repository-wide comment cleanup (#33249)

* [comment audit] Align legacy failure-read counts with cached display

* [comment audit] Condense shared provider lifecycle and browser recovery comments [skip ci]

* [comment audit] Condense shared generation, path and preview comments [skip ci]

* [comment audit] Condense host, file and Git contract rationale [skip ci]

* [comment audit] Tighten Git lifecycle and provider contract comments [skip ci]

* [comment audit] Clarify shared host authority and launch isolation [skip ci]

* [comment audit] simplify local access mobile and native chat contracts [skip ci]

* [comment audit] simplify native chat presentation and state contracts [skip ci]

* [comment audit] simplify native runtime and provider contracts [skip ci]

* [comment audit] simplify migration and orchestration contracts [skip ci]

* [comment audit] simplify plugin trust and process capture rationale [skip ci]

* [comment audit] simplify terminal delivery and remote compatibility rationale [skip ci]

* [comment audit] clarify remote recovery and launch wire rationale [skip ci]

* [comment audit] shorten shared RPC compatibility and runtime evidence comments [skip ci]

* [comment audit] simplify secure storage shell startup and sidebar rationale [skip ci]

* [comment audit] clarify sidebar, skill and SSH invariants [skip ci]

* [comment audit] condense structured chat delivery and timing rationale [skip ci]

* [comment audit] condense terminal replay and telemetry invariants [skip ci]

* [comment audit] clarify launch permission and terminal delivery rationale [skip ci]

* [comment audit] clarify workspace ownership and layout contracts [skip ci]

* [comment audit] wait for fitted and painted SSH terminal links

* [comment audit] clarify layout persistence and session ownership [skip ci]

* [comment audit] clarify host routing and workspace lifecycle contracts [skip ci]

* [comment audit] clarify startup persistence and app lifetime comments [skip ci]

* [comment audit] clarify recovery and activity boundaries [skip ci]

* [comment audit] condense activity and artifact invariants [skip ci]

* [comment audit] condense automation authority contracts [skip ci]

* [comment audit] simplify automation scheduling and history contracts [skip ci]

* [comment audit] clarify browser chrome and annotation lifecycles [skip ci]

* [comment audit] clarify browser routing and retained guest ownership [skip ci]

* [comment audit] clarify browser recovery and document ownership comments [skip ci]

* [comment audit] restore hosted runners required by native IME fixtures

* [comment audit] shorten preview, palette and dashboard invariants [skip ci]

* [comment audit] clarify terminal preview, dashboard and retention comments [skip ci]

* [comment audit] clarify diff-note recovery and editor lifecycle comments [skip ci]

* [comment audit] shorten rich-text and combined-diff ownership comments [skip ci]

* [comment audit] shorten autosave, draft recovery and model ownership comments [skip ci]

* [comment audit] shorten save-queue, notebook and Markdown constraints [skip ci]

* [comment audit] shorten Monaco, PDF and rich Markdown safeguards [skip ci]

* [comment audit] clarify rich Markdown source and selection safeguards [skip ci]

* [comment audit] clarify editor loading and emulator stream lifetimes [skip ci]

* [comment audit] trim emulator and feature tour narration [skip ci]

* [comment audit] clarify floating workspace and GitHub cache ownership [skip ci]

* [comment audit] trim provider controls and Linear filter explanations [skip ci]

* [comment audit] clarify pairing and chat resume lifetimes [skip ci]

* [comment audit] clarify chat delivery and transcript lifetimes [skip ci]

* [comment audit] clarify session options and host-owned status [skip ci]

* [comment audit] clarify draft durability and live-status precedence [skip ci]

* [comment audit] Trim chat send and model ownership explanations [skip ci]

* [comment audit] Shorten transcript ownership and send recovery comments [skip ci]

* [comment audit] Simplify native chat composer and transcript hook comments [skip ci]

* [comment audit] Shorten native chat scroll and structured session explanations [skip ci]

* [comment audit] Trim workspace picker and onboarding commentary [skip ci]

* [comment audit] Simplify pet rendering, review cache and quick open comments [skip ci]

* [comment audit] Trim sidebar and session history explanations [skip ci]

* [comment audit] Condense session resume and review checks comments [skip ci]

* [comment audit] Shorten checks state and file explorer refresh comments [skip ci]

* [comment audit] Trim plugin bridge and source-control action rationale [skip ci]

* [comment audit] Trim source-control listing and review rationale [skip ci]

* [comment audit] Shorten sidebar lifecycle and explorer safeguards [skip ci]

* [comment audit] Condense Settings account and appearance rationale [skip ci]

* [comment audit] Trim pairing and repository Settings explanations [skip ci]

* [comment audit] Condense Settings search SSH and terminal safeguards [skip ci]

* [comment audit] Shorten Settings voice and provider ownership comments [skip ci]

* [comment audit] Condense Settings draft persistence and host-state rationale [skip ci]

* [comment audit] Trim Settings hooks and sidebar setup rationale [skip ci]

* [comment audit] Condense sidebar deletion and board-filter safeguards [skip ci]

* [comment audit] Condense worktree cards, host ownership and Markdown contracts [skip ci]

* [comment audit] Condense sidebar drag, folder and filtering contracts [skip ci]

* [comment audit] Condense sidebar parity, sleep and host ownership contracts [skip ci]

* [comment audit] Prune focus-read baseline after comment cleanup

* [comment audit] Clarify sidebar visibility, board drops and agent evidence [skip ci]

* [comment audit] Tighten sidebar listing, drag and reveal contracts [skip ci]

* [comment audit] Clarify viewport geometry and skill update safeguards [skip ci]

* [comment audit] Tighten skill inventory, usage and host status comments [skip ci]

* [comment audit] Clarify usage ownership and resource kill safeguards [skip ci]

* [comment audit] Tighten tab selection, rename and search contracts [skip ci]

* [comment audit] Tighten tab-strip geometry and drag ownership comments [skip ci]

* [comment audit] Tighten Tasks recovery, pagination and mutation comments [skip ci]

* [comment audit] Tighten provider loading and terminal recovery comments [skip ci]

* [comment audit] Tighten completion, cadence and PTY ownership comments [skip ci]

* [comment audit] Clarify terminal restore and parked-pane boundaries [skip ci]

* [comment audit] Tighten PTY startup and replay explanations [skip ci]

* [comment audit] Clarify PTY identity and delivery ownership [skip ci]

* [comment audit] Shorten remote reconnect and input replay rationale [skip ci]

* [comment audit] Condense terminal parking clipboard and file link comments [skip ci]

* [comment audit] Clarify IME ownership and trim trace history [skip ci]

* [comment audit] Shorten pane recovery and parked watcher explanations [skip ci]

* [comment audit] Condense snapshot replay and visibility recovery rationale [skip ci]

* [comment audit] Shorten terminal parking and keyboard ownership rationale [skip ci]

* [comment audit] Trim renderer lifecycle and menu rationale [skip ci]

* [comment audit] Condense workspace cleanup ownership and settlement notes [skip ci]

* [comment audit] Clarify renderer hook evidence and source ownership [skip ci]

* [comment audit] Tighten IPC ownership and reconnect explanations [skip ci]

* [comment audit] Trim renderer focus, paste and attention comments [skip ci]

* [comment audit] Shorten scroll, locale and agent lifecycle comments [skip ci]

* [comment audit] Trim agent launch, resume and ownership commentary [skip ci]

* [comment audit] Shorten account, browser and editor comments [skip ci]

* [comment audit] Clarify host-mirror waits, previews and input ownership [skip ci]

* [comment audit] Condense launch recovery and editor tokenization comments [skip ci]

* [comment audit] Condense workspace selection and terminal pane rationale [skip ci]

* [comment audit] Condense terminal fitting and rendering recovery comments [skip ci]

* [comment audit] Condense terminal input and host routing explanations [skip ci]

* [comment audit] Condense ownership and sleeping-session recovery rationale [skip ci]

* [comment audit] Condense structured launch and terminal link explanations [skip ci]

* [comment audit] Condense terminal ownership and workspace file routing rationale [skip ci]

* [comment audit] Condense session persistence and workspace activation rationale [skip ci]

* [comment audit] Condense workspace routing and browser ownership rationale [skip ci]

* [comment audit] Condense mirror recovery and layout authority rationale [skip ci]

* [comment audit] Condense runtime transport and file safety rationale [skip ci]

* [comment audit] Condense runtime graph and structured session rationale [skip ci]

* [comment audit] Condense browser creation and remote tab recovery rationale [skip ci]

* [comment audit] Condense snapshot authority and publisher recovery rationale [skip ci]

* [comment audit] Condense frame, startup and provider cache rationale [skip ci]

* [comment audit] Condense host twin and agent lifecycle rationale [skip ci]

* [comment audit] Condense browser authority and editor cleanup rationale [skip ci]

* [comment audit] Condense editor buffer identity and review cache rationale [skip ci]

* [comment audit] Condense provider lifetime and repository ownership rationale [skip ci]

* [comment audit] Condense reconnect, hydration and terminal retirement rationale [skip ci]

* [comment audit] Condense recovery, reconciliation and persistence rationale [skip ci]

* [comment audit] Condense cleanup ownership and listing admission rationale [skip ci]

* [comment audit] Condense teardown ordering and terminal ownership rationale [skip ci]

* [comment audit] Condense recovery ledger and web host-boundary rationale [skip ci]

* [comment audit] Condense web reconnect and credential lifecycle rationale [skip ci]

* [comment audit] Condense cloud rollout and assignment safety rationale [skip ci]

* [comment audit] Condense relay session and database recovery rationale [skip ci]

* [comment audit] Condense relay placement and protocol compatibility rationale [skip ci]

* [comment audit] Condense build and packaging constraints [skip ci]

* [comment audit] Condense CI gates and validation isolation rationale [skip ci]

* [comment audit] Condense catalog identity and browser validation rationale [skip ci]

* [comment audit] Condense mobile browser and frame-budget rationale [skip ci]

* [comment audit] Shorten mobile route and browser test explanations [skip ci]

* [comment audit] Condense editor and session seam test rationale [skip ci]

* [comment audit] Trim session and terminal closure history [skip ci]

* [comment audit] Shorten terminal browser and census explanations [skip ci]

* [comment audit] Preserve root-only E2E diagnostic baseline

* [comment audit] Condense mobile seam and HTML preview test rationale [skip ci]

* [comment audit] Condense preview instrumentation and render harness rationale [skip ci]

* [comment audit] Condense bundle contracts and native-build constraints [skip ci]

* [comment audit] Shorten native packaging and CI routing rationale [skip ci]

* [comment audit] Condense release tooling and benchmark constraints [skip ci]

* [comment audit] Condense packaging and platform gate rationale [skip ci]

* [comment audit] Shorten mobile route and history rationale [skip ci]

* [comment audit] Condense mobile browser and component rationale [skip ci]

* [comment audit] Shorten drawer and workspace-form rationale [skip ci]

* [comment audit] Trim mobile review and editor-document explanations [skip ci]

* [comment audit] Condense editor lifecycle and Markdown explanations [skip ci]

* [comment audit] Trim mobile file, diagnostics and dictation commentary [skip ci]

* [comment audit] Simplify mobile host-list and workspace-view explanations [skip ci]

* [comment audit] Clarify mobile shell lifetime and bridge delivery constraints [skip ci]

* [comment audit] Condense bridge stream, native request and audio rationale [skip ci]

* [comment audit] Reduce bridge schema and page-client commentary [skip ci]

* [comment audit] Tighten mobile page bridge contracts [skip ci]

* [comment audit] Clarify mobile cache and session constraints [skip ci]

* [comment audit] Condense mobile route and lifecycle rationale [skip ci]

* [comment audit] Tighten mobile shell hook timing rationale [skip ci]

* [comment audit] Clarify route handoff and notification compatibility [skip ci]

* [comment audit] Clarify push registration and dictation cleanup constraints [skip ci]

* [comment audit] Condense keyboard geometry and native media ownership rationale [skip ci]

* [comment audit] Tighten mobile chat scroll and composer concurrency rationale [skip ci]

* [comment audit] Condense mobile reply compatibility and upload ownership rationale [skip ci]

* [comment audit] Condense optimistic echo and scroll ownership rationale [skip ci]

* [comment audit] Tighten stream replay and queued session ownership rationale [skip ci]

* [comment audit] Condense mobile composer and reply-schema constraints [skip ci]

* [comment audit] Clarify mobile transcript recovery and session ownership [skip ci]

* [comment audit] Clarify mobile queue and source-control compatibility [skip ci]

* [comment audit] Condense storage and task launch constraints [skip ci]

* [comment audit] Clarify task schemas and terminal document ownership [skip ci]

* [comment audit] Tighten terminal input and viewport rationale [skip ci]

* [comment audit] Shorten terminal and RPC recorder contracts [skip ci]

* [comment audit] Condense recorder engine and connection contracts [skip ci]

* [comment audit] Shorten host credential and relay recovery comments [skip ci]

* [comment audit] Tighten pairing recovery and bundle transport rationale [skip ci]

* [comment audit] Clarify RPC delivery and workspace catalog boundaries [skip ci]

* [comment audit] Condense catalog and relay entry contracts [skip ci]

* [comment audit] Shorten shutdown and SSH relay delivery contracts [skip ci]

* [comment audit] Condense WSL relay and E2E regression rationale [skip ci]

* [comment audit] Shorten cross-version harness and renderer fixture explanations [skip ci]

* [comment audit] Condense paired-host and E2E lifecycle rationale [skip ci]

* [comment audit] Shorten layout oracle and regression explanations [skip ci]

* [comment audit] Condense paired recovery and ownership regression contracts [skip ci]

* [comment audit] Shorten restart, sidebar and SSH lifecycle contracts [skip ci]

* [comment audit] Condense terminal rendering and IME regression rationale [skip ci]

* [comment audit] Shorten terminal recovery and benchmark contracts [skip ci]

* [comment audit] Condense tooling and Windows lifecycle proof rationale [skip ci]

* [comment audit] Condense workflow routing and release rationale [skip ci]

* [comment audit] Condense mobile security and installer contracts [skip ci]

* [comment audit] Shorten native rationale and stylesheet workarounds [skip ci]

* [comment audit] Shorten Windows visibility probe rationale [skip ci]

* [comment audit] Condense embedded release and signing rationale [skip ci]

* [comment audit] Retain current main test behavior in final reconciliation [skip ci]

* [comment audit] Complete final comment review across current source inventory

* [comment audit] Handle consolidated audit reports before final CI

* [comment audit] Preserve credential storage census detail [skip ci]

* [comment audit] Scale broad changed E2E selections with verified timing shards

* [comment audit] Align E2E assertions with browser close and settled session metadata

* [comment audit] Match existing E2Es to current resource and windowing contracts

* [comment audit] Observe the shared server outage notice in reconnect coverage

* [comment audit] Make existing renderer probes measure settled tab and PTY work

* [comment audit] Align palette E2E selector with task search

* [comment audit] Refresh remaining palette search selectors

* [comment audit] Keep selector correction within selected audit specs

* [comment audit] Await emoji fixture settings inside renderer evaluation

* [comment audit] Cancel fixture fit retries between terminal tests

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-10-25 04:05:46 -07:00
committed by GitHub
co-authored by OrcaWin
parent aa08c84f3b
commit 7d6603aa17
16196 changed files with 51015 additions and 179882 deletions
+3 -15
View File
@@ -1,9 +1,4 @@
/**
* Normalize persisted per-project GitHub account bindings.
*
* Why: host comparison and gh `--hostname` must be case-insensitive/canonical,
* while login casing is preserved for display and `gh auth token --user`.
*/
/** Canonicalize hosts for comparison/gh --hostname; preserve login casing for display and gh auth token --user. */
export type GhAccountBinding = {
host: string
@@ -25,7 +20,6 @@ export function normalizeGhAccountBinding(value: unknown): GhAccountBinding | nu
}
const host = hostRaw.trim().toLowerCase()
const user = userRaw.trim()
// Why: reject embedded NUL without a control-char regex (oxlint no-control-regex).
if (!host || !user || user.includes('\u0000') || !HOST_RE.test(host) || !USER_RE.test(user)) {
return null
}
@@ -43,14 +37,8 @@ export function ghAccountBindingsEqual(
return a.host === b.host && a.user === b.user
}
/**
* Choose the env var gh uses for a bound host.
*
* Why: github.com / github.localhost / *.ghe.com take `GH_TOKEN`; other hosts
* need `GH_ENTERPRISE_TOKEN`. Classifying `*.ghe.com` as cloud requires gh ≥
* 2.46 — older gh may ignore `GH_TOKEN` for those hosts and fail closed as
* `gh_bound_account_unavailable`.
*/
/** github.com/github.localhost/*.ghe.com use GH_TOKEN; other hosts use GH_ENTERPRISE_TOKEN.
* ghe.com cloud recognition requires gh 2.46+; older clients may ignore GH_TOKEN and fail closed as gh_bound_account_unavailable. */
export function ghTokenEnvVarForHost(host: string): 'GH_TOKEN' | 'GH_ENTERPRISE_TOKEN' {
const normalized = host.trim().toLowerCase()
if (
+6 -19
View File
@@ -1,34 +1,21 @@
// Why: a GitHub outage, a dropped network, or a rate-limit all surface as
// unstructured gh/Octokit error text. Detecting them from one shared place lets
// both the main process (PR-refresh classification) and the renderer (Tasks
// work-item fan-out) attribute the failure to GitHub — not to Orca — using the
// exact same rules, so the two surfaces never disagree about whether GitHub is
// reachable. Returns null for anything that is NOT a reachability problem
// (auth, permission, 404): those are user-actionable, not "GitHub is down".
// Share gh/Octokit reachability classification between main PR refresh and renderer task fan-out.
// Auth/permission/404 are user-actionable, not outages; return null for those.
export type GitHubUnavailableKind = 'server_error' | 'network' | 'rate_limited'
// Rate-limit first: a primary rate-limit response also carries "HTTP 403", so
// it must win over any 4xx/permission interpretation downstream.
// Rate-limit 403 must outrank ordinary permission interpretation.
const RATE_LIMITED_PATTERN =
/rate limit|secondary rate limit|abuse detection|\bhttp[\s/]*429\b|\b429 too many requests\b/i
// Server-side outage. Anchor on "HTTP 5xx" or named 5xx statuses rather than a
// bare 3-digit match so unrelated numbers in stderr can't be misread as an
// outage.
// Require HTTP/named 5xx diagnostics; arbitrary three-digit numbers do not prove outages.
const SERVER_ERROR_PATTERN =
/\bhttp[\s/]*5\d\d\b|\b5\d\d\s+(?:internal server error|bad gateway|service unavailable|gateway time-?out)\b|\binternal server error\b|\bbad gateway\b|\bservice unavailable\b|\bgateway time-?out\b|\bserver error\b|\btemporarily unavailable\b/i
// Transport-level failures — DNS, refused/reset connections, timeouts. Covers
// both Node (ENOTFOUND/ECONNRESET) and the gh Go client ("dial tcp", "i/o
// timeout", "no such host") shapes.
// Cover DNS/refusal/reset/timeouts in Node errno and gh Go-client forms.
const NETWORK_PATTERN =
/timeout|\btimed out\b|\bno such host\b|could not resolve host|could not resolve to a host|\bnetwork(?:error| (?:error|unavailable|unreachable|request failed))\b|\bconnection (?:refused|reset)\b|\berror connecting to\b|\bfailed to connect to\b|\bdial tcp\b|\bi\/o timeout\b|\bfetch failed\b|\bsocket hang up\b|ENOTFOUND|ECONNRESET|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|ENETUNREACH/i
/**
* Classify a gh/Octokit error message as a GitHub-reachability problem, or
* null when it is not one (auth, permission, 404, validation, etc.).
*/
/** Classify reachability failures; return null for auth, permission, 404, validation and other errors. */
export function classifyGitHubUnavailable(message: string): GitHubUnavailableKind | null {
if (!message) {
return null
+5 -27
View File
@@ -1,7 +1,3 @@
/**
* Shared types for `gh auth status` diagnostics surfaced to the renderer.
*/
import type { GhAccountBinding } from './account-binding'
export type GhAuthAccount = {
@@ -9,11 +5,7 @@ export type GhAuthAccount = {
user: string
/** True when this is the account gh would use for the next call. */
active: boolean
/**
* If gh reports the credential came from an environment variable, the
* variable's name. Null when the credential came from the keyring/file
* config. Env-token accounts can't be refreshed by `gh auth refresh`.
*/
/** Credential environment variable, or null for keyring/file auth. Env-token accounts cannot use gh auth refresh. */
envToken: 'GITHUB_TOKEN' | 'GH_TOKEN' | null
source: 'env' | 'keyring'
scopes: string[]
@@ -24,24 +16,13 @@ export type GhAuthDiagnostic = {
ghAvailable: boolean
activeAccount: GhAuthAccount | null
accounts: GhAuthAccount[]
/**
* Whether the Electron main process itself sees GITHUB_TOKEN/GH_TOKEN in
* its environment. Distinct from `activeAccount.envToken` because gh may
* report an env source even when the variable was set in a parent shell
* that didn't propagate to Electron, and vice versa.
*/
/** Token visible to Electron itself; gh may report a different environment source inherited through its shell. */
envTokenInProcess: 'GITHUB_TOKEN' | 'GH_TOKEN' | null
missingScopes: string[]
requiredScopes: string[]
/**
* True when there's a non-env keyring account on the same/another host
* that the user could fall back to by unsetting the env var.
*/
/** A non-env keyring account on this or another host is available after unsetting the token variable. */
hasKeyringFallback: boolean
/**
* The GitHub host the caller needs credentials for (e.g. a GHES origin).
* Null when the probe ran without host context.
*/
/** Host needing credentials (e.g. GHES); null when probing without host context. */
requiredHost: string | null
/** Whether gh has any account for `requiredHost`; null without host context. */
requiredHostAuthenticated: boolean | null
@@ -50,10 +31,7 @@ export type GhAuthDiagnostic = {
/** Per-runtime support for `gh auth token --user` (gh ≥ 2.40). */
export type GhMultiAccountCapability = 'supported' | 'unsupported' | 'unknown'
/**
* Repo-scoped inventory for Settings binding UI.
* Env-token accounts are listed but not bindable; keyring accounts are.
*/
/** Repo binding inventory: list env-token accounts but allow binding only keyring accounts. */
export type GhAccountBindingInventory = {
capability: GhMultiAccountCapability
accounts: GhAuthAccount[]
+2 -5
View File
@@ -9,16 +9,13 @@ export type PRCheckDetail = {
| 'neutral'
| 'skipped'
| 'pending'
// Why: a check suite needing manual action (e.g. a workflow awaiting "Approve
// and run") has no check run and is absent from statusCheckRollup, yet blocks
// auto-merge (GitHub returns "unstable status"). Surface it as its own state.
// Manual-approval suites may have no check run/rollup row yet block auto-merge as unstable; expose them separately.
| 'action_required'
| null
url: string | null
checkRunId?: number
workflowRunId?: number
// Why: the GitLab job trace API is addressed by numeric job id only, so the
// Checks panel cannot load a job log without carrying it on the row.
// GitLab trace retrieval requires numeric job id, so carry it through the Checks row.
gitlabJobId?: number
}
+2 -7
View File
@@ -28,8 +28,7 @@ export type PRComment = {
reactionSubjectId?: string
/** File path for inline review comments (absent for top-level conversation comments). */
path?: string
/** GraphQL node ID of the review thread — present only for inline review comments.
* Used to resolve/unresolve the thread via GitHub's GraphQL API. */
/** Inline review thread’s GraphQL ID, used to resolve/unresolve it; absent for conversation comments. */
threadId?: string
/** Whether the review thread has been resolved. Only meaningful when threadId is set. */
isResolved?: boolean
@@ -39,11 +38,7 @@ export type PRComment = {
line?: number
/** Start line of the review annotation range (1-based). Absent for single-line comments. */
startLine?: number
/** True when GitHub identifies the author as a bot (REST `user.type === 'Bot'` or
* GraphQL `__typename === 'Bot'`). Preferred over login-string heuristics because
* third-party review bots (e.g. qodo-ai-reviewer, coderabbitai) don't follow a
* predictable naming convention. Absent when the data source can't report it
* (non-GitHub fallbacks via `gh pr view`). */
/** Provider-reported bot identity beats login heuristics (qodo/coderabbit use varied names). Absent when non-GitHub gh fallbacks cannot report it. */
isBot?: boolean
}
+3 -8
View File
@@ -21,8 +21,7 @@ export function getGitHubRepoCacheKey(
executionHostId?: string | null,
hasRepoOwner = false
): string {
// Why: runtime/SSH lookups can observe different remotes than the local repo
// path, so cache keys include the repo's owning execution boundary.
// Scope caches by execution owner: runtime/SSH hosts may observe different remotes at the same repo path.
return composeRepoCacheKey(
getGitHubCacheHostScope(settings, connectionId, executionHostId, hasRepoOwner),
repoId ?? repoPath,
@@ -30,10 +29,7 @@ export function getGitHubRepoCacheKey(
)
}
/**
* The one shape of a repo-owned cache key: `[scope::]owner::suffix`, where scope is an execution
* host id or a task-source scope and no scope means this computer. Read back by repoCacheKeyHostId.
*/
/** Repo cache keys use [scope::]owner::suffix; absent scope means this computer. repoCacheKeyHostId reads the shape back. */
export function composeRepoCacheKey(
scope: string | null | undefined,
owner: string,
@@ -78,8 +74,7 @@ function getGitHubCacheHostScope(
if (sshConnectionId) {
return toSshExecutionHostId(sshConnectionId)
}
// Why: an existing repo with no remote/runtime owner is local; only missing
// owner context should inherit the focused runtime fallback.
// Known repos without remote/runtime ownership are local; only missing owner context inherits focused runtime scope.
if (hasRepoOwner) {
return null
}
+5 -17
View File
@@ -1,6 +1,4 @@
// Why shared: main's terminal side-effect tracker emits pr-link facts
// (terminal-side-effect-authority.md, slice 3) and needs the same GitHub URL
// parsing core the renderer link picker uses.
// Share URL parsing between main terminal side-effect PR facts and renderer link pickers (terminal-side-effect-authority.md, slice 3).
const GH_ITEM_PATH_RE = /^\/([^/]+)\/([^/]+)\/(issues|pull)\/(\d+)(?:\/.*)?$/i
export type RepoSlug = {
@@ -19,8 +17,7 @@ export function buildGitHubRepoUrl(slug: RepoSlug | null | undefined): string |
if (!slug?.owner || !slug.repo) {
return null
}
// Why: hosted identities carry the GHES host; links must point at that
// server, not github.com.
// Preserve GHES identity instead of defaulting Enterprise links to github.com.
const host = slug.host ?? 'github.com'
return `https://${host}/${encodeURIComponent(slug.owner)}/${encodeURIComponent(slug.repo)}`
}
@@ -34,10 +31,7 @@ function parseGitHubItemNumber(value: string): number | null {
return parsed > 0 ? parsed : null
}
/**
* Parses a GitHub issue/PR reference from plain input.
* Supports issue/PR numbers (e.g. "42"), "#42", and full GitHub URLs.
*/
/** Parse bare/# issue or PR numbers and full GitHub URLs. */
export function parseGitHubIssueOrPRNumber(input: string): number | null {
const trimmed = input.trim()
if (!trimmed) {
@@ -68,10 +62,7 @@ export function parseGitHubIssueOrPRNumber(input: string): number | null {
return parseGitHubItemNumber(match[4])
}
/**
* Parses an owner/repo slug plus issue/PR number from a GitHub URL. Returns
* null for anything that isn't a recognizable GitHub-shaped issue or pull URL.
*/
/** Parse owner/repo/number from GitHub-shaped issue/PR URLs; return null for other inputs. */
export function parseGitHubIssueOrPRLink(input: string): GitHubIssueOrPRLink | null {
const trimmed = input.trim()
if (!trimmed) {
@@ -99,10 +90,7 @@ export function parseGitHubIssueOrPRLink(input: string): GitHubIssueOrPRLink | n
}
return {
// Why: the URL proves the host — GHES links must keep their server identity
// instead of being treated as github.com slugs.
// Why: GHES installations on non-default ports require the full URL host;
// `hostname` would silently redirect later API calls to port 443.
// URL identity must preserve GHES server and non-default port; hostname would redirect API calls to 443.
slug: { owner: match[1], repo: match[2], host: url.host },
type: match[3].toLowerCase() === 'pull' ? 'pr' : 'issue',
number
+1 -4
View File
@@ -1,7 +1,4 @@
// Why: GitHub owner logins are alphanumerics and single hyphens — plus, for
// Enterprise Managed Users, the `_<shortcode>` GitHub itself appends — so `_` is
// valid after the first character and never leading. One source for main, renderer
// and mobile: three drifting copies is how EMU logins got rejected (#20449).
// Share owner grammar across main/renderer/mobile, including EMU _shortcode suffixes (#20449); leading underscores remain invalid.
const OWNER_SLUG_SOURCE = '[A-Za-z0-9][A-Za-z0-9_-]*'
export const GITHUB_OWNER_SLUG_RE = new RegExp(`^${OWNER_SLUG_SOURCE}$`)
@@ -7,7 +7,6 @@ import type {
} from './project-types'
export type ProjectBoardColumn = {
/** Stable key used for React reconciliation. */
key: string
label: string
/** GitHub single-select color token ('GREEN', …) when the column is one. */
+4 -13
View File
@@ -1,6 +1,4 @@
// Why: grouping and sorting of Project rows is deterministic shared logic
// driven by `selectedView` — it must not depend on fetch ordering. Keeping it
// in a pure shared module lets desktop and mobile render Project views the same.
// Pure selected-view grouping/sorting keeps desktop/mobile order independent of fetch order.
import type {
GitHubProjectField,
GitHubProjectRow,
@@ -9,9 +7,7 @@ import type {
} from './project-types'
export type ProjectGroup = {
/** Stable key used for React reconciliation. */
key: string
/** Human-readable label used in the group header. */
label: string
/** Iteration metadata for headers that render a date range + Current pill. */
iteration: {
@@ -47,9 +43,7 @@ function hasNonEmptyFieldValue(value: ProjectFieldValue | undefined): boolean {
}
}
// Why: use a finite sentinel instead of Infinity so subtractions in the sort
// comparator stay finite. `Infinity - Infinity` is NaN, which makes
// Array.sort's behavior implementation-defined and skips later tie-breaks.
// Finite sentinels avoid Infinity - Infinity yielding NaN and skipping sort tie-breaks.
const UNKNOWN_INDEX_SENTINEL = Number.MAX_SAFE_INTEGER
function createFieldOrderIndex(field: GitHubProjectField): ReadonlyMap<string, number> {
@@ -144,8 +138,7 @@ export function groupRows(
return groupRowsByField(groupField, rowsInOrder)
}
/** Buckets rows by an explicit field — the table view's groupBy and the board
* view's column field share this so both order and label groups identically. */
/** Share field grouping between table/board so labels and ordering agree. */
export function groupRowsByField(
groupField: GitHubProjectField,
rowsInOrder: GitHubProjectRow[]
@@ -174,7 +167,6 @@ export function groupRowsByField(
bucket.rows.push(row)
}
const entries = Array.from(buckets.entries())
// Ordering rules per design doc §Grouping.
entries.sort((a, b) => {
if (a[0] === EMPTY_PROJECT_GROUP_KEY) {
return 1
@@ -241,8 +233,7 @@ function compareSort(
} else if (aValue.kind === 'labels' && bValue.kind === 'labels') {
cmp = (aValue.labels[0]?.name ?? '').localeCompare(bValue.labels[0]?.name ?? '')
} else {
// Why: unknown sort-field kind — ignore this sort field and fall through
// to tie-breaks (and eventually row.position).
// Ignore unknown sort kinds and continue tie-breaks, ending at row.position.
return 0
}
return sort.direction === 'DESC' ? -cmp : cmp
+3 -11
View File
@@ -1,26 +1,18 @@
// Why: the request half of the Project IPC contract, consumed mainly by main
// and preload; kept apart from the ProjectV2 data model in `./project-types`
// so renderer components importing domain shapes don't pull in arg payloads.
// Keep Project request payloads separate from ProjectV2 domain shapes so renderer domain imports do not pull IPC arguments.
import type { GitHubProjectFieldMutationValue, GitHubProjectOwnerType } from './project-types'
import type { GitHubIssueUpdate } from '../issue-mutation-types'
// ─── IPC arg shapes (shared between main, preload, renderer) ──────────
export type GetProjectViewTableArgs = {
owner: string
ownerType: GitHubProjectOwnerType
projectNumber: number
/** GitHub host (e.g. GHES); absent means github.com. */
host?: string
/** View selection precedence: viewId > viewNumber > viewName > first
* TABLE_LAYOUT view. */
/** View selection: viewId > viewNumber > viewName > first TABLE_LAYOUT. */
viewId?: string
viewNumber?: number
viewName?: string
/** Ephemeral GitHub-search-syntax query that replaces the view's filter for
* this fetch only. The view's stored filter on GitHub is not modified.
* `undefined` uses the view's saved filter; `''` explicitly clears it for
* this fetch and gets a distinct renderer cache key. */
/** Fetch-only GitHub search override; never mutates the saved view filter. Undefined uses the saved filter, while empty clears it and has a distinct cache key. */
queryOverride?: string
}
+4 -17
View File
@@ -1,6 +1,4 @@
// Why: the ok/error envelopes returned by Project IPC calls all share one
// classified error, and they pull in cross-module work-item/comment/user types
// that the ProjectV2 data model in `./project-types` never needs.
// Project IPC envelopes share classified failures and cross-module work-item/comment/user types unnecessary in the ProjectV2 domain model.
import type { PRComment } from './comment-types'
import type {
GitHubIssueType,
@@ -12,8 +10,6 @@ import type {
import type { GitHubAssignableUser } from './pull-request-types'
import type { GitHubWorkItemDetails } from './work-item-types'
// ─── Classified errors ─────────────────────────────────────────────────
export type GitHubProjectViewErrorType =
| 'auth_required'
| 'scope_missing'
@@ -29,8 +25,7 @@ export type GitHubProjectViewErrorType =
export type GitHubProjectViewError = {
type: GitHubProjectViewErrorType
message: string
/** Populated when the error is classifiable from a GraphQL response. Never
* includes tokens or full command stdout. */
/** Classified GraphQL details only; never include tokens or full command stdout. */
details?: { path?: (string | number)[]; code?: string }
}
@@ -48,10 +43,7 @@ export type ListAccessibleProjectsResult =
| {
ok: true
projects: GitHubProjectSummary[]
/** Why: per-org discovery can partially fail (a single org 504s while
* the rest succeed). The picker renders a banner listing the affected
* org logins so the user knows their list is incomplete and can paste
* a URL to reach missing projects. Empty when discovery was clean. */
/** Org discovery can partially fail; name failed owners so the picker can show incompleteness and accept pasted URLs for missing projects. */
partialFailures?: { owner: string; message: string }[]
}
| { ok: false; error: GitHubProjectViewError }
@@ -64,10 +56,7 @@ export type ResolveProjectRefResult =
number: number
title: string
host?: string
// Why: when the input is a /views/{n} URL, surface the parsed view
// number so the picker can skip the view-pick step and commit the
// selection directly. Absent for owner/number shorthand and project
// URLs without a /views/ segment.
// Parsed /views/{n} lets the picker commit directly; absent for shorthand and plain project URLs.
viewNumber?: number
}
| { ok: false; error: GitHubProjectViewError }
@@ -80,8 +69,6 @@ export type ProjectWorkItemDetailsBySlugResult =
| { ok: true; details: GitHubWorkItemDetails }
| { ok: false; error: GitHubProjectViewError }
// ─── Mutations ─────────────────────────────────────────────────────────
export type GitHubProjectMutationResult =
| { ok: true }
| { ok: false; error: GitHubProjectViewError }
+13 -34
View File
@@ -1,8 +1,4 @@
// Why: GitHub's GraphQL API never exposes which fields a Roadmap view places
// its items on — `ProjectV2View` carries the layout and the field set, not the
// roadmap's date configuration. The placement is therefore derived from the
// view's own fields here, as pure logic, so desktop and mobile can draw the
// same bars and the geometry stays testable without a renderer.
// GraphQL ProjectV2View omits roadmap date configuration; derive placement from fields here for consistent, testable desktop/mobile bars.
import type { GitHubProjectField, GitHubProjectRow, GitHubProjectView } from './project-types'
export const ROADMAP_DAY_MS = 86_400_000
@@ -34,9 +30,7 @@ const TARGET_FIELD_PATTERN = /target|end|due|finish|complet|deadline|ship/i
const MONTHS_PER_UNIT: Record<RoadmapZoom, number> = { month: 1, quarter: 3, year: 12 }
const MIN_UNITS: Record<RoadmapZoom, number> = { month: 6, quarter: 4, year: 3 }
// Why: one bogus far-future date must not expand the grid to tens of thousands
// of columns. Beyond this the range stops growing and out-of-range bars clamp
// to the edge, which is visibly wrong in the right way rather than a hang.
// Cap typo-driven date ranges to avoid huge grids; out-of-range bars clamp visibly instead of hanging.
const MAX_TICKS = 480
function isDateField(field: GitHubProjectField): boolean {
@@ -54,8 +48,7 @@ function pickDateSource(
if (startField && targetField) {
return { kind: 'date-range', startField, targetField }
}
// Why: when only one name matched, keep it in its matched role and pair it
// with the remaining date field — a plain order fallback inverts the pair.
// Keep a singly matched date field in its role; plain field order could invert start/target.
if (startField) {
const other = dateFields.find((field) => field.id !== startField.id)
if (other) {
@@ -69,8 +62,7 @@ function pickDateSource(
}
}
const [first, second] = dateFields
// Why: localized or oddly named date fields still describe a range — fall
// back to the view's own field order rather than degrading to a marker.
// Fallback to view field order for localized/unusual names instead of degrading ranges to markers.
if (first && second) {
return { kind: 'date-range', startField: first, targetField: second }
}
@@ -87,8 +79,7 @@ export function resolveRoadmapDateSource(
view: GitHubProjectView,
rows: readonly GitHubProjectRow[] = []
): RoadmapDateSource | null {
// Why: roadmaps are commonly placed by fields hidden from the view's
// visible-field list — sort/group fields are the next best config signal.
// Placement fields can be hidden; sort/group fields provide secondary configuration evidence.
const seen = new Set<string>()
const candidates: GitHubProjectField[] = []
for (const field of [
@@ -105,8 +96,7 @@ export function resolveRoadmapDateSource(
candidates.filter(isDateField),
candidates.find((field) => field.kind === 'iteration') ?? null
)
// Why: item field values are fetched independently of the view config, so
// rows can carry usable dates even when no configured field exposes them.
// Row values may supply dates even when view configuration exposes no placement fields.
return fromConfig ?? pickDateSource(...collectRowPlacementFields(rows))
}
@@ -138,8 +128,7 @@ function collectRowPlacementFields(
return [Array.from(dateFieldsById.values()), iterationField]
}
/** Accepts the `YYYY-MM-DD` calendar dates GitHub returns, and tolerates a
* full ISO timestamp by reading its date part. */
/** Read GitHub YYYY-MM-DD dates, tolerating the date portion of full ISO timestamps. */
export function parseRoadmapDate(value: string): number | null {
const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(value)
if (!match) {
@@ -150,9 +139,7 @@ export function parseRoadmapDate(value: string): number | null {
if (Number.isNaN(ms)) {
return null
}
// Why: Date.UTC normalizes overflow (2026-02-30 → Mar 2) instead of failing;
// round-trip the components so an invalid calendar date is rejected, not
// silently moved to a different day.
// Date.UTC normalizes overflow (Feb 30 → Mar 2); round-trip components to reject invalid dates.
const roundTrip = new Date(ms)
if (
roundTrip.getUTCFullYear() !== Number(year) ||
@@ -195,8 +182,7 @@ export function getRoadmapSpan(
const known = startValue ?? targetValue
return known === null ? null : { startMs: known, endMs: known + ROADMAP_DAY_MS, point: true }
}
// Why: a target before the start is user data, not corruption — order the
// pair so the item still gets a visible bar.
// Reversed dates are user data; order them so the item retains a bar.
return {
startMs: Math.min(startValue, targetValue),
endMs: Math.max(startValue, targetValue) + ROADMAP_DAY_MS,
@@ -214,8 +200,7 @@ function unitStart(index: number, zoom: RoadmapZoom): number {
return Date.UTC(Math.floor(months / 12), months % 12, 1)
}
/** Builds the column grid covering every span plus today, padded by one unit
* on each side and widened to a readable minimum. */
/** Cover spans and today, with one unit of padding per side and a readable minimum width. */
export function buildRoadmapTicks(
spans: readonly RoadmapSpan[],
zoom: RoadmapZoom,
@@ -228,16 +213,12 @@ export function buildRoadmapTicks(
latest = Math.max(latest, span.endMs)
}
let firstIdx = unitIndexOf(earliest, zoom) - 1
// Why: span ends are exclusive, so step back a tick before resolving the
// containing unit — otherwise a span landing exactly on a boundary claims
// the next unit and the trailing padding drifts by one column.
// Exclusive span ends on unit boundaries belong to the previous tick; subtract before computing trailing padding.
let lastIdxExclusive = unitIndexOf(latest - 1, zoom) + 2
if (lastIdxExclusive - firstIdx < MIN_UNITS[zoom]) {
lastIdxExclusive = firstIdx + MIN_UNITS[zoom]
}
// Why: the cap must keep today inside the grid — a single typo'd date in
// either direction otherwise consumes every column and the whole roadmap
// clamps to one edge. Trim the side farther from today first.
// Keep today within the capped grid; trim the farther side first so one typo cannot consume all columns.
if (lastIdxExclusive - firstIdx > MAX_TICKS) {
const todayIdx = unitIndexOf(todayMs, zoom)
firstIdx = Math.max(firstIdx, todayIdx + 2 - MAX_TICKS)
@@ -255,9 +236,7 @@ export function buildRoadmapTicks(
return ticks
}
/** Maps a timestamp to a pixel offset inside the grid. Interpolating within
* the containing tick (rather than across the whole range) is what keeps bar
* edges aligned to the column rules, since months differ in length. */
/** Interpolate inside each tick to align bar edges despite unequal month lengths. */
export function roadmapOffsetPx(
ms: number,
ticks: readonly RoadmapTick[],
+10 -32
View File
@@ -1,23 +1,14 @@
// Why: ProjectV2 shapes are distinct enough from the issue/PR work-item types
// that we keep them in a dedicated module. Preload and main-process callers
// import from here directly — do not re-export through `./types.ts` just to
// match the existing import block; routing through the issue types module
// would obscure ownership of the Project surface.
// Import ProjectV2 shapes directly; re-exporting through issue/PR types.ts obscures Project ownership.
export type GitHubProjectViewLayout = 'TABLE_LAYOUT' | 'BOARD_LAYOUT' | 'ROADMAP_LAYOUT'
/** Allowlist shared by the host gate, tab strip, and picker — raw.layout is
* cast unchecked, so an unknown future GitHub layout must fail this check
* everywhere at once rather than drifting per call site. */
/** One layout allowlist serves host/tab/picker gates; unchecked raw.layout must reject unknown future values consistently. */
export function isRenderableProjectViewLayout(layout: string): boolean {
return layout === 'TABLE_LAYOUT' || layout === 'BOARD_LAYOUT' || layout === 'ROADMAP_LAYOUT'
}
export type GitHubProjectOwnerType = 'organization' | 'user'
// Why: anything outside this union must render as an empty cell — the
// normalizer must never throw on an unknown dataType. The `(string & {})`
// branch preserves unknown values verbatim for debuggability while still
// satisfying the distinct field-kind discriminants below.
// Unknown data types render empty, never throw; string & {} preserves unknown wire values while retaining known discriminants.
export type GitHubProjectFieldDataType =
| 'TITLE'
| 'ASSIGNEES'
@@ -88,17 +79,12 @@ export type GitHubProjectView = {
number: number
name: string
layout: GitHubProjectViewLayout
/** Normalized to '' when GitHub returns null. `ProjectV2.items(query:)` is
* declared `String = ""`, so sending '' and omitting the argument are the
* same request — there is no non-search item field to fall back to. '' is
* therefore only a UI signal: it means "this view is unfiltered". */
/** Null normalizes to empty: ProjectV2.items(query:) defaults to empty, equivalent to omitting query. Empty signals unfiltered UI; no separate non-search field exists. */
filter: string
fields: GitHubProjectField[]
groupByFields: GitHubProjectField[]
sortByFields: GitHubProjectSort[]
/** Board-layout column field(s). Optional for wire compat — older hosts
* don't send it, and hosts fall back to omitting it when the GraphQL
* schema lacks `verticalGroupByFields` (older GHES). */
/** Optional for older hosts/GHES schemas lacking verticalGroupByFields. */
verticalGroupByFields?: GitHubProjectField[]
}
@@ -119,10 +105,7 @@ export type GitHubProjectParentIssue = {
url: string
}
// Why: GitHub Issue Types are a repo-level taxonomy (Bug/Feature/Task/etc).
// Only repos opted into typed-issues expose a non-empty list. We carry both
// id and human-readable name so the picker can reflect updates without a
// re-fetch and the cell can render the chosen name with its color.
// Opted-in repos expose Issue Types; carry id/name/color so pickers and cells reflect changes without refetching.
export type GitHubIssueType = {
id: string
name: string
@@ -145,8 +128,7 @@ export type GitHubProjectFieldValue =
title: string
startDate: string
duration: number
/** Owning field's display name. Optional for wire compat — older hosts
* don't send it; used when the field is hidden from the view config. */
/** Owning field label for fields hidden in view config; older hosts omit it. */
fieldName?: string
}
| { kind: 'text'; fieldId: string; text: string }
@@ -168,8 +150,7 @@ export type GitHubProjectRow = {
body: string | null
url: string | null
state: string | null
/** Issue stateReason; null for PR/draft. Why: closed-as-not-planned needs
* a different glyph than a regular closed issue. */
/** Issue-only stateReason distinguishes closed-as-not-planned glyphs; null for PRs/drafts. */
stateReason: string | null
/** PullRequest.isDraft; null otherwise. */
isDraft: boolean | null
@@ -183,9 +164,7 @@ export type GitHubProjectRow = {
}
fieldValuesByFieldId: Record<string, GitHubProjectFieldValue>
updatedAt: string
/** Original fetched order (zero-based index in the fully paginated
* POSITION ASC stream). Used as the final tie-break so equal sort values
* keep GitHub rank order. */
/** Zero-based fully paginated POSITION ASC order; final tie-break preserves GitHub rank. */
position: number
}
@@ -203,8 +182,7 @@ export type GitHubProjectTable = {
rows: GitHubProjectRow[]
/** Echoes ProjectV2.items.totalCount for the view filter. */
totalCount: number
/** True when the `parent` retry fallback fired. The UI can hint
* "sub-issues unavailable" without claiming a hard error. */
/** True after parent-field fallback; permits a sub-issues-unavailable hint without a hard error. */
parentFieldDropped: boolean
}
@@ -1,12 +1,7 @@
import type { RepoKind } from '../repo-types'
import type { CheckStatus, PRInfo, PRMergeableState, PRState } from './pull-request-types'
/**
* Discriminates a classified GitHub PR-refresh failure. The renderer maps these
* to stable, non-destructive empty-state copy; a `hard` subset (auth, permission,
* repo_unavailable, gh_unavailable) means the existing-review lookup is currently
* impossible and must hide the Create composer.
*/
/** Classified failures drive stable empty-state copy. Auth/permission/repo_unavailable/gh_unavailable make existing-review lookup impossible and must hide Create. */
export type PRRefreshErrorType =
| 'rate_limited'
| 'auth'
@@ -28,10 +23,7 @@ export type PRRefreshOutcome =
errorType: PRRefreshErrorType
message: string
fetchedAt: number
// Unified retry schedule (see docs/reference/pr-panel-refresh-guidance.md).
// `nextAutoRetryAt`: earliest time main expects to auto-retry this key.
// `retryDisabledUntil`: earliest time a manual Retry / refreshPRNow is
// accepted (rate-limit gates only, never ordinary network/auth backoff).
// Shared schedule: nextAutoRetryAt is main’s earliest automatic retry; retryDisabledUntil gates manual retry only for rate limits.
nextAutoRetryAt?: number
retryDisabledUntil?: number
}
@@ -54,9 +46,7 @@ export type GitHubPRRefreshAlias = {
linkedPRNumber?: number | null
fallbackPRNumber?: number | null
fallbackPRSource?: 'explicit' | 'pr-cache' | 'hosted-review' | null
// Why: request-time worktree HEAD. Merged branch-matched PRs are only visible
// for heads that belong to the PR, and refresh consumers need this snapshot to
// clear a durable linked PR once main confirms the head diverged.
// Carry request-time HEAD so merged PR containment can confirm divergence before consumers clear durable links.
currentHeadOid?: string | null
}
+7 -20
View File
@@ -13,8 +13,7 @@ export type PRConflictSummary = {
localMergeState?: 'clean'
}
// Why: host must survive renderer/RPC boundaries so Enterprise review actions
// cannot silently fall back to a same-named repository on github.com.
// Keep GHES host identity across renderer/RPC boundaries to prevent actions targeting same-named github.com repos.
export type GitHubRepositoryIdentity = { owner: string; repo: string; host?: string }
export type GitHubPRMergeMethod = 'merge' | 'squash' | 'rebase'
@@ -64,25 +63,16 @@ export type PRInfo = {
mergeStateStatus?: string | null
/** GitHub-registered stack metadata. Absent for ordinary dependent PR chains. */
stack?: GitHubPRStack
// Why: check-runs are keyed by the PR head commit, not the mutable branch name.
// Keeping the head SHA in cached PR metadata lets the checks panel poll the
// correct commit without re-querying GitHub or guessing from local branch refs.
// Checks key by head commit; cache the SHA to poll without GitHub re-queries or mutable-branch guesses.
headSha?: string
// Why: a merged branch-matched PR stays visible when the worktree head is one
// of the PR's own commits (behind update-branch/web commits). Cache staleness
// checks must honor that confirmation without re-querying GitHub.
// Keep merged PRs visible for contained worktree heads, including behind update/web commits; cache validation honors this proof.
confirmedContainedHeadOid?: string
// Why: the worktree HEAD OID this merged linked PR was confirmed to have
// diverged from (a definite not-contained probe). Head-scoped, not a bare
// boolean, so a PR-number-coalesced refresh broadcast cannot clear a sibling
// worktree whose own head is still on the PR's line of work. Clearing a
// durable linked PR requires this positive signal for that exact head, never
// the mere absence of a containment confirmation after a rate-limit/error.
// Positive divergence proof applies only to this worktree HEAD. PR-coalesced broadcasts must not clear siblings still on the PR’s history.
// Clear durable links only with exact-head proof; missing containment after rate limits/errors proves nothing.
headDivergedFromMergedPRAtOid?: string
/** Target branch name for PR-created worktree compare-base repair. */
baseRefName?: string
/** PR head branch name. Lets linked-PR consumers detect that the worktree
* has switched to a different branch and the durable link is stale. */
/** PR branch name detects worktrees switching branches and stale durable links. */
headRefName?: string
prRepo?: GitHubRepositoryIdentity
headRepo?: GitHubRepositoryIdentity
@@ -150,8 +140,5 @@ export type GitHubPRFileContents = {
modifiedTooLarge?: boolean
}
// Why: declared here as a shared shape so IPC return envelopes and renderer
// slices can reference the same structural type without importing from main.
// Aliased as `OwnerRepo` in `src/main/github/gh-utils.ts` so main call sites
// can continue using the short local name.
// Share the identity shape across IPC/renderer without main imports; main’s gh-utils.ts retains the OwnerRepo alias.
export type GitHubOwnerRepo = GitHubRepositoryIdentity
+2 -8
View File
@@ -1,11 +1,5 @@
/**
* GitHub API rate-limit buckets surfaced in the TaskPage header so users can
* see remaining budget before they hit the wall. `core` = REST (5000/hr),
* `search` = Search API (30/min — hit by countWorkItems), `graphql` =
* GraphQL (5000 points/hr — hit by project-view + discovery). All three are
* the buckets this app actually stresses; other buckets (e.g. code_search)
* are not surfaced because we don't touch them.
*/
/** Task header quota snapshots cover REST core, work-item search and Project GraphQL usage so users can see remaining budget.
* Other buckets such as code_search are unused and omitted; actual limits come from each bucket. */
export type GitHubRateLimitBucket = {
remaining: number
limit: number
+4 -10
View File
@@ -1,13 +1,9 @@
// Why: the github.com-vs-GHES boundary is the core invariant of Enterprise
// support — cache identity, quota scoping, and exec-host routing must all
// agree on it, so the predicate lives here once.
// Share github.com/GHES detection across cache identity, quota scoping and execution routing.
export function isDefaultGitHubHost(host?: string): boolean {
return !host?.trim() || host.trim().toLowerCase() === 'github.com'
}
// Why: cache keys and equality checks for GitHub repos must include the host,
// or a GHES repo and a same-named github.com repo would collide. github.com is
// omitted so pre-Enterprise host-less keys stay stable.
// Include GHES hosts to prevent same-name collisions; omit github.com to preserve legacy keys.
export function githubRepoIdentityKey(repo: {
owner: string
repo: string
@@ -18,10 +14,8 @@ export function githubRepoIdentityKey(repo: {
return host && !isDefaultGitHubHost(host) ? `${host}/${slug}` : slug
}
// Why: callers that only kept the key (not the identity it came from) still need
// its host segment to scope a second, host-less identity into the same namespace.
// `owner` and `repo` never contain `/`, so a three-segment key is host-qualified.
// `undefined` means github.com, so never pass a key that may be unresolved.
// Recover host scope for callers retaining only keys: three segments imply a host because owner/repo cannot contain /.
// Undefined means github.com, so unresolved keys must not be passed here.
export function githubHostFromIdentityKey(key: string | null | undefined): string | undefined {
const segments = key?.split('/') ?? []
return segments.length === 3 ? segments[0] : undefined
+12 -47
View File
@@ -24,15 +24,11 @@ export type GitHubWorkItem = {
labels: string[]
updatedAt: string
author: string | null
// Why: GHE user logins don't exist on github.com, so the github.com/{login}.png
// fallback 404s. Carry the API-provided avatar_url so github.com + Enterprise
// both render; absent on the gh-pr-view path (gh omits avatar), then the UI
// falls back to the login URL and finally an initials placeholder. See #8784.
// Use API avatars for GHE logins absent on github.com (#8784). gh-pr-view omits them; UI falls back through login URL to initials.
authorAvatarUrl?: string
branchName?: string
baseRefName?: string
// Why: PR checks are keyed by head commit; carrying this lets task rows use
// the cached check-runs endpoint instead of one `gh pr checks` call per row.
// Head SHA enables cached check-run requests instead of per-row gh pr checks.
headSha?: string
prRepo?: GitHubRepositoryIdentity
additions?: number
@@ -50,23 +46,16 @@ export type GitHubWorkItem = {
mergeMethodSettings?: GitHubPRMergeMethodSettings
mergeStateStatus?: string | null
maintainerCanModify?: boolean
// Why: true when a PR's head lives on a fork (headRepositoryOwner !== selected repo owner).
// The Start-from picker passes this to resolvePrBase so fork heads use
// refs/pull/<N>/head for creation and a separate PR-head push target.
// Fork heads use refs/pull/<N>/head for creation and a separate push target in resolvePrBase.
isCrossRepository?: boolean
/** Why: required because the cross-repo view merges items from every selected
* repo — the table row's repo pill and the "open in browser" fallback need
* to know which repo an item came from. Stamped by the renderer fetcher
* (`fetchWorkItems`) and by optimistic stubs on the new-issue path. */
/** Renderer fetches/optimistic stubs stamp origin repo for cross-repo pills and open-in-browser routing. */
repoId: string
/** Why: one repo id can name a row on two hosts. The renderer fetcher stamps the host whose row
* returned the item, so its actions run there, not on whichever twin a by-id lookup finds. */
/** Repo ids can repeat across hosts; stamp the producing host so actions cannot target a same-id twin. */
repoHostId?: ExecutionHostId
}
export type GitHubWorkItemDetails = {
// Why: main-process doesn't know Orca's Repo.id, so this inner item omits
// repoId. The renderer stamps it when routing the details through the store.
// Main lacks Orca Repo.id; renderer stamps it when storing details.
item: Omit<GitHubWorkItem, 'repoId'>
body: string
comments: PRComment[]
@@ -87,47 +76,23 @@ export type GitHubWorkItemDetails = {
assignees?: string[]
}
/**
* Envelope for `gh:listWorkItems`. Carries resolved issue/PR sources so the
* renderer can render the "Issues from owner/repo" indicator without an
* extra IPC round-trip, and per-source classified errors so the UI can show
* a retryable banner when (e.g.) a private upstream 403s.
*
* Why piggyback instead of adding `gh:resolveWorkItemSources`: the renderer
* already round-trips this endpoint on every Tasks refresh, and the source
* data is a 2-field-per-side metadata add — cheaper than another IPC call.
*
* Invariant: `items` always contains whatever succeeded; `errors.issues` indicates
* the issues-side fetch failed, but any PR-side items that succeeded are still
* present in `items`. Consumers should render `items` alongside the error banner.
*/
/** Piggyback issue/PR sources on Tasks refresh to avoid another IPC round trip. Per-source failures preserve successful items;
* render items alongside error banners (e.g. private upstream 403), never discard the other side’s success. */
export type ListWorkItemsResult<T> = {
items: T[]
sources: {
issues: GitHubOwnerRepo | null
prs: GitHubOwnerRepo | null
/** Raw `origin` remote resolved for this repo, independent of the
* user's preference. Required-nullable so the renderer can compare raw
* remote candidates without inferring origin from the effective PR
* source. */
/** Raw origin independent of preferences; compare candidates without inferring from effective PR source. */
originCandidate: GitHubOwnerRepo | null
/** Raw `upstream` remote resolved for this repo, independent of the
* user's preference. Present so the renderer's issue-source selector
* can always decide whether to render (upstream exists & differs from
* origin) and show both slugs in its tooltips, even when the user has
* picked 'origin' and `sources.issues` has collapsed onto origin. */
/** Raw upstream independent of preferences; selector visibility/tooltips need it even when effective issues source is origin. */
upstreamCandidate: GitHubOwnerRepo | null
}
errors?: {
issues?: ClassifiedError
prs?: ClassifiedError
}
/** True when the user's per-repo preference was `'upstream'` but no upstream
* remote is configured, so the resolver fell back to origin. Renderer uses
* this to surface a one-time-per-session toast. Omitted when absent so
* existing consumers and test fixtures don't care about it.
* Typed as `?: true` (not `?: boolean`) to encode the invariant "present
* iff fell-back" — an explicit `false` write would be a bug, so make it a
* compile error. */
/** Present only when requested upstream was missing and origin was used; renderer shows a once-per-session toast.
* Optional literal true preserves legacy consumers and forbids erroneous explicit false writes. */
issueSourceFellBack?: true
}