Skip to content
1,831 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,
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar86 agentReviews,
87 AgentReviewLine,
Agents as a team: lifecycle, merge queue, billing and a new shell88 verdicts,
89} from "../../components/work";
Catching up with main takes seconds when the two sides touched different files90import { CatchUpProgress, ChecksSection, ConflictsSection, MergeabilityRow, runIdOf } from "../../components/merge-box";
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge91import { ViolationList } from "../../components/rules";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar92import { PullCodeOwnersPanel, TeamReviewer } from "../../components/codeowners";
Catching up with main takes seconds when the two sides touched different files93import { 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 put94import { notFound } from "../../lib/not-found.server";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look95import { computeNoteFor } from "../../lib/compute.server";
Merge branch 'worktree-agent-aca4790ea223aa235'96import { actions, agents, deployments, identity, inbox, projects, repos, work } from "../../lib/services.server";
Merge checks: statuses and check runs on every commit97import { commitChecksFor } from "../../lib/commit-checks.server";
98import { CommitChecksBadge } from "../../components/commit-checks";
Issues and pull requests replace intents and attempts99import { assertSameOrigin, getViewer, requireUser } from "../../lib/session.server";
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings100import { accessTo, refusal, repoFor } from "../../lib/access.server";
101import { SubscriptionBox } from "../../components/notifications";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending102import { REFRESH_MS, useRefreshWhile } from "../../lib/refresh";
Issues and pull requests replace intents and attempts103
104const EMPTY_COMPARISON: Comparison = { base: null, head: "", files: [], truncated: false };
105const TABS = ["conversation", "session", "changes"] as const;
106type Tab = (typeof TABS)[number];
107
Social cards for every page: og.g1t.sh108export function meta({ loaderData, params, ...args }: Route.MetaArgs) {
109 const pull = loaderData?.pull;
110 const title = pull ? `${pull.title} · Pull request #${pull.number} · ` : "";
111 const state = { draft: "Draft", open: "Open", merged: "Merged", closed: "Closed" }[pull?.status ?? "open"];
112 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-agent113 const opener = pull ? openedBy(pull) : null;
Social cards for every page: og.g1t.sh114 return page(args, {
115 title: `${title}${params.owner}/${params.repo} · g1t`,
116 description: pull
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent117 ? `${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.sh118 : null,
119 // The card shows the title and the state.
120 version: pull ? [pull.title, pull.status] : undefined,
121 type: "article",
122 });
Issues and pull requests replace intents and attempts123}
124
125export async function loader({ params, context, request }: Route.LoaderArgs) {
126 const viewer = getViewer(context);
127 const path = { namespace: params.owner, name: params.repo };
128 const number = Number(params.number);
129 const asked = new URL(request.url).searchParams.get("tab");
130 const tab: Tab = TABS.find((name) => name === asked) ?? "conversation";
131
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look132 // Members of the workspace pick reviewers and assignees from its people;
133 // what the viewer may do here goes by their role on the repository.
Agents as a team: lifecycle, merge queue, billing and a new shell134 const member = (viewer?.workspaces ?? []).some(
135 (membership) => membership.slug === params.owner,
136 );
Fast pages, required checks on the branch, self-hosted runners, honest incidents137 // Everything starts at once. Only what the viewer's role decides waits
138 // for the repository, which the layout is looking up in this request too
139 // (lib/access.server.ts), and only what the pull request decides waits
140 // for it.
Project dependencies: addresses, preview stacks, Affects, and agents who know141 const ref = { workspace: params.owner, slug: params.repo };
Fast pages, required checks on the branch, self-hosted runners, honest incidents142 const access = accessTo(context, params);
143 const pullFound = work.getPull(path, number, viewer);
144 const deps = projects.dependencies(params.owner, params.repo, viewer);
145 // Awaited below, unless the pull request is missing first.
146 deps.catch(() => null);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar147 // Its labels and milestone, with the repository's to choose from.
148 const labelsFound = work.listLabels(path, viewer);
149 const milestonesFound = work.listMilestones(path, viewer);
150 labelsFound.catch(() => null);
151 milestonesFound.catch(() => null);
152 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 incidents153 access,
154 pullFound,
Issues and pull requests replace intents and attempts155 repos.get(path, viewer),
Agents as a team: lifecycle, merge queue, billing and a new shell156 work.getSettings(path, viewer),
g1t's agents only for listed workspaces, whatever the state of billing157 env.RUNNER.enabled(viewer, path),
Agents as a team: lifecycle, merge queue, billing and a new shell158 // A member picks reviewers and assignees from the workspace's people.
159 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-agent160 // Before someone asks g1t for something: whether the plan lets it start.
Fast pages, required checks on the branch, self-hosted runners, honest incidents161 access.then(({ can }) => (can.run ? computeNoteFor(params.owner, "agent") : null)),
162 tab === "session" ? work.readSession(path, number, viewer) : null,
163 // Its preview, for people with a role here: beside the rest, not after.
164 access.then(({ insider }) => (insider ? deployments.list(ref, viewer).catch(() => null) : null)),
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings165 // Whether the viewer hears of it, for the sidebar's Notifications.
166 viewer
167 ? repoFor(context, params).then((found) =>
168 found.ok ? inbox.subscription(viewer, { repoId: found.value.id, number }).catch(() => null) : null,
169 )
170 : null,
Merge branch 'worktree-agent-aca4790ea223aa235'171 // Its agent's latest runs, for the Agent panel: with the page, not after it.
172 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 bar173 // A member asks the workspace's teams to review, and mentions them.
174 member ? identity.listTeams(viewer, params.owner).catch(() => null) : null,
Issues and pull requests replace intents and attempts175 ]);
176 if (!found.ok) {
177 // Issues and pull requests share numbers; this one may be an issue.
178 const issue = await work.getIssue(path, number, viewer);
179 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 put180 throw notFound("pull");
Issues and pull requests replace intents and attempts181 }
182 const { pull } = found.value;
Pull requests from branches183 const range = pullComparison(pull);
Agents and memory, checks and conflicts, profiles, slug renames, custom domains184 // The jobs of each workflow run on its head, to list checks job by job.
185 // 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 incidents186 // 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.md187 // A closed or merged one lists its head's checks too, to read only.
Agents and memory, checks and conflicts, profiles, slug renames, custom domains188 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 incidents189 const workflowJobs =
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md190 tab === "conversation" && pull.status !== "draft" && runIds.length > 0
Fast pages, required checks on the branch, self-hosted runners, honest incidents191 ? Promise.all(runIds.map((id) => actions.run(path, viewer, id).catch(() => null))).then((runs) => {
192 const jobs: Record<string, Job[]> = {};
193 for (const run of runs) if (run?.ok) jobs[run.value.run.id] = run.value.jobs;
194 return jobs;
195 })
196 .catch(() => ({}) as Record<string, Job[]>)
197 : Promise.resolve({} as Record<string, Job[]>);
198 // No checks at all on an open pull request: whether that is because the
199 // repository has no workflows, to offer a starter one.
200 const unchecked =
201 tab === "conversation" &&
202 pull.status === "open" &&
203 (found.value.statuses ?? []).length === 0 &&
204 (found.value.requiredChecks ?? []).length === 0;
205 const [comparison, used, noChecks] = await Promise.all([
Usage, Billing settings and prepaid AI credit; fixes from the UX audit206 // The first screens of it highlighted here, so their colours do not pop in.
207 tab === "changes"
208 ? repos
209 .compare(range.repoId, viewer, range.base, range.head, range.baseBranch)
210 .then(async (found) => (found.ok ? { ok: true as const, value: await highlightFirstFiles(found.value) } : found))
211 : null,
Fast pages, required checks on the branch, self-hosted runners, honest incidents212 deps,
213 unchecked
214 ? actions
215 .workflows(path, viewer)
216 .then((found) => found.ok && found.value.length === 0)
217 .catch(() => false)
218 : false,
Issues and pull requests replace intents and attempts219 ]);
Fast pages, required checks on the branch, self-hosted runners, honest incidents220 const affects = used.ok ? used.value.usedBy : [];
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md221 // Every pull request for its issue, to compare: streamed, and read only
222 // when there is more than this one.
223 const attempts =
224 tab === "conversation" && pull.issue != null && (found.value.issue?.pullCount ?? 0) > 1
225 ? attemptsFor(path, viewer, pull, found.value.comments).catch(() => [] as Attempt[])
226 : null;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar227 const [labels, milestones] = await Promise.all([labelsFound, milestonesFound]);
228 const repoDefault = repo.ok ? repo.value.defaultBranch : "main";
229 // The default branch's protection covers pull requests into it only.
230 const protectedBase = (pull.base ?? repoDefault) === repoDefault;
Issues and pull requests replace intents and attempts231 return {
232 ...found.value,
Merge checks: statuses and check runs on every commit233 // Every check on its head, as the commit's own list shows them: streamed.
234 headChecks: commitChecksFor(path, viewer, [pull.headCommit]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar235 labels: labels.ok ? labels.value : [],
236 milestones: milestones.ok ? milestones.value : [],
237 // Labelling and milestones: Triage and up; its author, its labels.
238 canTriage: can.triage,
Merge main (membership, two-factor, GitHub repo roles) into tokens239 // Making labels needs Write; applying them, Triage.
240 canMakeLabels: can.manage_labels,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar241 // The branch it merges into: Write and up.
242 canChangeBase: can.push,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains243 workflowJobs,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md244 attempts,
Issues and pull requests replace intents and attempts245 tab,
246 session: session?.ok ? session.value : [],
247 // An empty comparison if it could not be made.
248 comparison: comparison && (comparison.ok ? comparison.value : EMPTY_COMPARISON),
249 viewer,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look250 // Write and up can merge.
251 canMerge: can.merge,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights252 // Triage and up manage anyone's pull request; its author, their own,
253 // and whoever asked g1t for one, that one.
254 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 look255 // Telling its agent things, and re-running checks, spend compute: Write and up.
256 canRun: can.run,
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts257 // Maintain and up edit and delete anyone's comments; everyone, their own.
258 canModerate: can.manage_settings,
Agents as a team: lifecycle, merge queue, billing and a new shell259 // 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 rights260 // whoever it is for (who asked g1t for it, or its author), a branch
261 // from anyone who can push.
262 canUpdate: pull.fork ? viewer?.id === workOwner(pull).id : can.push,
Agents as a team: lifecycle, merge queue, billing and a new shell263 agentsEnabled,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look264 computeNote,
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings265 subscription,
Merge branch 'worktree-agent-aca4790ea223aa235'266 // As the project's agents.json has them; left out, the panel fetches them.
267 agentRuns: runs?.ok ? { runs: runs.value, member: can.run } : undefined,
Agents as a team: lifecycle, merge queue, billing and a new shell268 members: members?.ok ? members.value.map((person) => person.username) : [],
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar269 // `workspace/slug` and name of each team the viewer can see.
270 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 merge271 // What the rules of the branch it merges into ask, as they stack; the
272 // default branch's settings where the rules are not known.
273 requireUpToDate: found.value.rules ? found.value.rules.strict : protectedBase && settings.ok && settings.value.requireUpToDate,
274 mergeQueue: found.value.rules ? found.value.rules.merge_queue : protectedBase && settings.ok && settings.value.mergeQueue,
275 requiredApprovals: found.value.rules
276 ? found.value.rules.required_approvals
277 : protectedBase && settings.ok
278 ? settings.value.requiredApprovals
279 : 0,
280 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 incidents281 noChecks,
282 // Who may open the pull request that adds CI: anyone who can push.
283 canAddCi: can.push,
284 // Who may choose the required checks.
285 canProtect: can.manage_protection,
Issues and pull requests replace intents and attempts286 defaultBranch: repo.ok ? repo.value.defaultBranch : "main",
Fast pages, required checks on the branch, self-hosted runners, honest incidents287 affects,
288 // Streamed: the conversation shows first, the preview card after.
289 ...deploymentOf(deployed?.ok ? deployed.value : null, number, params.owner, affects, viewer),
Issues and pull requests replace intents and attempts290 };
291}
292
Pull requests say where they are live; the project menu shows what is coming293/**
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md294 * This pull request and the latest few others for its issue, each with its
295 * head's checks and where its review stands, this one first.
296 */
297async function attemptsFor(path: RepoPath, viewer: Viewer, pull: Pull, comments: Comment[]): Promise<Attempt[]> {
298 const found = await work.getIssue(path, pull.issue!, viewer);
299 if (!found.ok) return [];
300 const others = otherAttempts(found.value.pulls, pull.number).flatMap(
301 (number) => found.value.pulls.find((other) => other.number === number) ?? [],
302 );
303 const [details, checks] = await Promise.all([
304 Promise.all(others.map((other) => work.getPull(path, other.number, viewer).catch(() => null))),
305 commitChecksFor(path, viewer, [pull.headCommit, ...others.map((other) => other.headCommit)]),
306 ]);
307 const attempt = (one: Pull, said: Comment[], current: boolean): Attempt => ({
308 number: one.number,
309 title: one.title,
310 status: one.status,
311 supersededBy: one.supersededBy,
312 author: openedBy(one).name,
313 headCommit: one.headCommit,
314 files: one.files,
315 updatedAt: one.updatedAt,
316 current,
317 shared: current ? [] : sharedPaths(one.files, pull.files),
318 checks: (one.headCommit && checks?.[one.headCommit]) || null,
319 review: attemptReview(said, one.reviewers),
320 });
321 return [
322 attempt(pull, comments, true),
323 ...others.map((other, index) => {
324 const detail = details[index];
325 return attempt(other, detail?.ok ? detail.value.comments : [], false);
326 }),
327 ];
328}
329
330/**
Pull requests say where they are live; the project menu shows what is coming331 * Where this pull request is live: its preview (or its latest build, while
332 * one is going or after one failed), and the previews of the projects
333 * that use this one, built against it.
334 */
Fast pages, required checks on the branch, self-hosted runners, honest incidents335function deploymentOf(
Pull requests say where they are live; the project menu shows what is coming336 list: { deployments: Deployment[]; live: LiveApp[] } | null,
337 number: number,
338 owner: string,
339 affects: { slug: string; name: string }[],
340 viewer: Viewer,
Fast pages, required checks on the branch, self-hosted runners, honest incidents341): { preview: LiveApp | null; build: Deployment | null; stacked: Promise<Stacked[]> } {
342 const preview = list?.live.find((app) => app.kind === "preview" && app.number === number) ?? null;
343 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 coming344 const branch = preview?.branch ?? build?.branch ?? null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents345 // Streamed: another lookup per project, shown in the card once known.
346 const stacked: Promise<Stacked[]> = branch
347 ? Promise.all(
348 affects.slice(0, 5).map(async (project) => {
349 const theirs = await deployments.list({ workspace: owner, slug: project.slug }, viewer).catch(() => null);
350 const app = theirs?.ok ? theirs.value.live.find((a) => a.kind === "preview" && a.branch === branch) : undefined;
351 return app ? { name: project.name, slug: project.slug, url: app.url } : null;
352 }),
353 ).then((found) => found.filter((entry) => entry != null))
354 : Promise.resolve([]);
Pull requests say where they are live; the project menu shows what is coming355 return { preview, build, stacked };
356}
357
Fast pages, required checks on the branch, self-hosted runners, honest incidents358/** A project that uses this one, with its preview built against this change. */
359type Stacked = { name: string; slug: string; url: string };
360
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look361const PULL_NEEDS: Record<string, Capability> = {
362 stack: "run",
363 "agent-review": "run",
364 "rerun-workflow": "run",
Fast pages, required checks on the branch, self-hosted runners, honest incidents365 "rerun-failed": "run",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look366 message: "run",
367 merge: "merge",
368 unqueue: "merge",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar369 milestone: "triage",
370 base: "push",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look371};
372
Issues and pull requests replace intents and attempts373export async function action({ request, params, context }: Route.ActionArgs) {
374 assertSameOrigin(request);
375 const user = requireUser(context, request);
376 const form = await request.formData();
377 const path = { namespace: params.owner, name: params.repo };
378 const number = Number(params.number);
379 const action = form.get("action");
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look380 // What each form needs beyond reading; work checks the rest.
381 const needed = typeof action === "string" ? PULL_NEEDS[action] : undefined;
382 const refused = needed ? await refusal(context, params, needed) : null;
383 if (refused) return { error: refused, action: String(action) };
Acceptance checks in sandboxes, line comments and review verdicts384 const verdict = form.get("verdict");
385 const line = Number(form.get("line"));
Agents as a team: lifecycle, merge queue, billing and a new shell386 /** Names ticked in a people picker, plus those typed beside it. */
387 const picked = (field: string) => [
388 ...form.getAll(field).map(String),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar389 // `@acme/backend` is asked as `acme/backend`.
390 ...String(form.get("others") ?? "").split(/[\s,]+/).map((name) => name.replace(/^@/, "")),
Agents as a team: lifecycle, merge queue, billing and a new shell391 ];
Project dependencies: addresses, preview stacks, Affects, and agents who know392 // The projects that use this one, built against this pull request's preview.
393 if (action === "stack") {
394 const built = await deployments.stack(user, { workspace: params.owner, slug: params.repo }, String(form.get("branch") ?? ""));
395 return built.ok
396 ? { action, notice: `Building ${built.value.join(", ")} against this preview. They appear on their Deployments pages.` }
397 : { action, error: built.error.message };
398 }
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent399 // Asking g1t for its review records the request, then starts it.
Agents as a team: lifecycle, merge queue, billing and a new shell400 if (action === "agent-review") {
401 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-agent402 reviewers: [...form.getAll("reviewer").map(String), "g1t"],
Agents as a team: lifecycle, merge queue, billing and a new shell403 });
404 if (!asked.ok) return { error: asked.error.message, action };
405 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar406 // Its labels, milestone and base, from the sidebar's menus and the header.
407 if (action === "labels") {
408 const set = await work.setLabels(user, path, number, form.getAll("label").map(String));
409 return set.ok ? null : { error: set.error.message, action };
410 }
411 if (action === "milestone" || action === "base") {
412 const changed = await work.updatePull(
413 user,
414 path,
415 number,
416 action === "milestone"
417 ? { milestone: Number(form.get("milestone")) || 0 }
418 : { base: String(form.get("base") ?? "") },
419 );
420 return changed.ok ? null : { error: changed.error.message, action };
421 }
Catching up with main takes seconds when the two sides touched different files422 // Catching up: merged and pushed in seconds when the two sides changed
423 // different files; otherwise handed to a sandbox, which takes a minute.
424 if (action === "update") {
425 const caught = await work.catchUpPull(user, path, number);
426 if (!caught.ok) return { action, error: caught.error.message };
427 const update = caught.value;
428 if (update.outcome !== "needs_agent") {
429 return {
430 action,
431 updated: { commit: update.commit, already: update.outcome === "up_to_date", at: Date.now() },
432 };
433 }
434 const started = await env.RUNNER.update(user, path, number);
435 if (!started.ok) return { action, error: started.error.message };
436 return { action, agent: { update, startedAt: Date.now() } };
437 }
Agents and memory, checks and conflicts, profiles, slug renames, custom domains438 // A workflow run's failed jobs, run again. Who may is the actions service's call.
439 if (action === "rerun-workflow") {
440 const rerun = await actions.rerun(user, path, String(form.get("run") ?? ""), true);
441 return rerun.ok ? null : { error: rerun.error.message, action };
442 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents443 // Every failed run on its head, run again: Mission control's quick action.
444 if (action === "rerun-failed") {
445 const found = await work.getPull(path, number, user);
446 if (!found.ok) return { error: found.error.message, action };
447 const failed = [
448 ...new Set(
449 (found.value.statuses ?? [])
450 .filter((status) => status.state === "failure" || status.state === "error")
451 .map(runIdOf)
452 .filter((id) => id != null),
453 ),
454 ];
455 if (failed.length === 0) return { error: "No workflow run failed on its latest commit.", action };
456 const reruns = await Promise.all(failed.map((id) => actions.rerun(user, path, id, true)));
457 const refused = reruns.find((rerun) => !rerun.ok);
458 return refused && !refused.ok ? { error: refused.error.message, action } : null;
459 }
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts460 // A comment's own text, changed or taken away: its author's to do, or a
461 // maintainer's, which work checks.
462 if (action === "edit-comment" || action === "delete-comment") {
463 const id = String(form.get("comment") ?? "");
464 const changed =
465 action === "edit-comment"
466 ? await work.editComment(user, path, id, String(form.get("body") ?? ""))
467 : await work.deleteComment(user, path, id);
468 return changed.ok ? null : { error: changed.error.message, action, comment: id };
469 }
Issues and pull requests replace intents and attempts470 const result =
471 action === "merge"
Acceptance checks in sandboxes, line comments and review verdicts472 ? await work.mergePull(user, path, number, {
473 keepIssueOpen: form.get("keepIssueOpen") === "on",
474 ignoreChecks: form.get("ignoreChecks") === "on",
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge475 bypassRules: form.get("bypassRules") === "on",
Acceptance checks in sandboxes, line comments and review verdicts476 })
Agents as a team: lifecycle, merge queue, billing and a new shell477 : action === "unqueue"
478 ? await work.removeFromQueue(user, path, number)
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request479 : action === "message"
480 ? await work.messageAgent(user, path, number, String(form.get("body") ?? ""))
Issues and pull requests replace intents and attempts481 : action === "close"
482 ? await work.closePull(user, path, number)
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts483 : action === "reopen"
484 ? await work.reopenPull(user, path, number)
485 : action === "draft"
486 ? await work.convertPullToDraft(user, path, number)
Fast pages, required checks on the branch, self-hosted runners, honest incidents487 : action === "agent-review"
Agents as a team: lifecycle, merge queue, billing and a new shell488 ? await env.RUNNER.review(user, path, number)
489 : action === "reviewers"
490 ? await work.updatePull(user, path, number, { reviewers: picked("reviewer") })
491 : action === "assign"
492 ? await work.updatePull(user, path, number, { assignees: picked("assignee") })
Acceptance checks in sandboxes, line comments and review verdicts493 : action === "comment"
494 ? await work.addComment(user, path, number, {
495 body: String(form.get("body") ?? ""),
496 path: String(form.get("path") ?? "") || undefined,
497 line: line > 0 ? line : undefined,
498 verdict:
499 verdict === "approve" || verdict === "request_changes" ? verdict : undefined,
500 })
501 : await work.readyPull(user, path, number, String(form.get("summary") ?? ""));
Issues and pull requests replace intents and attempts502 return result.ok ? null : { error: result.error.message, action };
503}
504
Pull requests: title and tabs across the full width, with counts and the size of the change505/** How many, beside a tab's name, when there are any. */
506function TabCount({ n }: { n: number }) {
507 if (n <= 0) return null;
508 return (
509 <span className="rounded-full bg-raised px-1.5 py-px text-xs tabular-nums text-muted ring-1 ring-line">
510 {n.toLocaleString("en-US")}
511 </span>
512 );
513}
514
Issues and pull requests replace intents and attempts515function TabLink({
516 to,
517 active,
518 children,
519}: {
520 to: string;
521 active: boolean;
522 children: React.ReactNode;
523}) {
524 return (
525 <Link
526 to={to}
527 preventScrollReset
Usage, Billing settings and prepaid AI credit; fixes from the UX audit528 aria-current={active ? "page" : undefined}
Issues and pull requests replace intents and attempts529 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)530 "-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 attempts531 (active
532 ? "border-accent font-medium text-fg"
533 : "border-transparent text-muted hover:text-fg")
534 }
535 >
536 {children}
537 </Link>
538 );
539}
540
541/** One step of the session, on the timeline's rail. */
542function Entry({ entry, agent }: { entry: SessionEntry; agent: string }) {
543 const isTool = entry.kind === "tool_call" || entry.kind === "tool_result";
544 const Icon =
545 entry.kind === "prompt"
546 ? User
547 : entry.kind === "note"
548 ? StickyNote
549 : isTool
550 ? Wrench
551 : Bot;
552 return (
553 <li className="relative pl-10">
554 <span
555 className={`absolute top-0.5 left-0 flex size-7 items-center justify-center rounded-full border bg-bg ${
556 entry.kind === "prompt"
557 ? "border-accent/50 text-accent"
558 : "border-line text-faint"
559 }`}
560 >
561 <Icon size={14} />
562 </span>
563 {isTool ? (
564 <details className="group rounded-lg border border-line bg-surface">
565 <summary className="flex cursor-pointer list-none items-center gap-2 px-3 py-1.5 text-sm">
566 <ChevronRight
567 size={14}
568 className="text-faint transition-transform group-open:rotate-90"
569 />
570 <span className="font-mono text-xs text-accent">
571 {entry.tool ?? "tool"}
572 </span>
573 <span className="truncate font-mono text-xs text-muted">
574 {entry.kind === "tool_result" ? "→ " : ""}
575 {entry.text.split("\n")[0]}
576 </span>
577 </summary>
578 <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 line579 <code>{entry.text}</code>
Issues and pull requests replace intents and attempts580 </pre>
581 </details>
582 ) : (
583 <div>
584 <p className="text-xs font-medium text-faint">
585 {entry.kind === "prompt"
586 ? "Prompt"
587 : entry.kind === "note"
588 ? "Note"
589 : agent}
590 </p>
591 {entry.kind === "message" ? (
592 <div className="mt-1">
593 <Markdown source={entry.text} />
594 </div>
Agents as a team: lifecycle, merge queue, billing and a new shell595 ) : entry.kind === "prompt" ? (
596 <div className="mt-1.5 max-h-[32rem] overflow-y-auto rounded-xl border border-line bg-surface p-4">
597 <Markdown source={entry.text} />
598 </div>
Issues and pull requests replace intents and attempts599 ) : (
Agents as a team: lifecycle, merge queue, billing and a new shell600 <p className="mt-1 text-[0.9375rem] leading-relaxed wrap-break-word whitespace-pre-wrap">
Issues and pull requests replace intents and attempts601 {entry.text}
602 </p>
603 )}
604 </div>
605 )}
606 {entry.commit && (
607 <p className="mt-1.5 flex items-center gap-1 font-mono text-xs text-faint">
608 <GitCommitHorizontal size={12} />
609 {entry.commit.slice(0, 7)}
610 </p>
611 )}
612 </li>
613 );
614}
615
Agents as a team: lifecycle, merge queue, billing and a new shell616/** The box at the foot of the conversation saying what stands before a merge. */
617function StatusBox({ children }: { children: React.ReactNode }) {
618 return (
619 <div className="flex gap-3">
620 <span className="hidden w-8 shrink-0 sm:block" />
621 <section className="min-w-0 grow divide-y divide-line overflow-hidden rounded-xl border border-line-strong bg-surface">
622 {children}
623 </section>
624 </div>
625 );
626}
627
628function StatusRow({
629 icon,
630 title,
631 children,
632}: {
633 icon: React.ReactNode;
634 title: string;
635 children?: React.ReactNode;
636}) {
637 return (
638 <div className="flex gap-3 px-4 py-3 text-sm">
639 <span className="mt-0.5 shrink-0">{icon}</span>
640 <div className="min-w-0">
641 <p className="font-medium">{title}</p>
642 {children && <div className="mt-0.5 text-muted">{children}</div>}
643 </div>
644 </div>
645 );
646}
647
Issues and pull requests replace intents and attempts648export default function PullPage({ loaderData, actionData, params }: Route.ComponentProps) {
649 const {
650 pull,
651 issue,
652 comments,
Acceptance checks in sandboxes, line comments and review verdicts653 checks,
Agents as a team: lifecycle, merge queue, billing and a new shell654 overlaps,
655 behind,
656 reviewPending,
657 lifecycle,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request658 messages,
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs659 statuses = [],
Fast pages, required checks on the branch, self-hosted runners, honest incidents660 requiredChecks = [],
661 noChecks,
662 canAddCi,
663 canProtect,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains664 mergeable = "unknown",
665 conflicts = [],
666 earlierChecks = [],
667 workflowJobs,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md668 attempts,
Project dependencies: addresses, preview stacks, Affects, and agents who know669 affects,
670 preview,
Pull requests say where they are live; the project menu shows what is coming671 build,
672 stacked,
Agents as a team: lifecycle, merge queue, billing and a new shell673 landing,
674 stalled,
675 requireUpToDate,
676 mergeQueue,
677 requiredApprovals,
678 canIgnoreChecks,
679 canUpdate,
680 agentsEnabled,
Merge branch 'worktree-agent-aca4790ea223aa235'681 agentRuns,
Agents as a team: lifecycle, merge queue, billing and a new shell682 members,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar683 teams,
684 codeOwners,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge685 rules,
Issues and pull requests replace intents and attempts686 tab,
687 session,
688 comparison,
689 viewer,
690 canMerge,
691 canManage,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look692 canRun,
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts693 canModerate,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar694 defaultBranch: repoDefault,
Issues and pull requests replace intents and attempts695 } = loaderData;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar696 // The branch it merges into: what every line below names.
697 const defaultBranch = pull.base ?? repoDefault;
Issues and pull requests replace intents and attempts698 const base = `/${params.owner}/${params.repo}`;
699 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-agent700 // g1t, on a pull request it made: the person who asked for it is a line below.
701 const opener = openedBy(pull);
Merge branch 'worktree-agent-aca4790ea223aa235'702 const checksSection = (jobs: Record<string, Job[]>, loading = false) => (
Fast pages, required checks on the branch, self-hosted runners, honest incidents703 <ChecksSection
704 run={checks}
705 required={requiredChecks}
706 statuses={statuses}
707 jobs={jobs}
708 pull={pull}
709 base={base}
710 earlier={earlierChecks}
711 canRerunWorkflows={canMerge}
712 settingsUrl={canProtect ? `${base}/settings/branches` : null}
713 error={actionData?.action === "rerun-failed" || actionData?.action === "rerun-workflow" ? actionData.error : null}
Merge branch 'worktree-agent-aca4790ea223aa235'714 loading={loading}
Fast pages, required checks on the branch, self-hosted runners, honest incidents715 />
716 );
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md717 // A closed or merged pull request's checks, as they ended: to read, with
718 // nothing to re-run and nothing that holds a merge.
719 const settledChecks = (jobs: Record<string, Job[]>, loading = false) => (
720 <ChecksSection
721 run={null}
722 required={[]}
723 statuses={statuses}
724 jobs={jobs}
725 pull={pull}
726 base={base}
727 earlier={[]}
728 canRerunWorkflows={false}
729 settled
730 loading={loading}
731 />
732 );
Merge branch 'worktree-agent-aaf03bdceac799c89'733 const addresses = useAddresses();
Pull requests from branches734 const remote = pull.fork
Merge branch 'worktree-agent-aaf03bdceac799c89'735 ? cloneUrl(addresses, `${pull.fork.namespace}/${pull.fork.name}`)
736 : cloneUrl(addresses, `${params.owner}/${params.repo}`);
Issues and pull requests replace intents and attempts737 const active = pull.status === "draft" || pull.status === "open";
738
Acceptance checks in sandboxes, line comments and review verdicts739 // Follow an agent at work, or checks in progress, without a manual reload.
Issues and pull requests replace intents and attempts740 const working = pull.status === "draft";
Fast pages, required checks on the branch, self-hosted runners, honest incidents741 const checking = statuses.some((status) => status.state === "pending");
Acceptance checks in sandboxes, line comments and review verdicts742 const reviews = verdicts(comments);
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar743 // Agents' reviews: shown, never counted.
744 const advisory = agentReviews(comments);
Fast pages, required checks on the branch, self-hosted runners, honest incidents745 // What stands between this pull request and a merge, if anything: a
746 // required check that has not passed, or the merge queue taking it out.
747 const unchecked = requiredChecks.some((check) => check.state !== "success") || checks?.status === "failed";
748 const requiredFailed = requiredChecks.some((check) => check.state === "failure") || checks?.status === "failed";
Agents as a team: lifecycle, merge queue, billing and a new shell749 // 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.md750 // ones for the same issue are alternatives, and expected to: those are
751 // compared under their own heading.
752 const { collisions } = splitOverlaps(overlaps, pull.issue);
Agents as a team: lifecycle, merge queue, billing and a new shell753 const review = {
754 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 rights755 // Nobody reviews their own pull request, nor one g1t made for them.
756 canJudge: active && viewer != null && viewer.id !== workOwner(pull).id,
Agents as a team: lifecycle, merge queue, billing and a new shell757 };
758 // Everyone whose review was asked for, then anyone who reviewed unasked.
759 const reviewerNames = [
760 ...new Set([...pull.reviewers, ...reviews.map(({ reviewer }) => reviewer)]),
761 ];
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar762 const teamReviewers = pull.teamReviewers ?? [];
763 // Teams to ask: the workspace's, and any asked already.
764 const teamChoices = [
765 ...teams,
766 ...teamReviewers.filter((ref) => !teams.some((team) => team.ref === ref)).map((ref) => ({ ref, name: ref })),
767 ];
768 // Branch protection wants code owners' approval it does not have yet.
769 const ownersMissing = codeOwners?.required && codeOwners.missing ? codeOwners.missing : null;
Catching up with main takes seconds when the two sides touched different files770 // A catch-up: the click shows at once; the answer says whether it is
771 // done already or a sandbox is on it.
Merge branch 'worktree-agent-a985c01f43538af01'772 const catchUpPending = usePending({ action: "update" });
Catching up with main takes seconds when the two sides touched different files773 const catchUp = actionData?.action === "update" ? actionData : null;
774 const caughtUp = catchUp && "updated" in catchUp ? catchUp.updated : null;
775 const agentCatchUp = catchUp && "agent" in catchUp ? catchUp.agent : null;
776 // Pushed already: followed until the pull request's head is the merge,
777 // so its checks show starting again, for a minute at most.
778 const settling =
779 caughtUp != null && !caughtUp.already && pull.headCommit !== caughtUp.commit && Date.now() - caughtUp.at < 60_000;
780 // Followed until it is pushed, or for so long, never longer.
781 const catchingUp =
782 agentCatchUp != null && behind && Date.now() - agentCatchUp.startedAt < CATCH_UP_TIMEOUT_MS + REFRESH_MS;
Agents as a team: lifecycle, merge queue, billing and a new shell783 // g1t is taking a step of its own accord, so the page will change.
784 const moving =
785 lifecycle != null && lifecycle.stage !== "ready" && lifecycle.stage !== "needs_you";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains786 // Whether it merges cleanly is being worked out, so the box will change.
787 const probing = active && mergeable === "checking";
788 const conflicting = active && mergeable === "conflicting";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending789 useRefreshWhile(working || checking || reviewPending || catchingUp || settling || moving || landing || probing);
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge790 // The rules of its base it does not meet: checks show in their own
791 // section, so the rest stop the button here. Someone a ruleset lets
792 // bypass it may tick a box and merge past them.
793 const [bypassing, setBypassing] = useState(false);
794 const rulesUnmet = (rules?.unmet ?? []).filter((violation) => violation.rule !== "required_status_checks");
795 const rulesBypassable = rules?.bypassable ?? [];
Agents and memory, checks and conflicts, profiles, slug renames, custom domains796 // Why the merge button cannot be pressed, if it cannot.
797 const mergeBlocked = conflicting
798 ? "Resolve the conflicts first."
799 : probing
800 ? "Waiting to find out whether it merges cleanly."
801 : behind && requireUpToDate
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge802 ? `The rules for ${defaultBranch} require it to be up to date first.`
Fast pages, required checks on the branch, self-hosted runners, honest incidents803 : unchecked && !canIgnoreChecks
804 ? `The checks ${defaultBranch} requires have to pass first.`
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge805 : rulesUnmet.length > 0
806 ? rulesUnmet[0]!.message
807 : rulesBypassable.length > 0 && !bypassing
808 ? `Rules for ${defaultBranch} are not met. You may bypass them.`
809 : ownersMissing
810 ? "Code owners have to approve first."
811 : null;
Issues and pull requests replace intents and attempts812
813 return (
Agents as a team: lifecycle, merge queue, billing and a new shell814 // 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 change815 <div>
816 {/* The title and the tabs run the full width; what is happening on
817 the pull request sits at the top of Conversation. */}
818 <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)819 <h2 className="text-xl font-semibold tracking-tight text-balance wrap-break-word sm:text-2xl">
Issues and pull requests replace intents and attempts820 {pull.title} <span className="font-normal text-faint">#{pull.number}</span>
821 </h2>
822 <div className="mt-3 flex flex-wrap items-center gap-x-3 gap-y-2 text-sm text-muted">
823 <PullState status={pull.status} />
824 <span className="flex items-center gap-2">
Agents as a team: lifecycle, merge queue, billing and a new shell825 <Avatar
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent826 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Agents as a team: lifecycle, merge queue, billing and a new shell827 size={18}
828 />
Issues and pull requests replace intents and attempts829 <span>
Agents and memory, checks and conflicts, profiles, slug renames, custom domains830 <PersonLink
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent831 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Agents and memory, checks and conflicts, profiles, slug renames, custom domains832 className="font-medium text-fg hover:underline"
833 />{" "}
Pull requests from branches834 {pull.status === "merged" ? "merged" : "wants to merge"}
835 {pull.branch && (
836 <>
837 {" "}
838 <span className="font-mono text-fg">{pull.branch}</span>
839 </>
840 )}{" "}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar841 into{" "}
842 <BaseBranch
843 base={defaultBranch}
844 head={pull.fork ? null : pull.branch}
845 canChange={loaderData.canChangeBase && active}
846 branchesUrl={`${base}/branches.json`}
847 />
Issues and pull requests replace intents and attempts848 </span>
849 </span>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar850 {(pull.labels ?? []).map((name) => (
851 <LabelChip key={name} name={name} color={loaderData.labels.find((label) => label.name === name)?.color} />
852 ))}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent853 {opener.requestedBy && (
854 <span className="text-xs">
855 requested by{" "}
856 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
857 </span>
858 )}
859 {/* A pull request from a branch was made by its author, not an agent;
860 one g1t made already says so. */}
861 {!pull.branch && !opener.requestedBy && (
Pull requests from branches862 <span className="flex items-center gap-1.5 font-mono text-xs">
863 <Bot size={14} />
864 {pull.agent}
865 {pull.runtime === "hosted" && <span className="text-faint">on g1t</span>}
866 </span>
867 )}
Issues and pull requests replace intents and attempts868 </div>
869
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar870 {(reviews.length > 0 || advisory.length > 0) && (
Acceptance checks in sandboxes, line comments and review verdicts871 <p className="mt-3 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm">
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar872 {advisory.map(({ agent, verdict }) => (
873 <AgentReviewLine key={agent.id} agent={agent} verdict={verdict} base={base} />
874 ))}
Acceptance checks in sandboxes, line comments and review verdicts875 {reviews.map(({ reviewer, verdict }) => (
876 <span
877 key={reviewer}
878 className={`flex items-center gap-1.5 ${
Merge branch 'worktree-agent-ad7c6d88d93adc817'879 verdict === "approve" ? "text-success" : "text-danger"
Acceptance checks in sandboxes, line comments and review verdicts880 }`}
881 >
882 {verdict === "approve" ? <CircleCheck size={15} /> : <CircleSlash size={15} />}
883 {verdict === "approve" ? "Approved by" : "Changes requested by"}{" "}
884 <span className="font-medium">{reviewer}</span>
885 </span>
886 ))}
887 </p>
888 )}
889
Usage, Billing settings and prepaid AI credit; fixes from the UX audit890 <TabStrip label="Pull request" className="mt-6 items-end gap-1 border-b border-line">
Issues and pull requests replace intents and attempts891 <TabLink to={here} active={tab === "conversation"}>
892 <MessageSquare size={15} />
893 Conversation
Pull requests: title and tabs across the full width, with counts and the size of the change894 <TabCount n={comments.length} />
Issues and pull requests replace intents and attempts895 </TabLink>
896 <TabLink to={here + "?tab=session"} active={tab === "session"}>
897 <MessagesSquare size={15} />
898 Session
899 </TabLink>
900 <TabLink to={here + "?tab=changes"} active={tab === "changes"}>
901 <FileDiff size={15} />
Pull requests: title and tabs across the full width, with counts and the size of the change902 Files changed
903 <TabCount n={pull.files.length} />
Issues and pull requests replace intents and attempts904 </TabLink>
Pull requests: title and tabs across the full width, with counts and the size of the change905 {pull.files.length > 0 && (
906 <span className="ml-auto hidden pb-2.5 font-mono text-xs sm:inline">
Merge branch 'worktree-agent-ad7c6d88d93adc817'907 <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 change908 <span className="text-danger">−{pull.files.reduce((sum, file) => sum + file.deletions, 0).toLocaleString("en-US")}</span>
909 </span>
910 )}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit911 </TabStrip>
Pull requests: title and tabs across the full width, with counts and the size of the change912 </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)913 <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 change914 <div className="min-w-0">
915 {/* What is happening on it now: its stage, its agent, what people told
916 the agent, and what else touches the same files. On the conversation,
917 so the tabs sit right under the title. */}
918 {tab === "conversation" && (
919 <div>
920 {issue && (
921 <Link
922 to={`${base}/issues/${issue.number}`}
923 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"
924 >
925 <IssueIcon issue={issue} />
926 <span className="text-muted">
927 {pull.status === "merged" && issue.resolvedBy === pull.number
928 ? "Resolved"
929 : "For issue"}
930 </span>
931 <span className="truncate font-medium">{issue.title}</span>
932 <span className="text-faint">#{issue.number}</span>
933 {issue.pullCount > 1 && (
934 <span className="ml-auto shrink-0 text-xs text-faint">
935 1 of {issue.pullCount} pull requests
Pull requests show their tabs right under the title; agent panels move into Conversation936 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change937 )}
938 </Link>
939 )}
940 {pull.status === "merged" && (
941 <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">
942 <GitMerge size={16} className="shrink-0 text-merged" />
Agents as a team: lifecycle, merge queue, billing and a new shell943 <span>
Pull requests: title and tabs across the full width, with counts and the size of the change944 Merged into <span className="font-mono">{defaultBranch}</span> by{" "}
945 <span className="font-medium">{pull.mergedBy}</span>{" "}
946 {/* Not an account: the repository's settings said to merge it. */}
947 {pull.mergedBy === "g1t" && "automatically, once it was ready, "}
948 {pull.mergedAt && <TimeAgo at={pull.mergedAt} />}
949 {pull.headCommit && (
950 <span className="font-mono text-muted"> · {pull.headCommit.slice(0, 7)}</span>
Agents as a team: lifecycle, merge queue, billing and a new shell951 )}
952 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change953 </p>
954 )}
955 {pull.supersededBy != null && (
956 <p className="mt-4 rounded-xl border border-line bg-surface px-4 py-2.5 text-sm text-muted">
957 Closed because{" "}
958 <Link
959 to={`${base}/pull/${pull.supersededBy}`}
960 className="font-medium text-fg hover:underline"
961 >
962 #{pull.supersededBy}
963 </Link>{" "}
964 was merged for this issue instead.
965 </p>
966 )}
967 {lifecycle && <LifecyclePanel lifecycle={lifecycle} />}
968 {/* 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 step969 <AgentPanel
970 owner={params.owner}
971 repo={params.repo}
972 number={pull.number}
973 stage={lifecycle?.stage ?? null}
974 confidence={pull.confidence ?? null}
Merge branch 'worktree-agent-aca4790ea223aa235'975 runs={agentRuns}
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step976 />
Pull requests: title and tabs across the full width, with counts and the size of the change977
978 {/* 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 look979 {canRun &&
Pull requests: title and tabs across the full width, with counts and the size of the change980 pull.runtime === "hosted" &&
981 (working || ["working", "revising", "catching_up", "answering"].includes(lifecycle?.stage ?? "")) && (
Merge branch 'worktree-agent-a985c01f43538af01'982 // Keyed by the messages so the box empties once one shows below.
Merge branch 'worktree-agent-ad7c6d88d93adc817'983 <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 change984 <p className="flex items-center gap-2 text-sm font-medium">
Merge branch 'worktree-agent-ad7c6d88d93adc817'985 <Sparkles size={15} className="text-accent" />
Pull requests: title and tabs across the full width, with counts and the size of the change986 Message the agent
987 </p>
988 <p className="mt-1 text-xs text-muted">
989 A correction, a hint, a change of plan. It reads it at its next step, without
990 starting over.
991 </p>
992 <div className="mt-3 flex gap-2">
993 <input type="hidden" name="action" value="message" />
994 <input
995 name="body"
996 required
997 autoComplete="off"
998 data-1p-ignore
999 placeholder="Keep the old flag working too…"
Merge branch 'worktree-agent-ad7c6d88d93adc817'1000 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 shell1001 />
Merge branch 'worktree-agent-a985c01f43538af01'1002 <SubmitButton match={{ action: "message" }} pending="Sending…">
1003 Send
1004 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1005 </div>
1006 </Form>
Agents as a team: lifecycle, merge queue, billing and a new shell1007 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1008 {messages.length > 0 && (
1009 <ul className="mt-3 space-y-1.5">
1010 {messages.map((message) => (
1011 <li key={message.id} className="flex items-start gap-2 text-sm">
1012 <Avatar name={message.author} size={18} />
1013 <span className="min-w-0 grow">
1014 <span className="font-medium">
1015 {message.fromNumber != null ? `The agent on #${message.fromNumber}` : message.author}
1016 </span>{" "}
1017 <span className="text-muted">
1018 {message.kind === "question"
1019 ? "asked:"
1020 : message.kind === "handoff"
1021 ? "handed over:"
1022 : message.kind === "answer"
1023 ? "answered:"
1024 : "to the agent:"}
1025 </span>{" "}
1026 {message.body}
1027 {message.answer && (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1028 <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 change1029 {message.declined ? "Declined: " : "Answer: "}
1030 {message.answer}
1031 </span>
1032 )}
1033 </span>
Merge branch 'worktree-agent-ad7c6d88d93adc817'1034 <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 change1035 {message.deliveredAt ? "read by the agent" : "waiting for its next step"}
1036 </span>
1037 </li>
1038 ))}
1039 </ul>
1040 )}
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md1041 {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 change1042 {collisions.length > 0 && active && (
1043 <div className="mt-4 rounded-xl border border-line bg-surface px-4 py-3 text-sm">
1044 <p className="flex items-center gap-2.5 font-medium">
1045 <Radar size={16} className="shrink-0 text-info" />
1046 Other work is changing the same files
1047 </p>
1048 <ul className="mt-2 space-y-1.5">
1049 {collisions.map((other) => (
1050 <li key={other.number} className="flex flex-wrap items-baseline gap-x-2 text-muted">
1051 <Link
1052 to={`${base}/pull/${other.number}`}
1053 className="font-medium text-fg hover:underline"
1054 >
1055 {other.title} <span className="font-normal text-faint">#{other.number}</span>
1056 </Link>
1057 <span className="font-mono text-xs">{other.paths.join(", ")}</span>
1058 </li>
1059 ))}
1060 </ul>
1061 <p className="mt-2 text-xs text-faint">
1062 Whichever merges second will have to catch up, and may conflict.
1063 </p>
1064 </div>
1065 )}
1066 </div>
1067 )}
1068 <div className="mt-5">
1069 {comparison ? (
1070 <DiffView
1071 comparison={comparison}
1072 review={{
1073 comments: comments.filter((comment) => comment.path),
1074 canComment: Boolean(viewer),
1075 }}
1076 />
1077 ) : tab === "session" ? (
1078 session.length === 0 ? (
1079 <EmptyState title="Nothing recorded yet">
1080 The agent's prompts, reasoning and tool calls appear here as it
1081 works.
1082 </EmptyState>
1083 ) : (
1084 <ol className="relative space-y-5 before:absolute before:top-2 before:bottom-2 before:left-3.25 before:w-px before:bg-line">
1085 {session.map((entry) => (
1086 <Entry key={entry.seq} entry={entry} agent={pull.agent} />
1087 ))}
1088 </ol>
1089 )
1090 ) : (
1091 <div className="space-y-4">
1092 <TimelineItem
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1093 author={opener.name}
Pull requests: title and tabs across the full width, with counts and the size of the change1094 at={pull.createdAt}
1095 action={
1096 <span>
1097 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-agent1098 {opener.requestedBy ? (
Pull requests: title and tabs across the full width, with counts and the size of the change1099 <>
1100 {" "}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1101 for{" "}
1102 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
1103 </>
1104 ) : !pull.branch && (
1105 <>
1106 {" "}
Pull requests: title and tabs across the full width, with counts and the size of the change1107 with <span className="font-mono text-xs">{pull.agent}</span>
1108 </>
1109 )}
1110 </span>
1111 }
1112 >
1113 {pull.body ? (
1114 <Markdown source={pull.body} repo={{ namespace: params.owner, name: params.repo }} />
1115 ) : (
1116 <p className="text-sm text-muted">
1117 {working
1118 ? "No description yet. It is written when the pull request is marked ready for review."
1119 : "No description."}
1120 </p>
1121 )}
1122 {canManage && pull.status === "open" && (
1123 <details className="mt-3 border-t border-line pt-3 text-sm">
1124 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1125 Edit description
1126 </summary>
1127 <Form method="post" className="mt-3 space-y-2">
Merge branch 'worktree-agent-a985c01f43538af01'1128 <input type="hidden" name="action" value="describe" />
Pull requests: title and tabs across the full width, with counts and the size of the change1129 <Textarea
1130 name="summary"
1131 rows={6}
1132 placeholder="What changed and why"
1133 defaultValue={pull.body ?? ""}
1134 />
Merge branch 'worktree-agent-a985c01f43538af01'1135 <SubmitButton match={{ action: "describe" }} pending="Saving…">
1136 Save
1137 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1138 </Form>
1139 </details>
1140 )}
1141 </TimelineItem>
Agents as a team: lifecycle, merge queue, billing and a new shell1142
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1143 <CommentList
1144 comments={comments}
1145 review={review}
1146 base={base}
1147 viewerId={viewer?.id ?? null}
1148 canModerate={canModerate}
1149 failed={actionData && "comment" in actionData ? { comment: String(actionData.comment), error: actionData.error } : null}
1150 />
Agents as a team: lifecycle, merge queue, billing and a new shell1151
Merge branch 'worktree-agent-aca4790ea223aa235'1152 {(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 coming1153
Pull requests: title and tabs across the full width, with counts and the size of the change1154 {pull.status === "draft" && (
1155 <StatusBox>
1156 <StatusRow icon={<Loader size={16} className="text-faint" />} title="This is a draft">
1157 It is still being worked on. It can be reviewed and merged once it is
1158 marked ready.
1159 </StatusRow>
1160 {canManage && (
1161 <Form method="post" className="space-y-2 px-4 py-3">
Merge branch 'worktree-agent-a985c01f43538af01'1162 <input type="hidden" name="action" value="ready" />
Pull requests: title and tabs across the full width, with counts and the size of the change1163 <Textarea name="summary" rows={3} placeholder="What changed and why" />
Merge branch 'worktree-agent-a985c01f43538af01'1164 <SubmitButton match={{ action: "ready" }} pending="Marking ready…">
1165 Mark ready for review
1166 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1167 </Form>
1168 )}
1169 </StatusBox>
1170 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1171
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1172 {codeOwners && active && (
1173 <div className="flex gap-3">
1174 <span className="hidden w-8 shrink-0 sm:block" />
1175 <div className="min-w-0 grow">
1176 <PullCodeOwnersPanel
1177 owners={codeOwners}
1178 base={base}
1179 branch={defaultBranch}
1180 errorsHref={canProtect ? `${base}/settings/branches#codeowners` : `${base}/blob/${encodeURIComponent(defaultBranch)}/${codeOwners.path}`}
1181 />
1182 </div>
1183 </div>
1184 )}
1185
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md1186 {(pull.status === "merged" || pull.status === "closed") && statuses.length > 0 && (
1187 <StatusBox>
1188 <Suspense fallback={settledChecks({}, true)}>
1189 <Await resolve={workflowJobs}>{(jobs) => settledChecks(jobs)}</Await>
1190 </Suspense>
1191 </StatusBox>
1192 )}
1193
Pull requests: title and tabs across the full width, with counts and the size of the change1194 {pull.status === "open" && (
1195 <StatusBox>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1196 {/* Checks by status at once; job by job when the runs are read. */}
Merge branch 'worktree-agent-aca4790ea223aa235'1197 <Suspense fallback={checksSection({}, true)}>
1198 <Await resolve={workflowJobs}>{(jobs) => checksSection(jobs)}</Await>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1199 </Suspense>
1200 {noChecks && <AddCiPrompt owner={params.owner} repo={params.repo} canAdd={canAddCi} compact />}
Agents as a team: lifecycle, merge queue, billing and a new shell1201 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1202 icon={
1203 reviews.some(({ verdict }) => verdict === "request_changes") ? (
1204 <CircleSlash size={16} className="text-danger" />
1205 ) : reviews.length > 0 ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1206 <CircleCheck size={16} className="text-success" />
Pull requests: title and tabs across the full width, with counts and the size of the change1207 ) : (
1208 <MessageSquare size={16} className="text-faint" />
1209 )
1210 }
1211 title={
1212 reviews.length === 0
1213 ? "No reviews yet"
1214 : reviews
1215 .map(
1216 ({ reviewer, verdict }) =>
1217 `${verdict === "approve" ? "Approved by" : "Changes requested by"} ${reviewer}`,
1218 )
1219 .join(" · ")
1220 }
Agents as a team: lifecycle, merge queue, billing and a new shell1221 >
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1222 {reviewPending && "g1t is reviewing it now. "}
Pull requests: title and tabs across the full width, with counts and the size of the change1223 {requiredApprovals > 0 &&
1224 `This repository requires ${requiredApprovals} approving ${
1225 requiredApprovals === 1 ? "review" : "reviews"
1226 } before merging.`}
Agents as a team: lifecycle, merge queue, billing and a new shell1227 </StatusRow>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1228 {codeOwners?.required && (
1229 <StatusRow
1230 icon={
1231 ownersMissing ? (
1232 <CircleSlash size={16} className="text-warn" />
1233 ) : (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1234 <CircleCheck size={16} className="text-success" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1235 )
1236 }
1237 title={ownersMissing ? "Waiting for code owners" : "Code owners approved"}
1238 >
1239 {ownersMissing ?? `Every file it changes has its owners' approval.`}{" "}
1240 {ownersMissing && "This repository requires their review before merging."}
1241 </StatusRow>
1242 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1243 {landing ? (
Agents as a team: lifecycle, merge queue, billing and a new shell1244 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1245 icon={<Loader size={16} className="animate-spin text-accent" />}
1246 title="Merging"
1247 >
1248 {defaultBranch} has moved, so g1t is bringing this up to date first. It
1249 lands as soon as that is done. Watch it in the Session tab.
1250 </StatusRow>
Catching up with main takes seconds when the two sides touched different files1251 ) : agentCatchUp && behind ? (
1252 <CatchUpProgress
1253 owner={params.owner}
1254 repo={params.repo}
1255 number={pull.number}
1256 defaultBranch={defaultBranch}
1257 behind={behind}
1258 update={agentCatchUp.update}
1259 startedAt={agentCatchUp.startedAt}
1260 retrying={catchUpPending}
Merge branch 'worktree-agent-aca4790ea223aa235'1261 runs={agentRuns}
Catching up with main takes seconds when the two sides touched different files1262 />
Pull requests: title and tabs across the full width, with counts and the size of the change1263 ) : conflicting ? (
1264 <ConflictsSection
1265 conflicts={conflicts}
1266 pull={pull}
1267 owner={params.owner}
1268 repo={params.repo}
1269 defaultBranch={defaultBranch}
1270 changesUrl={here + "?tab=changes"}
1271 canResolve={canUpdate && agentsEnabled}
Catching up with main takes seconds when the two sides touched different files1272 resolving={catchUpPending}
1273 error={catchUp && "error" in catchUp ? catchUp.error : null}
Agents as a team: lifecycle, merge queue, billing and a new shell1274 />
Pull requests: title and tabs across the full width, with counts and the size of the change1275 ) : probing ? (
1276 <MergeabilityRow mergeable={mergeable} defaultBranch={defaultBranch} />
1277 ) : behind ? (
1278 <StatusRow
1279 icon={<GitPullRequestArrow size={16} className="text-info" />}
1280 title={`${defaultBranch} has moved since this was made`}
1281 >
1282 {mergeable === "clean" && "It has no conflicts with it. "}
1283 {requireUpToDate
1284 ? "This repository requires pull requests to be up to date, so it has to catch up before it can merge."
1285 : "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 files1286 {canUpdate && (
1287 <Form method="post" className="mt-2">
Pull requests: title and tabs across the full width, with counts and the size of the change1288 <input type="hidden" name="action" value="update" />
Merge branch 'worktree-agent-a985c01f43538af01'1289 <SubmitButton variant="quiet" match={{ action: "update" }} pending={`Merging ${defaultBranch} in…`}>
1290 Catch up with {defaultBranch} now
1291 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1292 </Form>
1293 )}
Catching up with main takes seconds when the two sides touched different files1294 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Pull requests: title and tabs across the full width, with counts and the size of the change1295 </StatusRow>
1296 ) : (
Merge branch 'worktree-agent-af9f0f386d52b6a73'1297 (pull.headCommit || (catchUp && "error" in catchUp)) && (
Pull requests: title and tabs across the full width, with counts and the size of the change1298 <StatusRow
Merge branch 'worktree-agent-ad7c6d88d93adc817'1299 icon={<CircleCheck size={16} className="text-success" />}
Catching up with main takes seconds when the two sides touched different files1300 title={
1301 (caughtUp && !caughtUp.already) || agentCatchUp
1302 ? `Brought up to date with ${defaultBranch}`
1303 : `Up to date with ${defaultBranch}`
1304 }
1305 >
1306 {caughtUp && !caughtUp.already && (
1307 <>
1308 Merged <span className="font-mono">{defaultBranch}</span> in as{" "}
1309 <span className="font-mono text-fg">{caughtUp.commit.slice(0, 7)}</span>. Its checks run
1310 again on the new commit.
1311 </>
1312 )}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1313 {agentCatchUp && "g1t merged it in and pushed the result. Its checks run again on the new commit."}
Merge branch 'worktree-agent-af9f0f386d52b6a73'1314 {/* A catch-up refused or failed after the pull request stopped being behind. */}
1315 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Catching up with main takes seconds when the two sides touched different files1316 </StatusRow>
Pull requests: title and tabs across the full width, with counts and the size of the change1317 )
1318 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1319 {active && rules && (rulesUnmet.length > 0 || rulesBypassable.length > 0 || rules.evaluate.length > 0) && (
1320 <StatusRow
1321 icon={
1322 <Scale
1323 size={16}
1324 className={rulesUnmet.length > 0 ? "text-danger" : rulesBypassable.length > 0 ? "text-warn" : "text-info"}
1325 />
1326 }
1327 title={
1328 rulesUnmet.length > 0
1329 ? `Rules for ${defaultBranch} are not met yet`
1330 : rulesBypassable.length > 0
1331 ? `You may bypass the rules for ${defaultBranch}`
1332 : "Rulesets being evaluated would refuse this merge"
1333 }
1334 >
1335 <div className="mt-2 space-y-3">
1336 {rulesUnmet.length > 0 && <ViolationList violations={rulesUnmet} />}
1337 {rulesBypassable.length > 0 && (
1338 <div>
1339 <p className="mb-1.5 text-xs text-muted">Not met, but a ruleset lets you bypass it:</p>
1340 <ViolationList violations={rulesBypassable} tone="warn" />
1341 </div>
1342 )}
1343 {rules.evaluate.length > 0 && (
1344 <div>
1345 <p className="mb-1.5 text-xs text-muted">Rulesets in evaluate would refuse it for this; nothing is held up:</p>
1346 <ViolationList violations={rules.evaluate} tone="info" />
1347 </div>
1348 )}
1349 {canProtect && (
1350 <Link
1351 to={`${base}/settings/rules?branch=${encodeURIComponent(defaultBranch)}`}
1352 className="inline-block text-xs text-muted underline underline-offset-2 hover:text-fg"
1353 >
1354 See every rule for {defaultBranch}
1355 </Link>
1356 )}
1357 </div>
1358 </StatusRow>
1359 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1360 {stalled && !lifecycle && (
1361 <StatusRow icon={<Hand size={16} className="text-warn" />} title="Needs you">
1362 {stalled}
1363 </StatusRow>
1364 )}
1365 {canMerge && lifecycle?.stage === "queued" && (
1366 <Form method="post" className="flex flex-wrap items-center gap-3 px-4 py-3">
1367 <ButtonLink to={`${base}/queue`}>
1368 <Layers size={15} />
1369 See the queue
1370 </ButtonLink>
Merge branch 'worktree-agent-a985c01f43538af01'1371 <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 change1372 Remove from the queue
Merge branch 'worktree-agent-a985c01f43538af01'1373 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1374 </Form>
1375 )}
1376 {canMerge && !landing && lifecycle?.stage !== "queued" && (
1377 <Form method="post" className="space-y-3 px-4 py-3">
1378 {issue?.state === "open" && (
1379 <CheckboxOption
1380 name="keepIssueOpen"
1381 label={`Keep #${issue.number} open. This is only part of the work.`}
1382 labelClassName="text-xs text-muted"
1383 />
1384 )}
1385 {unchecked && canIgnoreChecks && (
1386 <CheckboxOption
1387 name="ignoreChecks"
Fast pages, required checks on the branch, self-hosted runners, honest incidents1388 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 change1389 labelClassName="text-xs text-muted"
1390 />
1391 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1392 {rulesBypassable.length > 0 && rulesUnmet.length === 0 && (
1393 <CheckboxOption
1394 name="bypassRules"
1395 checked={bypassing}
1396 onCheckedChange={(checked) => setBypassing(checked === true)}
1397 label={`Bypass the rules for ${defaultBranch}: merge although they are not met. It is recorded.`}
1398 labelClassName="text-xs text-muted"
1399 />
1400 )}
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)1401 <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'1402 <Hint label={mergeBlocked} disabled={mergeBlocked != null}>
1403 <SubmitButton
1404 variant="accent"
1405 name="action"
1406 value="merge"
1407 disabled={mergeBlocked != null}
1408 pending={mergeQueue ? "Adding to the queue…" : "Merging…"}
1409 >
1410 {mergeQueue ? <Layers size={15} /> : <GitMerge size={15} />}
1411 {mergeQueue ? "Add to the merge queue" : `Merge into ${defaultBranch}`}
1412 </SubmitButton>
1413 </Hint>
Pull requests: title and tabs across the full width, with counts and the size of the change1414 <span className={`text-xs ${mergeBlocked ? "text-danger" : "text-muted"}`}>
1415 {mergeBlocked ?? (mergeQueue
1416 ? `Tested together with everything ahead of it, then lands on ${defaultBranch}.`
1417 : issue?.state === "open"
1418 ? `Closes issue #${issue.number}, and any other pull requests still open for it.`
1419 : `Lands its commits on ${defaultBranch}.`)}
1420 </span>
1421 </div>
1422 {actionData?.action === "merge" && (
1423 <ErrorText>{actionData.error}</ErrorText>
1424 )}
1425 </Form>
1426 )}
1427 </StatusBox>
1428 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1429
Pull requests: title and tabs across the full width, with counts and the size of the change1430 <CommentForm
1431 author={viewer?.username ?? null}
1432 resetKey={comments.length}
1433 review={review}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1434 handles={teams.map((team) => `@${team.ref}`)}
Pull requests: title and tabs across the full width, with counts and the size of the change1435 />
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1436 {canManage && (active || pull.status === "closed") && (
1437 <Form method="post" className="flex flex-wrap justify-end gap-2">
1438 {pull.status === "open" && (
1439 <SubmitButton variant="quiet" name="action" value="draft" pending="Converting…">
1440 Convert to draft
1441 </SubmitButton>
1442 )}
1443 {active ? (
1444 <SubmitButton variant="quiet" name="action" value="close" pending="Closing…">
1445 Close pull request
1446 </SubmitButton>
1447 ) : (
1448 <SubmitButton variant="quiet" name="action" value="reopen" pending="Reopening…">
1449 Reopen pull request
1450 </SubmitButton>
1451 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1452 </Form>
1453 )}
1454 {actionData &&
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1455 !["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 change1456 String(actionData.action),
1457 ) && <ErrorText>{actionData.error}</ErrorText>}
1458 </div>
1459 )}
1460 {actionData?.action === "comment" && (
1461 <div className="mt-2">
1462 <ErrorText>{actionData.error}</ErrorText>
1463 </div>
1464 )}
1465 </div>
1466 </div>
1467
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)1468 <DetailsDisclosure
1469 className={tab === "changes" ? "hidden" : "order-first lg:order-none"}
1470 summary={detailsSummary([
1471 [reviewerNames.length + teamReviewers.length, "reviewer", "reviewers"],
1472 [pull.assignees.length, "assignee", "assignees"],
1473 [(pull.labels ?? []).length, "label", "labels"],
1474 ])}
1475 >
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md1476 {/* Other than a draft, a pull request shows its checks in full in the conversation. */}
1477 {pull.status === "draft" && <WorkflowStatuses statuses={statuses} />}
Pull requests: title and tabs across the full width, with counts and the size of the change1478 {affects.length > 0 && (
1479 <section>
1480 <h3 className="flex items-center gap-1.5 text-sm font-medium">
1481 <Network size={14} className="text-faint" />
1482 Affects
1483 </h3>
1484 <p className="mt-1 text-xs text-muted">Projects that use this one, and so may feel this change:</p>
1485 <ul className="mt-2 space-y-1 text-sm">
1486 {affects.map((project) => (
1487 <li key={project.slug} className="flex items-center justify-between gap-2">
1488 <Link to={`/${params.owner}/${project.slug}`} className="hover:underline">
1489 {project.name}
1490 </Link>
1491 {project.as && <code className="font-mono text-xs text-faint">{project.as}</code>}
1492 </li>
1493 ))}
1494 </ul>
1495 {preview?.branch && canMerge && (
1496 <Form method="post" className="mt-3">
1497 <input type="hidden" name="action" value="stack" />
1498 <input type="hidden" name="branch" value={preview.branch} />
Merge branch 'worktree-agent-a985c01f43538af01'1499 <SubmitButton
1500 match={{ action: "stack" }}
1501 pending="Starting the builds…"
1502 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 change1503 >
1504 Preview them against this change
Merge branch 'worktree-agent-a985c01f43538af01'1505 </SubmitButton>
Agents as a team: lifecycle, merge queue, billing and a new shell1506 </Form>
1507 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1508 {actionData?.action === "stack" &&
1509 ("notice" in actionData ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1510 <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 change1511 ) : (
1512 <ErrorText>{actionData.error}</ErrorText>
1513 ))}
1514 </section>
Acceptance checks in sandboxes, line comments and review verdicts1515 )}
Issues and pull requests replace intents and attempts1516
Project dependencies: addresses, preview stacks, Affects, and agents who know1517 <section>
Pull requests: title and tabs across the full width, with counts and the size of the change1518 <h3 className="text-sm font-medium">Reviewers</h3>
1519 <ul className="mt-2 space-y-1.5 text-sm">
1520 {reviewerNames.map((name) => {
1521 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-agent1522 const pending = name === "g1t" && reviewPending;
Pull requests: title and tabs across the full width, with counts and the size of the change1523 return (
1524 <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-agent1525 {name === "g1t" ? (
Pull requests: title and tabs across the full width, with counts and the size of the change1526 <Sparkles size={16} className="shrink-0 text-accent" />
1527 ) : (
1528 <Avatar name={name} size={20} />
1529 )}
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)1530 <span className="grow truncate font-mono text-xs">
1531 <PersonLink name={name} className="hover:underline" />
1532 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change1533 {pending ? (
1534 <span className="flex items-center gap-1.5 text-xs text-muted">
1535 <span className="size-1.5 animate-pulse rounded-full bg-accent" />
1536 Reviewing
1537 </span>
1538 ) : verdict === "approve" ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1539 <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 change1540 <CircleCheck size={13} /> Approved
1541 </span>
1542 ) : verdict === "request_changes" ? (
1543 <span className="flex items-center gap-1 text-xs text-danger">
1544 <CircleSlash size={13} /> Changes requested
1545 </span>
1546 ) : (
1547 <span className="text-xs text-faint">Review requested</span>
1548 )}
1549 </li>
1550 );
1551 })}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1552 {teamReviewers.map((team) => (
1553 <TeamReviewer key={team} team={team} />
1554 ))}
1555 {reviewerNames.length === 0 && teamReviewers.length === 0 && (
Pull requests: title and tabs across the full width, with counts and the size of the change1556 <li className="px-1 text-xs text-faint">No reviews requested yet.</li>
1557 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1558 </ul>
Pull requests: title and tabs across the full width, with counts and the size of the change1559 {pull.status === "open" && canManage && (
1560 <div className="mt-3 space-y-2">
1561 {agentsEnabled && !reviewPending && (
1562 <Form method="post">
1563 <input type="hidden" name="action" value="agent-review" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1564 {[...pull.reviewers, ...teamReviewers].map((name) => (
Pull requests: title and tabs across the full width, with counts and the size of the change1565 <input key={name} type="hidden" name="reviewer" value={name} />
1566 ))}
1567 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1568 <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 change1569 <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-agent1570 Request review from g1t
Merge branch 'worktree-agent-a985c01f43538af01'1571 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1572 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1573 <ComputeNote note={loaderData.computeNote} />
Pull requests: title and tabs across the full width, with counts and the size of the change1574 </Form>
1575 )}
1576 <details>
1577 <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 bar1578 {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 change1579 </summary>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1580 <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 change1581 <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-agent1582 {pull.reviewers.includes("g1t") && (
1583 <input type="hidden" name="reviewer" value="g1t" />
Pull requests: title and tabs across the full width, with counts and the size of the change1584 )}
1585 <PeoplePicker
1586 name="reviewer"
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1587 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-agent1588 chosen={pull.reviewers.filter((name) => name !== "g1t")}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1589 placeholder="Other usernames or teams (acme/backend)"
Pull requests: title and tabs across the full width, with counts and the size of the change1590 />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1591 {teamChoices.length > 0 && (
1592 <div className="space-y-1.5 border-t border-line pt-2">
1593 <p className="text-xs text-faint">Teams</p>
1594 {teamChoices.map((team) => (
1595 <CheckboxOption
1596 key={team.ref}
1597 name="reviewer"
1598 value={team.ref}
1599 defaultChecked={teamReviewers.includes(team.ref)}
1600 className="items-center"
1601 labelClassName="flex min-w-0 items-center gap-2"
1602 label={
1603 <>
1604 <Users size={14} className="shrink-0 text-faint" />
1605 <span className="truncate font-mono text-xs">@{team.ref}</span>
1606 </>
1607 }
1608 />
1609 ))}
1610 </div>
1611 )}
Merge branch 'worktree-agent-a985c01f43538af01'1612 <SubmitButton variant="quiet" match={{ action: "reviewers" }} pending="Saving…">
Pull requests: title and tabs across the full width, with counts and the size of the change1613 Save reviewers
Merge branch 'worktree-agent-a985c01f43538af01'1614 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1615 </Form>
1616 </details>
1617 </div>
Project dependencies: addresses, preview stacks, Affects, and agents who know1618 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1619 {["agent-review", "reviewers"].includes(String(actionData?.action)) && (
1620 <ErrorText>{actionData?.error}</ErrorText>
1621 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1622 </section>
Acceptance checks in sandboxes, line comments and review verdicts1623
Pull requests: title and tabs across the full width, with counts and the size of the change1624 <section>
1625 <h3 className="text-sm font-medium">Assignees</h3>
1626 <ul className="mt-2 space-y-1.5 text-sm">
1627 {pull.assignees.map((name) => (
Agents as a team: lifecycle, merge queue, billing and a new shell1628 <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 change1629 <Avatar name={name} size={20} />
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)1630 <span className="grow truncate font-mono text-xs">
1631 <PersonLink name={name} className="hover:underline" />
1632 </span>
Agents as a team: lifecycle, merge queue, billing and a new shell1633 </li>
Pull requests: title and tabs across the full width, with counts and the size of the change1634 ))}
1635 {pull.assignees.length === 0 && (
1636 <li className="px-1 text-xs text-faint">No one yet.</li>
1637 )}
1638 </ul>
1639 {viewer && canManage && active && (
1640 <div className="mt-3 space-y-2">
1641 {!pull.assignees.includes(viewer.username) && (
1642 <Form method="post">
1643 <input type="hidden" name="action" value="assign" />
1644 {pull.assignees.map((name) => (
1645 <input key={name} type="hidden" name="assignee" value={name} />
1646 ))}
1647 <input type="hidden" name="assignee" value={viewer.username} />
1648 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1649 <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 change1650 Assign yourself
Merge branch 'worktree-agent-a985c01f43538af01'1651 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1652 </div>
1653 </Form>
1654 )}
1655 <details>
1656 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1657 Assign people
1658 </summary>
1659 <Form method="post" className="mt-2 space-y-2" key={pull.assignees.join()}>
1660 <input type="hidden" name="action" value="assign" />
1661 <PeoplePicker name="assignee" members={members} chosen={pull.assignees} />
Merge branch 'worktree-agent-a985c01f43538af01'1662 <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 change1663 Save assignees
Merge branch 'worktree-agent-a985c01f43538af01'1664 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1665 </Form>
1666 </details>
1667 </div>
Issues and pull requests replace intents and attempts1668 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1669 {actionData?.action === "assign" && <ErrorText>{actionData.error}</ErrorText>}
1670 </section>
Issues and pull requests replace intents and attempts1671
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1672 <LabelsBox
1673 labels={loaderData.labels}
1674 chosen={pull.labels ?? []}
1675 canEdit={canManage && active}
Merge main (membership, two-factor, GitHub repo roles) into tokens1676 canCreate={loaderData.canMakeLabels}
1677 manageUrl={loaderData.canMakeLabels ? `${base}/labels` : undefined}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1678 />
1679 <MilestoneBox
1680 milestones={loaderData.milestones}
1681 current={pull.milestone}
1682 canEdit={loaderData.canTriage}
1683 base={base}
1684 />
1685
Pull requests: title and tabs across the full width, with counts and the size of the change1686 <section>
1687 <h3 className="text-sm font-medium">Working copy</h3>
1688 {pull.branch ? (
1689 <>
1690 <p className="mt-1 text-xs text-muted">
1691 <GitBranch size={13} className="mr-1 inline align-[-2px]" />
1692 Branch <span className="font-mono text-fg">{pull.branch}</span> of this
1693 repository. Pushes to it show up here.
1694 </p>
1695 <div className="mt-2">
1696 <CopyLine text={`git clone -b ${pull.branch} ${remote}`} />
1697 </div>
1698 </>
1699 ) : (
1700 <>
1701 <p className="mt-1 text-xs text-muted">
1702 This fork belongs to the pull request. Pushes to it show up here.
1703 </p>
1704 <div className="mt-2">
1705 <CopyLine text={`git clone ${remote}`} />
1706 </div>
1707 </>
1708 )}
1709 <p className="mt-3 flex items-center gap-1.5 font-mono text-xs text-faint">
1710 <GitCommitHorizontal size={13} />
Merge checks: statuses and check runs on every commit1711 {pull.headCommit ? (
1712 <Link to={`${base}/commit/${pull.headCommit}`} className="hover:text-fg">
1713 {pull.headCommit.slice(0, 12)}
1714 </Link>
1715 ) : (
1716 "no commits pushed yet"
1717 )}
1718 {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 change1719 </p>
1720 </section>
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings1721 {loaderData.viewer && (
1722 <SubscriptionBox action={`${base}/notifications`} number={pull.number} kind="pull" subscription={loaderData.subscription} />
1723 )}
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)1724 </DetailsDisclosure>
Pull requests: title and tabs across the full width, with counts and the size of the change1725 </div>
Issues and pull requests replace intents and attempts1726 </div>
1727 );
1728}
Pull requests say where they are live; the project menu shows what is coming1729
1730/** Where the pull request's change is live, as GitHub shows a deployment. */
1731function DeploymentCard({
1732 preview,
1733 build,
1734 stacked,
Merge branch 'worktree-agent-aca4790ea223aa235'1735 stacking,
Pull requests say where they are live; the project menu shows what is coming1736 base,
1737}: {
1738 preview: LiveApp | null;
1739 build: Deployment | null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents1740 stacked: Promise<Stacked[]>;
Merge branch 'worktree-agent-aca4790ea223aa235'1741 /** Projects use this one, so previews built against it may be on their way. */
1742 stacking: boolean;
Pull requests say where they are live; the project menu shows what is coming1743 base: string;
1744}) {
1745 const building = build?.status === "queued" || build?.status === "building";
1746 const failed = build?.status === "failed";
1747 return (
1748 <section className="ml-12 overflow-hidden rounded-xl border border-line bg-surface">
1749 <div className="flex flex-wrap items-center gap-3 px-4 py-3">
1750 <span
1751 className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ${
Merge branch 'worktree-agent-ad7c6d88d93adc817'1752 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 coming1753 }`}
1754 >
1755 {building ? <Loader size={15} className="animate-spin" /> : <Rocket size={15} />}
1756 </span>
1757 <div className="min-w-0 grow">
1758 <p className="text-sm font-medium">
1759 {building
1760 ? preview
1761 ? "Deploying the latest push. The last preview is still live."
1762 : "Deploying a preview"
1763 : failed
1764 ? "The preview failed to deploy"
1765 : "This branch is live"}
1766 </p>
1767 {preview ? (
1768 <a href={preview.url} className="mt-0.5 block truncate font-mono text-xs text-muted hover:text-accent">
1769 {preview.url.replace(/^https?:[/][/]/, "")}
1770 </a>
1771 ) : (
1772 build?.error && <p className="mt-0.5 truncate text-xs text-muted">{build.error}</p>
1773 )}
1774 </div>
1775 <div className="flex shrink-0 items-center gap-2">
1776 {build && (
1777 <Link to={`${base}/deployments/${build.id}`} className="text-xs text-muted hover:text-fg">
1778 {failed ? "See why" : "Build log"}
1779 </Link>
1780 )}
1781 {preview && (
1782 <a
1783 href={preview.url}
1784 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"
1785 >
1786 Visit preview
1787 <ArrowUpRight size={13} />
1788 </a>
1789 )}
1790 </div>
1791 </div>
1792 {preview && (
1793 <p className="border-t border-line px-4 py-2 text-xs text-faint">
1794 <span className="font-mono">{preview.commit.slice(0, 7)}</span> · deployed <TimeAgo at={preview.deployedAt} />
1795 {preview.branch && ` · from ${preview.branch}`}
1796 </p>
1797 )}
Fast pages, required checks on the branch, self-hosted runners, honest incidents1798 {/* Read after the page: the card's own part shows at once. */}
Merge branch 'worktree-agent-aca4790ea223aa235'1799 <Suspense
1800 fallback={
1801 stacking && (
1802 <Loading className="border-t border-line px-4 py-2.5 text-xs">
1803 <SkeletonLine className="w-40" />
1804 <SkeletonLine className="mt-1.5 w-64 max-w-full" />
1805 </Loading>
1806 )
1807 }
1808 >
Fast pages, required checks on the branch, self-hosted runners, honest incidents1809 <Await resolve={stacked}>
1810 {(stacked) =>
1811 stacked.length > 0 && (
Merge branch 'worktree-agent-aca4790ea223aa235'1812 <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 incidents1813 <p className="text-xs text-muted">Built against this change:</p>
1814 <ul className="mt-1.5 space-y-1">
1815 {stacked.map((entry) => (
1816 <li key={entry.slug} className="flex items-center gap-2 text-xs">
1817 <span className="font-medium">{entry.name}</span>
1818 <a href={entry.url} className="truncate font-mono text-muted hover:text-accent">
1819 {entry.url.replace(/^https?:[/][/]/, "")}
1820 </a>
1821 </li>
1822 ))}
1823 </ul>
1824 </div>
1825 )
1826 }
1827 </Await>
1828 </Suspense>
Pull requests say where they are live; the project menu shows what is coming1829 </section>
1830 );
1831}

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