Skip to content
1,816 linesCodeBlameRaw

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.

Acceptance checks in sandboxes, line comments and review verdicts1import { env } from "cloudflare:workers";
Issues and pull requests replace intents and attempts2import {
3 Bot,
4 ChevronRight,
5 FileDiff,
Pull requests from branches6 GitBranch,
Issues and pull requests replace intents and attempts7 GitCommitHorizontal,
Acceptance checks in sandboxes, line comments and review verdicts8 CircleCheck,
9 CircleSlash,
Issues and pull requests replace intents and attempts10 GitMerge,
Agents as a team: lifecycle, merge queue, billing and a new shell11 Layers,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge12 Scale,
Agents as a team: lifecycle, merge queue, billing and a new shell13 GitPullRequestArrow,
14 Hand,
15 Loader,
16 Radar,
17 Sparkles,
Issues and pull requests replace intents and attempts18 MessageSquare,
Pull requests say where they are live; the project menu shows what is coming19 ArrowUpRight,
Issues and pull requests replace intents and attempts20 MessagesSquare,
Pull requests say where they are live; the project menu shows what is coming21 Rocket,
Project dependencies: addresses, preview stacks, Affects, and agents who know22 Network,
Issues and pull requests replace intents and attempts23 StickyNote,
24 User,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar25 Users,
Issues and pull requests replace intents and attempts26 Wrench,
27} from "lucide-react";
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge28import { Suspense, useState } from "react";
Merge branch 'worktree-agent-a985c01f43538af01'29import { Await, Form, Link, redirect } from "react-router";
Issues and pull requests replace intents and attempts30
Pull requests say where they are live; the project menu shows what is coming31import {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look32 type Capability,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md33 type Comment,
Pull requests say where they are live; the project menu shows what is coming34 type Comparison,
35 type Deployment,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains36 type Job,
Pull requests say where they are live; the project menu shows what is coming37 type LiveApp,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md38 type Pull,
39 type RepoPath,
Pull requests say where they are live; the project menu shows what is coming40 type SessionEntry,
41 type Viewer,
42 pullComparison,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights43 workOwner,
Pull requests say where they are live; the project menu shows what is coming44} from "@g1t/contracts";
Issues and pull requests replace intents and attempts45
46import type { Route } from "./+types/pull";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit47import { highlightFirstFiles } from "../../lib/highlight.server";
Social cards for every page: og.g1t.sh48import { excerpt, page } from "../../lib/meta";
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent49import { openedBy } from "../../lib/opened-by";
Merge branch 'worktree-agent-aaf03bdceac799c89'50import { cloneUrl, useAddresses } from "../../lib/addresses";
Issues and pull requests replace intents and attempts51import { DiffView } from "../../components/diff-view";
Agents as a team: lifecycle, merge queue, billing and a new shell52import { LifecyclePanel } from "../../components/lifecycle";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains53import { AgentPanel } from "../../components/agents";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar54import { BaseBranch } from "../../components/base-branch";
55import { LabelChip, LabelsBox, MilestoneBox } from "../../components/labels";
Issues and pull requests replace intents and attempts56import { Markdown } from "../../components/markdown";
57import {
58 Avatar,
Agents as a team: lifecycle, merge queue, billing and a new shell59 ButtonLink,
Issues and pull requests replace intents and attempts60 CopyLine,
61 EmptyState,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look62 ComputeNote,
Issues and pull requests replace intents and attempts63 ErrorText,
Merge branch 'worktree-agent-a985c01f43538af01'64 SubmitButton,
Issues and pull requests replace intents and attempts65 Textarea,
66 TimeAgo,
Merge branch 'worktree-agent-a985c01f43538af01'67 usePending,
Issues and pull requests replace intents and attempts68} from "../../components/ui";
Workspace names and icons, and a component kit for every control69import { CheckboxOption } from "../../components/ui/checkbox";
Merge branch 'worktree-agent-ae1299e92e4462012'70import { Hint } from "../../components/ui/hint";
Merge branch 'worktree-agent-aca4790ea223aa235'71import { Loading, SkeletonLine } from "../../components/ui/skeleton";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit72import { TabStrip } from "../../components/ui/tab-strip";
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs73import { WorkflowStatuses } from "../../components/actions";
Fast pages, required checks on the branch, self-hosted runners, honest incidents74import { AddCiPrompt } from "../../components/add-ci";
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md75import { AttemptsBox } from "../../components/attempts";
76import { type Attempt, attemptReview, otherAttempts, sharedPaths, splitOverlaps } from "../../lib/attempts";
Agents as a team: lifecycle, merge queue, billing and a new shell77import {
78 CommentForm,
79 CommentList,
80 IssueIcon,
81 PeoplePicker,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains82 PersonLink,
Agents as a team: lifecycle, merge queue, billing and a new shell83 PullState,
84 TimelineItem,
85 verdicts,
86} from "../../components/work";
Catching up with main takes seconds when the two sides touched different files87import { CatchUpProgress, ChecksSection, ConflictsSection, MergeabilityRow, runIdOf } from "../../components/merge-box";
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge88import { ViolationList } from "../../components/rules";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar89import { PullCodeOwnersPanel, TeamReviewer } from "../../components/codeowners";
Catching up with main takes seconds when the two sides touched different files90import { CATCH_UP_TIMEOUT_MS } from "../../lib/catch-up";
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put91import { notFound } from "../../lib/not-found.server";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look92import { computeNoteFor } from "../../lib/compute.server";
Merge branch 'worktree-agent-aca4790ea223aa235'93import { actions, agents, deployments, identity, inbox, projects, repos, work } from "../../lib/services.server";
Merge checks: statuses and check runs on every commit94import { commitChecksFor } from "../../lib/commit-checks.server";
95import { CommitChecksBadge } from "../../components/commit-checks";
Issues and pull requests replace intents and attempts96import { assertSameOrigin, getViewer, requireUser } from "../../lib/session.server";
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings97import { accessTo, refusal, repoFor } from "../../lib/access.server";
98import { SubscriptionBox } from "../../components/notifications";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending99import { REFRESH_MS, useRefreshWhile } from "../../lib/refresh";
Issues and pull requests replace intents and attempts100
101const EMPTY_COMPARISON: Comparison = { base: null, head: "", files: [], truncated: false };
102const TABS = ["conversation", "session", "changes"] as const;
103type Tab = (typeof TABS)[number];
104
Social cards for every page: og.g1t.sh105export function meta({ loaderData, params, ...args }: Route.MetaArgs) {
106 const pull = loaderData?.pull;
107 const title = pull ? `${pull.title} · Pull request #${pull.number} · ` : "";
108 const state = { draft: "Draft", open: "Open", merged: "Merged", closed: "Closed" }[pull?.status ?? "open"];
109 const body = excerpt(pull?.body);
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent110 const opener = pull ? openedBy(pull) : null;
Social cards for every page: og.g1t.sh111 return page(args, {
112 title: `${title}${params.owner}/${params.repo} · g1t`,
113 description: pull
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent114 ? `${state} pull request #${pull.number} on ${params.owner}/${params.repo} by ${opener!.name}${opener!.requestedBy ? `, requested by ${opener!.requestedBy}` : ""}.${body ? ` ${body}` : ""}`
Social cards for every page: og.g1t.sh115 : null,
116 // The card shows the title and the state.
117 version: pull ? [pull.title, pull.status] : undefined,
118 type: "article",
119 });
Issues and pull requests replace intents and attempts120}
121
122export async function loader({ params, context, request }: Route.LoaderArgs) {
123 const viewer = getViewer(context);
124 const path = { namespace: params.owner, name: params.repo };
125 const number = Number(params.number);
126 const asked = new URL(request.url).searchParams.get("tab");
127 const tab: Tab = TABS.find((name) => name === asked) ?? "conversation";
128
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look129 // Members of the workspace pick reviewers and assignees from its people;
130 // what the viewer may do here goes by their role on the repository.
Agents as a team: lifecycle, merge queue, billing and a new shell131 const member = (viewer?.workspaces ?? []).some(
132 (membership) => membership.slug === params.owner,
133 );
Fast pages, required checks on the branch, self-hosted runners, honest incidents134 // Everything starts at once. Only what the viewer's role decides waits
135 // for the repository, which the layout is looking up in this request too
136 // (lib/access.server.ts), and only what the pull request decides waits
137 // for it.
Project dependencies: addresses, preview stacks, Affects, and agents who know138 const ref = { workspace: params.owner, slug: params.repo };
Fast pages, required checks on the branch, self-hosted runners, honest incidents139 const access = accessTo(context, params);
140 const pullFound = work.getPull(path, number, viewer);
141 const deps = projects.dependencies(params.owner, params.repo, viewer);
142 // Awaited below, unless the pull request is missing first.
143 deps.catch(() => null);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar144 // Its labels and milestone, with the repository's to choose from.
145 const labelsFound = work.listLabels(path, viewer);
146 const milestonesFound = work.listMilestones(path, viewer);
147 labelsFound.catch(() => null);
148 milestonesFound.catch(() => null);
149 const [{ can }, found, repo, settings, agentsEnabled, members, computeNote, session, deployed, subscription, runs, teamList] = await Promise.all([
Fast pages, required checks on the branch, self-hosted runners, honest incidents150 access,
151 pullFound,
Issues and pull requests replace intents and attempts152 repos.get(path, viewer),
Agents as a team: lifecycle, merge queue, billing and a new shell153 work.getSettings(path, viewer),
g1t's agents only for listed workspaces, whatever the state of billing154 env.RUNNER.enabled(viewer, path),
Agents as a team: lifecycle, merge queue, billing and a new shell155 // A member picks reviewers and assignees from the workspace's people.
156 member ? identity.listMembers(params.owner, viewer) : null,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent157 // Before someone asks g1t for something: whether the plan lets it start.
Fast pages, required checks on the branch, self-hosted runners, honest incidents158 access.then(({ can }) => (can.run ? computeNoteFor(params.owner, "agent") : null)),
159 tab === "session" ? work.readSession(path, number, viewer) : null,
160 // Its preview, for people with a role here: beside the rest, not after.
161 access.then(({ insider }) => (insider ? deployments.list(ref, viewer).catch(() => null) : null)),
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings162 // Whether the viewer hears of it, for the sidebar's Notifications.
163 viewer
164 ? repoFor(context, params).then((found) =>
165 found.ok ? inbox.subscription(viewer, { repoId: found.value.id, number }).catch(() => null) : null,
166 )
167 : null,
Merge branch 'worktree-agent-aca4790ea223aa235'168 // Its agent's latest runs, for the Agent panel: with the page, not after it.
169 agents.listRuns(viewer, { repo: path, number, limit: 5 }).catch(() => null),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar170 // A member asks the workspace's teams to review, and mentions them.
171 member ? identity.listTeams(viewer, params.owner).catch(() => null) : null,
Issues and pull requests replace intents and attempts172 ]);
173 if (!found.ok) {
174 // Issues and pull requests share numbers; this one may be an issue.
175 const issue = await work.getIssue(path, number, viewer);
176 if (issue.ok) throw redirect(`/${params.owner}/${params.repo}/issues/${number}`);
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put177 throw notFound("pull");
Issues and pull requests replace intents and attempts178 }
179 const { pull } = found.value;
Pull requests from branches180 const range = pullComparison(pull);
Agents and memory, checks and conflicts, profiles, slug renames, custom domains181 // The jobs of each workflow run on its head, to list checks job by job.
182 // Read as the viewer: a run they cannot see is listed by its status alone.
Fast pages, required checks on the branch, self-hosted runners, honest incidents183 // Streamed: the checks show by status first, then job by job.
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md184 // A closed or merged one lists its head's checks too, to read only.
Agents and memory, checks and conflicts, profiles, slug renames, custom domains185 const runIds = [...new Set((found.value.statuses ?? []).map(runIdOf).filter((id) => id != null))].slice(0, 10);
Fast pages, required checks on the branch, self-hosted runners, honest incidents186 const workflowJobs =
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md187 tab === "conversation" && pull.status !== "draft" && runIds.length > 0
Fast pages, required checks on the branch, self-hosted runners, honest incidents188 ? Promise.all(runIds.map((id) => actions.run(path, viewer, id).catch(() => null))).then((runs) => {
189 const jobs: Record<string, Job[]> = {};
190 for (const run of runs) if (run?.ok) jobs[run.value.run.id] = run.value.jobs;
191 return jobs;
192 })
193 .catch(() => ({}) as Record<string, Job[]>)
194 : Promise.resolve({} as Record<string, Job[]>);
195 // No checks at all on an open pull request: whether that is because the
196 // repository has no workflows, to offer a starter one.
197 const unchecked =
198 tab === "conversation" &&
199 pull.status === "open" &&
200 (found.value.statuses ?? []).length === 0 &&
201 (found.value.requiredChecks ?? []).length === 0;
202 const [comparison, used, noChecks] = await Promise.all([
Usage, Billing settings and prepaid AI credit; fixes from the UX audit203 // The first screens of it highlighted here, so their colours do not pop in.
204 tab === "changes"
205 ? repos
206 .compare(range.repoId, viewer, range.base, range.head, range.baseBranch)
207 .then(async (found) => (found.ok ? { ok: true as const, value: await highlightFirstFiles(found.value) } : found))
208 : null,
Fast pages, required checks on the branch, self-hosted runners, honest incidents209 deps,
210 unchecked
211 ? actions
212 .workflows(path, viewer)
213 .then((found) => found.ok && found.value.length === 0)
214 .catch(() => false)
215 : false,
Issues and pull requests replace intents and attempts216 ]);
Fast pages, required checks on the branch, self-hosted runners, honest incidents217 const affects = used.ok ? used.value.usedBy : [];
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md218 // Every pull request for its issue, to compare: streamed, and read only
219 // when there is more than this one.
220 const attempts =
221 tab === "conversation" && pull.issue != null && (found.value.issue?.pullCount ?? 0) > 1
222 ? attemptsFor(path, viewer, pull, found.value.comments).catch(() => [] as Attempt[])
223 : null;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar224 const [labels, milestones] = await Promise.all([labelsFound, milestonesFound]);
225 const repoDefault = repo.ok ? repo.value.defaultBranch : "main";
226 // The default branch's protection covers pull requests into it only.
227 const protectedBase = (pull.base ?? repoDefault) === repoDefault;
Issues and pull requests replace intents and attempts228 return {
229 ...found.value,
Merge checks: statuses and check runs on every commit230 // Every check on its head, as the commit's own list shows them: streamed.
231 headChecks: commitChecksFor(path, viewer, [pull.headCommit]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar232 labels: labels.ok ? labels.value : [],
233 milestones: milestones.ok ? milestones.value : [],
234 // Labelling and milestones: Triage and up; its author, its labels.
235 canTriage: can.triage,
Merge main (membership, two-factor, GitHub repo roles) into tokens236 // Making labels needs Write; applying them, Triage.
237 canMakeLabels: can.manage_labels,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar238 // The branch it merges into: Write and up.
239 canChangeBase: can.push,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains240 workflowJobs,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md241 attempts,
Issues and pull requests replace intents and attempts242 tab,
243 session: session?.ok ? session.value : [],
244 // An empty comparison if it could not be made.
245 comparison: comparison && (comparison.ok ? comparison.value : EMPTY_COMPARISON),
246 viewer,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look247 // Write and up can merge.
248 canMerge: can.merge,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights249 // Triage and up manage anyone's pull request; its author, their own,
250 // and whoever asked g1t for one, that one.
251 canManage: can.triage || (viewer != null && viewer.id === workOwner(pull).id),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look252 // Telling its agent things, and re-running checks, spend compute: Write and up.
253 canRun: can.run,
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts254 // Maintain and up edit and delete anyone's comments; everyone, their own.
255 canModerate: can.manage_settings,
Agents as a team: lifecycle, merge queue, billing and a new shell256 // A catch-up is pushed as the viewer: a fork takes pushes only from
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights257 // whoever it is for (who asked g1t for it, or its author), a branch
258 // from anyone who can push.
259 canUpdate: pull.fork ? viewer?.id === workOwner(pull).id : can.push,
Agents as a team: lifecycle, merge queue, billing and a new shell260 agentsEnabled,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look261 computeNote,
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings262 subscription,
Merge branch 'worktree-agent-aca4790ea223aa235'263 // As the project's agents.json has them; left out, the panel fetches them.
264 agentRuns: runs?.ok ? { runs: runs.value, member: can.run } : undefined,
Agents as a team: lifecycle, merge queue, billing and a new shell265 members: members?.ok ? members.value.map((person) => person.username) : [],
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar266 // `workspace/slug` and name of each team the viewer can see.
267 teams: teamList?.ok ? teamList.value.map((team) => ({ ref: `${team.workspace}/${team.slug}`, name: team.name })) : [],
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge268 // What the rules of the branch it merges into ask, as they stack; the
269 // default branch's settings where the rules are not known.
270 requireUpToDate: found.value.rules ? found.value.rules.strict : protectedBase && settings.ok && settings.value.requireUpToDate,
271 mergeQueue: found.value.rules ? found.value.rules.merge_queue : protectedBase && settings.ok && settings.value.mergeQueue,
272 requiredApprovals: found.value.rules
273 ? found.value.rules.required_approvals
274 : protectedBase && settings.ok
275 ? settings.value.requiredApprovals
276 : 0,
277 canIgnoreChecks: found.value.rules ? found.value.rules.allow_bypass_on_merge : !settings.ok || settings.value.allowIgnoringChecks,
Fast pages, required checks on the branch, self-hosted runners, honest incidents278 noChecks,
279 // Who may open the pull request that adds CI: anyone who can push.
280 canAddCi: can.push,
281 // Who may choose the required checks.
282 canProtect: can.manage_protection,
Issues and pull requests replace intents and attempts283 defaultBranch: repo.ok ? repo.value.defaultBranch : "main",
Fast pages, required checks on the branch, self-hosted runners, honest incidents284 affects,
285 // Streamed: the conversation shows first, the preview card after.
286 ...deploymentOf(deployed?.ok ? deployed.value : null, number, params.owner, affects, viewer),
Issues and pull requests replace intents and attempts287 };
288}
289
Pull requests say where they are live; the project menu shows what is coming290/**
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md291 * This pull request and the latest few others for its issue, each with its
292 * head's checks and where its review stands, this one first.
293 */
294async function attemptsFor(path: RepoPath, viewer: Viewer, pull: Pull, comments: Comment[]): Promise<Attempt[]> {
295 const found = await work.getIssue(path, pull.issue!, viewer);
296 if (!found.ok) return [];
297 const others = otherAttempts(found.value.pulls, pull.number).flatMap(
298 (number) => found.value.pulls.find((other) => other.number === number) ?? [],
299 );
300 const [details, checks] = await Promise.all([
301 Promise.all(others.map((other) => work.getPull(path, other.number, viewer).catch(() => null))),
302 commitChecksFor(path, viewer, [pull.headCommit, ...others.map((other) => other.headCommit)]),
303 ]);
304 const attempt = (one: Pull, said: Comment[], current: boolean): Attempt => ({
305 number: one.number,
306 title: one.title,
307 status: one.status,
308 supersededBy: one.supersededBy,
309 author: openedBy(one).name,
310 headCommit: one.headCommit,
311 files: one.files,
312 updatedAt: one.updatedAt,
313 current,
314 shared: current ? [] : sharedPaths(one.files, pull.files),
315 checks: (one.headCommit && checks?.[one.headCommit]) || null,
316 review: attemptReview(said, one.reviewers),
317 });
318 return [
319 attempt(pull, comments, true),
320 ...others.map((other, index) => {
321 const detail = details[index];
322 return attempt(other, detail?.ok ? detail.value.comments : [], false);
323 }),
324 ];
325}
326
327/**
Pull requests say where they are live; the project menu shows what is coming328 * Where this pull request is live: its preview (or its latest build, while
329 * one is going or after one failed), and the previews of the projects
330 * that use this one, built against it.
331 */
Fast pages, required checks on the branch, self-hosted runners, honest incidents332function deploymentOf(
Pull requests say where they are live; the project menu shows what is coming333 list: { deployments: Deployment[]; live: LiveApp[] } | null,
334 number: number,
335 owner: string,
336 affects: { slug: string; name: string }[],
337 viewer: Viewer,
Fast pages, required checks on the branch, self-hosted runners, honest incidents338): { preview: LiveApp | null; build: Deployment | null; stacked: Promise<Stacked[]> } {
339 const preview = list?.live.find((app) => app.kind === "preview" && app.number === number) ?? null;
340 const build = list?.deployments.find((d) => d.kind === "preview" && d.number === number) ?? null;
Pull requests say where they are live; the project menu shows what is coming341 const branch = preview?.branch ?? build?.branch ?? null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents342 // Streamed: another lookup per project, shown in the card once known.
343 const stacked: Promise<Stacked[]> = branch
344 ? Promise.all(
345 affects.slice(0, 5).map(async (project) => {
346 const theirs = await deployments.list({ workspace: owner, slug: project.slug }, viewer).catch(() => null);
347 const app = theirs?.ok ? theirs.value.live.find((a) => a.kind === "preview" && a.branch === branch) : undefined;
348 return app ? { name: project.name, slug: project.slug, url: app.url } : null;
349 }),
350 ).then((found) => found.filter((entry) => entry != null))
351 : Promise.resolve([]);
Pull requests say where they are live; the project menu shows what is coming352 return { preview, build, stacked };
353}
354
Fast pages, required checks on the branch, self-hosted runners, honest incidents355/** A project that uses this one, with its preview built against this change. */
356type Stacked = { name: string; slug: string; url: string };
357
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look358const PULL_NEEDS: Record<string, Capability> = {
359 stack: "run",
360 "agent-review": "run",
361 "rerun-workflow": "run",
Fast pages, required checks on the branch, self-hosted runners, honest incidents362 "rerun-failed": "run",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look363 message: "run",
364 merge: "merge",
365 unqueue: "merge",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar366 milestone: "triage",
367 base: "push",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look368};
369
Issues and pull requests replace intents and attempts370export async function action({ request, params, context }: Route.ActionArgs) {
371 assertSameOrigin(request);
372 const user = requireUser(context, request);
373 const form = await request.formData();
374 const path = { namespace: params.owner, name: params.repo };
375 const number = Number(params.number);
376 const action = form.get("action");
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look377 // What each form needs beyond reading; work checks the rest.
378 const needed = typeof action === "string" ? PULL_NEEDS[action] : undefined;
379 const refused = needed ? await refusal(context, params, needed) : null;
380 if (refused) return { error: refused, action: String(action) };
Acceptance checks in sandboxes, line comments and review verdicts381 const verdict = form.get("verdict");
382 const line = Number(form.get("line"));
Agents as a team: lifecycle, merge queue, billing and a new shell383 /** Names ticked in a people picker, plus those typed beside it. */
384 const picked = (field: string) => [
385 ...form.getAll(field).map(String),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar386 // `@acme/backend` is asked as `acme/backend`.
387 ...String(form.get("others") ?? "").split(/[\s,]+/).map((name) => name.replace(/^@/, "")),
Agents as a team: lifecycle, merge queue, billing and a new shell388 ];
Project dependencies: addresses, preview stacks, Affects, and agents who know389 // The projects that use this one, built against this pull request's preview.
390 if (action === "stack") {
391 const built = await deployments.stack(user, { workspace: params.owner, slug: params.repo }, String(form.get("branch") ?? ""));
392 return built.ok
393 ? { action, notice: `Building ${built.value.join(", ")} against this preview. They appear on their Deployments pages.` }
394 : { action, error: built.error.message };
395 }
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent396 // Asking g1t for its review records the request, then starts it.
Agents as a team: lifecycle, merge queue, billing and a new shell397 if (action === "agent-review") {
398 const asked = await work.updatePull(user, path, number, {
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent399 reviewers: [...form.getAll("reviewer").map(String), "g1t"],
Agents as a team: lifecycle, merge queue, billing and a new shell400 });
401 if (!asked.ok) return { error: asked.error.message, action };
402 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar403 // Its labels, milestone and base, from the sidebar's menus and the header.
404 if (action === "labels") {
405 const set = await work.setLabels(user, path, number, form.getAll("label").map(String));
406 return set.ok ? null : { error: set.error.message, action };
407 }
408 if (action === "milestone" || action === "base") {
409 const changed = await work.updatePull(
410 user,
411 path,
412 number,
413 action === "milestone"
414 ? { milestone: Number(form.get("milestone")) || 0 }
415 : { base: String(form.get("base") ?? "") },
416 );
417 return changed.ok ? null : { error: changed.error.message, action };
418 }
Catching up with main takes seconds when the two sides touched different files419 // Catching up: merged and pushed in seconds when the two sides changed
420 // different files; otherwise handed to a sandbox, which takes a minute.
421 if (action === "update") {
422 const caught = await work.catchUpPull(user, path, number);
423 if (!caught.ok) return { action, error: caught.error.message };
424 const update = caught.value;
425 if (update.outcome !== "needs_agent") {
426 return {
427 action,
428 updated: { commit: update.commit, already: update.outcome === "up_to_date", at: Date.now() },
429 };
430 }
431 const started = await env.RUNNER.update(user, path, number);
432 if (!started.ok) return { action, error: started.error.message };
433 return { action, agent: { update, startedAt: Date.now() } };
434 }
Agents and memory, checks and conflicts, profiles, slug renames, custom domains435 // A workflow run's failed jobs, run again. Who may is the actions service's call.
436 if (action === "rerun-workflow") {
437 const rerun = await actions.rerun(user, path, String(form.get("run") ?? ""), true);
438 return rerun.ok ? null : { error: rerun.error.message, action };
439 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents440 // Every failed run on its head, run again: Mission control's quick action.
441 if (action === "rerun-failed") {
442 const found = await work.getPull(path, number, user);
443 if (!found.ok) return { error: found.error.message, action };
444 const failed = [
445 ...new Set(
446 (found.value.statuses ?? [])
447 .filter((status) => status.state === "failure" || status.state === "error")
448 .map(runIdOf)
449 .filter((id) => id != null),
450 ),
451 ];
452 if (failed.length === 0) return { error: "No workflow run failed on its latest commit.", action };
453 const reruns = await Promise.all(failed.map((id) => actions.rerun(user, path, id, true)));
454 const refused = reruns.find((rerun) => !rerun.ok);
455 return refused && !refused.ok ? { error: refused.error.message, action } : null;
456 }
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts457 // A comment's own text, changed or taken away: its author's to do, or a
458 // maintainer's, which work checks.
459 if (action === "edit-comment" || action === "delete-comment") {
460 const id = String(form.get("comment") ?? "");
461 const changed =
462 action === "edit-comment"
463 ? await work.editComment(user, path, id, String(form.get("body") ?? ""))
464 : await work.deleteComment(user, path, id);
465 return changed.ok ? null : { error: changed.error.message, action, comment: id };
466 }
Issues and pull requests replace intents and attempts467 const result =
468 action === "merge"
Acceptance checks in sandboxes, line comments and review verdicts469 ? await work.mergePull(user, path, number, {
470 keepIssueOpen: form.get("keepIssueOpen") === "on",
471 ignoreChecks: form.get("ignoreChecks") === "on",
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge472 bypassRules: form.get("bypassRules") === "on",
Acceptance checks in sandboxes, line comments and review verdicts473 })
Agents as a team: lifecycle, merge queue, billing and a new shell474 : action === "unqueue"
475 ? await work.removeFromQueue(user, path, number)
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request476 : action === "message"
477 ? await work.messageAgent(user, path, number, String(form.get("body") ?? ""))
Issues and pull requests replace intents and attempts478 : action === "close"
479 ? await work.closePull(user, path, number)
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts480 : action === "reopen"
481 ? await work.reopenPull(user, path, number)
482 : action === "draft"
483 ? await work.convertPullToDraft(user, path, number)
Fast pages, required checks on the branch, self-hosted runners, honest incidents484 : action === "agent-review"
Agents as a team: lifecycle, merge queue, billing and a new shell485 ? await env.RUNNER.review(user, path, number)
486 : action === "reviewers"
487 ? await work.updatePull(user, path, number, { reviewers: picked("reviewer") })
488 : action === "assign"
489 ? await work.updatePull(user, path, number, { assignees: picked("assignee") })
Acceptance checks in sandboxes, line comments and review verdicts490 : action === "comment"
491 ? await work.addComment(user, path, number, {
492 body: String(form.get("body") ?? ""),
493 path: String(form.get("path") ?? "") || undefined,
494 line: line > 0 ? line : undefined,
495 verdict:
496 verdict === "approve" || verdict === "request_changes" ? verdict : undefined,
497 })
498 : await work.readyPull(user, path, number, String(form.get("summary") ?? ""));
Issues and pull requests replace intents and attempts499 return result.ok ? null : { error: result.error.message, action };
500}
501
Pull requests: title and tabs across the full width, with counts and the size of the change502/** How many, beside a tab's name, when there are any. */
503function TabCount({ n }: { n: number }) {
504 if (n <= 0) return null;
505 return (
506 <span className="rounded-full bg-raised px-1.5 py-px text-xs tabular-nums text-muted ring-1 ring-line">
507 {n.toLocaleString("en-US")}
508 </span>
509 );
510}
511
Issues and pull requests replace intents and attempts512function TabLink({
513 to,
514 active,
515 children,
516}: {
517 to: string;
518 active: boolean;
519 children: React.ReactNode;
520}) {
521 return (
522 <Link
523 to={to}
524 preventScrollReset
Usage, Billing settings and prepaid AI credit; fixes from the UX audit525 aria-current={active ? "page" : undefined}
Issues and pull requests replace intents and attempts526 className={
Usage, Billing settings and prepaid AI credit; fixes from the UX audit527 "-mb-px flex items-center gap-2 border-b-2 px-3 pb-2.5 text-sm whitespace-nowrap transition-colors " +
Issues and pull requests replace intents and attempts528 (active
529 ? "border-accent font-medium text-fg"
530 : "border-transparent text-muted hover:text-fg")
531 }
532 >
533 {children}
534 </Link>
535 );
536}
537
538/** One step of the session, on the timeline's rail. */
539function Entry({ entry, agent }: { entry: SessionEntry; agent: string }) {
540 const isTool = entry.kind === "tool_call" || entry.kind === "tool_result";
541 const Icon =
542 entry.kind === "prompt"
543 ? User
544 : entry.kind === "note"
545 ? StickyNote
546 : isTool
547 ? Wrench
548 : Bot;
549 return (
550 <li className="relative pl-10">
551 <span
552 className={`absolute top-0.5 left-0 flex size-7 items-center justify-center rounded-full border bg-bg ${
553 entry.kind === "prompt"
554 ? "border-accent/50 text-accent"
555 : "border-line text-faint"
556 }`}
557 >
558 <Icon size={14} />
559 </span>
560 {isTool ? (
561 <details className="group rounded-lg border border-line bg-surface">
562 <summary className="flex cursor-pointer list-none items-center gap-2 px-3 py-1.5 text-sm">
563 <ChevronRight
564 size={14}
565 className="text-faint transition-transform group-open:rotate-90"
566 />
567 <span className="font-mono text-xs text-accent">
568 {entry.tool ?? "tool"}
569 </span>
570 <span className="truncate font-mono text-xs text-muted">
571 {entry.kind === "tool_result" ? "→ " : ""}
572 {entry.text.split("\n")[0]}
573 </span>
574 </summary>
575 <pre className="overflow-x-auto border-t border-line p-3 font-mono text-xs whitespace-pre-wrap text-muted">
Fix a hydration mismatch in output that starts with a blank line576 <code>{entry.text}</code>
Issues and pull requests replace intents and attempts577 </pre>
578 </details>
579 ) : (
580 <div>
581 <p className="text-xs font-medium text-faint">
582 {entry.kind === "prompt"
583 ? "Prompt"
584 : entry.kind === "note"
585 ? "Note"
586 : agent}
587 </p>
588 {entry.kind === "message" ? (
589 <div className="mt-1">
590 <Markdown source={entry.text} />
591 </div>
Agents as a team: lifecycle, merge queue, billing and a new shell592 ) : entry.kind === "prompt" ? (
593 <div className="mt-1.5 max-h-[32rem] overflow-y-auto rounded-xl border border-line bg-surface p-4">
594 <Markdown source={entry.text} />
595 </div>
Issues and pull requests replace intents and attempts596 ) : (
Agents as a team: lifecycle, merge queue, billing and a new shell597 <p className="mt-1 text-[0.9375rem] leading-relaxed wrap-break-word whitespace-pre-wrap">
Issues and pull requests replace intents and attempts598 {entry.text}
599 </p>
600 )}
601 </div>
602 )}
603 {entry.commit && (
604 <p className="mt-1.5 flex items-center gap-1 font-mono text-xs text-faint">
605 <GitCommitHorizontal size={12} />
606 {entry.commit.slice(0, 7)}
607 </p>
608 )}
609 </li>
610 );
611}
612
Agents as a team: lifecycle, merge queue, billing and a new shell613/** The box at the foot of the conversation saying what stands before a merge. */
614function StatusBox({ children }: { children: React.ReactNode }) {
615 return (
616 <div className="flex gap-3">
617 <span className="hidden w-8 shrink-0 sm:block" />
618 <section className="min-w-0 grow divide-y divide-line overflow-hidden rounded-xl border border-line-strong bg-surface">
619 {children}
620 </section>
621 </div>
622 );
623}
624
625function StatusRow({
626 icon,
627 title,
628 children,
629}: {
630 icon: React.ReactNode;
631 title: string;
632 children?: React.ReactNode;
633}) {
634 return (
635 <div className="flex gap-3 px-4 py-3 text-sm">
636 <span className="mt-0.5 shrink-0">{icon}</span>
637 <div className="min-w-0">
638 <p className="font-medium">{title}</p>
639 {children && <div className="mt-0.5 text-muted">{children}</div>}
640 </div>
641 </div>
642 );
643}
644
Issues and pull requests replace intents and attempts645export default function PullPage({ loaderData, actionData, params }: Route.ComponentProps) {
646 const {
647 pull,
648 issue,
649 comments,
Acceptance checks in sandboxes, line comments and review verdicts650 checks,
Agents as a team: lifecycle, merge queue, billing and a new shell651 overlaps,
652 behind,
653 reviewPending,
654 lifecycle,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request655 messages,
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs656 statuses = [],
Fast pages, required checks on the branch, self-hosted runners, honest incidents657 requiredChecks = [],
658 noChecks,
659 canAddCi,
660 canProtect,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains661 mergeable = "unknown",
662 conflicts = [],
663 earlierChecks = [],
664 workflowJobs,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md665 attempts,
Project dependencies: addresses, preview stacks, Affects, and agents who know666 affects,
667 preview,
Pull requests say where they are live; the project menu shows what is coming668 build,
669 stacked,
Agents as a team: lifecycle, merge queue, billing and a new shell670 landing,
671 stalled,
672 requireUpToDate,
673 mergeQueue,
674 requiredApprovals,
675 canIgnoreChecks,
676 canUpdate,
677 agentsEnabled,
Merge branch 'worktree-agent-aca4790ea223aa235'678 agentRuns,
Agents as a team: lifecycle, merge queue, billing and a new shell679 members,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar680 teams,
681 codeOwners,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge682 rules,
Issues and pull requests replace intents and attempts683 tab,
684 session,
685 comparison,
686 viewer,
687 canMerge,
688 canManage,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look689 canRun,
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts690 canModerate,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar691 defaultBranch: repoDefault,
Issues and pull requests replace intents and attempts692 } = loaderData;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar693 // The branch it merges into: what every line below names.
694 const defaultBranch = pull.base ?? repoDefault;
Issues and pull requests replace intents and attempts695 const base = `/${params.owner}/${params.repo}`;
696 const here = `${base}/pull/${pull.number}`;
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent697 // g1t, on a pull request it made: the person who asked for it is a line below.
698 const opener = openedBy(pull);
Merge branch 'worktree-agent-aca4790ea223aa235'699 const checksSection = (jobs: Record<string, Job[]>, loading = false) => (
Fast pages, required checks on the branch, self-hosted runners, honest incidents700 <ChecksSection
701 run={checks}
702 required={requiredChecks}
703 statuses={statuses}
704 jobs={jobs}
705 pull={pull}
706 base={base}
707 earlier={earlierChecks}
708 canRerunWorkflows={canMerge}
709 settingsUrl={canProtect ? `${base}/settings/branches` : null}
710 error={actionData?.action === "rerun-failed" || actionData?.action === "rerun-workflow" ? actionData.error : null}
Merge branch 'worktree-agent-aca4790ea223aa235'711 loading={loading}
Fast pages, required checks on the branch, self-hosted runners, honest incidents712 />
713 );
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md714 // A closed or merged pull request's checks, as they ended: to read, with
715 // nothing to re-run and nothing that holds a merge.
716 const settledChecks = (jobs: Record<string, Job[]>, loading = false) => (
717 <ChecksSection
718 run={null}
719 required={[]}
720 statuses={statuses}
721 jobs={jobs}
722 pull={pull}
723 base={base}
724 earlier={[]}
725 canRerunWorkflows={false}
726 settled
727 loading={loading}
728 />
729 );
Merge branch 'worktree-agent-aaf03bdceac799c89'730 const addresses = useAddresses();
Pull requests from branches731 const remote = pull.fork
Merge branch 'worktree-agent-aaf03bdceac799c89'732 ? cloneUrl(addresses, `${pull.fork.namespace}/${pull.fork.name}`)
733 : cloneUrl(addresses, `${params.owner}/${params.repo}`);
Issues and pull requests replace intents and attempts734 const active = pull.status === "draft" || pull.status === "open";
735
Acceptance checks in sandboxes, line comments and review verdicts736 // Follow an agent at work, or checks in progress, without a manual reload.
Issues and pull requests replace intents and attempts737 const working = pull.status === "draft";
Fast pages, required checks on the branch, self-hosted runners, honest incidents738 const checking = statuses.some((status) => status.state === "pending");
Acceptance checks in sandboxes, line comments and review verdicts739 const reviews = verdicts(comments);
Fast pages, required checks on the branch, self-hosted runners, honest incidents740 // What stands between this pull request and a merge, if anything: a
741 // required check that has not passed, or the merge queue taking it out.
742 const unchecked = requiredChecks.some((check) => check.state !== "success") || checks?.status === "failed";
743 const requiredFailed = requiredChecks.some((check) => check.state === "failure") || checks?.status === "failed";
Agents as a team: lifecycle, merge queue, billing and a new shell744 // Pull requests for other issues changing the same files will conflict;
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md745 // ones for the same issue are alternatives, and expected to: those are
746 // compared under their own heading.
747 const { collisions } = splitOverlaps(overlaps, pull.issue);
Agents as a team: lifecycle, merge queue, billing and a new shell748 const review = {
749 changesUrl: here + "?tab=changes",
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights750 // Nobody reviews their own pull request, nor one g1t made for them.
751 canJudge: active && viewer != null && viewer.id !== workOwner(pull).id,
Agents as a team: lifecycle, merge queue, billing and a new shell752 };
753 // Everyone whose review was asked for, then anyone who reviewed unasked.
754 const reviewerNames = [
755 ...new Set([...pull.reviewers, ...reviews.map(({ reviewer }) => reviewer)]),
756 ];
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar757 const teamReviewers = pull.teamReviewers ?? [];
758 // Teams to ask: the workspace's, and any asked already.
759 const teamChoices = [
760 ...teams,
761 ...teamReviewers.filter((ref) => !teams.some((team) => team.ref === ref)).map((ref) => ({ ref, name: ref })),
762 ];
763 // Branch protection wants code owners' approval it does not have yet.
764 const ownersMissing = codeOwners?.required && codeOwners.missing ? codeOwners.missing : null;
Catching up with main takes seconds when the two sides touched different files765 // A catch-up: the click shows at once; the answer says whether it is
766 // done already or a sandbox is on it.
Merge branch 'worktree-agent-a985c01f43538af01'767 const catchUpPending = usePending({ action: "update" });
Catching up with main takes seconds when the two sides touched different files768 const catchUp = actionData?.action === "update" ? actionData : null;
769 const caughtUp = catchUp && "updated" in catchUp ? catchUp.updated : null;
770 const agentCatchUp = catchUp && "agent" in catchUp ? catchUp.agent : null;
771 // Pushed already: followed until the pull request's head is the merge,
772 // so its checks show starting again, for a minute at most.
773 const settling =
774 caughtUp != null && !caughtUp.already && pull.headCommit !== caughtUp.commit && Date.now() - caughtUp.at < 60_000;
775 // Followed until it is pushed, or for so long, never longer.
776 const catchingUp =
777 agentCatchUp != null && behind && Date.now() - agentCatchUp.startedAt < CATCH_UP_TIMEOUT_MS + REFRESH_MS;
Agents as a team: lifecycle, merge queue, billing and a new shell778 // g1t is taking a step of its own accord, so the page will change.
779 const moving =
780 lifecycle != null && lifecycle.stage !== "ready" && lifecycle.stage !== "needs_you";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains781 // Whether it merges cleanly is being worked out, so the box will change.
782 const probing = active && mergeable === "checking";
783 const conflicting = active && mergeable === "conflicting";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending784 useRefreshWhile(working || checking || reviewPending || catchingUp || settling || moving || landing || probing);
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge785 // The rules of its base it does not meet: checks show in their own
786 // section, so the rest stop the button here. Someone a ruleset lets
787 // bypass it may tick a box and merge past them.
788 const [bypassing, setBypassing] = useState(false);
789 const rulesUnmet = (rules?.unmet ?? []).filter((violation) => violation.rule !== "required_status_checks");
790 const rulesBypassable = rules?.bypassable ?? [];
Agents and memory, checks and conflicts, profiles, slug renames, custom domains791 // Why the merge button cannot be pressed, if it cannot.
792 const mergeBlocked = conflicting
793 ? "Resolve the conflicts first."
794 : probing
795 ? "Waiting to find out whether it merges cleanly."
796 : behind && requireUpToDate
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge797 ? `The rules for ${defaultBranch} require it to be up to date first.`
Fast pages, required checks on the branch, self-hosted runners, honest incidents798 : unchecked && !canIgnoreChecks
799 ? `The checks ${defaultBranch} requires have to pass first.`
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge800 : rulesUnmet.length > 0
801 ? rulesUnmet[0]!.message
802 : rulesBypassable.length > 0 && !bypassing
803 ? `Rules for ${defaultBranch} are not met. You may bypass them.`
804 : ownersMissing
805 ? "Code owners have to approve first."
806 : null;
Issues and pull requests replace intents and attempts807
808 return (
Agents as a team: lifecycle, merge queue, billing and a new shell809 // The changes get the whole width; people and settings are a tab away.
Pull requests: title and tabs across the full width, with counts and the size of the change810 <div>
811 {/* The title and the tabs run the full width; what is happening on
812 the pull request sits at the top of Conversation. */}
813 <header>
Issues and pull requests replace intents and attempts814 <h2 className="text-2xl font-semibold tracking-tight text-balance">
815 {pull.title} <span className="font-normal text-faint">#{pull.number}</span>
816 </h2>
817 <div className="mt-3 flex flex-wrap items-center gap-x-3 gap-y-2 text-sm text-muted">
818 <PullState status={pull.status} />
819 <span className="flex items-center gap-2">
Agents as a team: lifecycle, merge queue, billing and a new shell820 <Avatar
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent821 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Agents as a team: lifecycle, merge queue, billing and a new shell822 size={18}
823 />
Issues and pull requests replace intents and attempts824 <span>
Agents and memory, checks and conflicts, profiles, slug renames, custom domains825 <PersonLink
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent826 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Agents and memory, checks and conflicts, profiles, slug renames, custom domains827 className="font-medium text-fg hover:underline"
828 />{" "}
Pull requests from branches829 {pull.status === "merged" ? "merged" : "wants to merge"}
830 {pull.branch && (
831 <>
832 {" "}
833 <span className="font-mono text-fg">{pull.branch}</span>
834 </>
835 )}{" "}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar836 into{" "}
837 <BaseBranch
838 base={defaultBranch}
839 head={pull.fork ? null : pull.branch}
840 canChange={loaderData.canChangeBase && active}
841 branchesUrl={`${base}/branches.json`}
842 />
Issues and pull requests replace intents and attempts843 </span>
844 </span>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar845 {(pull.labels ?? []).map((name) => (
846 <LabelChip key={name} name={name} color={loaderData.labels.find((label) => label.name === name)?.color} />
847 ))}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent848 {opener.requestedBy && (
849 <span className="text-xs">
850 requested by{" "}
851 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
852 </span>
853 )}
854 {/* A pull request from a branch was made by its author, not an agent;
855 one g1t made already says so. */}
856 {!pull.branch && !opener.requestedBy && (
Pull requests from branches857 <span className="flex items-center gap-1.5 font-mono text-xs">
858 <Bot size={14} />
859 {pull.agent}
860 {pull.runtime === "hosted" && <span className="text-faint">on g1t</span>}
861 </span>
862 )}
Issues and pull requests replace intents and attempts863 </div>
864
Acceptance checks in sandboxes, line comments and review verdicts865 {reviews.length > 0 && (
866 <p className="mt-3 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm">
867 {reviews.map(({ reviewer, verdict }) => (
868 <span
869 key={reviewer}
870 className={`flex items-center gap-1.5 ${
Merge branch 'worktree-agent-ad7c6d88d93adc817'871 verdict === "approve" ? "text-success" : "text-danger"
Acceptance checks in sandboxes, line comments and review verdicts872 }`}
873 >
874 {verdict === "approve" ? <CircleCheck size={15} /> : <CircleSlash size={15} />}
875 {verdict === "approve" ? "Approved by" : "Changes requested by"}{" "}
876 <span className="font-medium">{reviewer}</span>
877 </span>
878 ))}
879 </p>
880 )}
881
Usage, Billing settings and prepaid AI credit; fixes from the UX audit882 <TabStrip label="Pull request" className="mt-6 items-end gap-1 border-b border-line">
Issues and pull requests replace intents and attempts883 <TabLink to={here} active={tab === "conversation"}>
884 <MessageSquare size={15} />
885 Conversation
Pull requests: title and tabs across the full width, with counts and the size of the change886 <TabCount n={comments.length} />
Issues and pull requests replace intents and attempts887 </TabLink>
888 <TabLink to={here + "?tab=session"} active={tab === "session"}>
889 <MessagesSquare size={15} />
890 Session
891 </TabLink>
892 <TabLink to={here + "?tab=changes"} active={tab === "changes"}>
893 <FileDiff size={15} />
Pull requests: title and tabs across the full width, with counts and the size of the change894 Files changed
895 <TabCount n={pull.files.length} />
Issues and pull requests replace intents and attempts896 </TabLink>
Pull requests: title and tabs across the full width, with counts and the size of the change897 {pull.files.length > 0 && (
898 <span className="ml-auto hidden pb-2.5 font-mono text-xs sm:inline">
Merge branch 'worktree-agent-ad7c6d88d93adc817'899 <span className="text-success">+{pull.files.reduce((sum, file) => sum + file.additions, 0).toLocaleString("en-US")}</span>{" "}
Pull requests: title and tabs across the full width, with counts and the size of the change900 <span className="text-danger">−{pull.files.reduce((sum, file) => sum + file.deletions, 0).toLocaleString("en-US")}</span>
901 </span>
902 )}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit903 </TabStrip>
Pull requests: title and tabs across the full width, with counts and the size of the change904 </header>
905 <div className={`mt-6 grid gap-8 ${tab === "changes" ? "" : "lg:grid-cols-[1fr_19rem]"}`}>
906 <div className="min-w-0">
907 {/* What is happening on it now: its stage, its agent, what people told
908 the agent, and what else touches the same files. On the conversation,
909 so the tabs sit right under the title. */}
910 {tab === "conversation" && (
911 <div>
912 {issue && (
913 <Link
914 to={`${base}/issues/${issue.number}`}
915 className="mt-4 flex items-center gap-2.5 rounded-xl border border-line bg-surface px-4 py-2.5 text-sm transition-colors hover:border-line-strong"
916 >
917 <IssueIcon issue={issue} />
918 <span className="text-muted">
919 {pull.status === "merged" && issue.resolvedBy === pull.number
920 ? "Resolved"
921 : "For issue"}
922 </span>
923 <span className="truncate font-medium">{issue.title}</span>
924 <span className="text-faint">#{issue.number}</span>
925 {issue.pullCount > 1 && (
926 <span className="ml-auto shrink-0 text-xs text-faint">
927 1 of {issue.pullCount} pull requests
Pull requests show their tabs right under the title; agent panels move into Conversation928 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change929 )}
930 </Link>
931 )}
932 {pull.status === "merged" && (
933 <p className="mt-4 flex items-center gap-2.5 rounded-xl border border-merged/40 bg-merged/5 px-4 py-2.5 text-sm">
934 <GitMerge size={16} className="shrink-0 text-merged" />
Agents as a team: lifecycle, merge queue, billing and a new shell935 <span>
Pull requests: title and tabs across the full width, with counts and the size of the change936 Merged into <span className="font-mono">{defaultBranch}</span> by{" "}
937 <span className="font-medium">{pull.mergedBy}</span>{" "}
938 {/* Not an account: the repository's settings said to merge it. */}
939 {pull.mergedBy === "g1t" && "automatically, once it was ready, "}
940 {pull.mergedAt && <TimeAgo at={pull.mergedAt} />}
941 {pull.headCommit && (
942 <span className="font-mono text-muted"> · {pull.headCommit.slice(0, 7)}</span>
Agents as a team: lifecycle, merge queue, billing and a new shell943 )}
944 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change945 </p>
946 )}
947 {pull.supersededBy != null && (
948 <p className="mt-4 rounded-xl border border-line bg-surface px-4 py-2.5 text-sm text-muted">
949 Closed because{" "}
950 <Link
951 to={`${base}/pull/${pull.supersededBy}`}
952 className="font-medium text-fg hover:underline"
953 >
954 #{pull.supersededBy}
955 </Link>{" "}
956 was merged for this issue instead.
957 </p>
958 )}
959 {lifecycle && <LifecyclePanel lifecycle={lifecycle} />}
960 {/* The agent on it: who, doing what this minute, for how long, at what cost. */}
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step961 <AgentPanel
962 owner={params.owner}
963 repo={params.repo}
964 number={pull.number}
965 stage={lifecycle?.stage ?? null}
966 confidence={pull.confidence ?? null}
Merge branch 'worktree-agent-aca4790ea223aa235'967 runs={agentRuns}
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step968 />
Pull requests: title and tabs across the full width, with counts and the size of the change969
970 {/* Steering: while its agent works, people can tell it things. */}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look971 {canRun &&
Pull requests: title and tabs across the full width, with counts and the size of the change972 pull.runtime === "hosted" &&
973 (working || ["working", "revising", "catching_up", "answering"].includes(lifecycle?.stage ?? "")) && (
Merge branch 'worktree-agent-a985c01f43538af01'974 // Keyed by the messages so the box empties once one shows below.
Merge branch 'worktree-agent-ad7c6d88d93adc817'975 <Form method="post" className="mt-4 rounded-2xl bg-surface p-4 ring-1 ring-accent/30" key={messages.length}>
Pull requests: title and tabs across the full width, with counts and the size of the change976 <p className="flex items-center gap-2 text-sm font-medium">
Merge branch 'worktree-agent-ad7c6d88d93adc817'977 <Sparkles size={15} className="text-accent" />
Pull requests: title and tabs across the full width, with counts and the size of the change978 Message the agent
979 </p>
980 <p className="mt-1 text-xs text-muted">
981 A correction, a hint, a change of plan. It reads it at its next step, without
982 starting over.
983 </p>
984 <div className="mt-3 flex gap-2">
985 <input type="hidden" name="action" value="message" />
986 <input
987 name="body"
988 required
989 autoComplete="off"
990 data-1p-ignore
991 placeholder="Keep the old flag working too…"
Merge branch 'worktree-agent-ad7c6d88d93adc817'992 className="h-9 min-w-0 grow rounded-md bg-bg px-3 text-sm ring-1 ring-line outline-none placeholder:text-faint focus:ring-accent/60"
Agents as a team: lifecycle, merge queue, billing and a new shell993 />
Merge branch 'worktree-agent-a985c01f43538af01'994 <SubmitButton match={{ action: "message" }} pending="Sending…">
995 Send
996 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change997 </div>
998 </Form>
Agents as a team: lifecycle, merge queue, billing and a new shell999 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1000 {messages.length > 0 && (
1001 <ul className="mt-3 space-y-1.5">
1002 {messages.map((message) => (
1003 <li key={message.id} className="flex items-start gap-2 text-sm">
1004 <Avatar name={message.author} size={18} />
1005 <span className="min-w-0 grow">
1006 <span className="font-medium">
1007 {message.fromNumber != null ? `The agent on #${message.fromNumber}` : message.author}
1008 </span>{" "}
1009 <span className="text-muted">
1010 {message.kind === "question"
1011 ? "asked:"
1012 : message.kind === "handoff"
1013 ? "handed over:"
1014 : message.kind === "answer"
1015 ? "answered:"
1016 : "to the agent:"}
1017 </span>{" "}
1018 {message.body}
1019 {message.answer && (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1020 <span className="mt-1 block border-l-2 border-accent/40 pl-2 text-muted">
Pull requests: title and tabs across the full width, with counts and the size of the change1021 {message.declined ? "Declined: " : "Answer: "}
1022 {message.answer}
1023 </span>
1024 )}
1025 </span>
Merge branch 'worktree-agent-ad7c6d88d93adc817'1026 <span className={`shrink-0 text-xs ${message.deliveredAt ? "text-success" : "text-faint"}`}>
Pull requests: title and tabs across the full width, with counts and the size of the change1027 {message.deliveredAt ? "read by the agent" : "waiting for its next step"}
1028 </span>
1029 </li>
1030 ))}
1031 </ul>
1032 )}
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md1033 {attempts && pull.issue != null && <AttemptsBox attempts={attempts} issue={pull.issue} base={base} />}
Pull requests: title and tabs across the full width, with counts and the size of the change1034 {collisions.length > 0 && active && (
1035 <div className="mt-4 rounded-xl border border-line bg-surface px-4 py-3 text-sm">
1036 <p className="flex items-center gap-2.5 font-medium">
1037 <Radar size={16} className="shrink-0 text-info" />
1038 Other work is changing the same files
1039 </p>
1040 <ul className="mt-2 space-y-1.5">
1041 {collisions.map((other) => (
1042 <li key={other.number} className="flex flex-wrap items-baseline gap-x-2 text-muted">
1043 <Link
1044 to={`${base}/pull/${other.number}`}
1045 className="font-medium text-fg hover:underline"
1046 >
1047 {other.title} <span className="font-normal text-faint">#{other.number}</span>
1048 </Link>
1049 <span className="font-mono text-xs">{other.paths.join(", ")}</span>
1050 </li>
1051 ))}
1052 </ul>
1053 <p className="mt-2 text-xs text-faint">
1054 Whichever merges second will have to catch up, and may conflict.
1055 </p>
1056 </div>
1057 )}
1058 </div>
1059 )}
1060 <div className="mt-5">
1061 {comparison ? (
1062 <DiffView
1063 comparison={comparison}
1064 review={{
1065 comments: comments.filter((comment) => comment.path),
1066 canComment: Boolean(viewer),
1067 }}
1068 />
1069 ) : tab === "session" ? (
1070 session.length === 0 ? (
1071 <EmptyState title="Nothing recorded yet">
1072 The agent's prompts, reasoning and tool calls appear here as it
1073 works.
1074 </EmptyState>
1075 ) : (
1076 <ol className="relative space-y-5 before:absolute before:top-2 before:bottom-2 before:left-3.25 before:w-px before:bg-line">
1077 {session.map((entry) => (
1078 <Entry key={entry.seq} entry={entry} agent={pull.agent} />
1079 ))}
1080 </ol>
1081 )
1082 ) : (
1083 <div className="space-y-4">
1084 <TimelineItem
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1085 author={opener.name}
Pull requests: title and tabs across the full width, with counts and the size of the change1086 at={pull.createdAt}
1087 action={
1088 <span>
1089 opened this pull request
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1090 {opener.requestedBy ? (
Pull requests: title and tabs across the full width, with counts and the size of the change1091 <>
1092 {" "}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1093 for{" "}
1094 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
1095 </>
1096 ) : !pull.branch && (
1097 <>
1098 {" "}
Pull requests: title and tabs across the full width, with counts and the size of the change1099 with <span className="font-mono text-xs">{pull.agent}</span>
1100 </>
1101 )}
1102 </span>
1103 }
1104 >
1105 {pull.body ? (
1106 <Markdown source={pull.body} repo={{ namespace: params.owner, name: params.repo }} />
1107 ) : (
1108 <p className="text-sm text-muted">
1109 {working
1110 ? "No description yet. It is written when the pull request is marked ready for review."
1111 : "No description."}
1112 </p>
1113 )}
1114 {canManage && pull.status === "open" && (
1115 <details className="mt-3 border-t border-line pt-3 text-sm">
1116 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1117 Edit description
1118 </summary>
1119 <Form method="post" className="mt-3 space-y-2">
Merge branch 'worktree-agent-a985c01f43538af01'1120 <input type="hidden" name="action" value="describe" />
Pull requests: title and tabs across the full width, with counts and the size of the change1121 <Textarea
1122 name="summary"
1123 rows={6}
1124 placeholder="What changed and why"
1125 defaultValue={pull.body ?? ""}
1126 />
Merge branch 'worktree-agent-a985c01f43538af01'1127 <SubmitButton match={{ action: "describe" }} pending="Saving…">
1128 Save
1129 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1130 </Form>
1131 </details>
1132 )}
1133 </TimelineItem>
Agents as a team: lifecycle, merge queue, billing and a new shell1134
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1135 <CommentList
1136 comments={comments}
1137 review={review}
1138 base={base}
1139 viewerId={viewer?.id ?? null}
1140 canModerate={canModerate}
1141 failed={actionData && "comment" in actionData ? { comment: String(actionData.comment), error: actionData.error } : null}
1142 />
Agents as a team: lifecycle, merge queue, billing and a new shell1143
Merge branch 'worktree-agent-aca4790ea223aa235'1144 {(preview || build) && <DeploymentCard preview={preview} build={build} stacked={stacked} stacking={affects.length > 0} base={base} />}
Pull requests say where they are live; the project menu shows what is coming1145
Pull requests: title and tabs across the full width, with counts and the size of the change1146 {pull.status === "draft" && (
1147 <StatusBox>
1148 <StatusRow icon={<Loader size={16} className="text-faint" />} title="This is a draft">
1149 It is still being worked on. It can be reviewed and merged once it is
1150 marked ready.
1151 </StatusRow>
1152 {canManage && (
1153 <Form method="post" className="space-y-2 px-4 py-3">
Merge branch 'worktree-agent-a985c01f43538af01'1154 <input type="hidden" name="action" value="ready" />
Pull requests: title and tabs across the full width, with counts and the size of the change1155 <Textarea name="summary" rows={3} placeholder="What changed and why" />
Merge branch 'worktree-agent-a985c01f43538af01'1156 <SubmitButton match={{ action: "ready" }} pending="Marking ready…">
1157 Mark ready for review
1158 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1159 </Form>
1160 )}
1161 </StatusBox>
1162 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1163
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1164 {codeOwners && active && (
1165 <div className="flex gap-3">
1166 <span className="hidden w-8 shrink-0 sm:block" />
1167 <div className="min-w-0 grow">
1168 <PullCodeOwnersPanel
1169 owners={codeOwners}
1170 base={base}
1171 branch={defaultBranch}
1172 errorsHref={canProtect ? `${base}/settings/branches#codeowners` : `${base}/blob/${encodeURIComponent(defaultBranch)}/${codeOwners.path}`}
1173 />
1174 </div>
1175 </div>
1176 )}
1177
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md1178 {(pull.status === "merged" || pull.status === "closed") && statuses.length > 0 && (
1179 <StatusBox>
1180 <Suspense fallback={settledChecks({}, true)}>
1181 <Await resolve={workflowJobs}>{(jobs) => settledChecks(jobs)}</Await>
1182 </Suspense>
1183 </StatusBox>
1184 )}
1185
Pull requests: title and tabs across the full width, with counts and the size of the change1186 {pull.status === "open" && (
1187 <StatusBox>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1188 {/* Checks by status at once; job by job when the runs are read. */}
Merge branch 'worktree-agent-aca4790ea223aa235'1189 <Suspense fallback={checksSection({}, true)}>
1190 <Await resolve={workflowJobs}>{(jobs) => checksSection(jobs)}</Await>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1191 </Suspense>
1192 {noChecks && <AddCiPrompt owner={params.owner} repo={params.repo} canAdd={canAddCi} compact />}
Agents as a team: lifecycle, merge queue, billing and a new shell1193 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1194 icon={
1195 reviews.some(({ verdict }) => verdict === "request_changes") ? (
1196 <CircleSlash size={16} className="text-danger" />
1197 ) : reviews.length > 0 ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1198 <CircleCheck size={16} className="text-success" />
Pull requests: title and tabs across the full width, with counts and the size of the change1199 ) : (
1200 <MessageSquare size={16} className="text-faint" />
1201 )
1202 }
1203 title={
1204 reviews.length === 0
1205 ? "No reviews yet"
1206 : reviews
1207 .map(
1208 ({ reviewer, verdict }) =>
1209 `${verdict === "approve" ? "Approved by" : "Changes requested by"} ${reviewer}`,
1210 )
1211 .join(" · ")
1212 }
Agents as a team: lifecycle, merge queue, billing and a new shell1213 >
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1214 {reviewPending && "g1t is reviewing it now. "}
Pull requests: title and tabs across the full width, with counts and the size of the change1215 {requiredApprovals > 0 &&
1216 `This repository requires ${requiredApprovals} approving ${
1217 requiredApprovals === 1 ? "review" : "reviews"
1218 } before merging.`}
Agents as a team: lifecycle, merge queue, billing and a new shell1219 </StatusRow>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1220 {codeOwners?.required && (
1221 <StatusRow
1222 icon={
1223 ownersMissing ? (
1224 <CircleSlash size={16} className="text-warn" />
1225 ) : (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1226 <CircleCheck size={16} className="text-success" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1227 )
1228 }
1229 title={ownersMissing ? "Waiting for code owners" : "Code owners approved"}
1230 >
1231 {ownersMissing ?? `Every file it changes has its owners' approval.`}{" "}
1232 {ownersMissing && "This repository requires their review before merging."}
1233 </StatusRow>
1234 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1235 {landing ? (
Agents as a team: lifecycle, merge queue, billing and a new shell1236 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1237 icon={<Loader size={16} className="animate-spin text-accent" />}
1238 title="Merging"
1239 >
1240 {defaultBranch} has moved, so g1t is bringing this up to date first. It
1241 lands as soon as that is done. Watch it in the Session tab.
1242 </StatusRow>
Catching up with main takes seconds when the two sides touched different files1243 ) : agentCatchUp && behind ? (
1244 <CatchUpProgress
1245 owner={params.owner}
1246 repo={params.repo}
1247 number={pull.number}
1248 defaultBranch={defaultBranch}
1249 behind={behind}
1250 update={agentCatchUp.update}
1251 startedAt={agentCatchUp.startedAt}
1252 retrying={catchUpPending}
Merge branch 'worktree-agent-aca4790ea223aa235'1253 runs={agentRuns}
Catching up with main takes seconds when the two sides touched different files1254 />
Pull requests: title and tabs across the full width, with counts and the size of the change1255 ) : conflicting ? (
1256 <ConflictsSection
1257 conflicts={conflicts}
1258 pull={pull}
1259 owner={params.owner}
1260 repo={params.repo}
1261 defaultBranch={defaultBranch}
1262 changesUrl={here + "?tab=changes"}
1263 canResolve={canUpdate && agentsEnabled}
Catching up with main takes seconds when the two sides touched different files1264 resolving={catchUpPending}
1265 error={catchUp && "error" in catchUp ? catchUp.error : null}
Agents as a team: lifecycle, merge queue, billing and a new shell1266 />
Pull requests: title and tabs across the full width, with counts and the size of the change1267 ) : probing ? (
1268 <MergeabilityRow mergeable={mergeable} defaultBranch={defaultBranch} />
1269 ) : behind ? (
1270 <StatusRow
1271 icon={<GitPullRequestArrow size={16} className="text-info" />}
1272 title={`${defaultBranch} has moved since this was made`}
1273 >
1274 {mergeable === "clean" && "It has no conflicts with it. "}
1275 {requireUpToDate
1276 ? "This repository requires pull requests to be up to date, so it has to catch up before it can merge."
1277 : "That does not stop it merging: it is brought up to date as part of the merge."}
Catching up with main takes seconds when the two sides touched different files1278 {canUpdate && (
1279 <Form method="post" className="mt-2">
Pull requests: title and tabs across the full width, with counts and the size of the change1280 <input type="hidden" name="action" value="update" />
Merge branch 'worktree-agent-a985c01f43538af01'1281 <SubmitButton variant="quiet" match={{ action: "update" }} pending={`Merging ${defaultBranch} in…`}>
1282 Catch up with {defaultBranch} now
1283 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1284 </Form>
1285 )}
Catching up with main takes seconds when the two sides touched different files1286 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Pull requests: title and tabs across the full width, with counts and the size of the change1287 </StatusRow>
1288 ) : (
Merge branch 'worktree-agent-af9f0f386d52b6a73'1289 (pull.headCommit || (catchUp && "error" in catchUp)) && (
Pull requests: title and tabs across the full width, with counts and the size of the change1290 <StatusRow
Merge branch 'worktree-agent-ad7c6d88d93adc817'1291 icon={<CircleCheck size={16} className="text-success" />}
Catching up with main takes seconds when the two sides touched different files1292 title={
1293 (caughtUp && !caughtUp.already) || agentCatchUp
1294 ? `Brought up to date with ${defaultBranch}`
1295 : `Up to date with ${defaultBranch}`
1296 }
1297 >
1298 {caughtUp && !caughtUp.already && (
1299 <>
1300 Merged <span className="font-mono">{defaultBranch}</span> in as{" "}
1301 <span className="font-mono text-fg">{caughtUp.commit.slice(0, 7)}</span>. Its checks run
1302 again on the new commit.
1303 </>
1304 )}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1305 {agentCatchUp && "g1t merged it in and pushed the result. Its checks run again on the new commit."}
Merge branch 'worktree-agent-af9f0f386d52b6a73'1306 {/* A catch-up refused or failed after the pull request stopped being behind. */}
1307 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Catching up with main takes seconds when the two sides touched different files1308 </StatusRow>
Pull requests: title and tabs across the full width, with counts and the size of the change1309 )
1310 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1311 {active && rules && (rulesUnmet.length > 0 || rulesBypassable.length > 0 || rules.evaluate.length > 0) && (
1312 <StatusRow
1313 icon={
1314 <Scale
1315 size={16}
1316 className={rulesUnmet.length > 0 ? "text-danger" : rulesBypassable.length > 0 ? "text-warn" : "text-info"}
1317 />
1318 }
1319 title={
1320 rulesUnmet.length > 0
1321 ? `Rules for ${defaultBranch} are not met yet`
1322 : rulesBypassable.length > 0
1323 ? `You may bypass the rules for ${defaultBranch}`
1324 : "Rulesets being evaluated would refuse this merge"
1325 }
1326 >
1327 <div className="mt-2 space-y-3">
1328 {rulesUnmet.length > 0 && <ViolationList violations={rulesUnmet} />}
1329 {rulesBypassable.length > 0 && (
1330 <div>
1331 <p className="mb-1.5 text-xs text-muted">Not met, but a ruleset lets you bypass it:</p>
1332 <ViolationList violations={rulesBypassable} tone="warn" />
1333 </div>
1334 )}
1335 {rules.evaluate.length > 0 && (
1336 <div>
1337 <p className="mb-1.5 text-xs text-muted">Rulesets in evaluate would refuse it for this; nothing is held up:</p>
1338 <ViolationList violations={rules.evaluate} tone="info" />
1339 </div>
1340 )}
1341 {canProtect && (
1342 <Link
1343 to={`${base}/settings/rules?branch=${encodeURIComponent(defaultBranch)}`}
1344 className="inline-block text-xs text-muted underline underline-offset-2 hover:text-fg"
1345 >
1346 See every rule for {defaultBranch}
1347 </Link>
1348 )}
1349 </div>
1350 </StatusRow>
1351 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1352 {stalled && !lifecycle && (
1353 <StatusRow icon={<Hand size={16} className="text-warn" />} title="Needs you">
1354 {stalled}
1355 </StatusRow>
1356 )}
1357 {canMerge && lifecycle?.stage === "queued" && (
1358 <Form method="post" className="flex flex-wrap items-center gap-3 px-4 py-3">
1359 <ButtonLink to={`${base}/queue`}>
1360 <Layers size={15} />
1361 See the queue
1362 </ButtonLink>
Merge branch 'worktree-agent-a985c01f43538af01'1363 <SubmitButton variant="quiet" name="action" value="unqueue" pending="Removing…">
Pull requests: title and tabs across the full width, with counts and the size of the change1364 Remove from the queue
Merge branch 'worktree-agent-a985c01f43538af01'1365 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1366 </Form>
1367 )}
1368 {canMerge && !landing && lifecycle?.stage !== "queued" && (
1369 <Form method="post" className="space-y-3 px-4 py-3">
1370 {issue?.state === "open" && (
1371 <CheckboxOption
1372 name="keepIssueOpen"
1373 label={`Keep #${issue.number} open. This is only part of the work.`}
1374 labelClassName="text-xs text-muted"
1375 />
1376 )}
1377 {unchecked && canIgnoreChecks && (
1378 <CheckboxOption
1379 name="ignoreChecks"
Fast pages, required checks on the branch, self-hosted runners, honest incidents1380 label={`Bypass the required checks: merge although ${requiredFailed ? "one failed" : "they have not all passed"}.`}
Pull requests: title and tabs across the full width, with counts and the size of the change1381 labelClassName="text-xs text-muted"
1382 />
1383 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1384 {rulesBypassable.length > 0 && rulesUnmet.length === 0 && (
1385 <CheckboxOption
1386 name="bypassRules"
1387 checked={bypassing}
1388 onCheckedChange={(checked) => setBypassing(checked === true)}
1389 label={`Bypass the rules for ${defaultBranch}: merge although they are not met. It is recorded.`}
1390 labelClassName="text-xs text-muted"
1391 />
1392 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1393 <div className="flex flex-wrap items-center gap-3">
Merge branch 'worktree-agent-ae1299e92e4462012'1394 <Hint label={mergeBlocked} disabled={mergeBlocked != null}>
1395 <SubmitButton
1396 variant="accent"
1397 name="action"
1398 value="merge"
1399 disabled={mergeBlocked != null}
1400 pending={mergeQueue ? "Adding to the queue…" : "Merging…"}
1401 >
1402 {mergeQueue ? <Layers size={15} /> : <GitMerge size={15} />}
1403 {mergeQueue ? "Add to the merge queue" : `Merge into ${defaultBranch}`}
1404 </SubmitButton>
1405 </Hint>
Pull requests: title and tabs across the full width, with counts and the size of the change1406 <span className={`text-xs ${mergeBlocked ? "text-danger" : "text-muted"}`}>
1407 {mergeBlocked ?? (mergeQueue
1408 ? `Tested together with everything ahead of it, then lands on ${defaultBranch}.`
1409 : issue?.state === "open"
1410 ? `Closes issue #${issue.number}, and any other pull requests still open for it.`
1411 : `Lands its commits on ${defaultBranch}.`)}
1412 </span>
1413 </div>
1414 {actionData?.action === "merge" && (
1415 <ErrorText>{actionData.error}</ErrorText>
1416 )}
1417 </Form>
1418 )}
1419 </StatusBox>
1420 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1421
Pull requests: title and tabs across the full width, with counts and the size of the change1422 <CommentForm
1423 author={viewer?.username ?? null}
1424 resetKey={comments.length}
1425 review={review}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1426 handles={teams.map((team) => `@${team.ref}`)}
Pull requests: title and tabs across the full width, with counts and the size of the change1427 />
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1428 {canManage && (active || pull.status === "closed") && (
1429 <Form method="post" className="flex flex-wrap justify-end gap-2">
1430 {pull.status === "open" && (
1431 <SubmitButton variant="quiet" name="action" value="draft" pending="Converting…">
1432 Convert to draft
1433 </SubmitButton>
1434 )}
1435 {active ? (
1436 <SubmitButton variant="quiet" name="action" value="close" pending="Closing…">
1437 Close pull request
1438 </SubmitButton>
1439 ) : (
1440 <SubmitButton variant="quiet" name="action" value="reopen" pending="Reopening…">
1441 Reopen pull request
1442 </SubmitButton>
1443 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1444 </Form>
1445 )}
1446 {actionData &&
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1447 !["merge", "comment", "stack", "rerun-failed", "rerun-workflow", "update", "agent-review", "reviewers", "assign", "edit-comment", "delete-comment"].includes(
Pull requests: title and tabs across the full width, with counts and the size of the change1448 String(actionData.action),
1449 ) && <ErrorText>{actionData.error}</ErrorText>}
1450 </div>
1451 )}
1452 {actionData?.action === "comment" && (
1453 <div className="mt-2">
1454 <ErrorText>{actionData.error}</ErrorText>
1455 </div>
1456 )}
1457 </div>
1458 </div>
1459
1460 <aside className={tab === "changes" ? "hidden" : "space-y-6"}>
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md1461 {/* Other than a draft, a pull request shows its checks in full in the conversation. */}
1462 {pull.status === "draft" && <WorkflowStatuses statuses={statuses} />}
Pull requests: title and tabs across the full width, with counts and the size of the change1463 {affects.length > 0 && (
1464 <section>
1465 <h3 className="flex items-center gap-1.5 text-sm font-medium">
1466 <Network size={14} className="text-faint" />
1467 Affects
1468 </h3>
1469 <p className="mt-1 text-xs text-muted">Projects that use this one, and so may feel this change:</p>
1470 <ul className="mt-2 space-y-1 text-sm">
1471 {affects.map((project) => (
1472 <li key={project.slug} className="flex items-center justify-between gap-2">
1473 <Link to={`/${params.owner}/${project.slug}`} className="hover:underline">
1474 {project.name}
1475 </Link>
1476 {project.as && <code className="font-mono text-xs text-faint">{project.as}</code>}
1477 </li>
1478 ))}
1479 </ul>
1480 {preview?.branch && canMerge && (
1481 <Form method="post" className="mt-3">
1482 <input type="hidden" name="action" value="stack" />
1483 <input type="hidden" name="branch" value={preview.branch} />
Merge branch 'worktree-agent-a985c01f43538af01'1484 <SubmitButton
1485 match={{ action: "stack" }}
1486 pending="Starting the builds…"
1487 className="flex w-full items-center justify-center gap-1.5 rounded-md border border-line px-3 py-1.5 text-xs text-muted transition-colors hover:border-line-strong hover:text-fg disabled:opacity-50"
Pull requests: title and tabs across the full width, with counts and the size of the change1488 >
1489 Preview them against this change
Merge branch 'worktree-agent-a985c01f43538af01'1490 </SubmitButton>
Agents as a team: lifecycle, merge queue, billing and a new shell1491 </Form>
1492 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1493 {actionData?.action === "stack" &&
1494 ("notice" in actionData ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1495 <p className="mt-2 text-xs text-success">{String(actionData.notice)}</p>
Pull requests: title and tabs across the full width, with counts and the size of the change1496 ) : (
1497 <ErrorText>{actionData.error}</ErrorText>
1498 ))}
1499 </section>
Acceptance checks in sandboxes, line comments and review verdicts1500 )}
Issues and pull requests replace intents and attempts1501
Project dependencies: addresses, preview stacks, Affects, and agents who know1502 <section>
Pull requests: title and tabs across the full width, with counts and the size of the change1503 <h3 className="text-sm font-medium">Reviewers</h3>
1504 <ul className="mt-2 space-y-1.5 text-sm">
1505 {reviewerNames.map((name) => {
1506 const verdict = reviews.find(({ reviewer }) => reviewer === name)?.verdict;
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1507 const pending = name === "g1t" && reviewPending;
Pull requests: title and tabs across the full width, with counts and the size of the change1508 return (
1509 <li key={name} className="flex items-center gap-2 px-1">
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1510 {name === "g1t" ? (
Pull requests: title and tabs across the full width, with counts and the size of the change1511 <Sparkles size={16} className="shrink-0 text-accent" />
1512 ) : (
1513 <Avatar name={name} size={20} />
1514 )}
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)1515 <span className="grow truncate font-mono text-xs">
1516 <PersonLink name={name} className="hover:underline" />
1517 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change1518 {pending ? (
1519 <span className="flex items-center gap-1.5 text-xs text-muted">
1520 <span className="size-1.5 animate-pulse rounded-full bg-accent" />
1521 Reviewing
1522 </span>
1523 ) : verdict === "approve" ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1524 <span className="flex items-center gap-1 text-xs text-success">
Pull requests: title and tabs across the full width, with counts and the size of the change1525 <CircleCheck size={13} /> Approved
1526 </span>
1527 ) : verdict === "request_changes" ? (
1528 <span className="flex items-center gap-1 text-xs text-danger">
1529 <CircleSlash size={13} /> Changes requested
1530 </span>
1531 ) : (
1532 <span className="text-xs text-faint">Review requested</span>
1533 )}
1534 </li>
1535 );
1536 })}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1537 {teamReviewers.map((team) => (
1538 <TeamReviewer key={team} team={team} />
1539 ))}
1540 {reviewerNames.length === 0 && teamReviewers.length === 0 && (
Pull requests: title and tabs across the full width, with counts and the size of the change1541 <li className="px-1 text-xs text-faint">No reviews requested yet.</li>
1542 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1543 </ul>
Pull requests: title and tabs across the full width, with counts and the size of the change1544 {pull.status === "open" && canManage && (
1545 <div className="mt-3 space-y-2">
1546 {agentsEnabled && !reviewPending && (
1547 <Form method="post">
1548 <input type="hidden" name="action" value="agent-review" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1549 {[...pull.reviewers, ...teamReviewers].map((name) => (
Pull requests: title and tabs across the full width, with counts and the size of the change1550 <input key={name} type="hidden" name="reviewer" value={name} />
1551 ))}
1552 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1553 <SubmitButton variant="quiet" match={{ action: "agent-review" }} pending="Asking g1t…">
Pull requests: title and tabs across the full width, with counts and the size of the change1554 <Sparkles size={14} className="text-accent" />
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1555 Request review from g1t
Merge branch 'worktree-agent-a985c01f43538af01'1556 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1557 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1558 <ComputeNote note={loaderData.computeNote} />
Pull requests: title and tabs across the full width, with counts and the size of the change1559 </Form>
1560 )}
1561 <details>
1562 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1563 {teamChoices.length > 0 ? "Request review from people or teams" : "Request review from people"}
Pull requests: title and tabs across the full width, with counts and the size of the change1564 </summary>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1565 <Form method="post" className="mt-2 space-y-2" key={[...pull.reviewers, ...teamReviewers].join()}>
Pull requests: title and tabs across the full width, with counts and the size of the change1566 <input type="hidden" name="action" value="reviewers" />
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1567 {pull.reviewers.includes("g1t") && (
1568 <input type="hidden" name="reviewer" value="g1t" />
Pull requests: title and tabs across the full width, with counts and the size of the change1569 )}
1570 <PeoplePicker
1571 name="reviewer"
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1572 members={members.filter((name) => name !== workOwner(pull).username)}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1573 chosen={pull.reviewers.filter((name) => name !== "g1t")}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1574 placeholder="Other usernames or teams (acme/backend)"
Pull requests: title and tabs across the full width, with counts and the size of the change1575 />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1576 {teamChoices.length > 0 && (
1577 <div className="space-y-1.5 border-t border-line pt-2">
1578 <p className="text-xs text-faint">Teams</p>
1579 {teamChoices.map((team) => (
1580 <CheckboxOption
1581 key={team.ref}
1582 name="reviewer"
1583 value={team.ref}
1584 defaultChecked={teamReviewers.includes(team.ref)}
1585 className="items-center"
1586 labelClassName="flex min-w-0 items-center gap-2"
1587 label={
1588 <>
1589 <Users size={14} className="shrink-0 text-faint" />
1590 <span className="truncate font-mono text-xs">@{team.ref}</span>
1591 </>
1592 }
1593 />
1594 ))}
1595 </div>
1596 )}
Merge branch 'worktree-agent-a985c01f43538af01'1597 <SubmitButton variant="quiet" match={{ action: "reviewers" }} pending="Saving…">
Pull requests: title and tabs across the full width, with counts and the size of the change1598 Save reviewers
Merge branch 'worktree-agent-a985c01f43538af01'1599 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1600 </Form>
1601 </details>
1602 </div>
Project dependencies: addresses, preview stacks, Affects, and agents who know1603 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1604 {["agent-review", "reviewers"].includes(String(actionData?.action)) && (
1605 <ErrorText>{actionData?.error}</ErrorText>
1606 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1607 </section>
Acceptance checks in sandboxes, line comments and review verdicts1608
Pull requests: title and tabs across the full width, with counts and the size of the change1609 <section>
1610 <h3 className="text-sm font-medium">Assignees</h3>
1611 <ul className="mt-2 space-y-1.5 text-sm">
1612 {pull.assignees.map((name) => (
Agents as a team: lifecycle, merge queue, billing and a new shell1613 <li key={name} className="flex items-center gap-2 px-1">
Pull requests: title and tabs across the full width, with counts and the size of the change1614 <Avatar name={name} size={20} />
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)1615 <span className="grow truncate font-mono text-xs">
1616 <PersonLink name={name} className="hover:underline" />
1617 </span>
Agents as a team: lifecycle, merge queue, billing and a new shell1618 </li>
Pull requests: title and tabs across the full width, with counts and the size of the change1619 ))}
1620 {pull.assignees.length === 0 && (
1621 <li className="px-1 text-xs text-faint">No one yet.</li>
1622 )}
1623 </ul>
1624 {viewer && canManage && active && (
1625 <div className="mt-3 space-y-2">
1626 {!pull.assignees.includes(viewer.username) && (
1627 <Form method="post">
1628 <input type="hidden" name="action" value="assign" />
1629 {pull.assignees.map((name) => (
1630 <input key={name} type="hidden" name="assignee" value={name} />
1631 ))}
1632 <input type="hidden" name="assignee" value={viewer.username} />
1633 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1634 <SubmitButton variant="quiet" name="who" value="self" pending="Assigning…">
Pull requests: title and tabs across the full width, with counts and the size of the change1635 Assign yourself
Merge branch 'worktree-agent-a985c01f43538af01'1636 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1637 </div>
1638 </Form>
1639 )}
1640 <details>
1641 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1642 Assign people
1643 </summary>
1644 <Form method="post" className="mt-2 space-y-2" key={pull.assignees.join()}>
1645 <input type="hidden" name="action" value="assign" />
1646 <PeoplePicker name="assignee" members={members} chosen={pull.assignees} />
Merge branch 'worktree-agent-a985c01f43538af01'1647 <SubmitButton variant="quiet" name="who" value="picked" pending="Saving…">
Pull requests: title and tabs across the full width, with counts and the size of the change1648 Save assignees
Merge branch 'worktree-agent-a985c01f43538af01'1649 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1650 </Form>
1651 </details>
1652 </div>
Issues and pull requests replace intents and attempts1653 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1654 {actionData?.action === "assign" && <ErrorText>{actionData.error}</ErrorText>}
1655 </section>
Issues and pull requests replace intents and attempts1656
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1657 <LabelsBox
1658 labels={loaderData.labels}
1659 chosen={pull.labels ?? []}
1660 canEdit={canManage && active}
Merge main (membership, two-factor, GitHub repo roles) into tokens1661 canCreate={loaderData.canMakeLabels}
1662 manageUrl={loaderData.canMakeLabels ? `${base}/labels` : undefined}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1663 />
1664 <MilestoneBox
1665 milestones={loaderData.milestones}
1666 current={pull.milestone}
1667 canEdit={loaderData.canTriage}
1668 base={base}
1669 />
1670
Pull requests: title and tabs across the full width, with counts and the size of the change1671 <section>
1672 <h3 className="text-sm font-medium">Working copy</h3>
1673 {pull.branch ? (
1674 <>
1675 <p className="mt-1 text-xs text-muted">
1676 <GitBranch size={13} className="mr-1 inline align-[-2px]" />
1677 Branch <span className="font-mono text-fg">{pull.branch}</span> of this
1678 repository. Pushes to it show up here.
1679 </p>
1680 <div className="mt-2">
1681 <CopyLine text={`git clone -b ${pull.branch} ${remote}`} />
1682 </div>
1683 </>
1684 ) : (
1685 <>
1686 <p className="mt-1 text-xs text-muted">
1687 This fork belongs to the pull request. Pushes to it show up here.
1688 </p>
1689 <div className="mt-2">
1690 <CopyLine text={`git clone ${remote}`} />
1691 </div>
1692 </>
1693 )}
1694 <p className="mt-3 flex items-center gap-1.5 font-mono text-xs text-faint">
1695 <GitCommitHorizontal size={13} />
Merge checks: statuses and check runs on every commit1696 {pull.headCommit ? (
1697 <Link to={`${base}/commit/${pull.headCommit}`} className="hover:text-fg">
1698 {pull.headCommit.slice(0, 12)}
1699 </Link>
1700 ) : (
1701 "no commits pushed yet"
1702 )}
1703 {pull.headCommit && <CommitChecksBadge checks={loaderData.headChecks} sha={pull.headCommit} className="size-5" />}
Pull requests: title and tabs across the full width, with counts and the size of the change1704 </p>
1705 </section>
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings1706 {loaderData.viewer && (
1707 <SubscriptionBox action={`${base}/notifications`} number={pull.number} kind="pull" subscription={loaderData.subscription} />
1708 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1709 </aside>
1710 </div>
Issues and pull requests replace intents and attempts1711 </div>
1712 );
1713}
Pull requests say where they are live; the project menu shows what is coming1714
1715/** Where the pull request's change is live, as GitHub shows a deployment. */
1716function DeploymentCard({
1717 preview,
1718 build,
1719 stacked,
Merge branch 'worktree-agent-aca4790ea223aa235'1720 stacking,
Pull requests say where they are live; the project menu shows what is coming1721 base,
1722}: {
1723 preview: LiveApp | null;
1724 build: Deployment | null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents1725 stacked: Promise<Stacked[]>;
Merge branch 'worktree-agent-aca4790ea223aa235'1726 /** Projects use this one, so previews built against it may be on their way. */
1727 stacking: boolean;
Pull requests say where they are live; the project menu shows what is coming1728 base: string;
1729}) {
1730 const building = build?.status === "queued" || build?.status === "building";
1731 const failed = build?.status === "failed";
1732 return (
1733 <section className="ml-12 overflow-hidden rounded-xl border border-line bg-surface">
1734 <div className="flex flex-wrap items-center gap-3 px-4 py-3">
1735 <span
1736 className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ${
Merge branch 'worktree-agent-ad7c6d88d93adc817'1737 failed ? "text-danger ring-danger/40" : building ? "text-warn ring-warn/40" : "text-success ring-success/40"
Pull requests say where they are live; the project menu shows what is coming1738 }`}
1739 >
1740 {building ? <Loader size={15} className="animate-spin" /> : <Rocket size={15} />}
1741 </span>
1742 <div className="min-w-0 grow">
1743 <p className="text-sm font-medium">
1744 {building
1745 ? preview
1746 ? "Deploying the latest push. The last preview is still live."
1747 : "Deploying a preview"
1748 : failed
1749 ? "The preview failed to deploy"
1750 : "This branch is live"}
1751 </p>
1752 {preview ? (
1753 <a href={preview.url} className="mt-0.5 block truncate font-mono text-xs text-muted hover:text-accent">
1754 {preview.url.replace(/^https?:[/][/]/, "")}
1755 </a>
1756 ) : (
1757 build?.error && <p className="mt-0.5 truncate text-xs text-muted">{build.error}</p>
1758 )}
1759 </div>
1760 <div className="flex shrink-0 items-center gap-2">
1761 {build && (
1762 <Link to={`${base}/deployments/${build.id}`} className="text-xs text-muted hover:text-fg">
1763 {failed ? "See why" : "Build log"}
1764 </Link>
1765 )}
1766 {preview && (
1767 <a
1768 href={preview.url}
1769 className="inline-flex items-center gap-1.5 rounded-md bg-accent px-3 py-1.5 text-xs font-medium text-bg hover:opacity-90"
1770 >
1771 Visit preview
1772 <ArrowUpRight size={13} />
1773 </a>
1774 )}
1775 </div>
1776 </div>
1777 {preview && (
1778 <p className="border-t border-line px-4 py-2 text-xs text-faint">
1779 <span className="font-mono">{preview.commit.slice(0, 7)}</span> · deployed <TimeAgo at={preview.deployedAt} />
1780 {preview.branch && ` · from ${preview.branch}`}
1781 </p>
1782 )}
Fast pages, required checks on the branch, self-hosted runners, honest incidents1783 {/* Read after the page: the card's own part shows at once. */}
Merge branch 'worktree-agent-aca4790ea223aa235'1784 <Suspense
1785 fallback={
1786 stacking && (
1787 <Loading className="border-t border-line px-4 py-2.5 text-xs">
1788 <SkeletonLine className="w-40" />
1789 <SkeletonLine className="mt-1.5 w-64 max-w-full" />
1790 </Loading>
1791 )
1792 }
1793 >
Fast pages, required checks on the branch, self-hosted runners, honest incidents1794 <Await resolve={stacked}>
1795 {(stacked) =>
1796 stacked.length > 0 && (
Merge branch 'worktree-agent-aca4790ea223aa235'1797 <div className="animate-fade-in border-t border-line px-4 py-2.5">
Fast pages, required checks on the branch, self-hosted runners, honest incidents1798 <p className="text-xs text-muted">Built against this change:</p>
1799 <ul className="mt-1.5 space-y-1">
1800 {stacked.map((entry) => (
1801 <li key={entry.slug} className="flex items-center gap-2 text-xs">
1802 <span className="font-medium">{entry.name}</span>
1803 <a href={entry.url} className="truncate font-mono text-muted hover:text-accent">
1804 {entry.url.replace(/^https?:[/][/]/, "")}
1805 </a>
1806 </li>
1807 ))}
1808 </ul>
1809 </div>
1810 )
1811 }
1812 </Await>
1813 </Suspense>
Pull requests say where they are live; the project menu shows what is coming1814 </section>
1815 );
1816}

This file's history is long; its oldest lines are credited to the oldest commit read.