mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 17:17:38 +00:00
[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:
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,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. */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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[],
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user