Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| The docs' Copy page menu keeps its own spacing, with an icon per item | 1 | /** |
| 2 | * The parts of a pull request's merge box that say what stands between it | |
| 3 | * and a merge: the checks the branch requires and every other check, job by | |
| 4 | * job, and whether | |
| 5 | * the change merges cleanly into its target, with what to do when not. | |
| 6 | */ | |
| 7 | import { | |
| 8 | Check, | |
| 9 | ChevronRight, | |
| 10 | CircleAlert, | |
| 11 | CircleCheck, | |
| 12 | CircleX, | |
| 13 | Copy, | |
| 14 | GitCommitHorizontal, | |
| 15 | History, | |
| 16 | LoaderCircle, | |
| 17 | RotateCw, | |
| 18 | Terminal, | |
| 19 | TriangleAlert, | |
| 20 | } from "lucide-react"; | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 21 | import { useMemo, useState } from "react"; |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 22 | import { Form, Link } from "react-router"; |
| 23 | ||
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 24 | import type { CheckResult, CheckRun, CommitStatus, Job, Mergeable, Pull, RequiredCheck } from "@g1t/contracts"; |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 25 | |
| Merge branch 'worktree-agent-aaf03bdceac799c89' | 26 | import { useAddresses } from "../lib/addresses"; |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 27 | import { duration } from "./actions"; |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 28 | import { CopyLine, ErrorText, SubmitButton, TimeAgo } from "./ui"; |
| 29 | import { Badge } from "./ui/badge"; | |
| 30 | import { Button } from "./ui/button"; | |
| 31 | import { Card } from "./ui/card"; | |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 32 | import { Hint } from "./ui/hint"; |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 33 | import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; |
| Merge branch 'worktree-agent-aca4790ea223aa235' | 34 | import { SkeletonLine } from "./ui/skeleton"; |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 35 | |
| 36 | // --- Output --------------------------------------------------------------- | |
| 37 | ||
| 38 | /** What the sandbox puts first when it kept only the end of the output. */ | |
| 39 | const CUT_SHORT = "… (earlier output not shown)"; | |
| 40 | ||
| 41 | /** One run of text in one style, from a line with ANSI colour codes. */ | |
| 42 | type Segment = { text: string; className: string }; | |
| 43 | ||
| 44 | const ANSI_COLOURS: Record<number, string> = { | |
| 45 | 30: "text-faint", | |
| 46 | 31: "text-danger", | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 47 | 32: "text-success", |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 48 | 33: "text-warn", |
| 49 | 34: "text-info", | |
| 50 | 35: "text-merged", | |
| 51 | 36: "text-info", | |
| 52 | 37: "text-fg", | |
| 53 | 90: "text-faint", | |
| 54 | 91: "text-danger", | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 55 | 92: "text-success", |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 56 | 93: "text-warn", |
| 57 | 94: "text-info", | |
| 58 | 95: "text-merged", | |
| 59 | 96: "text-info", | |
| 60 | 97: "text-fg", | |
| 61 | }; | |
| 62 | ||
| 63 | /** | |
| 64 | * A line's text split where its colour changes. Colours and bold are kept; | |
| 65 | * every other escape sequence (cursor movement, erasing, links) is dropped. | |
| 66 | */ | |
| 67 | export function ansiSegments(line: string): Segment[] { | |
| 68 | const segments: Segment[] = []; | |
| 69 | let colour = ""; | |
| 70 | let bold = false; | |
| 71 | // SGR sequences are kept apart; anything else that starts with ESC goes. | |
| 72 | const pattern = /\x1b\[([0-9;]*)m|\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]/g; | |
| 73 | let at = 0; | |
| 74 | const push = (text: string) => { | |
| 75 | if (text) segments.push({ text, className: [colour, bold ? "font-semibold" : ""].filter(Boolean).join(" ") }); | |
| 76 | }; | |
| 77 | for (const match of line.matchAll(pattern)) { | |
| 78 | push(line.slice(at, match.index)); | |
| 79 | at = match.index + match[0].length; | |
| 80 | if (match[1] === undefined) continue; | |
| 81 | const codes = match[1] === "" ? [0] : match[1].split(";").map(Number); | |
| 82 | for (const code of codes) { | |
| 83 | if (code === 0) { | |
| 84 | colour = ""; | |
| 85 | bold = false; | |
| 86 | } else if (code === 1) bold = true; | |
| 87 | else if (code === 22) bold = false; | |
| 88 | else if (code === 39) colour = ""; | |
| 89 | else if (ANSI_COLOURS[code]) colour = ANSI_COLOURS[code]; | |
| 90 | } | |
| 91 | } | |
| 92 | push(line.slice(at)); | |
| 93 | return segments; | |
| 94 | } | |
| 95 | ||
| 96 | /** Text without any ANSI escape sequences, for copying. */ | |
| 97 | export function stripAnsi(text: string): string { | |
| 98 | return ansiSegments(text.replace(/\r\n/g, "\n")) | |
| 99 | .map((segment) => segment.text) | |
| 100 | .join(""); | |
| 101 | } | |
| 102 | ||
| 103 | /** Lines as a terminal would show them: a carriage return starts the line over. */ | |
| 104 | function terminalLines(text: string): string[] { | |
| 105 | return text | |
| 106 | .replace(/\r\n/g, "\n") | |
| 107 | .split("\n") | |
| 108 | .map((line) => { | |
| 109 | const parts = line.split("\r"); | |
| 110 | return parts[parts.length - 1] || parts.filter(Boolean).pop() || ""; | |
| 111 | }); | |
| 112 | } | |
| 113 | ||
| 114 | /** | |
| 115 | * A command's output as a log: numbered lines, colours rendered, a copy | |
| 116 | * button, and a note when only the end of it was kept. | |
| 117 | */ | |
| 118 | export function LogViewer({ text }: { text: string }) { | |
| 119 | const cut = text.startsWith(CUT_SHORT); | |
| 120 | const body = cut ? text.slice(CUT_SHORT.length).replace(/^\n/, "") : text; | |
| 121 | const lines = useMemo(() => { | |
| 122 | const all = terminalLines(body); | |
| 123 | while (all.length > 0 && all[all.length - 1].trim() === "") all.pop(); | |
| 124 | return all; | |
| 125 | }, [body]); | |
| 126 | const [copied, setCopied] = useState(false); | |
| 127 | if (lines.length === 0) { | |
| 128 | return <p className="px-3 py-2 font-mono text-xs text-faint">No output.</p>; | |
| 129 | } | |
| 130 | return ( | |
| 131 | <div className="relative"> | |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 132 | <Hint label="Copy the output"> |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 133 | <Button |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 134 | type="button" |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 135 | aria-label="Copy the output" variant="outline" size="inline" className="absolute top-1.5 right-3 z-10 bg-surface p-1.5 text-faint" onClick={() => { |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 136 | void navigator.clipboard.writeText(stripAnsi(body)); |
| 137 | setCopied(true); | |
| 138 | setTimeout(() => setCopied(false), 1500); | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 139 | }}> |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 140 | {copied ? <Check size={13} className="text-success" /> : <Copy size={13} />} |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 141 | </Button> |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 142 | </Hint> |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 143 | {cut && ( |
| 144 | <p className="border-b border-line px-3 py-1.5 text-xs text-faint"> | |
| 145 | Showing the end of the output. Earlier lines were not kept. | |
| 146 | </p> | |
| 147 | )} | |
| 148 | <div className="max-h-96 overflow-auto py-1.5 font-mono text-xs leading-5"> | |
| 149 | {lines.map((line, index) => ( | |
| 150 | <div key={index} className="flex gap-3 pr-12 pl-2 hover:bg-raised/40"> | |
| 151 | <span className="w-9 shrink-0 text-right text-faint/70 select-none">{index + 1}</span> | |
| 152 | <span className="min-w-0 break-all whitespace-pre-wrap text-fg/85"> | |
| 153 | {ansiSegments(line).map((segment, at) => | |
| 154 | segment.className ? ( | |
| 155 | <span key={at} className={segment.className}> | |
| 156 | {segment.text} | |
| 157 | </span> | |
| 158 | ) : ( | |
| 159 | segment.text | |
| 160 | ), | |
| 161 | )} | |
| 162 | </span> | |
| 163 | </div> | |
| 164 | ))} | |
| 165 | </div> | |
| 166 | </div> | |
| 167 | ); | |
| 168 | } | |
| 169 | ||
| 170 | // --- Checks --------------------------------------------------------------- | |
| 171 | ||
| 172 | function seconds(ms: number): string { | |
| 173 | if (ms < 1000) return `${ms}ms`; | |
| 174 | const s = Math.round(ms / 1000); | |
| 175 | return s < 60 ? `${(ms / 1000).toFixed(ms < 10_000 ? 1 : 0)}s` : `${Math.floor(s / 60)}m ${s % 60}s`; | |
| 176 | } | |
| 177 | ||
| 178 | type Standing = "passed" | "failed" | "running" | "queued" | "skipped"; | |
| 179 | ||
| 180 | function StandingIcon({ standing, size = 15 }: { standing: Standing; size?: number }) { | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 181 | if (standing === "passed") return <CircleCheck size={size} className="shrink-0 text-success" aria-label="Passed" />; |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 182 | if (standing === "failed") return <CircleX size={size} className="shrink-0 text-danger" aria-label="Failed" />; |
| 183 | if (standing === "running") | |
| 184 | return <LoaderCircle size={size} className="shrink-0 animate-spin text-warn" aria-label="Running" />; | |
| 185 | if (standing === "skipped") return <CircleAlert size={size} className="shrink-0 text-faint" aria-label="Skipped" />; | |
| 186 | return <LoaderCircle size={size} className="shrink-0 text-faint" aria-label="Queued" />; | |
| 187 | } | |
| 188 | ||
| 189 | /** The id of the workflow run a status links to, if it links to one. */ | |
| 190 | export function runIdOf(status: CommitStatus): string | null { | |
| 191 | return /\/actions\/runs\/([^/?#]+)/.exec(status.targetUrl ?? "")?.[1] ?? null; | |
| 192 | } | |
| 193 | ||
| 194 | /** Where a job stands, in the box's terms. */ | |
| 195 | function jobStanding(job: Job): Standing { | |
| 196 | if (job.status !== "completed") return job.status === "in_progress" || job.status === "calling" ? "running" : "queued"; | |
| 197 | if (job.conclusion === "success") return "passed"; | |
| 198 | if (job.conclusion === "skipped" || job.conclusion === "cancelled") return "skipped"; | |
| 199 | return "failed"; | |
| 200 | } | |
| 201 | ||
| 202 | function statusStanding(status: CommitStatus): Standing { | |
| 203 | if (status.state === "pending") return "running"; | |
| 204 | return status.state === "success" ? "passed" : "failed"; | |
| 205 | } | |
| 206 | ||
| 207 | /** One row of the checks list: an icon, a name, how long, and a way in. */ | |
| 208 | function CheckLine({ | |
| 209 | standing, | |
| 210 | name, | |
| 211 | detail, | |
| 212 | time, | |
| Merge branch 'worktree-agent-aca4790ea223aa235' | 213 | timing = false, |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 214 | to, |
| 215 | }: { | |
| 216 | standing: Standing; | |
| 217 | name: React.ReactNode; | |
| 218 | detail?: React.ReactNode; | |
| 219 | time?: string; | |
| Merge branch 'worktree-agent-aca4790ea223aa235' | 220 | /** Its jobs and their times are still being read. */ |
| 221 | timing?: boolean; | |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 222 | to?: string; |
| 223 | }) { | |
| 224 | return ( | |
| 225 | <div className="flex items-center gap-2.5 px-4 py-2 text-sm"> | |
| 226 | <StandingIcon standing={standing} /> | |
| 227 | <span className="min-w-0 grow truncate"> | |
| 228 | {name} | |
| 229 | {detail && <span className="text-muted"> — {detail}</span>} | |
| 230 | </span> | |
| Merge remote-tracking branch 'origin/main' into workspace-chat | 231 | {time && ( |
| 232 | // A running job's time counts to now, which differs between server and browser. | |
| 233 | <span className="shrink-0 animate-fade-in font-mono text-xs text-faint" suppressHydrationWarning> | |
| 234 | {time} | |
| 235 | </span> | |
| 236 | )} | |
| Merge branch 'worktree-agent-aca4790ea223aa235' | 237 | {timing && <SkeletonLine className="w-10 shrink-0 text-xs" />} |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 238 | {to && ( |
| 239 | <Link to={to} className="shrink-0 text-xs text-muted hover:text-fg hover:underline"> | |
| 240 | Details | |
| 241 | </Link> | |
| 242 | )} | |
| 243 | </div> | |
| 244 | ); | |
| 245 | } | |
| 246 | ||
| 247 | /** A command from a run recorded before checks were workflows: what it printed, open when it failed. */ | |
| 248 | function CommandResult({ result }: { result: CheckResult }) { | |
| 249 | const timedOut = result.exitCode == null && !result.passed; | |
| 250 | return ( | |
| 251 | <details className="group" open={!result.passed}> | |
| 252 | <summary className="flex cursor-pointer list-none items-center gap-2.5 px-4 py-2 text-sm hover:bg-raised/40"> | |
| 253 | <StandingIcon standing={result.passed ? "passed" : "failed"} /> | |
| 254 | <span className="min-w-0 grow truncate"> | |
| 255 | <code className="font-mono text-xs">{result.command}</code> | |
| 256 | {!result.passed && ( | |
| 257 | <span className="text-muted"> | |
| 258 | {" — "} | |
| 259 | {timedOut ? "stopped for taking too long" : `exited with code ${result.exitCode}`} | |
| 260 | </span> | |
| 261 | )} | |
| 262 | </span> | |
| 263 | <span className="shrink-0 font-mono text-xs text-faint">{seconds(result.durationMs)}</span> | |
| 264 | <ChevronRight size={14} className="shrink-0 text-faint transition-transform group-open:rotate-90" /> | |
| 265 | </summary> | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 266 | <Card tone="bg" radius="lg" className="mx-4 mb-3 overflow-hidden"> |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 267 | <LogViewer text={result.output} /> |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 268 | </Card> |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 269 | </details> |
| 270 | ); | |
| 271 | } | |
| 272 | ||
| 273 | /** Where a required check stands, in the box's terms. */ | |
| 274 | function requiredStanding(check: RequiredCheck): Standing { | |
| 275 | if (check.state === "success") return "passed"; | |
| 276 | if (check.state === "failure") return "failed"; | |
| 277 | return check.state === "pending" ? "running" : "queued"; | |
| 278 | } | |
| 279 | ||
| 280 | /** How the required checks stand together: "2 of 3 passing", and what holds the rest. */ | |
| 281 | export function requiredSummary(required: RequiredCheck[]): { title: string; sub: string; standing: Standing } { | |
| 282 | const count = (state: RequiredCheck["state"]) => required.filter((check) => check.state === state).length; | |
| 283 | const passing = count("success"); | |
| 284 | const failing = count("failure"); | |
| 285 | const running = count("pending"); | |
| 286 | const expected = count("expected"); | |
| 287 | const sub = [ | |
| 288 | failing > 0 && `${failing} failing`, | |
| 289 | running > 0 && `${running} running`, | |
| 290 | expected > 0 && `${expected} not reported yet`, | |
| 291 | ] | |
| 292 | .filter(Boolean) | |
| 293 | .join(", "); | |
| 294 | return { | |
| 295 | title: `Required checks: ${passing} of ${required.length} passing`, | |
| 296 | sub, | |
| 297 | standing: failing > 0 ? "failed" : running + expected > 0 ? "running" : "passed", | |
| 298 | }; | |
| 299 | } | |
| 300 | ||
| 301 | /** The repository-relative address of a status's link, when it is on g1t. */ | |
| 302 | function onSite(url: string | null): string | undefined { | |
| 303 | if (!url) return undefined; | |
| 304 | const path = url.replace(/^https:\/\/g1t\.sh(?=\/)/, ""); | |
| 305 | return path.startsWith("/") ? path : undefined; | |
| 306 | } | |
| 307 | ||
| 308 | /** | |
| 309 | * Every check on a pull request, at a glance and in detail: first the | |
| 310 | * checks the default branch requires, each as it stands on the head (one | |
| 311 | * nothing has reported is waited for), then every workflow run on the | |
| 312 | * head, job by job. A record of the merge queue taking it out comes last. | |
| 313 | */ | |
| 314 | export function ChecksSection({ | |
| 315 | run, | |
| 316 | required, | |
| 317 | statuses, | |
| 318 | jobs, | |
| 319 | pull, | |
| 320 | base, | |
| 321 | earlier, | |
| 322 | canRerunWorkflows, | |
| 323 | settingsUrl, | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' | 324 | conflicting = false, |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 325 | error, |
| Merge branch 'worktree-agent-aca4790ea223aa235' | 326 | loading = false, |
| Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md | 327 | settled = false, |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 328 | }: { |
| 329 | /** Set when the merge queue took it out, or for a run from before checks were workflows. */ | |
| 330 | run: CheckRun | null; | |
| 331 | required: RequiredCheck[]; | |
| 332 | statuses: CommitStatus[]; | |
| 333 | /** Each workflow run's jobs, by run id, where they could be read. */ | |
| 334 | jobs: Record<string, Job[]>; | |
| 335 | pull: Pull; | |
| 336 | /** The repository's path, `/<owner>/<repo>`. */ | |
| 337 | base: string; | |
| 338 | earlier: CheckRun[]; | |
| 339 | canRerunWorkflows: boolean; | |
| 340 | /** Where the required checks are chosen, for those who may. */ | |
| 341 | settingsUrl?: string | null; | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' | 342 | /** |
| 343 | * It conflicts with its target, so its `pull_request` workflows do not | |
| 344 | * run: there is no merge commit to build, as on GitHub. | |
| 345 | */ | |
| 346 | conflicting?: boolean; | |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 347 | error?: string | null; |
| Merge branch 'worktree-agent-aca4790ea223aa235' | 348 | /** The workflow runs' jobs are still being read: their rows wait, busy. */ |
| 349 | loading?: boolean; | |
| Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md | 350 | /** |
| 351 | * The pull request is closed or merged: its head's checks as they ended, | |
| 352 | * to read only. Nothing is said of holding a merge. | |
| 353 | */ | |
| 354 | settled?: boolean; | |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 355 | }) { |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' | 356 | const held = conflicting && !settled; |
| 357 | if (!run && statuses.length === 0 && required.length === 0 && !held) return null; | |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 358 | |
| 359 | // Every workflow row, so they can be counted the same way they are shown. | |
| 360 | const workflow: Standing[] = statuses.flatMap((status) => { | |
| 361 | const id = runIdOf(status); | |
| 362 | const theirs = id ? jobs[id] : undefined; | |
| 363 | return theirs && theirs.length > 0 ? theirs.map(jobStanding) : [statusStanding(status)]; | |
| 364 | }); | |
| 365 | const failed = workflow.filter((standing) => standing === "failed").length; | |
| 366 | const pending = workflow.filter((standing) => standing === "running" || standing === "queued").length; | |
| 367 | const passed = workflow.filter((standing) => standing === "passed").length; | |
| 368 | const skipped = workflow.filter((standing) => standing === "skipped").length; | |
| 369 | const total = workflow.length; | |
| 370 | const queueFailed = run?.status === "failed" && run.results.length === 0; | |
| 371 | ||
| 372 | const summary = required.length > 0 ? requiredSummary(required) : null; | |
| 373 | const headline = summary | |
| 374 | ? summary.title | |
| 375 | : failed > 0 | |
| 376 | ? `${failed} of ${total} ${total === 1 ? "check" : "checks"} failed` | |
| 377 | : pending > 0 | |
| 378 | ? `${pending} of ${total} ${total === 1 ? "check is" : "checks are"} still running` | |
| 379 | : total > 0 | |
| 380 | ? "All checks have passed" | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' | 381 | : held |
| 382 | ? "Workflows have not run" | |
| 383 | : "Checks"; | |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 384 | const others = [ |
| 385 | failed > 0 && `${failed} failed`, | |
| 386 | pending > 0 && `${pending} running`, | |
| 387 | passed > 0 && `${passed} passed`, | |
| 388 | skipped > 0 && `${skipped} skipped or cancelled`, | |
| 389 | ] | |
| 390 | .filter(Boolean) | |
| 391 | .join(", "); | |
| 392 | const sub = summary | |
| 393 | ? [summary.sub, others && `all checks: ${others}`].filter(Boolean).join(" · ") | |
| Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md | 394 | : settled |
| 395 | ? [others, pull.headCommit && `on ${pull.headCommit.slice(0, 7)}`].filter(Boolean).join(" · ") | |
| 396 | : [others, "none are required, so none hold the merge"].filter(Boolean).join(" · "); | |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 397 | const standing: Standing = summary |
| 398 | ? summary.standing | |
| 399 | : failed > 0 | |
| 400 | ? "failed" | |
| 401 | : pending > 0 | |
| 402 | ? "running" | |
| 403 | : "passed"; | |
| 404 | ||
| 405 | const commitPath = (sha: string) => | |
| 406 | pull.fork ? `/${pull.fork.namespace}/${pull.fork.name}/commit/${sha}` : `${base}/commit/${sha}`; | |
| 407 | const older = run != null && pull.headCommit != null && run.headCommit !== pull.headCommit; | |
| 408 | const failedRuns = statuses.filter((status) => statusStanding(status) === "failed" && runIdOf(status)); | |
| 409 | ||
| 410 | return ( | |
| Merge branch 'worktree-agent-aca4790ea223aa235' | 411 | <div aria-busy={loading || undefined}> |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 412 | <div className="flex items-start gap-3 px-4 py-3"> |
| 413 | <span className="mt-0.5 shrink-0"> | |
| 414 | {standing === "failed" || queueFailed ? ( | |
| 415 | <CircleX size={16} className="text-danger" /> | |
| 416 | ) : standing === "running" ? ( | |
| 417 | <LoaderCircle size={16} className="animate-spin text-warn" /> | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' | 418 | ) : held && total === 0 ? ( |
| 419 | <CircleAlert size={16} className="text-warn" /> | |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 420 | ) : ( |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 421 | <CircleCheck size={16} className="text-success" /> |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 422 | )} |
| 423 | </span> | |
| 424 | <div className="min-w-0 grow"> | |
| 425 | <p className="text-sm font-medium">{headline}</p> | |
| 426 | {sub && <p className="mt-0.5 text-xs text-muted">{sub}</p>} | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' | 427 | {held && ( |
| 428 | <p className="mt-0.5 text-xs text-muted"> | |
| 429 | It conflicts with the branch it merges into, so workflows that run on{" "} | |
| 430 | <code className="font-mono">pull_request</code> do not start for it: they build the pull request | |
| 431 | merged into that branch, and there is no such merge. They run again once the conflicts are resolved. | |
| 432 | </p> | |
| 433 | )} | |
| Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md | 434 | {!summary && !settled && settingsUrl && ( |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 435 | <p className="mt-0.5 text-xs text-muted"> |
| 436 | <Link to={settingsUrl} className="text-fg hover:underline"> | |
| 437 | Choose required checks | |
| 438 | </Link>{" "} | |
| 439 | to hold merges until they pass. | |
| 440 | </p> | |
| 441 | )} | |
| 442 | </div> | |
| 443 | <div className="flex shrink-0 flex-wrap items-center justify-end gap-2"> | |
| 444 | {canRerunWorkflows && | |
| 445 | failedRuns.map((status) => ( | |
| 446 | <Form method="post" key={status.context}> | |
| 447 | <input type="hidden" name="action" value="rerun-workflow" /> | |
| 448 | <input type="hidden" name="run" value={runIdOf(status) ?? ""} /> | |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 449 | <Hint label={`Re-run the failed jobs of ${status.context}`}> |
| 450 | <SubmitButton | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 451 | variant="outline" |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 452 | match={{ action: "rerun-workflow", run: runIdOf(status) ?? "" }} |
| 453 | pending="Re-running…" | |
| 454 | > | |
| 455 | <RotateCw size={13} /> | |
| 456 | Re-run failed jobs{failedRuns.length > 1 ? ` of ${status.context}` : ""} | |
| 457 | </SubmitButton> | |
| 458 | </Hint> | |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 459 | </Form> |
| 460 | ))} | |
| 461 | </div> | |
| 462 | </div> | |
| 463 | {error && ( | |
| 464 | <div className="px-4 pb-2"> | |
| 465 | <ErrorText>{error}</ErrorText> | |
| 466 | </div> | |
| 467 | )} | |
| 468 | ||
| 469 | <div className="divide-y divide-line border-t border-line"> | |
| 470 | {required.length > 0 && ( | |
| 471 | <section aria-label="Required checks" className="py-1"> | |
| 472 | <p className="px-4 pt-1.5 pb-1 text-xs font-medium text-muted">Required</p> | |
| 473 | {required.map((check) => ( | |
| 474 | <CheckLine | |
| 475 | key={check.name} | |
| 476 | standing={requiredStanding(check)} | |
| 477 | name={ | |
| 478 | <> | |
| 479 | {check.name} | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 480 | <Badge className="ml-1.5 px-1.5 text-[0.625rem] font-normal text-faint">Required</Badge> |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 481 | </> |
| 482 | } | |
| 483 | detail={ | |
| 484 | check.state === "expected" | |
| 485 | ? "Waiting for status to be reported" | |
| 486 | : (check.description ?? (check.state === "pending" ? "running" : undefined)) | |
| 487 | } | |
| 488 | to={onSite(check.targetUrl)} | |
| 489 | /> | |
| 490 | ))} | |
| 491 | </section> | |
| 492 | )} | |
| 493 | ||
| 494 | {statuses.length > 0 && ( | |
| 495 | <section aria-label="All checks" className="py-1"> | |
| 496 | <p className="px-4 pt-1.5 pb-1 text-xs font-medium text-muted">{required.length > 0 ? "All checks" : "Workflows"}</p> | |
| 497 | {statuses.map((status) => { | |
| 498 | const id = runIdOf(status); | |
| 499 | const runPath = id ? `${base}/actions/runs/${id}` : onSite(status.targetUrl); | |
| 500 | const theirs = id ? jobs[id] : undefined; | |
| 501 | if (!theirs || theirs.length === 0) { | |
| 502 | return ( | |
| 503 | <CheckLine | |
| 504 | key={status.context} | |
| 505 | standing={statusStanding(status)} | |
| 506 | name={status.context} | |
| 507 | detail={status.description ?? undefined} | |
| Merge branch 'worktree-agent-aca4790ea223aa235' | 508 | timing={loading && id != null} |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 509 | to={runPath} |
| 510 | /> | |
| 511 | ); | |
| 512 | } | |
| 513 | return theirs.map((job) => ( | |
| 514 | <CheckLine | |
| 515 | key={`${status.context}-${job.id}`} | |
| 516 | standing={jobStanding(job)} | |
| 517 | name={ | |
| 518 | <> | |
| 519 | <span className="text-muted">{status.context} / </span> | |
| 520 | {job.name} | |
| 521 | </> | |
| 522 | } | |
| 523 | detail={job.reason ?? (jobStanding(job) === "failed" ? "failed" : undefined)} | |
| 524 | time={duration(job.startedAt, job.finishedAt) || undefined} | |
| 525 | to={`${runPath}?job=${job.id}`} | |
| 526 | /> | |
| 527 | )); | |
| 528 | })} | |
| 529 | </section> | |
| 530 | )} | |
| 531 | ||
| 532 | {run && ( | |
| 533 | <section aria-label={queueFailed ? "Merge queue" : "Earlier commands"} className="py-1"> | |
| 534 | <p className="flex flex-wrap items-center gap-x-1.5 px-4 pt-1.5 pb-1 text-xs text-faint"> | |
| 535 | <span className="font-medium text-muted">{queueFailed ? "Merge queue" : "Commands from the issue"}</span> | |
| 536 | <span>· on</span> | |
| 537 | <Link to={commitPath(run.headCommit)} className="inline-flex items-center gap-1 font-mono hover:text-fg"> | |
| 538 | <GitCommitHorizontal size={12} /> | |
| 539 | {run.headCommit.slice(0, 7)} | |
| 540 | </Link> | |
| 541 | {run.finishedAt ? ( | |
| 542 | <span> | |
| 543 | · <TimeAgo at={run.finishedAt} /> | |
| 544 | </span> | |
| 545 | ) : ( | |
| 546 | <span> | |
| 547 | · started <TimeAgo at={run.createdAt} /> | |
| 548 | </span> | |
| 549 | )} | |
| 550 | </p> | |
| 551 | {older && !queueFailed && ( | |
| 552 | <p className="mx-4 my-1.5 flex items-start gap-2 rounded-md bg-warn/10 px-2.5 py-1.5 text-xs text-warn"> | |
| 553 | <TriangleAlert size={13} className="mt-0.5 shrink-0" /> | |
| 554 | These ran on an older commit, before the latest push,{" "} | |
| 555 | <span className="font-mono">{pull.headCommit?.slice(0, 7)}</span>. | |
| 556 | </p> | |
| 557 | )} | |
| 558 | {run.error && ( | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 559 | <Card asChild tone="bg" radius="lg" className="mx-4 my-1.5 px-3 py-2 text-sm text-muted"> |
| 560 | <p>{run.error}</p> | |
| 561 | </Card> | |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 562 | )} |
| 563 | {run.results.map((result, index) => ( | |
| 564 | <CommandResult key={`${result.command}-${index}`} result={result} /> | |
| 565 | ))} | |
| 566 | </section> | |
| 567 | )} | |
| 568 | ||
| 569 | {earlier.length > 0 && ( | |
| 570 | <details className="group"> | |
| 571 | <summary className="flex cursor-pointer list-none items-center gap-2 px-4 py-2 text-xs text-muted hover:text-fg"> | |
| 572 | <History size={13} /> | |
| 573 | Earlier records ({earlier.length}) | |
| 574 | <ChevronRight size={13} className="transition-transform group-open:rotate-90" /> | |
| 575 | </summary> | |
| 576 | <ul className="space-y-1 px-4 pb-3 text-xs"> | |
| 577 | {earlier.map((before) => ( | |
| 578 | <li key={before.id} className="flex items-center gap-2 text-muted"> | |
| 579 | <StandingIcon | |
| 580 | size={13} | |
| 581 | standing={ | |
| 582 | before.status === "passed" | |
| 583 | ? "passed" | |
| 584 | : before.status === "queued" || before.status === "running" | |
| 585 | ? "queued" | |
| 586 | : "failed" | |
| 587 | } | |
| 588 | /> | |
| 589 | <Link to={commitPath(before.headCommit)} className="font-mono hover:text-fg"> | |
| 590 | {before.headCommit.slice(0, 7)} | |
| 591 | </Link> | |
| 592 | <span className="min-w-0 truncate"> | |
| 593 | {before.status === "errored" | |
| 594 | ? `could not run${before.error ? `: ${before.error}` : ""}` | |
| 595 | : before.results.length > 0 | |
| 596 | ? `${before.results.filter((result) => result.passed).length} of ${before.results.length} passed` | |
| 597 | : (before.error ?? before.status)} | |
| 598 | </span> | |
| 599 | <span className="ml-auto shrink-0 text-faint"> | |
| 600 | <TimeAgo at={before.createdAt} /> | |
| 601 | </span> | |
| 602 | </li> | |
| 603 | ))} | |
| 604 | </ul> | |
| 605 | </details> | |
| 606 | )} | |
| 607 | </div> | |
| 608 | </div> | |
| 609 | ); | |
| 610 | } | |
| 611 | ||
| 612 | // --- Mergeability --------------------------------------------------------- | |
| 613 | ||
| 614 | /** How to resolve the conflicts by hand, step by step, for a branch or a fork. */ | |
| Merge branch 'worktree-agent-aaf03bdceac799c89' | 615 | export function commandLineSteps( |
| 616 | pull: Pull, | |
| 617 | owner: string, | |
| 618 | repo: string, | |
| 619 | defaultBranch: string, | |
| 620 | files: string[], | |
| 621 | site = "https://g1t.sh", | |
| 622 | ): string[] { | |
| 623 | const upstream = `${site}/${owner}/${repo}.git`; | |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 624 | const add = files.length > 0 && files.length <= 6 ? `git add ${files.join(" ")}` : "git add -A"; |
| 625 | if (pull.branch) { | |
| 626 | return [ | |
| 627 | `git fetch origin`, | |
| 628 | `git checkout ${pull.branch}`, | |
| 629 | `git merge origin/${defaultBranch}`, | |
| 630 | `${add} && git commit --no-edit`, | |
| 631 | `git push origin ${pull.branch}`, | |
| 632 | ]; | |
| 633 | } | |
| 634 | const fork = pull.fork!; | |
| 635 | return [ | |
| Merge branch 'worktree-agent-aaf03bdceac799c89' | 636 | `git clone ${site}/${fork.namespace}/${fork.name}.git && cd ${fork.name}`, |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 637 | `git pull --no-rebase ${upstream} ${defaultBranch}`, |
| 638 | `${add} && git commit --no-edit`, | |
| 639 | `git push`, | |
| 640 | ]; | |
| 641 | } | |
| 642 | ||
| 643 | /** | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 644 | * The branch conflicts with its target: which files, and the ways out: |
| 645 | * the browser (soon), or the command line. | |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 646 | */ |
| 647 | export function ConflictsSection({ | |
| 648 | conflicts, | |
| 649 | pull, | |
| 650 | owner, | |
| 651 | repo, | |
| 652 | defaultBranch, | |
| 653 | changesUrl, | |
| 654 | error, | |
| 655 | }: { | |
| 656 | conflicts: string[]; | |
| 657 | pull: Pull; | |
| 658 | owner: string; | |
| 659 | repo: string; | |
| 660 | defaultBranch: string; | |
| 661 | /** Where the pull request's changes are shown, to link each file. */ | |
| 662 | changesUrl: string; | |
| 663 | error?: string | null; | |
| 664 | }) { | |
| Merge branch 'worktree-agent-aaf03bdceac799c89' | 665 | const { site } = useAddresses(); |
| 666 | const steps = commandLineSteps(pull, owner, repo, defaultBranch, conflicts, site); | |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 667 | return ( |
| 668 | <div className="bg-danger/5"> | |
| 669 | <div className="flex gap-3 px-4 py-3"> | |
| 670 | <CircleAlert size={16} className="mt-0.5 shrink-0 text-danger" /> | |
| 671 | <div className="min-w-0 grow"> | |
| 672 | <p className="text-sm font-medium">This branch has conflicts that must be resolved</p> | |
| 673 | <p className="mt-0.5 text-sm text-muted"> | |
| 674 | Merging <span className="font-mono">{defaultBranch}</span> into it stops on{" "} | |
| 675 | {conflicts.length === 1 ? "one file" : `${conflicts.length} files`}. It cannot merge until they are resolved. | |
| 676 | </p> | |
| 677 | {conflicts.length > 0 && ( | |
| 678 | <> | |
| 679 | <p className="mt-3 text-xs font-medium text-muted">Conflicting files</p> | |
| 680 | <ul className="mt-1.5 space-y-1"> | |
| 681 | {conflicts.map((path) => ( | |
| 682 | <li key={path}> | |
| 683 | <Link | |
| 684 | to={`${changesUrl}#file-${path}`} | |
| 685 | className="font-mono text-xs text-fg hover:text-danger hover:underline" | |
| 686 | > | |
| 687 | {path} | |
| 688 | </Link> | |
| 689 | </li> | |
| 690 | ))} | |
| 691 | </ul> | |
| 692 | </> | |
| 693 | )} | |
| 694 | <div className="mt-3 flex flex-wrap items-center gap-2"> | |
| 695 | <Tooltip> | |
| 696 | <TooltipTrigger asChild> | |
| 697 | {/* A disabled button gets no pointer events; the span carries the tooltip. */} | |
| 698 | <span tabIndex={0} className="inline-flex"> | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 699 | <Button variant="outline" type="button" disabled aria-disabled="true"> |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 700 | Resolve in the browser |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 701 | <Badge className="px-1.5 py-0 text-[0.625rem] font-normal text-faint">Soon</Badge> |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 702 | </Button> |
| 703 | </span> | |
| 704 | </TooltipTrigger> | |
| 705 | <TooltipContent> | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 706 | An editor for conflicts in the browser is coming. Until then, use the command line. |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 707 | </TooltipContent> |
| 708 | </Tooltip> | |
| 709 | </div> | |
| 710 | {error && ( | |
| 711 | <div className="mt-2"> | |
| 712 | <ErrorText>{error}</ErrorText> | |
| 713 | </div> | |
| 714 | )} | |
| 715 | <details className="group mt-3"> | |
| 716 | <summary className="flex cursor-pointer list-none items-center gap-1.5 text-xs text-muted hover:text-fg"> | |
| 717 | <Terminal size={13} /> | |
| 718 | Resolve on the command line | |
| 719 | <ChevronRight size={13} className="transition-transform group-open:rotate-90" /> | |
| 720 | </summary> | |
| 721 | <ol className="mt-2 space-y-2 text-xs text-muted"> | |
| 722 | {steps.map((step, index) => ( | |
| 723 | <li key={step}> | |
| 724 | <p className="mb-1"> | |
| 725 | {index + 1}.{" "} | |
| 726 | {step.startsWith("git add") | |
| 727 | ? "Fix the conflicts in each file, removing the conflict markers, then commit the merge:" | |
| 728 | : step.startsWith("git push") | |
| 729 | ? "Push it. The pull request updates, and its checks run again:" | |
| 730 | : step.includes("merge") || step.includes("pull") | |
| 731 | ? `Merge ${defaultBranch} in. Git stops on the conflicting files:` | |
| 732 | : step.startsWith("git clone") | |
| 733 | ? "Clone the pull request's fork:" | |
| 734 | : step.startsWith("git checkout") | |
| 735 | ? "Check out the pull request's branch:" | |
| 736 | : "Get the latest of both:"} | |
| 737 | </p> | |
| 738 | <CopyLine text={step} prompt /> | |
| 739 | </li> | |
| 740 | ))} | |
| 741 | </ol> | |
| 742 | {pull.fork && ( | |
| 743 | <p className="mt-2 text-xs text-faint"> | |
| 744 | A pull request's fork takes pushes from whoever opened it. | |
| 745 | </p> | |
| 746 | )} | |
| 747 | </details> | |
| 748 | </div> | |
| 749 | </div> | |
| 750 | </div> | |
| 751 | ); | |
| 752 | } | |
| 753 | ||
| 754 | /** Whether the change merges cleanly, when that is not a conflict. */ | |
| 755 | export function MergeabilityRow({ | |
| 756 | mergeable, | |
| 757 | defaultBranch, | |
| 758 | }: { | |
| 759 | mergeable: Mergeable; | |
| 760 | defaultBranch: string; | |
| 761 | }) { | |
| 762 | if (mergeable === "checking") { | |
| 763 | return ( | |
| 764 | <div className="flex gap-3 px-4 py-3 text-sm"> | |
| 765 | <LoaderCircle size={16} className="mt-0.5 shrink-0 animate-spin text-faint" /> | |
| 766 | <div> | |
| 767 | <p className="font-medium">Checking whether this merges cleanly…</p> | |
| 768 | <p className="mt-0.5 text-muted"> | |
| 769 | It and <span className="font-mono">{defaultBranch}</span> changed some of the same files, so g1t is merging | |
| 770 | the two in a sandbox to see. | |
| 771 | </p> | |
| 772 | </div> | |
| 773 | </div> | |
| 774 | ); | |
| 775 | } | |
| 776 | if (mergeable === "clean") { | |
| 777 | return ( | |
| 778 | <div className="flex gap-3 px-4 py-3 text-sm"> | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 779 | <CircleCheck size={16} className="mt-0.5 shrink-0 text-success" /> |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 780 | <p className="font-medium"> |
| 781 | This branch has no conflicts with <span className="font-mono">{defaultBranch}</span> | |
| 782 | </p> | |
| 783 | </div> | |
| 784 | ); | |
| 785 | } | |
| 786 | return null; | |
| 787 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.