feat(projects): PR + CI/checks status chip via gh (W3)
Per-project chip: PR state · N checks passing · mergeable — glance from the phone,
re-engage only when it's red, instead of dropping into a terminal for `gh pr checks`.
- src/http/gh.ts (new): single `gh pr view --json number,state,title,url,isDraft,
mergeable,headRefName,baseRefName,statusCheckRollup` (execFile, no shell, cwd =
isValidGitDir repo, timeout + maxBuffer). summarizeChecks rolls the mixed
CheckRun/StatusContext rollup into {total,passing,failing,pending}. Never throws —
degrades to not-installed (ENOENT) / unauthenticated / no-pr / error / disabled.
Cache keyed by repoPath+branch (reuses projectScanTtlMs) with in-flight dedupe.
- src/types.ts: additive PrStatus/PrAvailability/PrCheckSummary; config GH_ENABLED
(default on) + GH_TIMEOUT_MS (8s).
- GET /projects/pr?path= (read-only, isValidGitDir); public/gh-chip.ts render-only
chip mounted in the project detail header (git repos only).
Read-only, host's own authed gh (same trust as the shell). No untrusted argv (only
the validated cwd); gh stdout/token never logged; the attacker-controllable PR title
is rendered inert via textContent (SEC-H4). Verified: typecheck + build:web clean,
1816 pass (gh tests 118). The 1 red is the known real-PTY ring-buffer timeout flake.
This commit is contained in:
@@ -1534,6 +1534,64 @@ body {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
/* W3: PR + CI status chip (mirrors the .proj-branch chip look). */
|
||||
.proj-pr-host {
|
||||
display: inline-flex;
|
||||
flex: none;
|
||||
}
|
||||
.proj-pr-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 11px;
|
||||
border-radius: 5px;
|
||||
padding: 2px 7px;
|
||||
white-space: nowrap;
|
||||
max-width: 260px;
|
||||
color: var(--text-faint);
|
||||
background: var(--accent-soft);
|
||||
}
|
||||
.proj-pr-chip .proj-pr-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--text-faint);
|
||||
}
|
||||
.proj-pr-loading {
|
||||
opacity: 0.6;
|
||||
}
|
||||
/* PR-state modifiers */
|
||||
.proj-pr-open .proj-pr-label {
|
||||
color: var(--green);
|
||||
}
|
||||
.proj-pr-draft .proj-pr-label {
|
||||
color: var(--text-faint);
|
||||
}
|
||||
.proj-pr-merged .proj-pr-label {
|
||||
color: var(--accent);
|
||||
}
|
||||
.proj-pr-closed .proj-pr-label {
|
||||
color: var(--red);
|
||||
}
|
||||
.proj-pr-none,
|
||||
.proj-pr-unavailable {
|
||||
color: var(--text-faint);
|
||||
background: transparent;
|
||||
}
|
||||
/* Checks-rollup modifiers (colour the whole chip label) */
|
||||
.proj-pr-checks-ok .proj-pr-label {
|
||||
color: var(--green);
|
||||
}
|
||||
.proj-pr-checks-fail .proj-pr-label {
|
||||
color: var(--red);
|
||||
}
|
||||
.proj-pr-checks-pending .proj-pr-label {
|
||||
color: var(--amber);
|
||||
}
|
||||
/* Mergeable = conflicting */
|
||||
.proj-pr-conflict {
|
||||
box-shadow: inset 0 0 0 1px var(--red);
|
||||
}
|
||||
|
||||
/* Meta line (last-active time) */
|
||||
.proj-meta {
|
||||
font-size: 11px;
|
||||
|
||||
Reference in New Issue
Block a user