Skip to content
1,718 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";
2import {
3 Bot,
4 ChevronRight,
5 FileDiff,
6 GitBranch,
7 GitCommitHorizontal,
8 CircleCheck,
9 CircleSlash,
10 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,
Acceptance checks in sandboxes, line comments and review verdicts18 MessageSquare,
Pull requests say where they are live; the project menu shows what is coming19 ArrowUpRight,
Acceptance checks in sandboxes, line comments and review verdicts20 MessagesSquare,
Pull requests say where they are live; the project menu shows what is coming21 Rocket,
Acceptance checks in sandboxes, line comments and review verdicts22 StickyNote,
23 User,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar24 Users,
Acceptance checks in sandboxes, line comments and review verdicts25 Wrench,
26} from "lucide-react";
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge27import { Suspense, useState } from "react";
Merge branch 'worktree-agent-a985c01f43538af01'28import { Await, Form, Link, redirect } from "react-router";
Acceptance checks in sandboxes, line comments and review verdicts29
Pull requests say where they are live; the project menu shows what is coming30import {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look31 type Capability,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md32 type Comment,
Pull requests say where they are live; the project menu shows what is coming33 type Comparison,
34 type Deployment,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains35 type Job,
Pull requests say where they are live; the project menu shows what is coming36 type LiveApp,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md37 type Pull,
38 type RepoPath,
Pull requests say where they are live; the project menu shows what is coming39 type SessionEntry,
40 type Viewer,
41 pullComparison,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights42 workOwner,
Pull requests say where they are live; the project menu shows what is coming43} from "@g1t/contracts";
Acceptance checks in sandboxes, line comments and review verdicts44
45import type { Route } from "./+types/pull";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit46import { highlightFirstFiles } from "../../lib/highlight.server";
Social cards for every page: og.g1t.sh47import { 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-agent48import { openedBy } from "../../lib/opened-by";
Merge branch 'worktree-agent-aaf03bdceac799c89'49import { cloneUrl, useAddresses } from "../../lib/addresses";
Acceptance checks in sandboxes, line comments and review verdicts50import { DiffView } from "../../components/diff-view";
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)51import { DetailsDisclosure, detailsSummary } from "../../components/details-disclosure";
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";
Acceptance checks in sandboxes, line comments and review verdicts56import { Markdown } from "../../components/markdown";
57import {
58 Avatar,
Agents as a team: lifecycle, merge queue, billing and a new shell59 ButtonLink,
Acceptance checks in sandboxes, line comments and review verdicts60 CopyLine,
61 EmptyState,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look62 ComputeNote,
Acceptance checks in sandboxes, line comments and review verdicts63 ErrorText,
Merge branch 'worktree-agent-a985c01f43538af01'64 SubmitButton,
Acceptance checks in sandboxes, line comments and review verdicts65 Textarea,
66 TimeAgo,
Merge branch 'worktree-agent-a985c01f43538af01'67 usePending,
Acceptance checks in sandboxes, line comments and review verdicts68} 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";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit71import { TabStrip } from "../../components/ui/tab-strip";
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs72import { WorkflowStatuses } from "../../components/actions";
Fast pages, required checks on the branch, self-hosted runners, honest incidents73import { AddCiPrompt } from "../../components/add-ci";
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md74import { AttemptsBox } from "../../components/attempts";
75import { type Attempt, attemptReview, otherAttempts, sharedPaths, splitOverlaps } from "../../lib/attempts";
Agents as a team: lifecycle, merge queue, billing and a new shell76import {
77 CommentForm,
78 CommentList,
79 IssueIcon,
80 PeoplePicker,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains81 PersonLink,
Agents as a team: lifecycle, merge queue, billing and a new shell82 PullState,
83 TimelineItem,
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar84 agentReviews,
85 AgentReviewLine,
Agents as a team: lifecycle, merge queue, billing and a new shell86 verdicts,
87} from "../../components/work";
Catching up with main takes seconds when the two sides touched different files88import { CatchUpProgress, ChecksSection, ConflictsSection, MergeabilityRow, runIdOf } from "../../components/merge-box";
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge89import { ViolationList } from "../../components/rules";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar90import { PullCodeOwnersPanel, TeamReviewer } from "../../components/codeowners";
Catching up with main takes seconds when the two sides touched different files91import { 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 put92import { notFound } from "../../lib/not-found.server";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look93import { computeNoteFor } from "../../lib/compute.server";
Projects no longer depend on each other: the dependsOn relation, its settings page (old links redirect), the overview card, a pull request's Affects panel and preview stacks, reference variables and agents' notes on what a project uses are gone; the table is dropped in a later deploy94import { actions, agents, deployments, identity, inbox, repos, work } from "../../lib/services.server";
Merge checks: statuses and check runs on every commit95import { commitChecksFor } from "../../lib/commit-checks.server";
96import { CommitChecksBadge } from "../../components/commit-checks";
Acceptance checks in sandboxes, line comments and review verdicts97import { assertSameOrigin, getViewer, requireUser } from "../../lib/session.server";
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings98import { accessTo, refusal, repoFor } from "../../lib/access.server";
99import { SubscriptionBox } from "../../components/notifications";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending100import { REFRESH_MS, useRefreshWhile } from "../../lib/refresh";
Acceptance checks in sandboxes, line comments and review verdicts101
102const EMPTY_COMPARISON: Comparison = { base: null, head: "", files: [], truncated: false };
103const TABS = ["conversation", "session", "changes"] as const;
104type Tab = (typeof TABS)[number];
105
Social cards for every page: og.g1t.sh106export function meta({ loaderData, params, ...args }: Route.MetaArgs) {
107 const pull = loaderData?.pull;
108 const title = pull ? `${pull.title} · Pull request #${pull.number} · ` : "";
109 const state = { draft: "Draft", open: "Open", merged: "Merged", closed: "Closed" }[pull?.status ?? "open"];
110 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-agent111 const opener = pull ? openedBy(pull) : null;
Social cards for every page: og.g1t.sh112 return page(args, {
113 title: `${title}${params.owner}/${params.repo} · g1t`,
114 description: pull
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent115 ? `${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.sh116 : null,
117 // The card shows the title and the state.
118 version: pull ? [pull.title, pull.status] : undefined,
119 type: "article",
120 });
Acceptance checks in sandboxes, line comments and review verdicts121}
122
123export async function loader({ params, context, request }: Route.LoaderArgs) {
124 const viewer = getViewer(context);
125 const path = { namespace: params.owner, name: params.repo };
126 const number = Number(params.number);
127 const asked = new URL(request.url).searchParams.get("tab");
128 const tab: Tab = TABS.find((name) => name === asked) ?? "conversation";
129
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look130 // Members of the workspace pick reviewers and assignees from its people;
131 // what the viewer may do here goes by their role on the repository.
Agents as a team: lifecycle, merge queue, billing and a new shell132 const member = (viewer?.workspaces ?? []).some(
133 (membership) => membership.slug === params.owner,
134 );
Fast pages, required checks on the branch, self-hosted runners, honest incidents135 // Everything starts at once. Only what the viewer's role decides waits
136 // for the repository, which the layout is looking up in this request too
137 // (lib/access.server.ts), and only what the pull request decides waits
138 // for it.
Project dependencies: addresses, preview stacks, Affects, and agents who know139 const ref = { workspace: params.owner, slug: params.repo };
Fast pages, required checks on the branch, self-hosted runners, honest incidents140 const access = accessTo(context, params);
141 const pullFound = work.getPull(path, number, viewer);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar142 // Its labels and milestone, with the repository's to choose from.
143 const labelsFound = work.listLabels(path, viewer);
144 const milestonesFound = work.listMilestones(path, viewer);
145 labelsFound.catch(() => null);
146 milestonesFound.catch(() => null);
147 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 incidents148 access,
149 pullFound,
Acceptance checks in sandboxes, line comments and review verdicts150 repos.get(path, viewer),
Agents as a team: lifecycle, merge queue, billing and a new shell151 work.getSettings(path, viewer),
g1t's agents only for listed workspaces, whatever the state of billing152 env.RUNNER.enabled(viewer, path),
Agents as a team: lifecycle, merge queue, billing and a new shell153 // A member picks reviewers and assignees from the workspace's people.
154 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-agent155 // Before someone asks g1t for something: whether the plan lets it start.
Fast pages, required checks on the branch, self-hosted runners, honest incidents156 access.then(({ can }) => (can.run ? computeNoteFor(params.owner, "agent") : null)),
157 tab === "session" ? work.readSession(path, number, viewer) : null,
158 // Its preview, for people with a role here: beside the rest, not after.
159 access.then(({ insider }) => (insider ? deployments.list(ref, viewer).catch(() => null) : null)),
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings160 // Whether the viewer hears of it, for the sidebar's Notifications.
161 viewer
162 ? repoFor(context, params).then((found) =>
163 found.ok ? inbox.subscription(viewer, { repoId: found.value.id, number }).catch(() => null) : null,
164 )
165 : null,
Merge branch 'worktree-agent-aca4790ea223aa235'166 // Its agent's latest runs, for the Agent panel: with the page, not after it.
167 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 bar168 // A member asks the workspace's teams to review, and mentions them.
169 member ? identity.listTeams(viewer, params.owner).catch(() => null) : null,
Acceptance checks in sandboxes, line comments and review verdicts170 ]);
171 if (!found.ok) {
172 // Issues and pull requests share numbers; this one may be an issue.
173 const issue = await work.getIssue(path, number, viewer);
174 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 put175 throw notFound("pull");
Acceptance checks in sandboxes, line comments and review verdicts176 }
177 const { pull } = found.value;
178 const range = pullComparison(pull);
Agents and memory, checks and conflicts, profiles, slug renames, custom domains179 // The jobs of each workflow run on its head, to list checks job by job.
180 // 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 incidents181 // 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.md182 // A closed or merged one lists its head's checks too, to read only.
Agents and memory, checks and conflicts, profiles, slug renames, custom domains183 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 incidents184 const workflowJobs =
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md185 tab === "conversation" && pull.status !== "draft" && runIds.length > 0
Fast pages, required checks on the branch, self-hosted runners, honest incidents186 ? Promise.all(runIds.map((id) => actions.run(path, viewer, id).catch(() => null))).then((runs) => {
187 const jobs: Record<string, Job[]> = {};
188 for (const run of runs) if (run?.ok) jobs[run.value.run.id] = run.value.jobs;
189 return jobs;
190 })
191 .catch(() => ({}) as Record<string, Job[]>)
192 : Promise.resolve({} as Record<string, Job[]>);
193 // No checks at all on an open pull request: whether that is because the
194 // repository has no workflows, to offer a starter one.
195 const unchecked =
196 tab === "conversation" &&
197 pull.status === "open" &&
198 (found.value.statuses ?? []).length === 0 &&
199 (found.value.requiredChecks ?? []).length === 0;
Projects no longer depend on each other: the dependsOn relation, its settings page (old links redirect), the overview card, a pull request's Affects panel and preview stacks, reference variables and agents' notes on what a project uses are gone; the table is dropped in a later deploy200 const [comparison, noChecks] = await Promise.all([
Usage, Billing settings and prepaid AI credit; fixes from the UX audit201 // The first screens of it highlighted here, so their colours do not pop in.
202 tab === "changes"
203 ? repos
204 .compare(range.repoId, viewer, range.base, range.head, range.baseBranch)
205 .then(async (found) => (found.ok ? { ok: true as const, value: await highlightFirstFiles(found.value) } : found))
206 : null,
Fast pages, required checks on the branch, self-hosted runners, honest incidents207 unchecked
208 ? actions
209 .workflows(path, viewer)
210 .then((found) => found.ok && found.value.length === 0)
211 .catch(() => false)
212 : false,
Acceptance checks in sandboxes, line comments and review verdicts213 ]);
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md214 // Every pull request for its issue, to compare: streamed, and read only
215 // when there is more than this one.
216 const attempts =
217 tab === "conversation" && pull.issue != null && (found.value.issue?.pullCount ?? 0) > 1
218 ? attemptsFor(path, viewer, pull, found.value.comments).catch(() => [] as Attempt[])
219 : null;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar220 const [labels, milestones] = await Promise.all([labelsFound, milestonesFound]);
221 const repoDefault = repo.ok ? repo.value.defaultBranch : "main";
222 // The default branch's protection covers pull requests into it only.
223 const protectedBase = (pull.base ?? repoDefault) === repoDefault;
Acceptance checks in sandboxes, line comments and review verdicts224 return {
225 ...found.value,
Merge checks: statuses and check runs on every commit226 // Every check on its head, as the commit's own list shows them: streamed.
227 headChecks: commitChecksFor(path, viewer, [pull.headCommit]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar228 labels: labels.ok ? labels.value : [],
229 milestones: milestones.ok ? milestones.value : [],
230 // Labelling and milestones: Triage and up; its author, its labels.
231 canTriage: can.triage,
Merge main (membership, two-factor, GitHub repo roles) into tokens232 // Making labels needs Write; applying them, Triage.
233 canMakeLabels: can.manage_labels,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar234 // The branch it merges into: Write and up.
235 canChangeBase: can.push,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains236 workflowJobs,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md237 attempts,
Acceptance checks in sandboxes, line comments and review verdicts238 tab,
239 session: session?.ok ? session.value : [],
240 // An empty comparison if it could not be made.
241 comparison: comparison && (comparison.ok ? comparison.value : EMPTY_COMPARISON),
242 viewer,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look243 // Write and up can merge.
244 canMerge: can.merge,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights245 // Triage and up manage anyone's pull request; its author, their own,
246 // and whoever asked g1t for one, that one.
247 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 look248 // Telling its agent things, and re-running checks, spend compute: Write and up.
249 canRun: can.run,
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts250 // Maintain and up edit and delete anyone's comments; everyone, their own.
251 canModerate: can.manage_settings,
Agents as a team: lifecycle, merge queue, billing and a new shell252 // 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 rights253 // whoever it is for (who asked g1t for it, or its author), a branch
254 // from anyone who can push.
255 canUpdate: pull.fork ? viewer?.id === workOwner(pull).id : can.push,
Agents as a team: lifecycle, merge queue, billing and a new shell256 agentsEnabled,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look257 computeNote,
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings258 subscription,
Merge branch 'worktree-agent-aca4790ea223aa235'259 // As the project's agents.json has them; left out, the panel fetches them.
260 agentRuns: runs?.ok ? { runs: runs.value, member: can.run } : undefined,
Agents as a team: lifecycle, merge queue, billing and a new shell261 members: members?.ok ? members.value.map((person) => person.username) : [],
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar262 // `workspace/slug` and name of each team the viewer can see.
263 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 merge264 // What the rules of the branch it merges into ask, as they stack; the
265 // default branch's settings where the rules are not known.
266 requireUpToDate: found.value.rules ? found.value.rules.strict : protectedBase && settings.ok && settings.value.requireUpToDate,
267 mergeQueue: found.value.rules ? found.value.rules.merge_queue : protectedBase && settings.ok && settings.value.mergeQueue,
268 requiredApprovals: found.value.rules
269 ? found.value.rules.required_approvals
270 : protectedBase && settings.ok
271 ? settings.value.requiredApprovals
272 : 0,
273 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 incidents274 noChecks,
275 // Who may open the pull request that adds CI: anyone who can push.
276 canAddCi: can.push,
277 // Who may choose the required checks.
278 canProtect: can.manage_protection,
Acceptance checks in sandboxes, line comments and review verdicts279 defaultBranch: repo.ok ? repo.value.defaultBranch : "main",
Projects no longer depend on each other: the dependsOn relation, its settings page (old links redirect), the overview card, a pull request's Affects panel and preview stacks, reference variables and agents' notes on what a project uses are gone; the table is dropped in a later deploy280 ...deploymentOf(deployed?.ok ? deployed.value : null, number),
Acceptance checks in sandboxes, line comments and review verdicts281 };
282}
283
Pull requests say where they are live; the project menu shows what is coming284/**
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md285 * This pull request and the latest few others for its issue, each with its
286 * head's checks and where its review stands, this one first.
287 */
288async function attemptsFor(path: RepoPath, viewer: Viewer, pull: Pull, comments: Comment[]): Promise<Attempt[]> {
289 const found = await work.getIssue(path, pull.issue!, viewer);
290 if (!found.ok) return [];
291 const others = otherAttempts(found.value.pulls, pull.number).flatMap(
292 (number) => found.value.pulls.find((other) => other.number === number) ?? [],
293 );
294 const [details, checks] = await Promise.all([
295 Promise.all(others.map((other) => work.getPull(path, other.number, viewer).catch(() => null))),
296 commitChecksFor(path, viewer, [pull.headCommit, ...others.map((other) => other.headCommit)]),
297 ]);
298 const attempt = (one: Pull, said: Comment[], current: boolean): Attempt => ({
299 number: one.number,
300 title: one.title,
301 status: one.status,
302 supersededBy: one.supersededBy,
303 author: openedBy(one).name,
304 headCommit: one.headCommit,
305 files: one.files,
306 updatedAt: one.updatedAt,
307 current,
308 shared: current ? [] : sharedPaths(one.files, pull.files),
309 checks: (one.headCommit && checks?.[one.headCommit]) || null,
310 review: attemptReview(said, one.reviewers),
311 });
312 return [
313 attempt(pull, comments, true),
314 ...others.map((other, index) => {
315 const detail = details[index];
316 return attempt(other, detail?.ok ? detail.value.comments : [], false);
317 }),
318 ];
319}
320
321/**
Pull requests say where they are live; the project menu shows what is coming322 * Where this pull request is live: its preview (or its latest build, while
Projects no longer depend on each other: the dependsOn relation, its settings page (old links redirect), the overview card, a pull request's Affects panel and preview stacks, reference variables and agents' notes on what a project uses are gone; the table is dropped in a later deploy323 * one is going or after one failed).
Pull requests say where they are live; the project menu shows what is coming324 */
Fast pages, required checks on the branch, self-hosted runners, honest incidents325function deploymentOf(
Pull requests say where they are live; the project menu shows what is coming326 list: { deployments: Deployment[]; live: LiveApp[] } | null,
327 number: number,
Projects no longer depend on each other: the dependsOn relation, its settings page (old links redirect), the overview card, a pull request's Affects panel and preview stacks, reference variables and agents' notes on what a project uses are gone; the table is dropped in a later deploy328): { preview: LiveApp | null; build: Deployment | null } {
Fast pages, required checks on the branch, self-hosted runners, honest incidents329 const preview = list?.live.find((app) => app.kind === "preview" && app.number === number) ?? null;
330 const build = list?.deployments.find((d) => d.kind === "preview" && d.number === number) ?? null;
Projects no longer depend on each other: the dependsOn relation, its settings page (old links redirect), the overview card, a pull request's Affects panel and preview stacks, reference variables and agents' notes on what a project uses are gone; the table is dropped in a later deploy331 return { preview, build };
Pull requests say where they are live; the project menu shows what is coming332}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look333const PULL_NEEDS: Record<string, Capability> = {
334 "agent-review": "run",
335 "rerun-workflow": "run",
Fast pages, required checks on the branch, self-hosted runners, honest incidents336 "rerun-failed": "run",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look337 message: "run",
338 merge: "merge",
339 unqueue: "merge",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar340 milestone: "triage",
341 base: "push",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look342};
343
Acceptance checks in sandboxes, line comments and review verdicts344export async function action({ request, params, context }: Route.ActionArgs) {
345 assertSameOrigin(request);
346 const user = requireUser(context, request);
347 const form = await request.formData();
348 const path = { namespace: params.owner, name: params.repo };
349 const number = Number(params.number);
350 const action = form.get("action");
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look351 // What each form needs beyond reading; work checks the rest.
352 const needed = typeof action === "string" ? PULL_NEEDS[action] : undefined;
353 const refused = needed ? await refusal(context, params, needed) : null;
354 if (refused) return { error: refused, action: String(action) };
Acceptance checks in sandboxes, line comments and review verdicts355 const verdict = form.get("verdict");
356 const line = Number(form.get("line"));
Agents as a team: lifecycle, merge queue, billing and a new shell357 /** Names ticked in a people picker, plus those typed beside it. */
358 const picked = (field: string) => [
359 ...form.getAll(field).map(String),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar360 // `@acme/backend` is asked as `acme/backend`.
361 ...String(form.get("others") ?? "").split(/[\s,]+/).map((name) => name.replace(/^@/, "")),
Agents as a team: lifecycle, merge queue, billing and a new shell362 ];
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent363 // Asking g1t for its review records the request, then starts it.
Agents as a team: lifecycle, merge queue, billing and a new shell364 if (action === "agent-review") {
365 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-agent366 reviewers: [...form.getAll("reviewer").map(String), "g1t"],
Agents as a team: lifecycle, merge queue, billing and a new shell367 });
368 if (!asked.ok) return { error: asked.error.message, action };
369 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar370 // Its labels, milestone and base, from the sidebar's menus and the header.
371 if (action === "labels") {
372 const set = await work.setLabels(user, path, number, form.getAll("label").map(String));
373 return set.ok ? null : { error: set.error.message, action };
374 }
375 if (action === "milestone" || action === "base") {
376 const changed = await work.updatePull(
377 user,
378 path,
379 number,
380 action === "milestone"
381 ? { milestone: Number(form.get("milestone")) || 0 }
382 : { base: String(form.get("base") ?? "") },
383 );
384 return changed.ok ? null : { error: changed.error.message, action };
385 }
Catching up with main takes seconds when the two sides touched different files386 // Catching up: merged and pushed in seconds when the two sides changed
387 // different files; otherwise handed to a sandbox, which takes a minute.
388 if (action === "update") {
389 const caught = await work.catchUpPull(user, path, number);
390 if (!caught.ok) return { action, error: caught.error.message };
391 const update = caught.value;
392 if (update.outcome !== "needs_agent") {
393 return {
394 action,
395 updated: { commit: update.commit, already: update.outcome === "up_to_date", at: Date.now() },
396 };
397 }
398 const started = await env.RUNNER.update(user, path, number);
399 if (!started.ok) return { action, error: started.error.message };
400 return { action, agent: { update, startedAt: Date.now() } };
401 }
Agents and memory, checks and conflicts, profiles, slug renames, custom domains402 // A workflow run's failed jobs, run again. Who may is the actions service's call.
403 if (action === "rerun-workflow") {
404 const rerun = await actions.rerun(user, path, String(form.get("run") ?? ""), true);
405 return rerun.ok ? null : { error: rerun.error.message, action };
406 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents407 // Every failed run on its head, run again: Mission control's quick action.
408 if (action === "rerun-failed") {
409 const found = await work.getPull(path, number, user);
410 if (!found.ok) return { error: found.error.message, action };
411 const failed = [
412 ...new Set(
413 (found.value.statuses ?? [])
414 .filter((status) => status.state === "failure" || status.state === "error")
415 .map(runIdOf)
416 .filter((id) => id != null),
417 ),
418 ];
419 if (failed.length === 0) return { error: "No workflow run failed on its latest commit.", action };
420 const reruns = await Promise.all(failed.map((id) => actions.rerun(user, path, id, true)));
421 const refused = reruns.find((rerun) => !rerun.ok);
422 return refused && !refused.ok ? { error: refused.error.message, action } : null;
423 }
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts424 // A comment's own text, changed or taken away: its author's to do, or a
425 // maintainer's, which work checks.
426 if (action === "edit-comment" || action === "delete-comment") {
427 const id = String(form.get("comment") ?? "");
428 const changed =
429 action === "edit-comment"
430 ? await work.editComment(user, path, id, String(form.get("body") ?? ""))
431 : await work.deleteComment(user, path, id);
432 return changed.ok ? null : { error: changed.error.message, action, comment: id };
433 }
Acceptance checks in sandboxes, line comments and review verdicts434 const result =
435 action === "merge"
436 ? await work.mergePull(user, path, number, {
437 keepIssueOpen: form.get("keepIssueOpen") === "on",
438 ignoreChecks: form.get("ignoreChecks") === "on",
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge439 bypassRules: form.get("bypassRules") === "on",
Acceptance checks in sandboxes, line comments and review verdicts440 })
Agents as a team: lifecycle, merge queue, billing and a new shell441 : action === "unqueue"
442 ? await work.removeFromQueue(user, path, number)
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request443 : action === "message"
444 ? await work.messageAgent(user, path, number, String(form.get("body") ?? ""))
Acceptance checks in sandboxes, line comments and review verdicts445 : action === "close"
446 ? await work.closePull(user, path, number)
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts447 : action === "reopen"
448 ? await work.reopenPull(user, path, number)
449 : action === "draft"
450 ? await work.convertPullToDraft(user, path, number)
Fast pages, required checks on the branch, self-hosted runners, honest incidents451 : action === "agent-review"
Agents as a team: lifecycle, merge queue, billing and a new shell452 ? await env.RUNNER.review(user, path, number)
453 : action === "reviewers"
454 ? await work.updatePull(user, path, number, { reviewers: picked("reviewer") })
455 : action === "assign"
456 ? await work.updatePull(user, path, number, { assignees: picked("assignee") })
Acceptance checks in sandboxes, line comments and review verdicts457 : action === "comment"
458 ? await work.addComment(user, path, number, {
459 body: String(form.get("body") ?? ""),
460 path: String(form.get("path") ?? "") || undefined,
461 line: line > 0 ? line : undefined,
462 verdict:
463 verdict === "approve" || verdict === "request_changes" ? verdict : undefined,
464 })
465 : await work.readyPull(user, path, number, String(form.get("summary") ?? ""));
466 return result.ok ? null : { error: result.error.message, action };
467}
468
Pull requests: title and tabs across the full width, with counts and the size of the change469/** How many, beside a tab's name, when there are any. */
470function TabCount({ n }: { n: number }) {
471 if (n <= 0) return null;
472 return (
473 <span className="rounded-full bg-raised px-1.5 py-px text-xs tabular-nums text-muted ring-1 ring-line">
474 {n.toLocaleString("en-US")}
475 </span>
476 );
477}
478
Acceptance checks in sandboxes, line comments and review verdicts479function TabLink({
480 to,
481 active,
482 children,
483}: {
484 to: string;
485 active: boolean;
486 children: React.ReactNode;
487}) {
488 return (
489 <Link
490 to={to}
491 preventScrollReset
Usage, Billing settings and prepaid AI credit; fixes from the UX audit492 aria-current={active ? "page" : undefined}
Acceptance checks in sandboxes, line comments and review verdicts493 className={
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)494 "-mb-px flex min-h-11 items-center gap-2 border-b-2 px-2.5 pb-2.5 text-sm whitespace-nowrap transition-colors sm:min-h-0 sm:px-3 [&>svg]:hidden sm:[&>svg]:block " +
Acceptance checks in sandboxes, line comments and review verdicts495 (active
496 ? "border-accent font-medium text-fg"
497 : "border-transparent text-muted hover:text-fg")
498 }
499 >
500 {children}
501 </Link>
502 );
503}
504
505/** One step of the session, on the timeline's rail. */
506function Entry({ entry, agent }: { entry: SessionEntry; agent: string }) {
507 const isTool = entry.kind === "tool_call" || entry.kind === "tool_result";
508 const Icon =
509 entry.kind === "prompt"
510 ? User
511 : entry.kind === "note"
512 ? StickyNote
513 : isTool
514 ? Wrench
515 : Bot;
516 return (
517 <li className="relative pl-10">
518 <span
519 className={`absolute top-0.5 left-0 flex size-7 items-center justify-center rounded-full border bg-bg ${
520 entry.kind === "prompt"
521 ? "border-accent/50 text-accent"
522 : "border-line text-faint"
523 }`}
524 >
525 <Icon size={14} />
526 </span>
527 {isTool ? (
528 <details className="group rounded-lg border border-line bg-surface">
529 <summary className="flex cursor-pointer list-none items-center gap-2 px-3 py-1.5 text-sm">
530 <ChevronRight
531 size={14}
532 className="text-faint transition-transform group-open:rotate-90"
533 />
534 <span className="font-mono text-xs text-accent">
535 {entry.tool ?? "tool"}
536 </span>
537 <span className="truncate font-mono text-xs text-muted">
538 {entry.kind === "tool_result" ? "→ " : ""}
539 {entry.text.split("\n")[0]}
540 </span>
541 </summary>
542 <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 line543 <code>{entry.text}</code>
Acceptance checks in sandboxes, line comments and review verdicts544 </pre>
545 </details>
546 ) : (
547 <div>
548 <p className="text-xs font-medium text-faint">
549 {entry.kind === "prompt"
550 ? "Prompt"
551 : entry.kind === "note"
552 ? "Note"
553 : agent}
554 </p>
555 {entry.kind === "message" ? (
556 <div className="mt-1">
557 <Markdown source={entry.text} />
558 </div>
Agents as a team: lifecycle, merge queue, billing and a new shell559 ) : entry.kind === "prompt" ? (
560 <div className="mt-1.5 max-h-[32rem] overflow-y-auto rounded-xl border border-line bg-surface p-4">
561 <Markdown source={entry.text} />
562 </div>
Acceptance checks in sandboxes, line comments and review verdicts563 ) : (
Agents as a team: lifecycle, merge queue, billing and a new shell564 <p className="mt-1 text-[0.9375rem] leading-relaxed wrap-break-word whitespace-pre-wrap">
Acceptance checks in sandboxes, line comments and review verdicts565 {entry.text}
566 </p>
567 )}
568 </div>
569 )}
570 {entry.commit && (
571 <p className="mt-1.5 flex items-center gap-1 font-mono text-xs text-faint">
572 <GitCommitHorizontal size={12} />
573 {entry.commit.slice(0, 7)}
574 </p>
575 )}
576 </li>
577 );
578}
579
Agents as a team: lifecycle, merge queue, billing and a new shell580/** The box at the foot of the conversation saying what stands before a merge. */
581function StatusBox({ children }: { children: React.ReactNode }) {
582 return (
583 <div className="flex gap-3">
584 <span className="hidden w-8 shrink-0 sm:block" />
585 <section className="min-w-0 grow divide-y divide-line overflow-hidden rounded-xl border border-line-strong bg-surface">
586 {children}
587 </section>
588 </div>
589 );
590}
591
592function StatusRow({
593 icon,
594 title,
595 children,
596}: {
597 icon: React.ReactNode;
598 title: string;
599 children?: React.ReactNode;
600}) {
601 return (
602 <div className="flex gap-3 px-4 py-3 text-sm">
603 <span className="mt-0.5 shrink-0">{icon}</span>
604 <div className="min-w-0">
605 <p className="font-medium">{title}</p>
606 {children && <div className="mt-0.5 text-muted">{children}</div>}
607 </div>
608 </div>
609 );
610}
611
Acceptance checks in sandboxes, line comments and review verdicts612export default function PullPage({ loaderData, actionData, params }: Route.ComponentProps) {
613 const {
614 pull,
615 issue,
616 comments,
617 checks,
Agents as a team: lifecycle, merge queue, billing and a new shell618 overlaps,
619 behind,
620 reviewPending,
621 lifecycle,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request622 messages,
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs623 statuses = [],
Fast pages, required checks on the branch, self-hosted runners, honest incidents624 requiredChecks = [],
625 noChecks,
626 canAddCi,
627 canProtect,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains628 mergeable = "unknown",
629 conflicts = [],
630 earlierChecks = [],
631 workflowJobs,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md632 attempts,
Project dependencies: addresses, preview stacks, Affects, and agents who know633 preview,
Pull requests say where they are live; the project menu shows what is coming634 build,
Agents as a team: lifecycle, merge queue, billing and a new shell635 landing,
636 stalled,
637 requireUpToDate,
638 mergeQueue,
639 requiredApprovals,
640 canIgnoreChecks,
641 canUpdate,
642 agentsEnabled,
Merge branch 'worktree-agent-aca4790ea223aa235'643 agentRuns,
Agents as a team: lifecycle, merge queue, billing and a new shell644 members,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar645 teams,
646 codeOwners,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge647 rules,
Acceptance checks in sandboxes, line comments and review verdicts648 tab,
649 session,
650 comparison,
651 viewer,
652 canMerge,
653 canManage,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look654 canRun,
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts655 canModerate,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar656 defaultBranch: repoDefault,
Acceptance checks in sandboxes, line comments and review verdicts657 } = loaderData;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar658 // The branch it merges into: what every line below names.
659 const defaultBranch = pull.base ?? repoDefault;
Acceptance checks in sandboxes, line comments and review verdicts660 const base = `/${params.owner}/${params.repo}`;
661 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-agent662 // g1t, on a pull request it made: the person who asked for it is a line below.
663 const opener = openedBy(pull);
Merge branch 'worktree-agent-aca4790ea223aa235'664 const checksSection = (jobs: Record<string, Job[]>, loading = false) => (
Fast pages, required checks on the branch, self-hosted runners, honest incidents665 <ChecksSection
666 run={checks}
667 required={requiredChecks}
668 statuses={statuses}
669 jobs={jobs}
670 pull={pull}
671 base={base}
672 earlier={earlierChecks}
673 canRerunWorkflows={canMerge}
674 settingsUrl={canProtect ? `${base}/settings/branches` : null}
675 error={actionData?.action === "rerun-failed" || actionData?.action === "rerun-workflow" ? actionData.error : null}
Merge branch 'worktree-agent-aca4790ea223aa235'676 loading={loading}
Fast pages, required checks on the branch, self-hosted runners, honest incidents677 />
678 );
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md679 // A closed or merged pull request's checks, as they ended: to read, with
680 // nothing to re-run and nothing that holds a merge.
681 const settledChecks = (jobs: Record<string, Job[]>, loading = false) => (
682 <ChecksSection
683 run={null}
684 required={[]}
685 statuses={statuses}
686 jobs={jobs}
687 pull={pull}
688 base={base}
689 earlier={[]}
690 canRerunWorkflows={false}
691 settled
692 loading={loading}
693 />
694 );
Merge branch 'worktree-agent-aaf03bdceac799c89'695 const addresses = useAddresses();
Acceptance checks in sandboxes, line comments and review verdicts696 const remote = pull.fork
Merge branch 'worktree-agent-aaf03bdceac799c89'697 ? cloneUrl(addresses, `${pull.fork.namespace}/${pull.fork.name}`)
698 : cloneUrl(addresses, `${params.owner}/${params.repo}`);
Acceptance checks in sandboxes, line comments and review verdicts699 const active = pull.status === "draft" || pull.status === "open";
700
701 // Follow an agent at work, or checks in progress, without a manual reload.
702 const working = pull.status === "draft";
Fast pages, required checks on the branch, self-hosted runners, honest incidents703 const checking = statuses.some((status) => status.state === "pending");
Acceptance checks in sandboxes, line comments and review verdicts704 const reviews = verdicts(comments);
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar705 // Agents' reviews: shown, never counted.
706 const advisory = agentReviews(comments);
Fast pages, required checks on the branch, self-hosted runners, honest incidents707 // What stands between this pull request and a merge, if anything: a
708 // required check that has not passed, or the merge queue taking it out.
709 const unchecked = requiredChecks.some((check) => check.state !== "success") || checks?.status === "failed";
710 const requiredFailed = requiredChecks.some((check) => check.state === "failure") || checks?.status === "failed";
Agents as a team: lifecycle, merge queue, billing and a new shell711 // 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.md712 // ones for the same issue are alternatives, and expected to: those are
713 // compared under their own heading.
714 const { collisions } = splitOverlaps(overlaps, pull.issue);
Agents as a team: lifecycle, merge queue, billing and a new shell715 const review = {
716 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 rights717 // Nobody reviews their own pull request, nor one g1t made for them.
718 canJudge: active && viewer != null && viewer.id !== workOwner(pull).id,
Agents as a team: lifecycle, merge queue, billing and a new shell719 };
720 // Everyone whose review was asked for, then anyone who reviewed unasked.
721 const reviewerNames = [
722 ...new Set([...pull.reviewers, ...reviews.map(({ reviewer }) => reviewer)]),
723 ];
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar724 const teamReviewers = pull.teamReviewers ?? [];
725 // Teams to ask: the workspace's, and any asked already.
726 const teamChoices = [
727 ...teams,
728 ...teamReviewers.filter((ref) => !teams.some((team) => team.ref === ref)).map((ref) => ({ ref, name: ref })),
729 ];
730 // Branch protection wants code owners' approval it does not have yet.
731 const ownersMissing = codeOwners?.required && codeOwners.missing ? codeOwners.missing : null;
Catching up with main takes seconds when the two sides touched different files732 // A catch-up: the click shows at once; the answer says whether it is
733 // done already or a sandbox is on it.
Merge branch 'worktree-agent-a985c01f43538af01'734 const catchUpPending = usePending({ action: "update" });
Catching up with main takes seconds when the two sides touched different files735 const catchUp = actionData?.action === "update" ? actionData : null;
736 const caughtUp = catchUp && "updated" in catchUp ? catchUp.updated : null;
737 const agentCatchUp = catchUp && "agent" in catchUp ? catchUp.agent : null;
738 // Pushed already: followed until the pull request's head is the merge,
739 // so its checks show starting again, for a minute at most.
740 const settling =
741 caughtUp != null && !caughtUp.already && pull.headCommit !== caughtUp.commit && Date.now() - caughtUp.at < 60_000;
742 // Followed until it is pushed, or for so long, never longer.
743 const catchingUp =
744 agentCatchUp != null && behind && Date.now() - agentCatchUp.startedAt < CATCH_UP_TIMEOUT_MS + REFRESH_MS;
Agents as a team: lifecycle, merge queue, billing and a new shell745 // g1t is taking a step of its own accord, so the page will change.
746 const moving =
747 lifecycle != null && lifecycle.stage !== "ready" && lifecycle.stage !== "needs_you";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains748 // Whether it merges cleanly is being worked out, so the box will change.
749 const probing = active && mergeable === "checking";
750 const conflicting = active && mergeable === "conflicting";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending751 useRefreshWhile(working || checking || reviewPending || catchingUp || settling || moving || landing || probing);
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge752 // The rules of its base it does not meet: checks show in their own
753 // section, so the rest stop the button here. Someone a ruleset lets
754 // bypass it may tick a box and merge past them.
755 const [bypassing, setBypassing] = useState(false);
756 const rulesUnmet = (rules?.unmet ?? []).filter((violation) => violation.rule !== "required_status_checks");
757 const rulesBypassable = rules?.bypassable ?? [];
Agents and memory, checks and conflicts, profiles, slug renames, custom domains758 // Why the merge button cannot be pressed, if it cannot.
759 const mergeBlocked = conflicting
760 ? "Resolve the conflicts first."
761 : probing
762 ? "Waiting to find out whether it merges cleanly."
763 : behind && requireUpToDate
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge764 ? `The rules for ${defaultBranch} require it to be up to date first.`
Fast pages, required checks on the branch, self-hosted runners, honest incidents765 : unchecked && !canIgnoreChecks
766 ? `The checks ${defaultBranch} requires have to pass first.`
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge767 : rulesUnmet.length > 0
768 ? rulesUnmet[0]!.message
769 : rulesBypassable.length > 0 && !bypassing
770 ? `Rules for ${defaultBranch} are not met. You may bypass them.`
771 : ownersMissing
772 ? "Code owners have to approve first."
773 : null;
Acceptance checks in sandboxes, line comments and review verdicts774
775 return (
Agents as a team: lifecycle, merge queue, billing and a new shell776 // 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 change777 <div>
778 {/* The title and the tabs run the full width; what is happening on
779 the pull request sits at the top of Conversation. */}
780 <header>
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)781 <h2 className="text-xl font-semibold tracking-tight text-balance wrap-break-word sm:text-2xl">
Acceptance checks in sandboxes, line comments and review verdicts782 {pull.title} <span className="font-normal text-faint">#{pull.number}</span>
783 </h2>
784 <div className="mt-3 flex flex-wrap items-center gap-x-3 gap-y-2 text-sm text-muted">
785 <PullState status={pull.status} />
786 <span className="flex items-center gap-2">
Agents as a team: lifecycle, merge queue, billing and a new shell787 <Avatar
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent788 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Agents as a team: lifecycle, merge queue, billing and a new shell789 size={18}
790 />
Acceptance checks in sandboxes, line comments and review verdicts791 <span>
Agents and memory, checks and conflicts, profiles, slug renames, custom domains792 <PersonLink
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent793 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Agents and memory, checks and conflicts, profiles, slug renames, custom domains794 className="font-medium text-fg hover:underline"
795 />{" "}
Acceptance checks in sandboxes, line comments and review verdicts796 {pull.status === "merged" ? "merged" : "wants to merge"}
797 {pull.branch && (
798 <>
799 {" "}
800 <span className="font-mono text-fg">{pull.branch}</span>
801 </>
802 )}{" "}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar803 into{" "}
804 <BaseBranch
805 base={defaultBranch}
806 head={pull.fork ? null : pull.branch}
807 canChange={loaderData.canChangeBase && active}
808 branchesUrl={`${base}/branches.json`}
809 />
Acceptance checks in sandboxes, line comments and review verdicts810 </span>
811 </span>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar812 {(pull.labels ?? []).map((name) => (
813 <LabelChip key={name} name={name} color={loaderData.labels.find((label) => label.name === name)?.color} />
814 ))}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent815 {opener.requestedBy && (
816 <span className="text-xs">
817 requested by{" "}
818 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
819 </span>
820 )}
821 {/* A pull request from a branch was made by its author, not an agent;
822 one g1t made already says so. */}
823 {!pull.branch && !opener.requestedBy && (
Acceptance checks in sandboxes, line comments and review verdicts824 <span className="flex items-center gap-1.5 font-mono text-xs">
825 <Bot size={14} />
826 {pull.agent}
827 {pull.runtime === "hosted" && <span className="text-faint">on g1t</span>}
828 </span>
829 )}
830 </div>
831
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar832 {(reviews.length > 0 || advisory.length > 0) && (
Acceptance checks in sandboxes, line comments and review verdicts833 <p className="mt-3 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm">
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar834 {advisory.map(({ agent, verdict }) => (
835 <AgentReviewLine key={agent.id} agent={agent} verdict={verdict} base={base} />
836 ))}
Acceptance checks in sandboxes, line comments and review verdicts837 {reviews.map(({ reviewer, verdict }) => (
838 <span
839 key={reviewer}
840 className={`flex items-center gap-1.5 ${
Merge branch 'worktree-agent-ad7c6d88d93adc817'841 verdict === "approve" ? "text-success" : "text-danger"
Acceptance checks in sandboxes, line comments and review verdicts842 }`}
843 >
844 {verdict === "approve" ? <CircleCheck size={15} /> : <CircleSlash size={15} />}
845 {verdict === "approve" ? "Approved by" : "Changes requested by"}{" "}
846 <span className="font-medium">{reviewer}</span>
847 </span>
848 ))}
849 </p>
850 )}
851
Usage, Billing settings and prepaid AI credit; fixes from the UX audit852 <TabStrip label="Pull request" className="mt-6 items-end gap-1 border-b border-line">
Acceptance checks in sandboxes, line comments and review verdicts853 <TabLink to={here} active={tab === "conversation"}>
854 <MessageSquare size={15} />
855 Conversation
Pull requests: title and tabs across the full width, with counts and the size of the change856 <TabCount n={comments.length} />
Acceptance checks in sandboxes, line comments and review verdicts857 </TabLink>
858 <TabLink to={here + "?tab=session"} active={tab === "session"}>
859 <MessagesSquare size={15} />
860 Session
861 </TabLink>
862 <TabLink to={here + "?tab=changes"} active={tab === "changes"}>
863 <FileDiff size={15} />
Pull requests: title and tabs across the full width, with counts and the size of the change864 Files changed
865 <TabCount n={pull.files.length} />
Acceptance checks in sandboxes, line comments and review verdicts866 </TabLink>
Pull requests: title and tabs across the full width, with counts and the size of the change867 {pull.files.length > 0 && (
868 <span className="ml-auto hidden pb-2.5 font-mono text-xs sm:inline">
Merge branch 'worktree-agent-ad7c6d88d93adc817'869 <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 change870 <span className="text-danger">−{pull.files.reduce((sum, file) => sum + file.deletions, 0).toLocaleString("en-US")}</span>
871 </span>
872 )}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit873 </TabStrip>
Pull requests: title and tabs across the full width, with counts and the size of the change874 </header>
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)875 <div className={`mt-6 grid gap-6 lg:gap-8 ${tab === "changes" ? "" : "lg:grid-cols-[1fr_19rem]"}`}>
Pull requests: title and tabs across the full width, with counts and the size of the change876 <div className="min-w-0">
877 {/* What is happening on it now: its stage, its agent, what people told
878 the agent, and what else touches the same files. On the conversation,
879 so the tabs sit right under the title. */}
880 {tab === "conversation" && (
881 <div>
882 {issue && (
883 <Link
884 to={`${base}/issues/${issue.number}`}
885 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"
886 >
887 <IssueIcon issue={issue} />
888 <span className="text-muted">
889 {pull.status === "merged" && issue.resolvedBy === pull.number
890 ? "Resolved"
891 : "For issue"}
892 </span>
893 <span className="truncate font-medium">{issue.title}</span>
894 <span className="text-faint">#{issue.number}</span>
895 {issue.pullCount > 1 && (
896 <span className="ml-auto shrink-0 text-xs text-faint">
897 1 of {issue.pullCount} pull requests
Pull requests show their tabs right under the title; agent panels move into Conversation898 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change899 )}
900 </Link>
901 )}
902 {pull.status === "merged" && (
903 <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">
904 <GitMerge size={16} className="shrink-0 text-merged" />
Agents as a team: lifecycle, merge queue, billing and a new shell905 <span>
Pull requests: title and tabs across the full width, with counts and the size of the change906 Merged into <span className="font-mono">{defaultBranch}</span> by{" "}
907 <span className="font-medium">{pull.mergedBy}</span>{" "}
908 {/* Not an account: the repository's settings said to merge it. */}
909 {pull.mergedBy === "g1t" && "automatically, once it was ready, "}
910 {pull.mergedAt && <TimeAgo at={pull.mergedAt} />}
911 {pull.headCommit && (
912 <span className="font-mono text-muted"> · {pull.headCommit.slice(0, 7)}</span>
Agents as a team: lifecycle, merge queue, billing and a new shell913 )}
914 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change915 </p>
916 )}
917 {pull.supersededBy != null && (
918 <p className="mt-4 rounded-xl border border-line bg-surface px-4 py-2.5 text-sm text-muted">
919 Closed because{" "}
920 <Link
921 to={`${base}/pull/${pull.supersededBy}`}
922 className="font-medium text-fg hover:underline"
923 >
924 #{pull.supersededBy}
925 </Link>{" "}
926 was merged for this issue instead.
927 </p>
928 )}
929 {lifecycle && <LifecyclePanel lifecycle={lifecycle} />}
930 {/* 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 step931 <AgentPanel
932 owner={params.owner}
933 repo={params.repo}
934 number={pull.number}
935 stage={lifecycle?.stage ?? null}
936 confidence={pull.confidence ?? null}
Merge branch 'worktree-agent-aca4790ea223aa235'937 runs={agentRuns}
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step938 />
Pull requests: title and tabs across the full width, with counts and the size of the change939
940 {/* 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 look941 {canRun &&
Pull requests: title and tabs across the full width, with counts and the size of the change942 pull.runtime === "hosted" &&
943 (working || ["working", "revising", "catching_up", "answering"].includes(lifecycle?.stage ?? "")) && (
Merge branch 'worktree-agent-a985c01f43538af01'944 // Keyed by the messages so the box empties once one shows below.
Merge branch 'worktree-agent-ad7c6d88d93adc817'945 <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 change946 <p className="flex items-center gap-2 text-sm font-medium">
Merge branch 'worktree-agent-ad7c6d88d93adc817'947 <Sparkles size={15} className="text-accent" />
Pull requests: title and tabs across the full width, with counts and the size of the change948 Message the agent
949 </p>
950 <p className="mt-1 text-xs text-muted">
951 A correction, a hint, a change of plan. It reads it at its next step, without
952 starting over.
953 </p>
954 <div className="mt-3 flex gap-2">
955 <input type="hidden" name="action" value="message" />
956 <input
957 name="body"
958 required
959 autoComplete="off"
960 data-1p-ignore
961 placeholder="Keep the old flag working too…"
Merge branch 'worktree-agent-ad7c6d88d93adc817'962 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 shell963 />
Merge branch 'worktree-agent-a985c01f43538af01'964 <SubmitButton match={{ action: "message" }} pending="Sending…">
965 Send
966 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change967 </div>
968 </Form>
Agents as a team: lifecycle, merge queue, billing and a new shell969 )}
Pull requests: title and tabs across the full width, with counts and the size of the change970 {messages.length > 0 && (
971 <ul className="mt-3 space-y-1.5">
972 {messages.map((message) => (
973 <li key={message.id} className="flex items-start gap-2 text-sm">
974 <Avatar name={message.author} size={18} />
975 <span className="min-w-0 grow">
976 <span className="font-medium">
977 {message.fromNumber != null ? `The agent on #${message.fromNumber}` : message.author}
978 </span>{" "}
979 <span className="text-muted">
980 {message.kind === "question"
981 ? "asked:"
982 : message.kind === "handoff"
983 ? "handed over:"
984 : message.kind === "answer"
985 ? "answered:"
986 : "to the agent:"}
987 </span>{" "}
988 {message.body}
989 {message.answer && (
Merge branch 'worktree-agent-ad7c6d88d93adc817'990 <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 change991 {message.declined ? "Declined: " : "Answer: "}
992 {message.answer}
993 </span>
994 )}
995 </span>
Merge branch 'worktree-agent-ad7c6d88d93adc817'996 <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 change997 {message.deliveredAt ? "read by the agent" : "waiting for its next step"}
998 </span>
999 </li>
1000 ))}
1001 </ul>
1002 )}
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md1003 {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 change1004 {collisions.length > 0 && active && (
1005 <div className="mt-4 rounded-xl border border-line bg-surface px-4 py-3 text-sm">
1006 <p className="flex items-center gap-2.5 font-medium">
1007 <Radar size={16} className="shrink-0 text-info" />
1008 Other work is changing the same files
1009 </p>
1010 <ul className="mt-2 space-y-1.5">
1011 {collisions.map((other) => (
1012 <li key={other.number} className="flex flex-wrap items-baseline gap-x-2 text-muted">
1013 <Link
1014 to={`${base}/pull/${other.number}`}
1015 className="font-medium text-fg hover:underline"
1016 >
1017 {other.title} <span className="font-normal text-faint">#{other.number}</span>
1018 </Link>
1019 <span className="font-mono text-xs">{other.paths.join(", ")}</span>
1020 </li>
1021 ))}
1022 </ul>
1023 <p className="mt-2 text-xs text-faint">
1024 Whichever merges second will have to catch up, and may conflict.
1025 </p>
1026 </div>
1027 )}
1028 </div>
1029 )}
1030 <div className="mt-5">
1031 {comparison ? (
1032 <DiffView
1033 comparison={comparison}
1034 review={{
1035 comments: comments.filter((comment) => comment.path),
1036 canComment: Boolean(viewer),
1037 }}
1038 />
1039 ) : tab === "session" ? (
1040 session.length === 0 ? (
1041 <EmptyState title="Nothing recorded yet">
1042 The agent's prompts, reasoning and tool calls appear here as it
1043 works.
1044 </EmptyState>
1045 ) : (
1046 <ol className="relative space-y-5 before:absolute before:top-2 before:bottom-2 before:left-3.25 before:w-px before:bg-line">
1047 {session.map((entry) => (
1048 <Entry key={entry.seq} entry={entry} agent={pull.agent} />
1049 ))}
1050 </ol>
1051 )
1052 ) : (
1053 <div className="space-y-4">
1054 <TimelineItem
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1055 author={opener.name}
Pull requests: title and tabs across the full width, with counts and the size of the change1056 at={pull.createdAt}
1057 action={
1058 <span>
1059 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-agent1060 {opener.requestedBy ? (
Pull requests: title and tabs across the full width, with counts and the size of the change1061 <>
1062 {" "}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1063 for{" "}
1064 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
1065 </>
1066 ) : !pull.branch && (
1067 <>
1068 {" "}
Pull requests: title and tabs across the full width, with counts and the size of the change1069 with <span className="font-mono text-xs">{pull.agent}</span>
1070 </>
1071 )}
1072 </span>
1073 }
1074 >
1075 {pull.body ? (
1076 <Markdown source={pull.body} repo={{ namespace: params.owner, name: params.repo }} />
1077 ) : (
1078 <p className="text-sm text-muted">
1079 {working
1080 ? "No description yet. It is written when the pull request is marked ready for review."
1081 : "No description."}
1082 </p>
1083 )}
1084 {canManage && pull.status === "open" && (
1085 <details className="mt-3 border-t border-line pt-3 text-sm">
1086 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1087 Edit description
1088 </summary>
1089 <Form method="post" className="mt-3 space-y-2">
Merge branch 'worktree-agent-a985c01f43538af01'1090 <input type="hidden" name="action" value="describe" />
Pull requests: title and tabs across the full width, with counts and the size of the change1091 <Textarea
1092 name="summary"
1093 rows={6}
1094 placeholder="What changed and why"
1095 defaultValue={pull.body ?? ""}
1096 />
Merge branch 'worktree-agent-a985c01f43538af01'1097 <SubmitButton match={{ action: "describe" }} pending="Saving…">
1098 Save
1099 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1100 </Form>
1101 </details>
1102 )}
1103 </TimelineItem>
Agents as a team: lifecycle, merge queue, billing and a new shell1104
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1105 <CommentList
1106 comments={comments}
1107 review={review}
1108 base={base}
1109 viewerId={viewer?.id ?? null}
1110 canModerate={canModerate}
1111 failed={actionData && "comment" in actionData ? { comment: String(actionData.comment), error: actionData.error } : null}
1112 />
Agents as a team: lifecycle, merge queue, billing and a new shell1113
Projects no longer depend on each other: the dependsOn relation, its settings page (old links redirect), the overview card, a pull request's Affects panel and preview stacks, reference variables and agents' notes on what a project uses are gone; the table is dropped in a later deploy1114 {(preview || build) && <DeploymentCard preview={preview} build={build} base={base} />}
Pull requests say where they are live; the project menu shows what is coming1115
Pull requests: title and tabs across the full width, with counts and the size of the change1116 {pull.status === "draft" && (
1117 <StatusBox>
1118 <StatusRow icon={<Loader size={16} className="text-faint" />} title="This is a draft">
1119 It is still being worked on. It can be reviewed and merged once it is
1120 marked ready.
1121 </StatusRow>
1122 {canManage && (
1123 <Form method="post" className="space-y-2 px-4 py-3">
Merge branch 'worktree-agent-a985c01f43538af01'1124 <input type="hidden" name="action" value="ready" />
Pull requests: title and tabs across the full width, with counts and the size of the change1125 <Textarea name="summary" rows={3} placeholder="What changed and why" />
Merge branch 'worktree-agent-a985c01f43538af01'1126 <SubmitButton match={{ action: "ready" }} pending="Marking ready…">
1127 Mark ready for review
1128 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1129 </Form>
1130 )}
1131 </StatusBox>
1132 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1133
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1134 {codeOwners && active && (
1135 <div className="flex gap-3">
1136 <span className="hidden w-8 shrink-0 sm:block" />
1137 <div className="min-w-0 grow">
1138 <PullCodeOwnersPanel
1139 owners={codeOwners}
1140 base={base}
1141 branch={defaultBranch}
1142 errorsHref={canProtect ? `${base}/settings/branches#codeowners` : `${base}/blob/${encodeURIComponent(defaultBranch)}/${codeOwners.path}`}
1143 />
1144 </div>
1145 </div>
1146 )}
1147
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md1148 {(pull.status === "merged" || pull.status === "closed") && statuses.length > 0 && (
1149 <StatusBox>
1150 <Suspense fallback={settledChecks({}, true)}>
1151 <Await resolve={workflowJobs}>{(jobs) => settledChecks(jobs)}</Await>
1152 </Suspense>
1153 </StatusBox>
1154 )}
1155
Pull requests: title and tabs across the full width, with counts and the size of the change1156 {pull.status === "open" && (
1157 <StatusBox>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1158 {/* Checks by status at once; job by job when the runs are read. */}
Merge branch 'worktree-agent-aca4790ea223aa235'1159 <Suspense fallback={checksSection({}, true)}>
1160 <Await resolve={workflowJobs}>{(jobs) => checksSection(jobs)}</Await>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1161 </Suspense>
1162 {noChecks && <AddCiPrompt owner={params.owner} repo={params.repo} canAdd={canAddCi} compact />}
Agents as a team: lifecycle, merge queue, billing and a new shell1163 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1164 icon={
1165 reviews.some(({ verdict }) => verdict === "request_changes") ? (
1166 <CircleSlash size={16} className="text-danger" />
1167 ) : reviews.length > 0 ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1168 <CircleCheck size={16} className="text-success" />
Pull requests: title and tabs across the full width, with counts and the size of the change1169 ) : (
1170 <MessageSquare size={16} className="text-faint" />
1171 )
1172 }
1173 title={
1174 reviews.length === 0
1175 ? "No reviews yet"
1176 : reviews
1177 .map(
1178 ({ reviewer, verdict }) =>
1179 `${verdict === "approve" ? "Approved by" : "Changes requested by"} ${reviewer}`,
1180 )
1181 .join(" · ")
1182 }
Agents as a team: lifecycle, merge queue, billing and a new shell1183 >
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1184 {reviewPending && "g1t is reviewing it now. "}
Pull requests: title and tabs across the full width, with counts and the size of the change1185 {requiredApprovals > 0 &&
1186 `This repository requires ${requiredApprovals} approving ${
1187 requiredApprovals === 1 ? "review" : "reviews"
1188 } before merging.`}
Agents as a team: lifecycle, merge queue, billing and a new shell1189 </StatusRow>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1190 {codeOwners?.required && (
1191 <StatusRow
1192 icon={
1193 ownersMissing ? (
1194 <CircleSlash size={16} className="text-warn" />
1195 ) : (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1196 <CircleCheck size={16} className="text-success" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1197 )
1198 }
1199 title={ownersMissing ? "Waiting for code owners" : "Code owners approved"}
1200 >
1201 {ownersMissing ?? `Every file it changes has its owners' approval.`}{" "}
1202 {ownersMissing && "This repository requires their review before merging."}
1203 </StatusRow>
1204 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1205 {landing ? (
Agents as a team: lifecycle, merge queue, billing and a new shell1206 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1207 icon={<Loader size={16} className="animate-spin text-accent" />}
1208 title="Merging"
1209 >
1210 {defaultBranch} has moved, so g1t is bringing this up to date first. It
1211 lands as soon as that is done. Watch it in the Session tab.
1212 </StatusRow>
Catching up with main takes seconds when the two sides touched different files1213 ) : agentCatchUp && behind ? (
1214 <CatchUpProgress
1215 owner={params.owner}
1216 repo={params.repo}
1217 number={pull.number}
1218 defaultBranch={defaultBranch}
1219 behind={behind}
1220 update={agentCatchUp.update}
1221 startedAt={agentCatchUp.startedAt}
1222 retrying={catchUpPending}
Merge branch 'worktree-agent-aca4790ea223aa235'1223 runs={agentRuns}
Catching up with main takes seconds when the two sides touched different files1224 />
Pull requests: title and tabs across the full width, with counts and the size of the change1225 ) : conflicting ? (
1226 <ConflictsSection
1227 conflicts={conflicts}
1228 pull={pull}
1229 owner={params.owner}
1230 repo={params.repo}
1231 defaultBranch={defaultBranch}
1232 changesUrl={here + "?tab=changes"}
1233 canResolve={canUpdate && agentsEnabled}
Catching up with main takes seconds when the two sides touched different files1234 resolving={catchUpPending}
1235 error={catchUp && "error" in catchUp ? catchUp.error : null}
Agents as a team: lifecycle, merge queue, billing and a new shell1236 />
Pull requests: title and tabs across the full width, with counts and the size of the change1237 ) : probing ? (
1238 <MergeabilityRow mergeable={mergeable} defaultBranch={defaultBranch} />
1239 ) : behind ? (
1240 <StatusRow
1241 icon={<GitPullRequestArrow size={16} className="text-info" />}
1242 title={`${defaultBranch} has moved since this was made`}
1243 >
1244 {mergeable === "clean" && "It has no conflicts with it. "}
1245 {requireUpToDate
1246 ? "This repository requires pull requests to be up to date, so it has to catch up before it can merge."
1247 : "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 files1248 {canUpdate && (
1249 <Form method="post" className="mt-2">
Pull requests: title and tabs across the full width, with counts and the size of the change1250 <input type="hidden" name="action" value="update" />
Merge branch 'worktree-agent-a985c01f43538af01'1251 <SubmitButton variant="quiet" match={{ action: "update" }} pending={`Merging ${defaultBranch} in…`}>
1252 Catch up with {defaultBranch} now
1253 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1254 </Form>
1255 )}
Catching up with main takes seconds when the two sides touched different files1256 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Pull requests: title and tabs across the full width, with counts and the size of the change1257 </StatusRow>
1258 ) : (
Merge branch 'worktree-agent-af9f0f386d52b6a73'1259 (pull.headCommit || (catchUp && "error" in catchUp)) && (
Pull requests: title and tabs across the full width, with counts and the size of the change1260 <StatusRow
Merge branch 'worktree-agent-ad7c6d88d93adc817'1261 icon={<CircleCheck size={16} className="text-success" />}
Catching up with main takes seconds when the two sides touched different files1262 title={
1263 (caughtUp && !caughtUp.already) || agentCatchUp
1264 ? `Brought up to date with ${defaultBranch}`
1265 : `Up to date with ${defaultBranch}`
1266 }
1267 >
1268 {caughtUp && !caughtUp.already && (
1269 <>
1270 Merged <span className="font-mono">{defaultBranch}</span> in as{" "}
1271 <span className="font-mono text-fg">{caughtUp.commit.slice(0, 7)}</span>. Its checks run
1272 again on the new commit.
1273 </>
1274 )}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1275 {agentCatchUp && "g1t merged it in and pushed the result. Its checks run again on the new commit."}
Merge branch 'worktree-agent-af9f0f386d52b6a73'1276 {/* A catch-up refused or failed after the pull request stopped being behind. */}
1277 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Catching up with main takes seconds when the two sides touched different files1278 </StatusRow>
Pull requests: title and tabs across the full width, with counts and the size of the change1279 )
1280 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1281 {active && rules && (rulesUnmet.length > 0 || rulesBypassable.length > 0 || rules.evaluate.length > 0) && (
1282 <StatusRow
1283 icon={
1284 <Scale
1285 size={16}
1286 className={rulesUnmet.length > 0 ? "text-danger" : rulesBypassable.length > 0 ? "text-warn" : "text-info"}
1287 />
1288 }
1289 title={
1290 rulesUnmet.length > 0
1291 ? `Rules for ${defaultBranch} are not met yet`
1292 : rulesBypassable.length > 0
1293 ? `You may bypass the rules for ${defaultBranch}`
1294 : "Rulesets being evaluated would refuse this merge"
1295 }
1296 >
1297 <div className="mt-2 space-y-3">
1298 {rulesUnmet.length > 0 && <ViolationList violations={rulesUnmet} />}
1299 {rulesBypassable.length > 0 && (
1300 <div>
1301 <p className="mb-1.5 text-xs text-muted">Not met, but a ruleset lets you bypass it:</p>
1302 <ViolationList violations={rulesBypassable} tone="warn" />
1303 </div>
1304 )}
1305 {rules.evaluate.length > 0 && (
1306 <div>
1307 <p className="mb-1.5 text-xs text-muted">Rulesets in evaluate would refuse it for this; nothing is held up:</p>
1308 <ViolationList violations={rules.evaluate} tone="info" />
1309 </div>
1310 )}
1311 {canProtect && (
1312 <Link
1313 to={`${base}/settings/rules?branch=${encodeURIComponent(defaultBranch)}`}
1314 className="inline-block text-xs text-muted underline underline-offset-2 hover:text-fg"
1315 >
1316 See every rule for {defaultBranch}
1317 </Link>
1318 )}
1319 </div>
1320 </StatusRow>
1321 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1322 {stalled && !lifecycle && (
1323 <StatusRow icon={<Hand size={16} className="text-warn" />} title="Needs you">
1324 {stalled}
1325 </StatusRow>
1326 )}
1327 {canMerge && lifecycle?.stage === "queued" && (
1328 <Form method="post" className="flex flex-wrap items-center gap-3 px-4 py-3">
1329 <ButtonLink to={`${base}/queue`}>
1330 <Layers size={15} />
1331 See the queue
1332 </ButtonLink>
Merge branch 'worktree-agent-a985c01f43538af01'1333 <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 change1334 Remove from the queue
Merge branch 'worktree-agent-a985c01f43538af01'1335 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1336 </Form>
1337 )}
1338 {canMerge && !landing && lifecycle?.stage !== "queued" && (
1339 <Form method="post" className="space-y-3 px-4 py-3">
1340 {issue?.state === "open" && (
1341 <CheckboxOption
1342 name="keepIssueOpen"
1343 label={`Keep #${issue.number} open. This is only part of the work.`}
1344 labelClassName="text-xs text-muted"
1345 />
1346 )}
1347 {unchecked && canIgnoreChecks && (
1348 <CheckboxOption
1349 name="ignoreChecks"
Fast pages, required checks on the branch, self-hosted runners, honest incidents1350 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 change1351 labelClassName="text-xs text-muted"
1352 />
1353 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1354 {rulesBypassable.length > 0 && rulesUnmet.length === 0 && (
1355 <CheckboxOption
1356 name="bypassRules"
1357 checked={bypassing}
1358 onCheckedChange={(checked) => setBypassing(checked === true)}
1359 label={`Bypass the rules for ${defaultBranch}: merge although they are not met. It is recorded.`}
1360 labelClassName="text-xs text-muted"
1361 />
1362 )}
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)1363 <div className="flex flex-col gap-2 sm:flex-row sm:flex-wrap sm:items-center sm:gap-3 *:w-full sm:*:w-auto [&_button]:min-h-11 [&_button]:w-full [&_button]:justify-center sm:[&_button]:min-h-0 sm:[&_button]:w-auto">
Merge branch 'worktree-agent-ae1299e92e4462012'1364 <Hint label={mergeBlocked} disabled={mergeBlocked != null}>
1365 <SubmitButton
1366 variant="accent"
1367 name="action"
1368 value="merge"
1369 disabled={mergeBlocked != null}
1370 pending={mergeQueue ? "Adding to the queue…" : "Merging…"}
1371 >
1372 {mergeQueue ? <Layers size={15} /> : <GitMerge size={15} />}
1373 {mergeQueue ? "Add to the merge queue" : `Merge into ${defaultBranch}`}
1374 </SubmitButton>
1375 </Hint>
Pull requests: title and tabs across the full width, with counts and the size of the change1376 <span className={`text-xs ${mergeBlocked ? "text-danger" : "text-muted"}`}>
1377 {mergeBlocked ?? (mergeQueue
1378 ? `Tested together with everything ahead of it, then lands on ${defaultBranch}.`
1379 : issue?.state === "open"
1380 ? `Closes issue #${issue.number}, and any other pull requests still open for it.`
1381 : `Lands its commits on ${defaultBranch}.`)}
1382 </span>
1383 </div>
1384 {actionData?.action === "merge" && (
1385 <ErrorText>{actionData.error}</ErrorText>
1386 )}
1387 </Form>
1388 )}
1389 </StatusBox>
1390 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1391
Pull requests: title and tabs across the full width, with counts and the size of the change1392 <CommentForm
1393 author={viewer?.username ?? null}
1394 resetKey={comments.length}
1395 review={review}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1396 handles={teams.map((team) => `@${team.ref}`)}
Pull requests: title and tabs across the full width, with counts and the size of the change1397 />
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1398 {canManage && (active || pull.status === "closed") && (
1399 <Form method="post" className="flex flex-wrap justify-end gap-2">
1400 {pull.status === "open" && (
1401 <SubmitButton variant="quiet" name="action" value="draft" pending="Converting…">
1402 Convert to draft
1403 </SubmitButton>
1404 )}
1405 {active ? (
1406 <SubmitButton variant="quiet" name="action" value="close" pending="Closing…">
1407 Close pull request
1408 </SubmitButton>
1409 ) : (
1410 <SubmitButton variant="quiet" name="action" value="reopen" pending="Reopening…">
1411 Reopen pull request
1412 </SubmitButton>
1413 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1414 </Form>
1415 )}
1416 {actionData &&
Projects no longer depend on each other: the dependsOn relation, its settings page (old links redirect), the overview card, a pull request's Affects panel and preview stacks, reference variables and agents' notes on what a project uses are gone; the table is dropped in a later deploy1417 !["merge", "comment", "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 change1418 String(actionData.action),
1419 ) && <ErrorText>{actionData.error}</ErrorText>}
1420 </div>
1421 )}
1422 {actionData?.action === "comment" && (
1423 <div className="mt-2">
1424 <ErrorText>{actionData.error}</ErrorText>
1425 </div>
1426 )}
1427 </div>
1428 </div>
1429
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)1430 <DetailsDisclosure
1431 className={tab === "changes" ? "hidden" : "order-first lg:order-none"}
1432 summary={detailsSummary([
1433 [reviewerNames.length + teamReviewers.length, "reviewer", "reviewers"],
1434 [pull.assignees.length, "assignee", "assignees"],
1435 [(pull.labels ?? []).length, "label", "labels"],
1436 ])}
1437 >
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md1438 {/* Other than a draft, a pull request shows its checks in full in the conversation. */}
1439 {pull.status === "draft" && <WorkflowStatuses statuses={statuses} />}
Project dependencies: addresses, preview stacks, Affects, and agents who know1440 <section>
Pull requests: title and tabs across the full width, with counts and the size of the change1441 <h3 className="text-sm font-medium">Reviewers</h3>
1442 <ul className="mt-2 space-y-1.5 text-sm">
1443 {reviewerNames.map((name) => {
1444 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-agent1445 const pending = name === "g1t" && reviewPending;
Pull requests: title and tabs across the full width, with counts and the size of the change1446 return (
1447 <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-agent1448 {name === "g1t" ? (
Pull requests: title and tabs across the full width, with counts and the size of the change1449 <Sparkles size={16} className="shrink-0 text-accent" />
1450 ) : (
1451 <Avatar name={name} size={20} />
1452 )}
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)1453 <span className="grow truncate font-mono text-xs">
1454 <PersonLink name={name} className="hover:underline" />
1455 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change1456 {pending ? (
1457 <span className="flex items-center gap-1.5 text-xs text-muted">
1458 <span className="size-1.5 animate-pulse rounded-full bg-accent" />
1459 Reviewing
1460 </span>
1461 ) : verdict === "approve" ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1462 <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 change1463 <CircleCheck size={13} /> Approved
1464 </span>
1465 ) : verdict === "request_changes" ? (
1466 <span className="flex items-center gap-1 text-xs text-danger">
1467 <CircleSlash size={13} /> Changes requested
1468 </span>
1469 ) : (
1470 <span className="text-xs text-faint">Review requested</span>
1471 )}
1472 </li>
1473 );
1474 })}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1475 {teamReviewers.map((team) => (
1476 <TeamReviewer key={team} team={team} />
1477 ))}
1478 {reviewerNames.length === 0 && teamReviewers.length === 0 && (
Pull requests: title and tabs across the full width, with counts and the size of the change1479 <li className="px-1 text-xs text-faint">No reviews requested yet.</li>
1480 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1481 </ul>
Pull requests: title and tabs across the full width, with counts and the size of the change1482 {pull.status === "open" && canManage && (
1483 <div className="mt-3 space-y-2">
1484 {agentsEnabled && !reviewPending && (
1485 <Form method="post">
1486 <input type="hidden" name="action" value="agent-review" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1487 {[...pull.reviewers, ...teamReviewers].map((name) => (
Pull requests: title and tabs across the full width, with counts and the size of the change1488 <input key={name} type="hidden" name="reviewer" value={name} />
1489 ))}
1490 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1491 <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 change1492 <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-agent1493 Request review from g1t
Merge branch 'worktree-agent-a985c01f43538af01'1494 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1495 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1496 <ComputeNote note={loaderData.computeNote} />
Pull requests: title and tabs across the full width, with counts and the size of the change1497 </Form>
1498 )}
1499 <details>
1500 <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 bar1501 {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 change1502 </summary>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1503 <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 change1504 <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-agent1505 {pull.reviewers.includes("g1t") && (
1506 <input type="hidden" name="reviewer" value="g1t" />
Pull requests: title and tabs across the full width, with counts and the size of the change1507 )}
1508 <PeoplePicker
1509 name="reviewer"
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1510 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-agent1511 chosen={pull.reviewers.filter((name) => name !== "g1t")}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1512 placeholder="Other usernames or teams (acme/backend)"
Pull requests: title and tabs across the full width, with counts and the size of the change1513 />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1514 {teamChoices.length > 0 && (
1515 <div className="space-y-1.5 border-t border-line pt-2">
1516 <p className="text-xs text-faint">Teams</p>
1517 {teamChoices.map((team) => (
1518 <CheckboxOption
1519 key={team.ref}
1520 name="reviewer"
1521 value={team.ref}
1522 defaultChecked={teamReviewers.includes(team.ref)}
1523 className="items-center"
1524 labelClassName="flex min-w-0 items-center gap-2"
1525 label={
1526 <>
1527 <Users size={14} className="shrink-0 text-faint" />
1528 <span className="truncate font-mono text-xs">@{team.ref}</span>
1529 </>
1530 }
1531 />
1532 ))}
1533 </div>
1534 )}
Merge branch 'worktree-agent-a985c01f43538af01'1535 <SubmitButton variant="quiet" match={{ action: "reviewers" }} pending="Saving…">
Pull requests: title and tabs across the full width, with counts and the size of the change1536 Save reviewers
Merge branch 'worktree-agent-a985c01f43538af01'1537 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1538 </Form>
1539 </details>
1540 </div>
Project dependencies: addresses, preview stacks, Affects, and agents who know1541 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1542 {["agent-review", "reviewers"].includes(String(actionData?.action)) && (
1543 <ErrorText>{actionData?.error}</ErrorText>
1544 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1545 </section>
Acceptance checks in sandboxes, line comments and review verdicts1546
Pull requests: title and tabs across the full width, with counts and the size of the change1547 <section>
1548 <h3 className="text-sm font-medium">Assignees</h3>
1549 <ul className="mt-2 space-y-1.5 text-sm">
1550 {pull.assignees.map((name) => (
Agents as a team: lifecycle, merge queue, billing and a new shell1551 <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 change1552 <Avatar name={name} size={20} />
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)1553 <span className="grow truncate font-mono text-xs">
1554 <PersonLink name={name} className="hover:underline" />
1555 </span>
Agents as a team: lifecycle, merge queue, billing and a new shell1556 </li>
Pull requests: title and tabs across the full width, with counts and the size of the change1557 ))}
1558 {pull.assignees.length === 0 && (
1559 <li className="px-1 text-xs text-faint">No one yet.</li>
1560 )}
1561 </ul>
1562 {viewer && canManage && active && (
1563 <div className="mt-3 space-y-2">
1564 {!pull.assignees.includes(viewer.username) && (
1565 <Form method="post">
1566 <input type="hidden" name="action" value="assign" />
1567 {pull.assignees.map((name) => (
1568 <input key={name} type="hidden" name="assignee" value={name} />
1569 ))}
1570 <input type="hidden" name="assignee" value={viewer.username} />
1571 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1572 <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 change1573 Assign yourself
Merge branch 'worktree-agent-a985c01f43538af01'1574 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1575 </div>
1576 </Form>
1577 )}
1578 <details>
1579 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1580 Assign people
1581 </summary>
1582 <Form method="post" className="mt-2 space-y-2" key={pull.assignees.join()}>
1583 <input type="hidden" name="action" value="assign" />
1584 <PeoplePicker name="assignee" members={members} chosen={pull.assignees} />
Merge branch 'worktree-agent-a985c01f43538af01'1585 <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 change1586 Save assignees
Merge branch 'worktree-agent-a985c01f43538af01'1587 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1588 </Form>
1589 </details>
1590 </div>
Acceptance checks in sandboxes, line comments and review verdicts1591 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1592 {actionData?.action === "assign" && <ErrorText>{actionData.error}</ErrorText>}
1593 </section>
Acceptance checks in sandboxes, line comments and review verdicts1594
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1595 <LabelsBox
1596 labels={loaderData.labels}
1597 chosen={pull.labels ?? []}
1598 canEdit={canManage && active}
Merge main (membership, two-factor, GitHub repo roles) into tokens1599 canCreate={loaderData.canMakeLabels}
1600 manageUrl={loaderData.canMakeLabels ? `${base}/labels` : undefined}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1601 />
1602 <MilestoneBox
1603 milestones={loaderData.milestones}
1604 current={pull.milestone}
1605 canEdit={loaderData.canTriage}
1606 base={base}
1607 />
1608
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">Working copy</h3>
1611 {pull.branch ? (
1612 <>
1613 <p className="mt-1 text-xs text-muted">
1614 <GitBranch size={13} className="mr-1 inline align-[-2px]" />
1615 Branch <span className="font-mono text-fg">{pull.branch}</span> of this
1616 repository. Pushes to it show up here.
1617 </p>
1618 <div className="mt-2">
1619 <CopyLine text={`git clone -b ${pull.branch} ${remote}`} />
1620 </div>
1621 </>
1622 ) : (
1623 <>
1624 <p className="mt-1 text-xs text-muted">
1625 This fork belongs to the pull request. Pushes to it show up here.
1626 </p>
1627 <div className="mt-2">
1628 <CopyLine text={`git clone ${remote}`} />
1629 </div>
1630 </>
1631 )}
1632 <p className="mt-3 flex items-center gap-1.5 font-mono text-xs text-faint">
1633 <GitCommitHorizontal size={13} />
Merge checks: statuses and check runs on every commit1634 {pull.headCommit ? (
1635 <Link to={`${base}/commit/${pull.headCommit}`} className="hover:text-fg">
1636 {pull.headCommit.slice(0, 12)}
1637 </Link>
1638 ) : (
1639 "no commits pushed yet"
1640 )}
1641 {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 change1642 </p>
1643 </section>
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings1644 {loaderData.viewer && (
1645 <SubscriptionBox action={`${base}/notifications`} number={pull.number} kind="pull" subscription={loaderData.subscription} />
1646 )}
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)1647 </DetailsDisclosure>
Pull requests: title and tabs across the full width, with counts and the size of the change1648 </div>
Acceptance checks in sandboxes, line comments and review verdicts1649 </div>
1650 );
1651}
Pull requests say where they are live; the project menu shows what is coming1652
1653/** Where the pull request's change is live, as GitHub shows a deployment. */
1654function DeploymentCard({
1655 preview,
1656 build,
1657 base,
1658}: {
1659 preview: LiveApp | null;
1660 build: Deployment | null;
1661 base: string;
1662}) {
1663 const building = build?.status === "queued" || build?.status === "building";
1664 const failed = build?.status === "failed";
1665 return (
1666 <section className="ml-12 overflow-hidden rounded-xl border border-line bg-surface">
1667 <div className="flex flex-wrap items-center gap-3 px-4 py-3">
1668 <span
1669 className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ${
Merge branch 'worktree-agent-ad7c6d88d93adc817'1670 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 coming1671 }`}
1672 >
1673 {building ? <Loader size={15} className="animate-spin" /> : <Rocket size={15} />}
1674 </span>
1675 <div className="min-w-0 grow">
1676 <p className="text-sm font-medium">
1677 {building
1678 ? preview
1679 ? "Deploying the latest push. The last preview is still live."
1680 : "Deploying a preview"
1681 : failed
1682 ? "The preview failed to deploy"
1683 : "This branch is live"}
1684 </p>
1685 {preview ? (
1686 <a href={preview.url} className="mt-0.5 block truncate font-mono text-xs text-muted hover:text-accent">
1687 {preview.url.replace(/^https?:[/][/]/, "")}
1688 </a>
1689 ) : (
1690 build?.error && <p className="mt-0.5 truncate text-xs text-muted">{build.error}</p>
1691 )}
1692 </div>
1693 <div className="flex shrink-0 items-center gap-2">
1694 {build && (
1695 <Link to={`${base}/deployments/${build.id}`} className="text-xs text-muted hover:text-fg">
1696 {failed ? "See why" : "Build log"}
1697 </Link>
1698 )}
1699 {preview && (
1700 <a
1701 href={preview.url}
1702 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"
1703 >
1704 Visit preview
1705 <ArrowUpRight size={13} />
1706 </a>
1707 )}
1708 </div>
1709 </div>
1710 {preview && (
1711 <p className="border-t border-line px-4 py-2 text-xs text-faint">
1712 <span className="font-mono">{preview.commit.slice(0, 7)}</span> · deployed <TimeAgo at={preview.deployedAt} />
1713 {preview.branch && ` · from ${preview.branch}`}
1714 </p>
1715 )}
1716 </section>
1717 );
1718}

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