Skip to content
1,824 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Acceptance checks in sandboxes, line comments and review verdicts1import { env } from "cloudflare:workers";
Issues and pull requests replace intents and attempts2import {
3 Bot,
4 ChevronRight,
5 FileDiff,
Pull requests from branches6 GitBranch,
Issues and pull requests replace intents and attempts7 GitCommitHorizontal,
Acceptance checks in sandboxes, line comments and review verdicts8 CircleCheck,
9 CircleSlash,
Issues and pull requests replace intents and attempts10 GitMerge,
Agents as a team: lifecycle, merge queue, billing and a new shell11 Layers,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge12 Scale,
Agents as a team: lifecycle, merge queue, billing and a new shell13 GitPullRequestArrow,
14 Hand,
15 Loader,
16 Radar,
17 Sparkles,
Issues and pull requests replace intents and attempts18 MessageSquare,
Pull requests say where they are live; the project menu shows what is coming19 ArrowUpRight,
Issues and pull requests replace intents and attempts20 MessagesSquare,
Pull requests say where they are live; the project menu shows what is coming21 Rocket,
Project dependencies: addresses, preview stacks, Affects, and agents who know22 Network,
Issues and pull requests replace intents and attempts23 StickyNote,
24 User,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar25 Users,
Issues and pull requests replace intents and attempts26 Wrench,
27} from "lucide-react";
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge28import { Suspense, useState } from "react";
Merge branch 'worktree-agent-a985c01f43538af01'29import { Await, Form, Link, redirect } from "react-router";
Issues and pull requests replace intents and attempts30
Pull requests say where they are live; the project menu shows what is coming31import {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look32 type Capability,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md33 type Comment,
Pull requests say where they are live; the project menu shows what is coming34 type Comparison,
35 type Deployment,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains36 type Job,
Pull requests say where they are live; the project menu shows what is coming37 type LiveApp,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md38 type Pull,
39 type RepoPath,
Pull requests say where they are live; the project menu shows what is coming40 type SessionEntry,
41 type Viewer,
42 pullComparison,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights43 workOwner,
Pull requests say where they are live; the project menu shows what is coming44} from "@g1t/contracts";
Issues and pull requests replace intents and attempts45
46import type { Route } from "./+types/pull";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit47import { highlightFirstFiles } from "../../lib/highlight.server";
Social cards for every page: og.g1t.sh48import { excerpt, page } from "../../lib/meta";
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent49import { openedBy } from "../../lib/opened-by";
Merge branch 'worktree-agent-aaf03bdceac799c89'50import { cloneUrl, useAddresses } from "../../lib/addresses";
Issues and pull requests replace intents and attempts51import { DiffView } from "../../components/diff-view";
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)52import { DetailsDisclosure, detailsSummary } from "../../components/details-disclosure";
Agents as a team: lifecycle, merge queue, billing and a new shell53import { LifecyclePanel } from "../../components/lifecycle";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains54import { AgentPanel } from "../../components/agents";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar55import { BaseBranch } from "../../components/base-branch";
56import { LabelChip, LabelsBox, MilestoneBox } from "../../components/labels";
Issues and pull requests replace intents and attempts57import { Markdown } from "../../components/markdown";
58import {
59 Avatar,
Agents as a team: lifecycle, merge queue, billing and a new shell60 ButtonLink,
Issues and pull requests replace intents and attempts61 CopyLine,
62 EmptyState,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look63 ComputeNote,
Issues and pull requests replace intents and attempts64 ErrorText,
Merge branch 'worktree-agent-a985c01f43538af01'65 SubmitButton,
Issues and pull requests replace intents and attempts66 Textarea,
67 TimeAgo,
Merge branch 'worktree-agent-a985c01f43538af01'68 usePending,
Issues and pull requests replace intents and attempts69} from "../../components/ui";
Workspace names and icons, and a component kit for every control70import { CheckboxOption } from "../../components/ui/checkbox";
Merge branch 'worktree-agent-ae1299e92e4462012'71import { Hint } from "../../components/ui/hint";
Merge branch 'worktree-agent-aca4790ea223aa235'72import { Loading, SkeletonLine } from "../../components/ui/skeleton";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit73import { TabStrip } from "../../components/ui/tab-strip";
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs74import { WorkflowStatuses } from "../../components/actions";
Fast pages, required checks on the branch, self-hosted runners, honest incidents75import { AddCiPrompt } from "../../components/add-ci";
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md76import { AttemptsBox } from "../../components/attempts";
77import { type Attempt, attemptReview, otherAttempts, sharedPaths, splitOverlaps } from "../../lib/attempts";
Agents as a team: lifecycle, merge queue, billing and a new shell78import {
79 CommentForm,
80 CommentList,
81 IssueIcon,
82 PeoplePicker,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains83 PersonLink,
Agents as a team: lifecycle, merge queue, billing and a new shell84 PullState,
85 TimelineItem,
86 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";
Merge branch 'worktree-agent-aca4790ea223aa235'94import { actions, agents, deployments, identity, inbox, projects, 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";
Issues and pull requests replace intents and attempts97import { 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";
Issues and pull requests replace intents and attempts101
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 });
Issues and pull requests replace intents and attempts121}
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);
142 const deps = projects.dependencies(params.owner, params.repo, viewer);
143 // Awaited below, unless the pull request is missing first.
144 deps.catch(() => null);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar145 // Its labels and milestone, with the repository's to choose from.
146 const labelsFound = work.listLabels(path, viewer);
147 const milestonesFound = work.listMilestones(path, viewer);
148 labelsFound.catch(() => null);
149 milestonesFound.catch(() => null);
150 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 incidents151 access,
152 pullFound,
Issues and pull requests replace intents and attempts153 repos.get(path, viewer),
Agents as a team: lifecycle, merge queue, billing and a new shell154 work.getSettings(path, viewer),
g1t's agents only for listed workspaces, whatever the state of billing155 env.RUNNER.enabled(viewer, path),
Agents as a team: lifecycle, merge queue, billing and a new shell156 // A member picks reviewers and assignees from the workspace's people.
157 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-agent158 // Before someone asks g1t for something: whether the plan lets it start.
Fast pages, required checks on the branch, self-hosted runners, honest incidents159 access.then(({ can }) => (can.run ? computeNoteFor(params.owner, "agent") : null)),
160 tab === "session" ? work.readSession(path, number, viewer) : null,
161 // Its preview, for people with a role here: beside the rest, not after.
162 access.then(({ insider }) => (insider ? deployments.list(ref, viewer).catch(() => null) : null)),
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings163 // Whether the viewer hears of it, for the sidebar's Notifications.
164 viewer
165 ? repoFor(context, params).then((found) =>
166 found.ok ? inbox.subscription(viewer, { repoId: found.value.id, number }).catch(() => null) : null,
167 )
168 : null,
Merge branch 'worktree-agent-aca4790ea223aa235'169 // Its agent's latest runs, for the Agent panel: with the page, not after it.
170 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 bar171 // A member asks the workspace's teams to review, and mentions them.
172 member ? identity.listTeams(viewer, params.owner).catch(() => null) : null,
Issues and pull requests replace intents and attempts173 ]);
174 if (!found.ok) {
175 // Issues and pull requests share numbers; this one may be an issue.
176 const issue = await work.getIssue(path, number, viewer);
177 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 put178 throw notFound("pull");
Issues and pull requests replace intents and attempts179 }
180 const { pull } = found.value;
Pull requests from branches181 const range = pullComparison(pull);
Agents and memory, checks and conflicts, profiles, slug renames, custom domains182 // The jobs of each workflow run on its head, to list checks job by job.
183 // 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 incidents184 // 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.md185 // A closed or merged one lists its head's checks too, to read only.
Agents and memory, checks and conflicts, profiles, slug renames, custom domains186 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 incidents187 const workflowJobs =
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md188 tab === "conversation" && pull.status !== "draft" && runIds.length > 0
Fast pages, required checks on the branch, self-hosted runners, honest incidents189 ? Promise.all(runIds.map((id) => actions.run(path, viewer, id).catch(() => null))).then((runs) => {
190 const jobs: Record<string, Job[]> = {};
191 for (const run of runs) if (run?.ok) jobs[run.value.run.id] = run.value.jobs;
192 return jobs;
193 })
194 .catch(() => ({}) as Record<string, Job[]>)
195 : Promise.resolve({} as Record<string, Job[]>);
196 // No checks at all on an open pull request: whether that is because the
197 // repository has no workflows, to offer a starter one.
198 const unchecked =
199 tab === "conversation" &&
200 pull.status === "open" &&
201 (found.value.statuses ?? []).length === 0 &&
202 (found.value.requiredChecks ?? []).length === 0;
203 const [comparison, used, noChecks] = await Promise.all([
Usage, Billing settings and prepaid AI credit; fixes from the UX audit204 // The first screens of it highlighted here, so their colours do not pop in.
205 tab === "changes"
206 ? repos
207 .compare(range.repoId, viewer, range.base, range.head, range.baseBranch)
208 .then(async (found) => (found.ok ? { ok: true as const, value: await highlightFirstFiles(found.value) } : found))
209 : null,
Fast pages, required checks on the branch, self-hosted runners, honest incidents210 deps,
211 unchecked
212 ? actions
213 .workflows(path, viewer)
214 .then((found) => found.ok && found.value.length === 0)
215 .catch(() => false)
216 : false,
Issues and pull requests replace intents and attempts217 ]);
Fast pages, required checks on the branch, self-hosted runners, honest incidents218 const affects = used.ok ? used.value.usedBy : [];
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md219 // Every pull request for its issue, to compare: streamed, and read only
220 // when there is more than this one.
221 const attempts =
222 tab === "conversation" && pull.issue != null && (found.value.issue?.pullCount ?? 0) > 1
223 ? attemptsFor(path, viewer, pull, found.value.comments).catch(() => [] as Attempt[])
224 : null;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar225 const [labels, milestones] = await Promise.all([labelsFound, milestonesFound]);
226 const repoDefault = repo.ok ? repo.value.defaultBranch : "main";
227 // The default branch's protection covers pull requests into it only.
228 const protectedBase = (pull.base ?? repoDefault) === repoDefault;
Issues and pull requests replace intents and attempts229 return {
230 ...found.value,
Merge checks: statuses and check runs on every commit231 // Every check on its head, as the commit's own list shows them: streamed.
232 headChecks: commitChecksFor(path, viewer, [pull.headCommit]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar233 labels: labels.ok ? labels.value : [],
234 milestones: milestones.ok ? milestones.value : [],
235 // Labelling and milestones: Triage and up; its author, its labels.
236 canTriage: can.triage,
Merge main (membership, two-factor, GitHub repo roles) into tokens237 // Making labels needs Write; applying them, Triage.
238 canMakeLabels: can.manage_labels,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar239 // The branch it merges into: Write and up.
240 canChangeBase: can.push,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains241 workflowJobs,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md242 attempts,
Issues and pull requests replace intents and attempts243 tab,
244 session: session?.ok ? session.value : [],
245 // An empty comparison if it could not be made.
246 comparison: comparison && (comparison.ok ? comparison.value : EMPTY_COMPARISON),
247 viewer,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look248 // Write and up can merge.
249 canMerge: can.merge,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights250 // Triage and up manage anyone's pull request; its author, their own,
251 // and whoever asked g1t for one, that one.
252 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 look253 // Telling its agent things, and re-running checks, spend compute: Write and up.
254 canRun: can.run,
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts255 // Maintain and up edit and delete anyone's comments; everyone, their own.
256 canModerate: can.manage_settings,
Agents as a team: lifecycle, merge queue, billing and a new shell257 // 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 rights258 // whoever it is for (who asked g1t for it, or its author), a branch
259 // from anyone who can push.
260 canUpdate: pull.fork ? viewer?.id === workOwner(pull).id : can.push,
Agents as a team: lifecycle, merge queue, billing and a new shell261 agentsEnabled,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look262 computeNote,
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings263 subscription,
Merge branch 'worktree-agent-aca4790ea223aa235'264 // As the project's agents.json has them; left out, the panel fetches them.
265 agentRuns: runs?.ok ? { runs: runs.value, member: can.run } : undefined,
Agents as a team: lifecycle, merge queue, billing and a new shell266 members: members?.ok ? members.value.map((person) => person.username) : [],
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar267 // `workspace/slug` and name of each team the viewer can see.
268 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 merge269 // What the rules of the branch it merges into ask, as they stack; the
270 // default branch's settings where the rules are not known.
271 requireUpToDate: found.value.rules ? found.value.rules.strict : protectedBase && settings.ok && settings.value.requireUpToDate,
272 mergeQueue: found.value.rules ? found.value.rules.merge_queue : protectedBase && settings.ok && settings.value.mergeQueue,
273 requiredApprovals: found.value.rules
274 ? found.value.rules.required_approvals
275 : protectedBase && settings.ok
276 ? settings.value.requiredApprovals
277 : 0,
278 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 incidents279 noChecks,
280 // Who may open the pull request that adds CI: anyone who can push.
281 canAddCi: can.push,
282 // Who may choose the required checks.
283 canProtect: can.manage_protection,
Issues and pull requests replace intents and attempts284 defaultBranch: repo.ok ? repo.value.defaultBranch : "main",
Fast pages, required checks on the branch, self-hosted runners, honest incidents285 affects,
286 // Streamed: the conversation shows first, the preview card after.
287 ...deploymentOf(deployed?.ok ? deployed.value : null, number, params.owner, affects, viewer),
Issues and pull requests replace intents and attempts288 };
289}
290
Pull requests say where they are live; the project menu shows what is coming291/**
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md292 * This pull request and the latest few others for its issue, each with its
293 * head's checks and where its review stands, this one first.
294 */
295async function attemptsFor(path: RepoPath, viewer: Viewer, pull: Pull, comments: Comment[]): Promise<Attempt[]> {
296 const found = await work.getIssue(path, pull.issue!, viewer);
297 if (!found.ok) return [];
298 const others = otherAttempts(found.value.pulls, pull.number).flatMap(
299 (number) => found.value.pulls.find((other) => other.number === number) ?? [],
300 );
301 const [details, checks] = await Promise.all([
302 Promise.all(others.map((other) => work.getPull(path, other.number, viewer).catch(() => null))),
303 commitChecksFor(path, viewer, [pull.headCommit, ...others.map((other) => other.headCommit)]),
304 ]);
305 const attempt = (one: Pull, said: Comment[], current: boolean): Attempt => ({
306 number: one.number,
307 title: one.title,
308 status: one.status,
309 supersededBy: one.supersededBy,
310 author: openedBy(one).name,
311 headCommit: one.headCommit,
312 files: one.files,
313 updatedAt: one.updatedAt,
314 current,
315 shared: current ? [] : sharedPaths(one.files, pull.files),
316 checks: (one.headCommit && checks?.[one.headCommit]) || null,
317 review: attemptReview(said, one.reviewers),
318 });
319 return [
320 attempt(pull, comments, true),
321 ...others.map((other, index) => {
322 const detail = details[index];
323 return attempt(other, detail?.ok ? detail.value.comments : [], false);
324 }),
325 ];
326}
327
328/**
Pull requests say where they are live; the project menu shows what is coming329 * Where this pull request is live: its preview (or its latest build, while
330 * one is going or after one failed), and the previews of the projects
331 * that use this one, built against it.
332 */
Fast pages, required checks on the branch, self-hosted runners, honest incidents333function deploymentOf(
Pull requests say where they are live; the project menu shows what is coming334 list: { deployments: Deployment[]; live: LiveApp[] } | null,
335 number: number,
336 owner: string,
337 affects: { slug: string; name: string }[],
338 viewer: Viewer,
Fast pages, required checks on the branch, self-hosted runners, honest incidents339): { preview: LiveApp | null; build: Deployment | null; stacked: Promise<Stacked[]> } {
340 const preview = list?.live.find((app) => app.kind === "preview" && app.number === number) ?? null;
341 const build = list?.deployments.find((d) => d.kind === "preview" && d.number === number) ?? null;
Pull requests say where they are live; the project menu shows what is coming342 const branch = preview?.branch ?? build?.branch ?? null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents343 // Streamed: another lookup per project, shown in the card once known.
344 const stacked: Promise<Stacked[]> = branch
345 ? Promise.all(
346 affects.slice(0, 5).map(async (project) => {
347 const theirs = await deployments.list({ workspace: owner, slug: project.slug }, viewer).catch(() => null);
348 const app = theirs?.ok ? theirs.value.live.find((a) => a.kind === "preview" && a.branch === branch) : undefined;
349 return app ? { name: project.name, slug: project.slug, url: app.url } : null;
350 }),
351 ).then((found) => found.filter((entry) => entry != null))
352 : Promise.resolve([]);
Pull requests say where they are live; the project menu shows what is coming353 return { preview, build, stacked };
354}
355
Fast pages, required checks on the branch, self-hosted runners, honest incidents356/** A project that uses this one, with its preview built against this change. */
357type Stacked = { name: string; slug: string; url: string };
358
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look359const PULL_NEEDS: Record<string, Capability> = {
360 stack: "run",
361 "agent-review": "run",
362 "rerun-workflow": "run",
Fast pages, required checks on the branch, self-hosted runners, honest incidents363 "rerun-failed": "run",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look364 message: "run",
365 merge: "merge",
366 unqueue: "merge",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar367 milestone: "triage",
368 base: "push",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look369};
370
Issues and pull requests replace intents and attempts371export async function action({ request, params, context }: Route.ActionArgs) {
372 assertSameOrigin(request);
373 const user = requireUser(context, request);
374 const form = await request.formData();
375 const path = { namespace: params.owner, name: params.repo };
376 const number = Number(params.number);
377 const action = form.get("action");
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look378 // What each form needs beyond reading; work checks the rest.
379 const needed = typeof action === "string" ? PULL_NEEDS[action] : undefined;
380 const refused = needed ? await refusal(context, params, needed) : null;
381 if (refused) return { error: refused, action: String(action) };
Acceptance checks in sandboxes, line comments and review verdicts382 const verdict = form.get("verdict");
383 const line = Number(form.get("line"));
Agents as a team: lifecycle, merge queue, billing and a new shell384 /** Names ticked in a people picker, plus those typed beside it. */
385 const picked = (field: string) => [
386 ...form.getAll(field).map(String),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar387 // `@acme/backend` is asked as `acme/backend`.
388 ...String(form.get("others") ?? "").split(/[\s,]+/).map((name) => name.replace(/^@/, "")),
Agents as a team: lifecycle, merge queue, billing and a new shell389 ];
Project dependencies: addresses, preview stacks, Affects, and agents who know390 // The projects that use this one, built against this pull request's preview.
391 if (action === "stack") {
392 const built = await deployments.stack(user, { workspace: params.owner, slug: params.repo }, String(form.get("branch") ?? ""));
393 return built.ok
394 ? { action, notice: `Building ${built.value.join(", ")} against this preview. They appear on their Deployments pages.` }
395 : { action, error: built.error.message };
396 }
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent397 // Asking g1t for its review records the request, then starts it.
Agents as a team: lifecycle, merge queue, billing and a new shell398 if (action === "agent-review") {
399 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-agent400 reviewers: [...form.getAll("reviewer").map(String), "g1t"],
Agents as a team: lifecycle, merge queue, billing and a new shell401 });
402 if (!asked.ok) return { error: asked.error.message, action };
403 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar404 // Its labels, milestone and base, from the sidebar's menus and the header.
405 if (action === "labels") {
406 const set = await work.setLabels(user, path, number, form.getAll("label").map(String));
407 return set.ok ? null : { error: set.error.message, action };
408 }
409 if (action === "milestone" || action === "base") {
410 const changed = await work.updatePull(
411 user,
412 path,
413 number,
414 action === "milestone"
415 ? { milestone: Number(form.get("milestone")) || 0 }
416 : { base: String(form.get("base") ?? "") },
417 );
418 return changed.ok ? null : { error: changed.error.message, action };
419 }
Catching up with main takes seconds when the two sides touched different files420 // Catching up: merged and pushed in seconds when the two sides changed
421 // different files; otherwise handed to a sandbox, which takes a minute.
422 if (action === "update") {
423 const caught = await work.catchUpPull(user, path, number);
424 if (!caught.ok) return { action, error: caught.error.message };
425 const update = caught.value;
426 if (update.outcome !== "needs_agent") {
427 return {
428 action,
429 updated: { commit: update.commit, already: update.outcome === "up_to_date", at: Date.now() },
430 };
431 }
432 const started = await env.RUNNER.update(user, path, number);
433 if (!started.ok) return { action, error: started.error.message };
434 return { action, agent: { update, startedAt: Date.now() } };
435 }
Agents and memory, checks and conflicts, profiles, slug renames, custom domains436 // A workflow run's failed jobs, run again. Who may is the actions service's call.
437 if (action === "rerun-workflow") {
438 const rerun = await actions.rerun(user, path, String(form.get("run") ?? ""), true);
439 return rerun.ok ? null : { error: rerun.error.message, action };
440 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents441 // Every failed run on its head, run again: Mission control's quick action.
442 if (action === "rerun-failed") {
443 const found = await work.getPull(path, number, user);
444 if (!found.ok) return { error: found.error.message, action };
445 const failed = [
446 ...new Set(
447 (found.value.statuses ?? [])
448 .filter((status) => status.state === "failure" || status.state === "error")
449 .map(runIdOf)
450 .filter((id) => id != null),
451 ),
452 ];
453 if (failed.length === 0) return { error: "No workflow run failed on its latest commit.", action };
454 const reruns = await Promise.all(failed.map((id) => actions.rerun(user, path, id, true)));
455 const refused = reruns.find((rerun) => !rerun.ok);
456 return refused && !refused.ok ? { error: refused.error.message, action } : null;
457 }
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts458 // A comment's own text, changed or taken away: its author's to do, or a
459 // maintainer's, which work checks.
460 if (action === "edit-comment" || action === "delete-comment") {
461 const id = String(form.get("comment") ?? "");
462 const changed =
463 action === "edit-comment"
464 ? await work.editComment(user, path, id, String(form.get("body") ?? ""))
465 : await work.deleteComment(user, path, id);
466 return changed.ok ? null : { error: changed.error.message, action, comment: id };
467 }
Issues and pull requests replace intents and attempts468 const result =
469 action === "merge"
Acceptance checks in sandboxes, line comments and review verdicts470 ? await work.mergePull(user, path, number, {
471 keepIssueOpen: form.get("keepIssueOpen") === "on",
472 ignoreChecks: form.get("ignoreChecks") === "on",
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge473 bypassRules: form.get("bypassRules") === "on",
Acceptance checks in sandboxes, line comments and review verdicts474 })
Agents as a team: lifecycle, merge queue, billing and a new shell475 : action === "unqueue"
476 ? await work.removeFromQueue(user, path, number)
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request477 : action === "message"
478 ? await work.messageAgent(user, path, number, String(form.get("body") ?? ""))
Issues and pull requests replace intents and attempts479 : action === "close"
480 ? await work.closePull(user, path, number)
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts481 : action === "reopen"
482 ? await work.reopenPull(user, path, number)
483 : action === "draft"
484 ? await work.convertPullToDraft(user, path, number)
Fast pages, required checks on the branch, self-hosted runners, honest incidents485 : action === "agent-review"
Agents as a team: lifecycle, merge queue, billing and a new shell486 ? await env.RUNNER.review(user, path, number)
487 : action === "reviewers"
488 ? await work.updatePull(user, path, number, { reviewers: picked("reviewer") })
489 : action === "assign"
490 ? await work.updatePull(user, path, number, { assignees: picked("assignee") })
Acceptance checks in sandboxes, line comments and review verdicts491 : action === "comment"
492 ? await work.addComment(user, path, number, {
493 body: String(form.get("body") ?? ""),
494 path: String(form.get("path") ?? "") || undefined,
495 line: line > 0 ? line : undefined,
496 verdict:
497 verdict === "approve" || verdict === "request_changes" ? verdict : undefined,
498 })
499 : await work.readyPull(user, path, number, String(form.get("summary") ?? ""));
Issues and pull requests replace intents and attempts500 return result.ok ? null : { error: result.error.message, action };
501}
502
Pull requests: title and tabs across the full width, with counts and the size of the change503/** How many, beside a tab's name, when there are any. */
504function TabCount({ n }: { n: number }) {
505 if (n <= 0) return null;
506 return (
507 <span className="rounded-full bg-raised px-1.5 py-px text-xs tabular-nums text-muted ring-1 ring-line">
508 {n.toLocaleString("en-US")}
509 </span>
510 );
511}
512
Issues and pull requests replace intents and attempts513function TabLink({
514 to,
515 active,
516 children,
517}: {
518 to: string;
519 active: boolean;
520 children: React.ReactNode;
521}) {
522 return (
523 <Link
524 to={to}
525 preventScrollReset
Usage, Billing settings and prepaid AI credit; fixes from the UX audit526 aria-current={active ? "page" : undefined}
Issues and pull requests replace intents and attempts527 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)528 "-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 " +
Issues and pull requests replace intents and attempts529 (active
530 ? "border-accent font-medium text-fg"
531 : "border-transparent text-muted hover:text-fg")
532 }
533 >
534 {children}
535 </Link>
536 );
537}
538
539/** One step of the session, on the timeline's rail. */
540function Entry({ entry, agent }: { entry: SessionEntry; agent: string }) {
541 const isTool = entry.kind === "tool_call" || entry.kind === "tool_result";
542 const Icon =
543 entry.kind === "prompt"
544 ? User
545 : entry.kind === "note"
546 ? StickyNote
547 : isTool
548 ? Wrench
549 : Bot;
550 return (
551 <li className="relative pl-10">
552 <span
553 className={`absolute top-0.5 left-0 flex size-7 items-center justify-center rounded-full border bg-bg ${
554 entry.kind === "prompt"
555 ? "border-accent/50 text-accent"
556 : "border-line text-faint"
557 }`}
558 >
559 <Icon size={14} />
560 </span>
561 {isTool ? (
562 <details className="group rounded-lg border border-line bg-surface">
563 <summary className="flex cursor-pointer list-none items-center gap-2 px-3 py-1.5 text-sm">
564 <ChevronRight
565 size={14}
566 className="text-faint transition-transform group-open:rotate-90"
567 />
568 <span className="font-mono text-xs text-accent">
569 {entry.tool ?? "tool"}
570 </span>
571 <span className="truncate font-mono text-xs text-muted">
572 {entry.kind === "tool_result" ? "→ " : ""}
573 {entry.text.split("\n")[0]}
574 </span>
575 </summary>
576 <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 line577 <code>{entry.text}</code>
Issues and pull requests replace intents and attempts578 </pre>
579 </details>
580 ) : (
581 <div>
582 <p className="text-xs font-medium text-faint">
583 {entry.kind === "prompt"
584 ? "Prompt"
585 : entry.kind === "note"
586 ? "Note"
587 : agent}
588 </p>
589 {entry.kind === "message" ? (
590 <div className="mt-1">
591 <Markdown source={entry.text} />
592 </div>
Agents as a team: lifecycle, merge queue, billing and a new shell593 ) : entry.kind === "prompt" ? (
594 <div className="mt-1.5 max-h-[32rem] overflow-y-auto rounded-xl border border-line bg-surface p-4">
595 <Markdown source={entry.text} />
596 </div>
Issues and pull requests replace intents and attempts597 ) : (
Agents as a team: lifecycle, merge queue, billing and a new shell598 <p className="mt-1 text-[0.9375rem] leading-relaxed wrap-break-word whitespace-pre-wrap">
Issues and pull requests replace intents and attempts599 {entry.text}
600 </p>
601 )}
602 </div>
603 )}
604 {entry.commit && (
605 <p className="mt-1.5 flex items-center gap-1 font-mono text-xs text-faint">
606 <GitCommitHorizontal size={12} />
607 {entry.commit.slice(0, 7)}
608 </p>
609 )}
610 </li>
611 );
612}
613
Agents as a team: lifecycle, merge queue, billing and a new shell614/** The box at the foot of the conversation saying what stands before a merge. */
615function StatusBox({ children }: { children: React.ReactNode }) {
616 return (
617 <div className="flex gap-3">
618 <span className="hidden w-8 shrink-0 sm:block" />
619 <section className="min-w-0 grow divide-y divide-line overflow-hidden rounded-xl border border-line-strong bg-surface">
620 {children}
621 </section>
622 </div>
623 );
624}
625
626function StatusRow({
627 icon,
628 title,
629 children,
630}: {
631 icon: React.ReactNode;
632 title: string;
633 children?: React.ReactNode;
634}) {
635 return (
636 <div className="flex gap-3 px-4 py-3 text-sm">
637 <span className="mt-0.5 shrink-0">{icon}</span>
638 <div className="min-w-0">
639 <p className="font-medium">{title}</p>
640 {children && <div className="mt-0.5 text-muted">{children}</div>}
641 </div>
642 </div>
643 );
644}
645
Issues and pull requests replace intents and attempts646export default function PullPage({ loaderData, actionData, params }: Route.ComponentProps) {
647 const {
648 pull,
649 issue,
650 comments,
Acceptance checks in sandboxes, line comments and review verdicts651 checks,
Agents as a team: lifecycle, merge queue, billing and a new shell652 overlaps,
653 behind,
654 reviewPending,
655 lifecycle,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request656 messages,
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs657 statuses = [],
Fast pages, required checks on the branch, self-hosted runners, honest incidents658 requiredChecks = [],
659 noChecks,
660 canAddCi,
661 canProtect,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains662 mergeable = "unknown",
663 conflicts = [],
664 earlierChecks = [],
665 workflowJobs,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md666 attempts,
Project dependencies: addresses, preview stacks, Affects, and agents who know667 affects,
668 preview,
Pull requests say where they are live; the project menu shows what is coming669 build,
670 stacked,
Agents as a team: lifecycle, merge queue, billing and a new shell671 landing,
672 stalled,
673 requireUpToDate,
674 mergeQueue,
675 requiredApprovals,
676 canIgnoreChecks,
677 canUpdate,
678 agentsEnabled,
Merge branch 'worktree-agent-aca4790ea223aa235'679 agentRuns,
Agents as a team: lifecycle, merge queue, billing and a new shell680 members,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar681 teams,
682 codeOwners,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge683 rules,
Issues and pull requests replace intents and attempts684 tab,
685 session,
686 comparison,
687 viewer,
688 canMerge,
689 canManage,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look690 canRun,
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts691 canModerate,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar692 defaultBranch: repoDefault,
Issues and pull requests replace intents and attempts693 } = loaderData;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar694 // The branch it merges into: what every line below names.
695 const defaultBranch = pull.base ?? repoDefault;
Issues and pull requests replace intents and attempts696 const base = `/${params.owner}/${params.repo}`;
697 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-agent698 // g1t, on a pull request it made: the person who asked for it is a line below.
699 const opener = openedBy(pull);
Merge branch 'worktree-agent-aca4790ea223aa235'700 const checksSection = (jobs: Record<string, Job[]>, loading = false) => (
Fast pages, required checks on the branch, self-hosted runners, honest incidents701 <ChecksSection
702 run={checks}
703 required={requiredChecks}
704 statuses={statuses}
705 jobs={jobs}
706 pull={pull}
707 base={base}
708 earlier={earlierChecks}
709 canRerunWorkflows={canMerge}
710 settingsUrl={canProtect ? `${base}/settings/branches` : null}
711 error={actionData?.action === "rerun-failed" || actionData?.action === "rerun-workflow" ? actionData.error : null}
Merge branch 'worktree-agent-aca4790ea223aa235'712 loading={loading}
Fast pages, required checks on the branch, self-hosted runners, honest incidents713 />
714 );
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md715 // A closed or merged pull request's checks, as they ended: to read, with
716 // nothing to re-run and nothing that holds a merge.
717 const settledChecks = (jobs: Record<string, Job[]>, loading = false) => (
718 <ChecksSection
719 run={null}
720 required={[]}
721 statuses={statuses}
722 jobs={jobs}
723 pull={pull}
724 base={base}
725 earlier={[]}
726 canRerunWorkflows={false}
727 settled
728 loading={loading}
729 />
730 );
Merge branch 'worktree-agent-aaf03bdceac799c89'731 const addresses = useAddresses();
Pull requests from branches732 const remote = pull.fork
Merge branch 'worktree-agent-aaf03bdceac799c89'733 ? cloneUrl(addresses, `${pull.fork.namespace}/${pull.fork.name}`)
734 : cloneUrl(addresses, `${params.owner}/${params.repo}`);
Issues and pull requests replace intents and attempts735 const active = pull.status === "draft" || pull.status === "open";
736
Acceptance checks in sandboxes, line comments and review verdicts737 // Follow an agent at work, or checks in progress, without a manual reload.
Issues and pull requests replace intents and attempts738 const working = pull.status === "draft";
Fast pages, required checks on the branch, self-hosted runners, honest incidents739 const checking = statuses.some((status) => status.state === "pending");
Acceptance checks in sandboxes, line comments and review verdicts740 const reviews = verdicts(comments);
Fast pages, required checks on the branch, self-hosted runners, honest incidents741 // What stands between this pull request and a merge, if anything: a
742 // required check that has not passed, or the merge queue taking it out.
743 const unchecked = requiredChecks.some((check) => check.state !== "success") || checks?.status === "failed";
744 const requiredFailed = requiredChecks.some((check) => check.state === "failure") || checks?.status === "failed";
Agents as a team: lifecycle, merge queue, billing and a new shell745 // 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.md746 // ones for the same issue are alternatives, and expected to: those are
747 // compared under their own heading.
748 const { collisions } = splitOverlaps(overlaps, pull.issue);
Agents as a team: lifecycle, merge queue, billing and a new shell749 const review = {
750 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 rights751 // Nobody reviews their own pull request, nor one g1t made for them.
752 canJudge: active && viewer != null && viewer.id !== workOwner(pull).id,
Agents as a team: lifecycle, merge queue, billing and a new shell753 };
754 // Everyone whose review was asked for, then anyone who reviewed unasked.
755 const reviewerNames = [
756 ...new Set([...pull.reviewers, ...reviews.map(({ reviewer }) => reviewer)]),
757 ];
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar758 const teamReviewers = pull.teamReviewers ?? [];
759 // Teams to ask: the workspace's, and any asked already.
760 const teamChoices = [
761 ...teams,
762 ...teamReviewers.filter((ref) => !teams.some((team) => team.ref === ref)).map((ref) => ({ ref, name: ref })),
763 ];
764 // Branch protection wants code owners' approval it does not have yet.
765 const ownersMissing = codeOwners?.required && codeOwners.missing ? codeOwners.missing : null;
Catching up with main takes seconds when the two sides touched different files766 // A catch-up: the click shows at once; the answer says whether it is
767 // done already or a sandbox is on it.
Merge branch 'worktree-agent-a985c01f43538af01'768 const catchUpPending = usePending({ action: "update" });
Catching up with main takes seconds when the two sides touched different files769 const catchUp = actionData?.action === "update" ? actionData : null;
770 const caughtUp = catchUp && "updated" in catchUp ? catchUp.updated : null;
771 const agentCatchUp = catchUp && "agent" in catchUp ? catchUp.agent : null;
772 // Pushed already: followed until the pull request's head is the merge,
773 // so its checks show starting again, for a minute at most.
774 const settling =
775 caughtUp != null && !caughtUp.already && pull.headCommit !== caughtUp.commit && Date.now() - caughtUp.at < 60_000;
776 // Followed until it is pushed, or for so long, never longer.
777 const catchingUp =
778 agentCatchUp != null && behind && Date.now() - agentCatchUp.startedAt < CATCH_UP_TIMEOUT_MS + REFRESH_MS;
Agents as a team: lifecycle, merge queue, billing and a new shell779 // g1t is taking a step of its own accord, so the page will change.
780 const moving =
781 lifecycle != null && lifecycle.stage !== "ready" && lifecycle.stage !== "needs_you";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains782 // Whether it merges cleanly is being worked out, so the box will change.
783 const probing = active && mergeable === "checking";
784 const conflicting = active && mergeable === "conflicting";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending785 useRefreshWhile(working || checking || reviewPending || catchingUp || settling || moving || landing || probing);
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge786 // The rules of its base it does not meet: checks show in their own
787 // section, so the rest stop the button here. Someone a ruleset lets
788 // bypass it may tick a box and merge past them.
789 const [bypassing, setBypassing] = useState(false);
790 const rulesUnmet = (rules?.unmet ?? []).filter((violation) => violation.rule !== "required_status_checks");
791 const rulesBypassable = rules?.bypassable ?? [];
Agents and memory, checks and conflicts, profiles, slug renames, custom domains792 // Why the merge button cannot be pressed, if it cannot.
793 const mergeBlocked = conflicting
794 ? "Resolve the conflicts first."
795 : probing
796 ? "Waiting to find out whether it merges cleanly."
797 : behind && requireUpToDate
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge798 ? `The rules for ${defaultBranch} require it to be up to date first.`
Fast pages, required checks on the branch, self-hosted runners, honest incidents799 : unchecked && !canIgnoreChecks
800 ? `The checks ${defaultBranch} requires have to pass first.`
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge801 : rulesUnmet.length > 0
802 ? rulesUnmet[0]!.message
803 : rulesBypassable.length > 0 && !bypassing
804 ? `Rules for ${defaultBranch} are not met. You may bypass them.`
805 : ownersMissing
806 ? "Code owners have to approve first."
807 : null;
Issues and pull requests replace intents and attempts808
809 return (
Agents as a team: lifecycle, merge queue, billing and a new shell810 // 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 change811 <div>
812 {/* The title and the tabs run the full width; what is happening on
813 the pull request sits at the top of Conversation. */}
814 <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)815 <h2 className="text-xl font-semibold tracking-tight text-balance wrap-break-word sm:text-2xl">
Issues and pull requests replace intents and attempts816 {pull.title} <span className="font-normal text-faint">#{pull.number}</span>
817 </h2>
818 <div className="mt-3 flex flex-wrap items-center gap-x-3 gap-y-2 text-sm text-muted">
819 <PullState status={pull.status} />
820 <span className="flex items-center gap-2">
Agents as a team: lifecycle, merge queue, billing and a new shell821 <Avatar
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent822 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Agents as a team: lifecycle, merge queue, billing and a new shell823 size={18}
824 />
Issues and pull requests replace intents and attempts825 <span>
Agents and memory, checks and conflicts, profiles, slug renames, custom domains826 <PersonLink
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent827 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Agents and memory, checks and conflicts, profiles, slug renames, custom domains828 className="font-medium text-fg hover:underline"
829 />{" "}
Pull requests from branches830 {pull.status === "merged" ? "merged" : "wants to merge"}
831 {pull.branch && (
832 <>
833 {" "}
834 <span className="font-mono text-fg">{pull.branch}</span>
835 </>
836 )}{" "}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar837 into{" "}
838 <BaseBranch
839 base={defaultBranch}
840 head={pull.fork ? null : pull.branch}
841 canChange={loaderData.canChangeBase && active}
842 branchesUrl={`${base}/branches.json`}
843 />
Issues and pull requests replace intents and attempts844 </span>
845 </span>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar846 {(pull.labels ?? []).map((name) => (
847 <LabelChip key={name} name={name} color={loaderData.labels.find((label) => label.name === name)?.color} />
848 ))}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent849 {opener.requestedBy && (
850 <span className="text-xs">
851 requested by{" "}
852 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
853 </span>
854 )}
855 {/* A pull request from a branch was made by its author, not an agent;
856 one g1t made already says so. */}
857 {!pull.branch && !opener.requestedBy && (
Pull requests from branches858 <span className="flex items-center gap-1.5 font-mono text-xs">
859 <Bot size={14} />
860 {pull.agent}
861 {pull.runtime === "hosted" && <span className="text-faint">on g1t</span>}
862 </span>
863 )}
Issues and pull requests replace intents and attempts864 </div>
865
Acceptance checks in sandboxes, line comments and review verdicts866 {reviews.length > 0 && (
867 <p className="mt-3 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm">
868 {reviews.map(({ reviewer, verdict }) => (
869 <span
870 key={reviewer}
871 className={`flex items-center gap-1.5 ${
Merge branch 'worktree-agent-ad7c6d88d93adc817'872 verdict === "approve" ? "text-success" : "text-danger"
Acceptance checks in sandboxes, line comments and review verdicts873 }`}
874 >
875 {verdict === "approve" ? <CircleCheck size={15} /> : <CircleSlash size={15} />}
876 {verdict === "approve" ? "Approved by" : "Changes requested by"}{" "}
877 <span className="font-medium">{reviewer}</span>
878 </span>
879 ))}
880 </p>
881 )}
882
Usage, Billing settings and prepaid AI credit; fixes from the UX audit883 <TabStrip label="Pull request" className="mt-6 items-end gap-1 border-b border-line">
Issues and pull requests replace intents and attempts884 <TabLink to={here} active={tab === "conversation"}>
885 <MessageSquare size={15} />
886 Conversation
Pull requests: title and tabs across the full width, with counts and the size of the change887 <TabCount n={comments.length} />
Issues and pull requests replace intents and attempts888 </TabLink>
889 <TabLink to={here + "?tab=session"} active={tab === "session"}>
890 <MessagesSquare size={15} />
891 Session
892 </TabLink>
893 <TabLink to={here + "?tab=changes"} active={tab === "changes"}>
894 <FileDiff size={15} />
Pull requests: title and tabs across the full width, with counts and the size of the change895 Files changed
896 <TabCount n={pull.files.length} />
Issues and pull requests replace intents and attempts897 </TabLink>
Pull requests: title and tabs across the full width, with counts and the size of the change898 {pull.files.length > 0 && (
899 <span className="ml-auto hidden pb-2.5 font-mono text-xs sm:inline">
Merge branch 'worktree-agent-ad7c6d88d93adc817'900 <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 change901 <span className="text-danger">−{pull.files.reduce((sum, file) => sum + file.deletions, 0).toLocaleString("en-US")}</span>
902 </span>
903 )}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit904 </TabStrip>
Pull requests: title and tabs across the full width, with counts and the size of the change905 </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)906 <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 change907 <div className="min-w-0">
908 {/* What is happening on it now: its stage, its agent, what people told
909 the agent, and what else touches the same files. On the conversation,
910 so the tabs sit right under the title. */}
911 {tab === "conversation" && (
912 <div>
913 {issue && (
914 <Link
915 to={`${base}/issues/${issue.number}`}
916 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"
917 >
918 <IssueIcon issue={issue} />
919 <span className="text-muted">
920 {pull.status === "merged" && issue.resolvedBy === pull.number
921 ? "Resolved"
922 : "For issue"}
923 </span>
924 <span className="truncate font-medium">{issue.title}</span>
925 <span className="text-faint">#{issue.number}</span>
926 {issue.pullCount > 1 && (
927 <span className="ml-auto shrink-0 text-xs text-faint">
928 1 of {issue.pullCount} pull requests
Pull requests show their tabs right under the title; agent panels move into Conversation929 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change930 )}
931 </Link>
932 )}
933 {pull.status === "merged" && (
934 <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">
935 <GitMerge size={16} className="shrink-0 text-merged" />
Agents as a team: lifecycle, merge queue, billing and a new shell936 <span>
Pull requests: title and tabs across the full width, with counts and the size of the change937 Merged into <span className="font-mono">{defaultBranch}</span> by{" "}
938 <span className="font-medium">{pull.mergedBy}</span>{" "}
939 {/* Not an account: the repository's settings said to merge it. */}
940 {pull.mergedBy === "g1t" && "automatically, once it was ready, "}
941 {pull.mergedAt && <TimeAgo at={pull.mergedAt} />}
942 {pull.headCommit && (
943 <span className="font-mono text-muted"> · {pull.headCommit.slice(0, 7)}</span>
Agents as a team: lifecycle, merge queue, billing and a new shell944 )}
945 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change946 </p>
947 )}
948 {pull.supersededBy != null && (
949 <p className="mt-4 rounded-xl border border-line bg-surface px-4 py-2.5 text-sm text-muted">
950 Closed because{" "}
951 <Link
952 to={`${base}/pull/${pull.supersededBy}`}
953 className="font-medium text-fg hover:underline"
954 >
955 #{pull.supersededBy}
956 </Link>{" "}
957 was merged for this issue instead.
958 </p>
959 )}
960 {lifecycle && <LifecyclePanel lifecycle={lifecycle} />}
961 {/* 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 step962 <AgentPanel
963 owner={params.owner}
964 repo={params.repo}
965 number={pull.number}
966 stage={lifecycle?.stage ?? null}
967 confidence={pull.confidence ?? null}
Merge branch 'worktree-agent-aca4790ea223aa235'968 runs={agentRuns}
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step969 />
Pull requests: title and tabs across the full width, with counts and the size of the change970
971 {/* 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 look972 {canRun &&
Pull requests: title and tabs across the full width, with counts and the size of the change973 pull.runtime === "hosted" &&
974 (working || ["working", "revising", "catching_up", "answering"].includes(lifecycle?.stage ?? "")) && (
Merge branch 'worktree-agent-a985c01f43538af01'975 // Keyed by the messages so the box empties once one shows below.
Merge branch 'worktree-agent-ad7c6d88d93adc817'976 <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 change977 <p className="flex items-center gap-2 text-sm font-medium">
Merge branch 'worktree-agent-ad7c6d88d93adc817'978 <Sparkles size={15} className="text-accent" />
Pull requests: title and tabs across the full width, with counts and the size of the change979 Message the agent
980 </p>
981 <p className="mt-1 text-xs text-muted">
982 A correction, a hint, a change of plan. It reads it at its next step, without
983 starting over.
984 </p>
985 <div className="mt-3 flex gap-2">
986 <input type="hidden" name="action" value="message" />
987 <input
988 name="body"
989 required
990 autoComplete="off"
991 data-1p-ignore
992 placeholder="Keep the old flag working too…"
Merge branch 'worktree-agent-ad7c6d88d93adc817'993 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 shell994 />
Merge branch 'worktree-agent-a985c01f43538af01'995 <SubmitButton match={{ action: "message" }} pending="Sending…">
996 Send
997 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change998 </div>
999 </Form>
Agents as a team: lifecycle, merge queue, billing and a new shell1000 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1001 {messages.length > 0 && (
1002 <ul className="mt-3 space-y-1.5">
1003 {messages.map((message) => (
1004 <li key={message.id} className="flex items-start gap-2 text-sm">
1005 <Avatar name={message.author} size={18} />
1006 <span className="min-w-0 grow">
1007 <span className="font-medium">
1008 {message.fromNumber != null ? `The agent on #${message.fromNumber}` : message.author}
1009 </span>{" "}
1010 <span className="text-muted">
1011 {message.kind === "question"
1012 ? "asked:"
1013 : message.kind === "handoff"
1014 ? "handed over:"
1015 : message.kind === "answer"
1016 ? "answered:"
1017 : "to the agent:"}
1018 </span>{" "}
1019 {message.body}
1020 {message.answer && (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1021 <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 change1022 {message.declined ? "Declined: " : "Answer: "}
1023 {message.answer}
1024 </span>
1025 )}
1026 </span>
Merge branch 'worktree-agent-ad7c6d88d93adc817'1027 <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 change1028 {message.deliveredAt ? "read by the agent" : "waiting for its next step"}
1029 </span>
1030 </li>
1031 ))}
1032 </ul>
1033 )}
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md1034 {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 change1035 {collisions.length > 0 && active && (
1036 <div className="mt-4 rounded-xl border border-line bg-surface px-4 py-3 text-sm">
1037 <p className="flex items-center gap-2.5 font-medium">
1038 <Radar size={16} className="shrink-0 text-info" />
1039 Other work is changing the same files
1040 </p>
1041 <ul className="mt-2 space-y-1.5">
1042 {collisions.map((other) => (
1043 <li key={other.number} className="flex flex-wrap items-baseline gap-x-2 text-muted">
1044 <Link
1045 to={`${base}/pull/${other.number}`}
1046 className="font-medium text-fg hover:underline"
1047 >
1048 {other.title} <span className="font-normal text-faint">#{other.number}</span>
1049 </Link>
1050 <span className="font-mono text-xs">{other.paths.join(", ")}</span>
1051 </li>
1052 ))}
1053 </ul>
1054 <p className="mt-2 text-xs text-faint">
1055 Whichever merges second will have to catch up, and may conflict.
1056 </p>
1057 </div>
1058 )}
1059 </div>
1060 )}
1061 <div className="mt-5">
1062 {comparison ? (
1063 <DiffView
1064 comparison={comparison}
1065 review={{
1066 comments: comments.filter((comment) => comment.path),
1067 canComment: Boolean(viewer),
1068 }}
1069 />
1070 ) : tab === "session" ? (
1071 session.length === 0 ? (
1072 <EmptyState title="Nothing recorded yet">
1073 The agent's prompts, reasoning and tool calls appear here as it
1074 works.
1075 </EmptyState>
1076 ) : (
1077 <ol className="relative space-y-5 before:absolute before:top-2 before:bottom-2 before:left-3.25 before:w-px before:bg-line">
1078 {session.map((entry) => (
1079 <Entry key={entry.seq} entry={entry} agent={pull.agent} />
1080 ))}
1081 </ol>
1082 )
1083 ) : (
1084 <div className="space-y-4">
1085 <TimelineItem
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1086 author={opener.name}
Pull requests: title and tabs across the full width, with counts and the size of the change1087 at={pull.createdAt}
1088 action={
1089 <span>
1090 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-agent1091 {opener.requestedBy ? (
Pull requests: title and tabs across the full width, with counts and the size of the change1092 <>
1093 {" "}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1094 for{" "}
1095 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
1096 </>
1097 ) : !pull.branch && (
1098 <>
1099 {" "}
Pull requests: title and tabs across the full width, with counts and the size of the change1100 with <span className="font-mono text-xs">{pull.agent}</span>
1101 </>
1102 )}
1103 </span>
1104 }
1105 >
1106 {pull.body ? (
1107 <Markdown source={pull.body} repo={{ namespace: params.owner, name: params.repo }} />
1108 ) : (
1109 <p className="text-sm text-muted">
1110 {working
1111 ? "No description yet. It is written when the pull request is marked ready for review."
1112 : "No description."}
1113 </p>
1114 )}
1115 {canManage && pull.status === "open" && (
1116 <details className="mt-3 border-t border-line pt-3 text-sm">
1117 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1118 Edit description
1119 </summary>
1120 <Form method="post" className="mt-3 space-y-2">
Merge branch 'worktree-agent-a985c01f43538af01'1121 <input type="hidden" name="action" value="describe" />
Pull requests: title and tabs across the full width, with counts and the size of the change1122 <Textarea
1123 name="summary"
1124 rows={6}
1125 placeholder="What changed and why"
1126 defaultValue={pull.body ?? ""}
1127 />
Merge branch 'worktree-agent-a985c01f43538af01'1128 <SubmitButton match={{ action: "describe" }} pending="Saving…">
1129 Save
1130 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1131 </Form>
1132 </details>
1133 )}
1134 </TimelineItem>
Agents as a team: lifecycle, merge queue, billing and a new shell1135
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1136 <CommentList
1137 comments={comments}
1138 review={review}
1139 base={base}
1140 viewerId={viewer?.id ?? null}
1141 canModerate={canModerate}
1142 failed={actionData && "comment" in actionData ? { comment: String(actionData.comment), error: actionData.error } : null}
1143 />
Agents as a team: lifecycle, merge queue, billing and a new shell1144
Merge branch 'worktree-agent-aca4790ea223aa235'1145 {(preview || build) && <DeploymentCard preview={preview} build={build} stacked={stacked} stacking={affects.length > 0} base={base} />}
Pull requests say where they are live; the project menu shows what is coming1146
Pull requests: title and tabs across the full width, with counts and the size of the change1147 {pull.status === "draft" && (
1148 <StatusBox>
1149 <StatusRow icon={<Loader size={16} className="text-faint" />} title="This is a draft">
1150 It is still being worked on. It can be reviewed and merged once it is
1151 marked ready.
1152 </StatusRow>
1153 {canManage && (
1154 <Form method="post" className="space-y-2 px-4 py-3">
Merge branch 'worktree-agent-a985c01f43538af01'1155 <input type="hidden" name="action" value="ready" />
Pull requests: title and tabs across the full width, with counts and the size of the change1156 <Textarea name="summary" rows={3} placeholder="What changed and why" />
Merge branch 'worktree-agent-a985c01f43538af01'1157 <SubmitButton match={{ action: "ready" }} pending="Marking ready…">
1158 Mark ready for review
1159 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1160 </Form>
1161 )}
1162 </StatusBox>
1163 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1164
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1165 {codeOwners && active && (
1166 <div className="flex gap-3">
1167 <span className="hidden w-8 shrink-0 sm:block" />
1168 <div className="min-w-0 grow">
1169 <PullCodeOwnersPanel
1170 owners={codeOwners}
1171 base={base}
1172 branch={defaultBranch}
1173 errorsHref={canProtect ? `${base}/settings/branches#codeowners` : `${base}/blob/${encodeURIComponent(defaultBranch)}/${codeOwners.path}`}
1174 />
1175 </div>
1176 </div>
1177 )}
1178
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md1179 {(pull.status === "merged" || pull.status === "closed") && statuses.length > 0 && (
1180 <StatusBox>
1181 <Suspense fallback={settledChecks({}, true)}>
1182 <Await resolve={workflowJobs}>{(jobs) => settledChecks(jobs)}</Await>
1183 </Suspense>
1184 </StatusBox>
1185 )}
1186
Pull requests: title and tabs across the full width, with counts and the size of the change1187 {pull.status === "open" && (
1188 <StatusBox>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1189 {/* Checks by status at once; job by job when the runs are read. */}
Merge branch 'worktree-agent-aca4790ea223aa235'1190 <Suspense fallback={checksSection({}, true)}>
1191 <Await resolve={workflowJobs}>{(jobs) => checksSection(jobs)}</Await>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1192 </Suspense>
1193 {noChecks && <AddCiPrompt owner={params.owner} repo={params.repo} canAdd={canAddCi} compact />}
Agents as a team: lifecycle, merge queue, billing and a new shell1194 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1195 icon={
1196 reviews.some(({ verdict }) => verdict === "request_changes") ? (
1197 <CircleSlash size={16} className="text-danger" />
1198 ) : reviews.length > 0 ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1199 <CircleCheck size={16} className="text-success" />
Pull requests: title and tabs across the full width, with counts and the size of the change1200 ) : (
1201 <MessageSquare size={16} className="text-faint" />
1202 )
1203 }
1204 title={
1205 reviews.length === 0
1206 ? "No reviews yet"
1207 : reviews
1208 .map(
1209 ({ reviewer, verdict }) =>
1210 `${verdict === "approve" ? "Approved by" : "Changes requested by"} ${reviewer}`,
1211 )
1212 .join(" · ")
1213 }
Agents as a team: lifecycle, merge queue, billing and a new shell1214 >
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1215 {reviewPending && "g1t is reviewing it now. "}
Pull requests: title and tabs across the full width, with counts and the size of the change1216 {requiredApprovals > 0 &&
1217 `This repository requires ${requiredApprovals} approving ${
1218 requiredApprovals === 1 ? "review" : "reviews"
1219 } before merging.`}
Agents as a team: lifecycle, merge queue, billing and a new shell1220 </StatusRow>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1221 {codeOwners?.required && (
1222 <StatusRow
1223 icon={
1224 ownersMissing ? (
1225 <CircleSlash size={16} className="text-warn" />
1226 ) : (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1227 <CircleCheck size={16} className="text-success" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1228 )
1229 }
1230 title={ownersMissing ? "Waiting for code owners" : "Code owners approved"}
1231 >
1232 {ownersMissing ?? `Every file it changes has its owners' approval.`}{" "}
1233 {ownersMissing && "This repository requires their review before merging."}
1234 </StatusRow>
1235 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1236 {landing ? (
Agents as a team: lifecycle, merge queue, billing and a new shell1237 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1238 icon={<Loader size={16} className="animate-spin text-accent" />}
1239 title="Merging"
1240 >
1241 {defaultBranch} has moved, so g1t is bringing this up to date first. It
1242 lands as soon as that is done. Watch it in the Session tab.
1243 </StatusRow>
Catching up with main takes seconds when the two sides touched different files1244 ) : agentCatchUp && behind ? (
1245 <CatchUpProgress
1246 owner={params.owner}
1247 repo={params.repo}
1248 number={pull.number}
1249 defaultBranch={defaultBranch}
1250 behind={behind}
1251 update={agentCatchUp.update}
1252 startedAt={agentCatchUp.startedAt}
1253 retrying={catchUpPending}
Merge branch 'worktree-agent-aca4790ea223aa235'1254 runs={agentRuns}
Catching up with main takes seconds when the two sides touched different files1255 />
Pull requests: title and tabs across the full width, with counts and the size of the change1256 ) : conflicting ? (
1257 <ConflictsSection
1258 conflicts={conflicts}
1259 pull={pull}
1260 owner={params.owner}
1261 repo={params.repo}
1262 defaultBranch={defaultBranch}
1263 changesUrl={here + "?tab=changes"}
1264 canResolve={canUpdate && agentsEnabled}
Catching up with main takes seconds when the two sides touched different files1265 resolving={catchUpPending}
1266 error={catchUp && "error" in catchUp ? catchUp.error : null}
Agents as a team: lifecycle, merge queue, billing and a new shell1267 />
Pull requests: title and tabs across the full width, with counts and the size of the change1268 ) : probing ? (
1269 <MergeabilityRow mergeable={mergeable} defaultBranch={defaultBranch} />
1270 ) : behind ? (
1271 <StatusRow
1272 icon={<GitPullRequestArrow size={16} className="text-info" />}
1273 title={`${defaultBranch} has moved since this was made`}
1274 >
1275 {mergeable === "clean" && "It has no conflicts with it. "}
1276 {requireUpToDate
1277 ? "This repository requires pull requests to be up to date, so it has to catch up before it can merge."
1278 : "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 files1279 {canUpdate && (
1280 <Form method="post" className="mt-2">
Pull requests: title and tabs across the full width, with counts and the size of the change1281 <input type="hidden" name="action" value="update" />
Merge branch 'worktree-agent-a985c01f43538af01'1282 <SubmitButton variant="quiet" match={{ action: "update" }} pending={`Merging ${defaultBranch} in…`}>
1283 Catch up with {defaultBranch} now
1284 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1285 </Form>
1286 )}
Catching up with main takes seconds when the two sides touched different files1287 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Pull requests: title and tabs across the full width, with counts and the size of the change1288 </StatusRow>
1289 ) : (
Merge branch 'worktree-agent-af9f0f386d52b6a73'1290 (pull.headCommit || (catchUp && "error" in catchUp)) && (
Pull requests: title and tabs across the full width, with counts and the size of the change1291 <StatusRow
Merge branch 'worktree-agent-ad7c6d88d93adc817'1292 icon={<CircleCheck size={16} className="text-success" />}
Catching up with main takes seconds when the two sides touched different files1293 title={
1294 (caughtUp && !caughtUp.already) || agentCatchUp
1295 ? `Brought up to date with ${defaultBranch}`
1296 : `Up to date with ${defaultBranch}`
1297 }
1298 >
1299 {caughtUp && !caughtUp.already && (
1300 <>
1301 Merged <span className="font-mono">{defaultBranch}</span> in as{" "}
1302 <span className="font-mono text-fg">{caughtUp.commit.slice(0, 7)}</span>. Its checks run
1303 again on the new commit.
1304 </>
1305 )}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1306 {agentCatchUp && "g1t merged it in and pushed the result. Its checks run again on the new commit."}
Merge branch 'worktree-agent-af9f0f386d52b6a73'1307 {/* A catch-up refused or failed after the pull request stopped being behind. */}
1308 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Catching up with main takes seconds when the two sides touched different files1309 </StatusRow>
Pull requests: title and tabs across the full width, with counts and the size of the change1310 )
1311 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1312 {active && rules && (rulesUnmet.length > 0 || rulesBypassable.length > 0 || rules.evaluate.length > 0) && (
1313 <StatusRow
1314 icon={
1315 <Scale
1316 size={16}
1317 className={rulesUnmet.length > 0 ? "text-danger" : rulesBypassable.length > 0 ? "text-warn" : "text-info"}
1318 />
1319 }
1320 title={
1321 rulesUnmet.length > 0
1322 ? `Rules for ${defaultBranch} are not met yet`
1323 : rulesBypassable.length > 0
1324 ? `You may bypass the rules for ${defaultBranch}`
1325 : "Rulesets being evaluated would refuse this merge"
1326 }
1327 >
1328 <div className="mt-2 space-y-3">
1329 {rulesUnmet.length > 0 && <ViolationList violations={rulesUnmet} />}
1330 {rulesBypassable.length > 0 && (
1331 <div>
1332 <p className="mb-1.5 text-xs text-muted">Not met, but a ruleset lets you bypass it:</p>
1333 <ViolationList violations={rulesBypassable} tone="warn" />
1334 </div>
1335 )}
1336 {rules.evaluate.length > 0 && (
1337 <div>
1338 <p className="mb-1.5 text-xs text-muted">Rulesets in evaluate would refuse it for this; nothing is held up:</p>
1339 <ViolationList violations={rules.evaluate} tone="info" />
1340 </div>
1341 )}
1342 {canProtect && (
1343 <Link
1344 to={`${base}/settings/rules?branch=${encodeURIComponent(defaultBranch)}`}
1345 className="inline-block text-xs text-muted underline underline-offset-2 hover:text-fg"
1346 >
1347 See every rule for {defaultBranch}
1348 </Link>
1349 )}
1350 </div>
1351 </StatusRow>
1352 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1353 {stalled && !lifecycle && (
1354 <StatusRow icon={<Hand size={16} className="text-warn" />} title="Needs you">
1355 {stalled}
1356 </StatusRow>
1357 )}
1358 {canMerge && lifecycle?.stage === "queued" && (
1359 <Form method="post" className="flex flex-wrap items-center gap-3 px-4 py-3">
1360 <ButtonLink to={`${base}/queue`}>
1361 <Layers size={15} />
1362 See the queue
1363 </ButtonLink>
Merge branch 'worktree-agent-a985c01f43538af01'1364 <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 change1365 Remove from the queue
Merge branch 'worktree-agent-a985c01f43538af01'1366 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1367 </Form>
1368 )}
1369 {canMerge && !landing && lifecycle?.stage !== "queued" && (
1370 <Form method="post" className="space-y-3 px-4 py-3">
1371 {issue?.state === "open" && (
1372 <CheckboxOption
1373 name="keepIssueOpen"
1374 label={`Keep #${issue.number} open. This is only part of the work.`}
1375 labelClassName="text-xs text-muted"
1376 />
1377 )}
1378 {unchecked && canIgnoreChecks && (
1379 <CheckboxOption
1380 name="ignoreChecks"
Fast pages, required checks on the branch, self-hosted runners, honest incidents1381 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 change1382 labelClassName="text-xs text-muted"
1383 />
1384 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1385 {rulesBypassable.length > 0 && rulesUnmet.length === 0 && (
1386 <CheckboxOption
1387 name="bypassRules"
1388 checked={bypassing}
1389 onCheckedChange={(checked) => setBypassing(checked === true)}
1390 label={`Bypass the rules for ${defaultBranch}: merge although they are not met. It is recorded.`}
1391 labelClassName="text-xs text-muted"
1392 />
1393 )}
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)1394 <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'1395 <Hint label={mergeBlocked} disabled={mergeBlocked != null}>
1396 <SubmitButton
1397 variant="accent"
1398 name="action"
1399 value="merge"
1400 disabled={mergeBlocked != null}
1401 pending={mergeQueue ? "Adding to the queue…" : "Merging…"}
1402 >
1403 {mergeQueue ? <Layers size={15} /> : <GitMerge size={15} />}
1404 {mergeQueue ? "Add to the merge queue" : `Merge into ${defaultBranch}`}
1405 </SubmitButton>
1406 </Hint>
Pull requests: title and tabs across the full width, with counts and the size of the change1407 <span className={`text-xs ${mergeBlocked ? "text-danger" : "text-muted"}`}>
1408 {mergeBlocked ?? (mergeQueue
1409 ? `Tested together with everything ahead of it, then lands on ${defaultBranch}.`
1410 : issue?.state === "open"
1411 ? `Closes issue #${issue.number}, and any other pull requests still open for it.`
1412 : `Lands its commits on ${defaultBranch}.`)}
1413 </span>
1414 </div>
1415 {actionData?.action === "merge" && (
1416 <ErrorText>{actionData.error}</ErrorText>
1417 )}
1418 </Form>
1419 )}
1420 </StatusBox>
1421 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1422
Pull requests: title and tabs across the full width, with counts and the size of the change1423 <CommentForm
1424 author={viewer?.username ?? null}
1425 resetKey={comments.length}
1426 review={review}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1427 handles={teams.map((team) => `@${team.ref}`)}
Pull requests: title and tabs across the full width, with counts and the size of the change1428 />
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1429 {canManage && (active || pull.status === "closed") && (
1430 <Form method="post" className="flex flex-wrap justify-end gap-2">
1431 {pull.status === "open" && (
1432 <SubmitButton variant="quiet" name="action" value="draft" pending="Converting…">
1433 Convert to draft
1434 </SubmitButton>
1435 )}
1436 {active ? (
1437 <SubmitButton variant="quiet" name="action" value="close" pending="Closing…">
1438 Close pull request
1439 </SubmitButton>
1440 ) : (
1441 <SubmitButton variant="quiet" name="action" value="reopen" pending="Reopening…">
1442 Reopen pull request
1443 </SubmitButton>
1444 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1445 </Form>
1446 )}
1447 {actionData &&
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1448 !["merge", "comment", "stack", "rerun-failed", "rerun-workflow", "update", "agent-review", "reviewers", "assign", "edit-comment", "delete-comment"].includes(
Pull requests: title and tabs across the full width, with counts and the size of the change1449 String(actionData.action),
1450 ) && <ErrorText>{actionData.error}</ErrorText>}
1451 </div>
1452 )}
1453 {actionData?.action === "comment" && (
1454 <div className="mt-2">
1455 <ErrorText>{actionData.error}</ErrorText>
1456 </div>
1457 )}
1458 </div>
1459 </div>
1460
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)1461 <DetailsDisclosure
1462 className={tab === "changes" ? "hidden" : "order-first lg:order-none"}
1463 summary={detailsSummary([
1464 [reviewerNames.length + teamReviewers.length, "reviewer", "reviewers"],
1465 [pull.assignees.length, "assignee", "assignees"],
1466 [(pull.labels ?? []).length, "label", "labels"],
1467 ])}
1468 >
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md1469 {/* Other than a draft, a pull request shows its checks in full in the conversation. */}
1470 {pull.status === "draft" && <WorkflowStatuses statuses={statuses} />}
Pull requests: title and tabs across the full width, with counts and the size of the change1471 {affects.length > 0 && (
1472 <section>
1473 <h3 className="flex items-center gap-1.5 text-sm font-medium">
1474 <Network size={14} className="text-faint" />
1475 Affects
1476 </h3>
1477 <p className="mt-1 text-xs text-muted">Projects that use this one, and so may feel this change:</p>
1478 <ul className="mt-2 space-y-1 text-sm">
1479 {affects.map((project) => (
1480 <li key={project.slug} className="flex items-center justify-between gap-2">
1481 <Link to={`/${params.owner}/${project.slug}`} className="hover:underline">
1482 {project.name}
1483 </Link>
1484 {project.as && <code className="font-mono text-xs text-faint">{project.as}</code>}
1485 </li>
1486 ))}
1487 </ul>
1488 {preview?.branch && canMerge && (
1489 <Form method="post" className="mt-3">
1490 <input type="hidden" name="action" value="stack" />
1491 <input type="hidden" name="branch" value={preview.branch} />
Merge branch 'worktree-agent-a985c01f43538af01'1492 <SubmitButton
1493 match={{ action: "stack" }}
1494 pending="Starting the builds…"
1495 className="flex w-full items-center justify-center gap-1.5 rounded-md border border-line px-3 py-1.5 text-xs text-muted transition-colors hover:border-line-strong hover:text-fg disabled:opacity-50"
Pull requests: title and tabs across the full width, with counts and the size of the change1496 >
1497 Preview them against this change
Merge branch 'worktree-agent-a985c01f43538af01'1498 </SubmitButton>
Agents as a team: lifecycle, merge queue, billing and a new shell1499 </Form>
1500 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1501 {actionData?.action === "stack" &&
1502 ("notice" in actionData ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1503 <p className="mt-2 text-xs text-success">{String(actionData.notice)}</p>
Pull requests: title and tabs across the full width, with counts and the size of the change1504 ) : (
1505 <ErrorText>{actionData.error}</ErrorText>
1506 ))}
1507 </section>
Acceptance checks in sandboxes, line comments and review verdicts1508 )}
Issues and pull requests replace intents and attempts1509
Project dependencies: addresses, preview stacks, Affects, and agents who know1510 <section>
Pull requests: title and tabs across the full width, with counts and the size of the change1511 <h3 className="text-sm font-medium">Reviewers</h3>
1512 <ul className="mt-2 space-y-1.5 text-sm">
1513 {reviewerNames.map((name) => {
1514 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-agent1515 const pending = name === "g1t" && reviewPending;
Pull requests: title and tabs across the full width, with counts and the size of the change1516 return (
1517 <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-agent1518 {name === "g1t" ? (
Pull requests: title and tabs across the full width, with counts and the size of the change1519 <Sparkles size={16} className="shrink-0 text-accent" />
1520 ) : (
1521 <Avatar name={name} size={20} />
1522 )}
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)1523 <span className="grow truncate font-mono text-xs">
1524 <PersonLink name={name} className="hover:underline" />
1525 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change1526 {pending ? (
1527 <span className="flex items-center gap-1.5 text-xs text-muted">
1528 <span className="size-1.5 animate-pulse rounded-full bg-accent" />
1529 Reviewing
1530 </span>
1531 ) : verdict === "approve" ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1532 <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 change1533 <CircleCheck size={13} /> Approved
1534 </span>
1535 ) : verdict === "request_changes" ? (
1536 <span className="flex items-center gap-1 text-xs text-danger">
1537 <CircleSlash size={13} /> Changes requested
1538 </span>
1539 ) : (
1540 <span className="text-xs text-faint">Review requested</span>
1541 )}
1542 </li>
1543 );
1544 })}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1545 {teamReviewers.map((team) => (
1546 <TeamReviewer key={team} team={team} />
1547 ))}
1548 {reviewerNames.length === 0 && teamReviewers.length === 0 && (
Pull requests: title and tabs across the full width, with counts and the size of the change1549 <li className="px-1 text-xs text-faint">No reviews requested yet.</li>
1550 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1551 </ul>
Pull requests: title and tabs across the full width, with counts and the size of the change1552 {pull.status === "open" && canManage && (
1553 <div className="mt-3 space-y-2">
1554 {agentsEnabled && !reviewPending && (
1555 <Form method="post">
1556 <input type="hidden" name="action" value="agent-review" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1557 {[...pull.reviewers, ...teamReviewers].map((name) => (
Pull requests: title and tabs across the full width, with counts and the size of the change1558 <input key={name} type="hidden" name="reviewer" value={name} />
1559 ))}
1560 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1561 <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 change1562 <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-agent1563 Request review from g1t
Merge branch 'worktree-agent-a985c01f43538af01'1564 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1565 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1566 <ComputeNote note={loaderData.computeNote} />
Pull requests: title and tabs across the full width, with counts and the size of the change1567 </Form>
1568 )}
1569 <details>
1570 <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 bar1571 {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 change1572 </summary>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1573 <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 change1574 <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-agent1575 {pull.reviewers.includes("g1t") && (
1576 <input type="hidden" name="reviewer" value="g1t" />
Pull requests: title and tabs across the full width, with counts and the size of the change1577 )}
1578 <PeoplePicker
1579 name="reviewer"
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1580 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-agent1581 chosen={pull.reviewers.filter((name) => name !== "g1t")}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1582 placeholder="Other usernames or teams (acme/backend)"
Pull requests: title and tabs across the full width, with counts and the size of the change1583 />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1584 {teamChoices.length > 0 && (
1585 <div className="space-y-1.5 border-t border-line pt-2">
1586 <p className="text-xs text-faint">Teams</p>
1587 {teamChoices.map((team) => (
1588 <CheckboxOption
1589 key={team.ref}
1590 name="reviewer"
1591 value={team.ref}
1592 defaultChecked={teamReviewers.includes(team.ref)}
1593 className="items-center"
1594 labelClassName="flex min-w-0 items-center gap-2"
1595 label={
1596 <>
1597 <Users size={14} className="shrink-0 text-faint" />
1598 <span className="truncate font-mono text-xs">@{team.ref}</span>
1599 </>
1600 }
1601 />
1602 ))}
1603 </div>
1604 )}
Merge branch 'worktree-agent-a985c01f43538af01'1605 <SubmitButton variant="quiet" match={{ action: "reviewers" }} pending="Saving…">
Pull requests: title and tabs across the full width, with counts and the size of the change1606 Save reviewers
Merge branch 'worktree-agent-a985c01f43538af01'1607 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1608 </Form>
1609 </details>
1610 </div>
Project dependencies: addresses, preview stacks, Affects, and agents who know1611 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1612 {["agent-review", "reviewers"].includes(String(actionData?.action)) && (
1613 <ErrorText>{actionData?.error}</ErrorText>
1614 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1615 </section>
Acceptance checks in sandboxes, line comments and review verdicts1616
Pull requests: title and tabs across the full width, with counts and the size of the change1617 <section>
1618 <h3 className="text-sm font-medium">Assignees</h3>
1619 <ul className="mt-2 space-y-1.5 text-sm">
1620 {pull.assignees.map((name) => (
Agents as a team: lifecycle, merge queue, billing and a new shell1621 <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 change1622 <Avatar name={name} size={20} />
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)1623 <span className="grow truncate font-mono text-xs">
1624 <PersonLink name={name} className="hover:underline" />
1625 </span>
Agents as a team: lifecycle, merge queue, billing and a new shell1626 </li>
Pull requests: title and tabs across the full width, with counts and the size of the change1627 ))}
1628 {pull.assignees.length === 0 && (
1629 <li className="px-1 text-xs text-faint">No one yet.</li>
1630 )}
1631 </ul>
1632 {viewer && canManage && active && (
1633 <div className="mt-3 space-y-2">
1634 {!pull.assignees.includes(viewer.username) && (
1635 <Form method="post">
1636 <input type="hidden" name="action" value="assign" />
1637 {pull.assignees.map((name) => (
1638 <input key={name} type="hidden" name="assignee" value={name} />
1639 ))}
1640 <input type="hidden" name="assignee" value={viewer.username} />
1641 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1642 <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 change1643 Assign yourself
Merge branch 'worktree-agent-a985c01f43538af01'1644 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1645 </div>
1646 </Form>
1647 )}
1648 <details>
1649 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1650 Assign people
1651 </summary>
1652 <Form method="post" className="mt-2 space-y-2" key={pull.assignees.join()}>
1653 <input type="hidden" name="action" value="assign" />
1654 <PeoplePicker name="assignee" members={members} chosen={pull.assignees} />
Merge branch 'worktree-agent-a985c01f43538af01'1655 <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 change1656 Save assignees
Merge branch 'worktree-agent-a985c01f43538af01'1657 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1658 </Form>
1659 </details>
1660 </div>
Issues and pull requests replace intents and attempts1661 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1662 {actionData?.action === "assign" && <ErrorText>{actionData.error}</ErrorText>}
1663 </section>
Issues and pull requests replace intents and attempts1664
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1665 <LabelsBox
1666 labels={loaderData.labels}
1667 chosen={pull.labels ?? []}
1668 canEdit={canManage && active}
Merge main (membership, two-factor, GitHub repo roles) into tokens1669 canCreate={loaderData.canMakeLabels}
1670 manageUrl={loaderData.canMakeLabels ? `${base}/labels` : undefined}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1671 />
1672 <MilestoneBox
1673 milestones={loaderData.milestones}
1674 current={pull.milestone}
1675 canEdit={loaderData.canTriage}
1676 base={base}
1677 />
1678
Pull requests: title and tabs across the full width, with counts and the size of the change1679 <section>
1680 <h3 className="text-sm font-medium">Working copy</h3>
1681 {pull.branch ? (
1682 <>
1683 <p className="mt-1 text-xs text-muted">
1684 <GitBranch size={13} className="mr-1 inline align-[-2px]" />
1685 Branch <span className="font-mono text-fg">{pull.branch}</span> of this
1686 repository. Pushes to it show up here.
1687 </p>
1688 <div className="mt-2">
1689 <CopyLine text={`git clone -b ${pull.branch} ${remote}`} />
1690 </div>
1691 </>
1692 ) : (
1693 <>
1694 <p className="mt-1 text-xs text-muted">
1695 This fork belongs to the pull request. Pushes to it show up here.
1696 </p>
1697 <div className="mt-2">
1698 <CopyLine text={`git clone ${remote}`} />
1699 </div>
1700 </>
1701 )}
1702 <p className="mt-3 flex items-center gap-1.5 font-mono text-xs text-faint">
1703 <GitCommitHorizontal size={13} />
Merge checks: statuses and check runs on every commit1704 {pull.headCommit ? (
1705 <Link to={`${base}/commit/${pull.headCommit}`} className="hover:text-fg">
1706 {pull.headCommit.slice(0, 12)}
1707 </Link>
1708 ) : (
1709 "no commits pushed yet"
1710 )}
1711 {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 change1712 </p>
1713 </section>
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings1714 {loaderData.viewer && (
1715 <SubscriptionBox action={`${base}/notifications`} number={pull.number} kind="pull" subscription={loaderData.subscription} />
1716 )}
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)1717 </DetailsDisclosure>
Pull requests: title and tabs across the full width, with counts and the size of the change1718 </div>
Issues and pull requests replace intents and attempts1719 </div>
1720 );
1721}
Pull requests say where they are live; the project menu shows what is coming1722
1723/** Where the pull request's change is live, as GitHub shows a deployment. */
1724function DeploymentCard({
1725 preview,
1726 build,
1727 stacked,
Merge branch 'worktree-agent-aca4790ea223aa235'1728 stacking,
Pull requests say where they are live; the project menu shows what is coming1729 base,
1730}: {
1731 preview: LiveApp | null;
1732 build: Deployment | null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents1733 stacked: Promise<Stacked[]>;
Merge branch 'worktree-agent-aca4790ea223aa235'1734 /** Projects use this one, so previews built against it may be on their way. */
1735 stacking: boolean;
Pull requests say where they are live; the project menu shows what is coming1736 base: string;
1737}) {
1738 const building = build?.status === "queued" || build?.status === "building";
1739 const failed = build?.status === "failed";
1740 return (
1741 <section className="ml-12 overflow-hidden rounded-xl border border-line bg-surface">
1742 <div className="flex flex-wrap items-center gap-3 px-4 py-3">
1743 <span
1744 className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ${
Merge branch 'worktree-agent-ad7c6d88d93adc817'1745 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 coming1746 }`}
1747 >
1748 {building ? <Loader size={15} className="animate-spin" /> : <Rocket size={15} />}
1749 </span>
1750 <div className="min-w-0 grow">
1751 <p className="text-sm font-medium">
1752 {building
1753 ? preview
1754 ? "Deploying the latest push. The last preview is still live."
1755 : "Deploying a preview"
1756 : failed
1757 ? "The preview failed to deploy"
1758 : "This branch is live"}
1759 </p>
1760 {preview ? (
1761 <a href={preview.url} className="mt-0.5 block truncate font-mono text-xs text-muted hover:text-accent">
1762 {preview.url.replace(/^https?:[/][/]/, "")}
1763 </a>
1764 ) : (
1765 build?.error && <p className="mt-0.5 truncate text-xs text-muted">{build.error}</p>
1766 )}
1767 </div>
1768 <div className="flex shrink-0 items-center gap-2">
1769 {build && (
1770 <Link to={`${base}/deployments/${build.id}`} className="text-xs text-muted hover:text-fg">
1771 {failed ? "See why" : "Build log"}
1772 </Link>
1773 )}
1774 {preview && (
1775 <a
1776 href={preview.url}
1777 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"
1778 >
1779 Visit preview
1780 <ArrowUpRight size={13} />
1781 </a>
1782 )}
1783 </div>
1784 </div>
1785 {preview && (
1786 <p className="border-t border-line px-4 py-2 text-xs text-faint">
1787 <span className="font-mono">{preview.commit.slice(0, 7)}</span> · deployed <TimeAgo at={preview.deployedAt} />
1788 {preview.branch && ` · from ${preview.branch}`}
1789 </p>
1790 )}
Fast pages, required checks on the branch, self-hosted runners, honest incidents1791 {/* Read after the page: the card's own part shows at once. */}
Merge branch 'worktree-agent-aca4790ea223aa235'1792 <Suspense
1793 fallback={
1794 stacking && (
1795 <Loading className="border-t border-line px-4 py-2.5 text-xs">
1796 <SkeletonLine className="w-40" />
1797 <SkeletonLine className="mt-1.5 w-64 max-w-full" />
1798 </Loading>
1799 )
1800 }
1801 >
Fast pages, required checks on the branch, self-hosted runners, honest incidents1802 <Await resolve={stacked}>
1803 {(stacked) =>
1804 stacked.length > 0 && (
Merge branch 'worktree-agent-aca4790ea223aa235'1805 <div className="animate-fade-in border-t border-line px-4 py-2.5">
Fast pages, required checks on the branch, self-hosted runners, honest incidents1806 <p className="text-xs text-muted">Built against this change:</p>
1807 <ul className="mt-1.5 space-y-1">
1808 {stacked.map((entry) => (
1809 <li key={entry.slug} className="flex items-center gap-2 text-xs">
1810 <span className="font-medium">{entry.name}</span>
1811 <a href={entry.url} className="truncate font-mono text-muted hover:text-accent">
1812 {entry.url.replace(/^https?:[/][/]/, "")}
1813 </a>
1814 </li>
1815 ))}
1816 </ul>
1817 </div>
1818 )
1819 }
1820 </Await>
1821 </Suspense>
Pull requests say where they are live; the project menu shows what is coming1822 </section>
1823 );
1824}

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