Skip to content
1,719 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.

Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1import { env } from "cloudflare:workers";
2import {
3 Bot,
4 ChevronRight,
5 FileDiff,
6 GitBranch,
7 GitCommitHorizontal,
8 CircleCheck,
9 CircleSlash,
10 GitMerge,
11 Layers,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge12 Scale,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request13 GitPullRequestArrow,
14 Hand,
15 Loader,
16 Radar,
17 Sparkles,
18 MessageSquare,
Pull requests say where they are live; the project menu shows what is coming19 ArrowUpRight,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request20 MessagesSquare,
Pull requests say where they are live; the project menu shows what is coming21 Rocket,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request22 StickyNote,
23 User,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar24 Users,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request25 Wrench,
26} from "lucide-react";
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge27import { Suspense, useState } from "react";
Merge branch 'worktree-agent-a985c01f43538af01'28import { Await, Form, Link, redirect } from "react-router";
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request29
Pull requests say where they are live; the project menu shows what is coming30import {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look31 type Capability,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md32 type Comment,
Pull requests say where they are live; the project menu shows what is coming33 type Comparison,
34 type Deployment,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains35 type Job,
Pull requests say where they are live; the project menu shows what is coming36 type LiveApp,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md37 type Pull,
38 type RepoPath,
Pull requests say where they are live; the project menu shows what is coming39 type SessionEntry,
40 type Viewer,
41 pullComparison,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights42 workOwner,
Pull requests say where they are live; the project menu shows what is coming43} from "@g1t/contracts";
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request44
45import type { Route } from "./+types/pull";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit46import { highlightFirstFiles } from "../../lib/highlight.server";
Social cards for every page: og.g1t.sh47import { excerpt, page } from "../../lib/meta";
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent48import { openedBy } from "../../lib/opened-by";
Merge branch 'worktree-agent-aaf03bdceac799c89'49import { cloneUrl, useAddresses } from "../../lib/addresses";
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request50import { DiffView } from "../../components/diff-view";
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)51import { DetailsDisclosure, detailsSummary } from "../../components/details-disclosure";
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request52import { LifecyclePanel } from "../../components/lifecycle";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains53import { AgentPanel } from "../../components/agents";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar54import { BaseBranch } from "../../components/base-branch";
55import { LabelChip, LabelsBox, MilestoneBox } from "../../components/labels";
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request56import { Markdown } from "../../components/markdown";
57import {
58 Avatar,
59 ButtonLink,
60 CopyLine,
61 EmptyState,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look62 ComputeNote,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request63 ErrorText,
Merge branch 'worktree-agent-a985c01f43538af01'64 SubmitButton,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request65 Textarea,
66 TimeAgo,
Merge branch 'worktree-agent-a985c01f43538af01'67 usePending,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request68} from "../../components/ui";
Workspace names and icons, and a component kit for every control69import { CheckboxOption } from "../../components/ui/checkbox";
Merge branch 'worktree-agent-ae1299e92e4462012'70import { Hint } from "../../components/ui/hint";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit71import { TabStrip } from "../../components/ui/tab-strip";
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs72import { WorkflowStatuses } from "../../components/actions";
Fast pages, required checks on the branch, self-hosted runners, honest incidents73import { AddCiPrompt } from "../../components/add-ci";
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md74import { AttemptsBox } from "../../components/attempts";
75import { type Attempt, attemptReview, otherAttempts, sharedPaths, splitOverlaps } from "../../lib/attempts";
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request76import {
77 CommentForm,
78 CommentList,
79 IssueIcon,
80 PeoplePicker,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains81 PersonLink,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request82 PullState,
83 TimelineItem,
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar84 agentReviews,
85 AgentReviewLine,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request86 verdicts,
87} from "../../components/work";
Catching up with main takes seconds when the two sides touched different files88import { CatchUpProgress, ChecksSection, ConflictsSection, MergeabilityRow, runIdOf } from "../../components/merge-box";
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge89import { ViolationList } from "../../components/rules";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar90import { PullCodeOwnersPanel, TeamReviewer } from "../../components/codeowners";
Catching up with main takes seconds when the two sides touched different files91import { CATCH_UP_TIMEOUT_MS } from "../../lib/catch-up";
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put92import { notFound } from "../../lib/not-found.server";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look93import { computeNoteFor } from "../../lib/compute.server";
Projects no longer depend on each other: the dependsOn relation, its settings page (old links redirect), the overview card, a pull request's Affects panel and preview stacks, reference variables and agents' notes on what a project uses are gone; the table is dropped in a later deploy94import { actions, agents, deployments, identity, inbox, repos, work } from "../../lib/services.server";
Merge checks: statuses and check runs on every commit95import { commitChecksFor } from "../../lib/commit-checks.server";
96import { CommitChecksBadge } from "../../components/commit-checks";
Merge branch 'site-fixes' into fast-push97import { DeployLink } from "../../components/deploy";
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request98import { assertSameOrigin, getViewer, requireUser } from "../../lib/session.server";
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings99import { accessTo, refusal, repoFor } from "../../lib/access.server";
100import { SubscriptionBox } from "../../components/notifications";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending101import { REFRESH_MS, useRefreshWhile } from "../../lib/refresh";
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request102
103const EMPTY_COMPARISON: Comparison = { base: null, head: "", files: [], truncated: false };
104const TABS = ["conversation", "session", "changes"] as const;
105type Tab = (typeof TABS)[number];
106
Social cards for every page: og.g1t.sh107export function meta({ loaderData, params, ...args }: Route.MetaArgs) {
108 const pull = loaderData?.pull;
109 const title = pull ? `${pull.title} · Pull request #${pull.number} · ` : "";
110 const state = { draft: "Draft", open: "Open", merged: "Merged", closed: "Closed" }[pull?.status ?? "open"];
111 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-agent112 const opener = pull ? openedBy(pull) : null;
Social cards for every page: og.g1t.sh113 return page(args, {
114 title: `${title}${params.owner}/${params.repo} · g1t`,
115 description: pull
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent116 ? `${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.sh117 : null,
118 // The card shows the title and the state.
119 version: pull ? [pull.title, pull.status] : undefined,
120 type: "article",
121 });
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request122}
123
124export async function loader({ params, context, request }: Route.LoaderArgs) {
125 const viewer = getViewer(context);
126 const path = { namespace: params.owner, name: params.repo };
127 const number = Number(params.number);
128 const asked = new URL(request.url).searchParams.get("tab");
129 const tab: Tab = TABS.find((name) => name === asked) ?? "conversation";
130
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look131 // Members of the workspace pick reviewers and assignees from its people;
132 // what the viewer may do here goes by their role on the repository.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request133 const member = (viewer?.workspaces ?? []).some(
134 (membership) => membership.slug === params.owner,
135 );
Fast pages, required checks on the branch, self-hosted runners, honest incidents136 // Everything starts at once. Only what the viewer's role decides waits
137 // for the repository, which the layout is looking up in this request too
138 // (lib/access.server.ts), and only what the pull request decides waits
139 // for it.
Project dependencies: addresses, preview stacks, Affects, and agents who know140 const ref = { workspace: params.owner, slug: params.repo };
Fast pages, required checks on the branch, self-hosted runners, honest incidents141 const access = accessTo(context, params);
142 const pullFound = work.getPull(path, number, viewer);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar143 // Its labels and milestone, with the repository's to choose from.
144 const labelsFound = work.listLabels(path, viewer);
145 const milestonesFound = work.listMilestones(path, viewer);
146 labelsFound.catch(() => null);
147 milestonesFound.catch(() => null);
148 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 incidents149 access,
150 pullFound,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request151 repos.get(path, viewer),
152 work.getSettings(path, viewer),
g1t's agents only for listed workspaces, whatever the state of billing153 env.RUNNER.enabled(viewer, path),
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request154 // A member picks reviewers and assignees from the workspace's people.
155 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-agent156 // Before someone asks g1t for something: whether the plan lets it start.
Fast pages, required checks on the branch, self-hosted runners, honest incidents157 access.then(({ can }) => (can.run ? computeNoteFor(params.owner, "agent") : null)),
158 tab === "session" ? work.readSession(path, number, viewer) : null,
159 // Its preview, for people with a role here: beside the rest, not after.
160 access.then(({ insider }) => (insider ? deployments.list(ref, viewer).catch(() => null) : null)),
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings161 // Whether the viewer hears of it, for the sidebar's Notifications.
162 viewer
163 ? repoFor(context, params).then((found) =>
164 found.ok ? inbox.subscription(viewer, { repoId: found.value.id, number }).catch(() => null) : null,
165 )
166 : null,
Merge branch 'worktree-agent-aca4790ea223aa235'167 // Its agent's latest runs, for the Agent panel: with the page, not after it.
168 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 bar169 // A member asks the workspace's teams to review, and mentions them.
170 member ? identity.listTeams(viewer, params.owner).catch(() => null) : null,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request171 ]);
172 if (!found.ok) {
173 // Issues and pull requests share numbers; this one may be an issue.
174 const issue = await work.getIssue(path, number, viewer);
175 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 put176 throw notFound("pull");
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request177 }
178 const { pull } = found.value;
179 const range = pullComparison(pull);
Agents and memory, checks and conflicts, profiles, slug renames, custom domains180 // The jobs of each workflow run on its head, to list checks job by job.
181 // 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 incidents182 // 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.md183 // A closed or merged one lists its head's checks too, to read only.
Agents and memory, checks and conflicts, profiles, slug renames, custom domains184 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 incidents185 const workflowJobs =
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md186 tab === "conversation" && pull.status !== "draft" && runIds.length > 0
Fast pages, required checks on the branch, self-hosted runners, honest incidents187 ? Promise.all(runIds.map((id) => actions.run(path, viewer, id).catch(() => null))).then((runs) => {
188 const jobs: Record<string, Job[]> = {};
189 for (const run of runs) if (run?.ok) jobs[run.value.run.id] = run.value.jobs;
190 return jobs;
191 })
192 .catch(() => ({}) as Record<string, Job[]>)
193 : Promise.resolve({} as Record<string, Job[]>);
194 // No checks at all on an open pull request: whether that is because the
195 // repository has no workflows, to offer a starter one.
196 const unchecked =
197 tab === "conversation" &&
198 pull.status === "open" &&
199 (found.value.statuses ?? []).length === 0 &&
200 (found.value.requiredChecks ?? []).length === 0;
Projects no longer depend on each other: the dependsOn relation, its settings page (old links redirect), the overview card, a pull request's Affects panel and preview stacks, reference variables and agents' notes on what a project uses are gone; the table is dropped in a later deploy201 const [comparison, noChecks] = await Promise.all([
Usage, Billing settings and prepaid AI credit; fixes from the UX audit202 // The first screens of it highlighted here, so their colours do not pop in.
203 tab === "changes"
204 ? repos
205 .compare(range.repoId, viewer, range.base, range.head, range.baseBranch)
206 .then(async (found) => (found.ok ? { ok: true as const, value: await highlightFirstFiles(found.value) } : found))
207 : null,
Fast pages, required checks on the branch, self-hosted runners, honest incidents208 unchecked
209 ? actions
210 .workflows(path, viewer)
211 .then((found) => found.ok && found.value.length === 0)
212 .catch(() => false)
213 : false,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request214 ]);
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md215 // Every pull request for its issue, to compare: streamed, and read only
216 // when there is more than this one.
217 const attempts =
218 tab === "conversation" && pull.issue != null && (found.value.issue?.pullCount ?? 0) > 1
219 ? attemptsFor(path, viewer, pull, found.value.comments).catch(() => [] as Attempt[])
220 : null;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar221 const [labels, milestones] = await Promise.all([labelsFound, milestonesFound]);
222 const repoDefault = repo.ok ? repo.value.defaultBranch : "main";
223 // The default branch's protection covers pull requests into it only.
224 const protectedBase = (pull.base ?? repoDefault) === repoDefault;
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request225 return {
226 ...found.value,
Merge checks: statuses and check runs on every commit227 // Every check on its head, as the commit's own list shows them: streamed.
228 headChecks: commitChecksFor(path, viewer, [pull.headCommit]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar229 labels: labels.ok ? labels.value : [],
230 milestones: milestones.ok ? milestones.value : [],
231 // Labelling and milestones: Triage and up; its author, its labels.
232 canTriage: can.triage,
Merge main (membership, two-factor, GitHub repo roles) into tokens233 // Making labels needs Write; applying them, Triage.
234 canMakeLabels: can.manage_labels,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar235 // The branch it merges into: Write and up.
236 canChangeBase: can.push,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains237 workflowJobs,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md238 attempts,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request239 tab,
240 session: session?.ok ? session.value : [],
241 // An empty comparison if it could not be made.
242 comparison: comparison && (comparison.ok ? comparison.value : EMPTY_COMPARISON),
243 viewer,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look244 // Write and up can merge.
245 canMerge: can.merge,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights246 // Triage and up manage anyone's pull request; its author, their own,
247 // and whoever asked g1t for one, that one.
248 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 look249 // Telling its agent things, and re-running checks, spend compute: Write and up.
250 canRun: can.run,
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts251 // Maintain and up edit and delete anyone's comments; everyone, their own.
252 canModerate: can.manage_settings,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request253 // 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 rights254 // whoever it is for (who asked g1t for it, or its author), a branch
255 // from anyone who can push.
256 canUpdate: pull.fork ? viewer?.id === workOwner(pull).id : can.push,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request257 agentsEnabled,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look258 computeNote,
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings259 subscription,
Merge branch 'worktree-agent-aca4790ea223aa235'260 // As the project's agents.json has them; left out, the panel fetches them.
261 agentRuns: runs?.ok ? { runs: runs.value, member: can.run } : undefined,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request262 members: members?.ok ? members.value.map((person) => person.username) : [],
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar263 // `workspace/slug` and name of each team the viewer can see.
264 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 merge265 // What the rules of the branch it merges into ask, as they stack; the
266 // default branch's settings where the rules are not known.
267 requireUpToDate: found.value.rules ? found.value.rules.strict : protectedBase && settings.ok && settings.value.requireUpToDate,
268 mergeQueue: found.value.rules ? found.value.rules.merge_queue : protectedBase && settings.ok && settings.value.mergeQueue,
269 requiredApprovals: found.value.rules
270 ? found.value.rules.required_approvals
271 : protectedBase && settings.ok
272 ? settings.value.requiredApprovals
273 : 0,
274 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 incidents275 noChecks,
276 // Who may open the pull request that adds CI: anyone who can push.
277 canAddCi: can.push,
278 // Who may choose the required checks.
279 canProtect: can.manage_protection,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request280 defaultBranch: repo.ok ? repo.value.defaultBranch : "main",
Projects no longer depend on each other: the dependsOn relation, its settings page (old links redirect), the overview card, a pull request's Affects panel and preview stacks, reference variables and agents' notes on what a project uses are gone; the table is dropped in a later deploy281 ...deploymentOf(deployed?.ok ? deployed.value : null, number),
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request282 };
283}
284
Pull requests say where they are live; the project menu shows what is coming285/**
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md286 * This pull request and the latest few others for its issue, each with its
287 * head's checks and where its review stands, this one first.
288 */
289async function attemptsFor(path: RepoPath, viewer: Viewer, pull: Pull, comments: Comment[]): Promise<Attempt[]> {
290 const found = await work.getIssue(path, pull.issue!, viewer);
291 if (!found.ok) return [];
292 const others = otherAttempts(found.value.pulls, pull.number).flatMap(
293 (number) => found.value.pulls.find((other) => other.number === number) ?? [],
294 );
295 const [details, checks] = await Promise.all([
296 Promise.all(others.map((other) => work.getPull(path, other.number, viewer).catch(() => null))),
297 commitChecksFor(path, viewer, [pull.headCommit, ...others.map((other) => other.headCommit)]),
298 ]);
299 const attempt = (one: Pull, said: Comment[], current: boolean): Attempt => ({
300 number: one.number,
301 title: one.title,
302 status: one.status,
303 supersededBy: one.supersededBy,
304 author: openedBy(one).name,
305 headCommit: one.headCommit,
306 files: one.files,
307 updatedAt: one.updatedAt,
308 current,
309 shared: current ? [] : sharedPaths(one.files, pull.files),
310 checks: (one.headCommit && checks?.[one.headCommit]) || null,
311 review: attemptReview(said, one.reviewers),
312 });
313 return [
314 attempt(pull, comments, true),
315 ...others.map((other, index) => {
316 const detail = details[index];
317 return attempt(other, detail?.ok ? detail.value.comments : [], false);
318 }),
319 ];
320}
321
322/**
Pull requests say where they are live; the project menu shows what is coming323 * Where this pull request is live: its preview (or its latest build, while
Projects no longer depend on each other: the dependsOn relation, its settings page (old links redirect), the overview card, a pull request's Affects panel and preview stacks, reference variables and agents' notes on what a project uses are gone; the table is dropped in a later deploy324 * one is going or after one failed).
Pull requests say where they are live; the project menu shows what is coming325 */
Fast pages, required checks on the branch, self-hosted runners, honest incidents326function deploymentOf(
Pull requests say where they are live; the project menu shows what is coming327 list: { deployments: Deployment[]; live: LiveApp[] } | null,
328 number: number,
Projects no longer depend on each other: the dependsOn relation, its settings page (old links redirect), the overview card, a pull request's Affects panel and preview stacks, reference variables and agents' notes on what a project uses are gone; the table is dropped in a later deploy329): { preview: LiveApp | null; build: Deployment | null } {
Fast pages, required checks on the branch, self-hosted runners, honest incidents330 const preview = list?.live.find((app) => app.kind === "preview" && app.number === number) ?? null;
331 const build = list?.deployments.find((d) => d.kind === "preview" && d.number === number) ?? null;
Projects no longer depend on each other: the dependsOn relation, its settings page (old links redirect), the overview card, a pull request's Affects panel and preview stacks, reference variables and agents' notes on what a project uses are gone; the table is dropped in a later deploy332 return { preview, build };
Pull requests say where they are live; the project menu shows what is coming333}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look334const PULL_NEEDS: Record<string, Capability> = {
335 "agent-review": "run",
336 "rerun-workflow": "run",
Fast pages, required checks on the branch, self-hosted runners, honest incidents337 "rerun-failed": "run",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look338 message: "run",
339 merge: "merge",
340 unqueue: "merge",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar341 milestone: "triage",
342 base: "push",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look343};
344
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request345export async function action({ request, params, context }: Route.ActionArgs) {
346 assertSameOrigin(request);
347 const user = requireUser(context, request);
348 const form = await request.formData();
349 const path = { namespace: params.owner, name: params.repo };
350 const number = Number(params.number);
351 const action = form.get("action");
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look352 // What each form needs beyond reading; work checks the rest.
353 const needed = typeof action === "string" ? PULL_NEEDS[action] : undefined;
354 const refused = needed ? await refusal(context, params, needed) : null;
355 if (refused) return { error: refused, action: String(action) };
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request356 const verdict = form.get("verdict");
357 const line = Number(form.get("line"));
358 /** Names ticked in a people picker, plus those typed beside it. */
359 const picked = (field: string) => [
360 ...form.getAll(field).map(String),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar361 // `@acme/backend` is asked as `acme/backend`.
362 ...String(form.get("others") ?? "").split(/[\s,]+/).map((name) => name.replace(/^@/, "")),
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request363 ];
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent364 // Asking g1t for its review records the request, then starts it.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request365 if (action === "agent-review") {
366 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-agent367 reviewers: [...form.getAll("reviewer").map(String), "g1t"],
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request368 });
369 if (!asked.ok) return { error: asked.error.message, action };
370 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar371 // Its labels, milestone and base, from the sidebar's menus and the header.
372 if (action === "labels") {
373 const set = await work.setLabels(user, path, number, form.getAll("label").map(String));
374 return set.ok ? null : { error: set.error.message, action };
375 }
376 if (action === "milestone" || action === "base") {
377 const changed = await work.updatePull(
378 user,
379 path,
380 number,
381 action === "milestone"
382 ? { milestone: Number(form.get("milestone")) || 0 }
383 : { base: String(form.get("base") ?? "") },
384 );
385 return changed.ok ? null : { error: changed.error.message, action };
386 }
Catching up with main takes seconds when the two sides touched different files387 // Catching up: merged and pushed in seconds when the two sides changed
388 // different files; otherwise handed to a sandbox, which takes a minute.
389 if (action === "update") {
390 const caught = await work.catchUpPull(user, path, number);
391 if (!caught.ok) return { action, error: caught.error.message };
392 const update = caught.value;
393 if (update.outcome !== "needs_agent") {
394 return {
395 action,
396 updated: { commit: update.commit, already: update.outcome === "up_to_date", at: Date.now() },
397 };
398 }
399 const started = await env.RUNNER.update(user, path, number);
400 if (!started.ok) return { action, error: started.error.message };
401 return { action, agent: { update, startedAt: Date.now() } };
402 }
Agents and memory, checks and conflicts, profiles, slug renames, custom domains403 // A workflow run's failed jobs, run again. Who may is the actions service's call.
404 if (action === "rerun-workflow") {
405 const rerun = await actions.rerun(user, path, String(form.get("run") ?? ""), true);
406 return rerun.ok ? null : { error: rerun.error.message, action };
407 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents408 // Every failed run on its head, run again: Mission control's quick action.
409 if (action === "rerun-failed") {
410 const found = await work.getPull(path, number, user);
411 if (!found.ok) return { error: found.error.message, action };
412 const failed = [
413 ...new Set(
414 (found.value.statuses ?? [])
415 .filter((status) => status.state === "failure" || status.state === "error")
416 .map(runIdOf)
417 .filter((id) => id != null),
418 ),
419 ];
420 if (failed.length === 0) return { error: "No workflow run failed on its latest commit.", action };
421 const reruns = await Promise.all(failed.map((id) => actions.rerun(user, path, id, true)));
422 const refused = reruns.find((rerun) => !rerun.ok);
423 return refused && !refused.ok ? { error: refused.error.message, action } : null;
424 }
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts425 // A comment's own text, changed or taken away: its author's to do, or a
426 // maintainer's, which work checks.
427 if (action === "edit-comment" || action === "delete-comment") {
428 const id = String(form.get("comment") ?? "");
429 const changed =
430 action === "edit-comment"
431 ? await work.editComment(user, path, id, String(form.get("body") ?? ""))
432 : await work.deleteComment(user, path, id);
433 return changed.ok ? null : { error: changed.error.message, action, comment: id };
434 }
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request435 const result =
436 action === "merge"
437 ? await work.mergePull(user, path, number, {
438 keepIssueOpen: form.get("keepIssueOpen") === "on",
439 ignoreChecks: form.get("ignoreChecks") === "on",
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge440 bypassRules: form.get("bypassRules") === "on",
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request441 })
442 : action === "unqueue"
443 ? await work.removeFromQueue(user, path, number)
444 : action === "message"
445 ? await work.messageAgent(user, path, number, String(form.get("body") ?? ""))
446 : action === "close"
447 ? await work.closePull(user, path, number)
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts448 : action === "reopen"
449 ? await work.reopenPull(user, path, number)
450 : action === "draft"
451 ? await work.convertPullToDraft(user, path, number)
Fast pages, required checks on the branch, self-hosted runners, honest incidents452 : action === "agent-review"
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request453 ? await env.RUNNER.review(user, path, number)
454 : action === "reviewers"
455 ? await work.updatePull(user, path, number, { reviewers: picked("reviewer") })
456 : action === "assign"
457 ? await work.updatePull(user, path, number, { assignees: picked("assignee") })
458 : action === "comment"
459 ? await work.addComment(user, path, number, {
460 body: String(form.get("body") ?? ""),
461 path: String(form.get("path") ?? "") || undefined,
462 line: line > 0 ? line : undefined,
463 verdict:
464 verdict === "approve" || verdict === "request_changes" ? verdict : undefined,
465 })
466 : await work.readyPull(user, path, number, String(form.get("summary") ?? ""));
467 return result.ok ? null : { error: result.error.message, action };
468}
469
Pull requests: title and tabs across the full width, with counts and the size of the change470/** How many, beside a tab's name, when there are any. */
471function TabCount({ n }: { n: number }) {
472 if (n <= 0) return null;
473 return (
474 <span className="rounded-full bg-raised px-1.5 py-px text-xs tabular-nums text-muted ring-1 ring-line">
475 {n.toLocaleString("en-US")}
476 </span>
477 );
478}
479
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request480function TabLink({
481 to,
482 active,
483 children,
484}: {
485 to: string;
486 active: boolean;
487 children: React.ReactNode;
488}) {
489 return (
490 <Link
491 to={to}
492 preventScrollReset
Usage, Billing settings and prepaid AI credit; fixes from the UX audit493 aria-current={active ? "page" : undefined}
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request494 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)495 "-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 " +
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request496 (active
497 ? "border-accent font-medium text-fg"
498 : "border-transparent text-muted hover:text-fg")
499 }
500 >
501 {children}
502 </Link>
503 );
504}
505
506/** One step of the session, on the timeline's rail. */
507function Entry({ entry, agent }: { entry: SessionEntry; agent: string }) {
508 const isTool = entry.kind === "tool_call" || entry.kind === "tool_result";
509 const Icon =
510 entry.kind === "prompt"
511 ? User
512 : entry.kind === "note"
513 ? StickyNote
514 : isTool
515 ? Wrench
516 : Bot;
517 return (
518 <li className="relative pl-10">
519 <span
520 className={`absolute top-0.5 left-0 flex size-7 items-center justify-center rounded-full border bg-bg ${
521 entry.kind === "prompt"
522 ? "border-accent/50 text-accent"
523 : "border-line text-faint"
524 }`}
525 >
526 <Icon size={14} />
527 </span>
528 {isTool ? (
529 <details className="group rounded-lg border border-line bg-surface">
530 <summary className="flex cursor-pointer list-none items-center gap-2 px-3 py-1.5 text-sm">
531 <ChevronRight
532 size={14}
533 className="text-faint transition-transform group-open:rotate-90"
534 />
535 <span className="font-mono text-xs text-accent">
536 {entry.tool ?? "tool"}
537 </span>
538 <span className="truncate font-mono text-xs text-muted">
539 {entry.kind === "tool_result" ? "→ " : ""}
540 {entry.text.split("\n")[0]}
541 </span>
542 </summary>
543 <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 line544 <code>{entry.text}</code>
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request545 </pre>
546 </details>
547 ) : (
548 <div>
549 <p className="text-xs font-medium text-faint">
550 {entry.kind === "prompt"
551 ? "Prompt"
552 : entry.kind === "note"
553 ? "Note"
554 : agent}
555 </p>
556 {entry.kind === "message" ? (
557 <div className="mt-1">
558 <Markdown source={entry.text} />
559 </div>
560 ) : entry.kind === "prompt" ? (
561 <div className="mt-1.5 max-h-[32rem] overflow-y-auto rounded-xl border border-line bg-surface p-4">
562 <Markdown source={entry.text} />
563 </div>
564 ) : (
565 <p className="mt-1 text-[0.9375rem] leading-relaxed wrap-break-word whitespace-pre-wrap">
566 {entry.text}
567 </p>
568 )}
569 </div>
570 )}
571 {entry.commit && (
572 <p className="mt-1.5 flex items-center gap-1 font-mono text-xs text-faint">
573 <GitCommitHorizontal size={12} />
574 {entry.commit.slice(0, 7)}
575 </p>
576 )}
577 </li>
578 );
579}
580
581/** The box at the foot of the conversation saying what stands before a merge. */
582function StatusBox({ children }: { children: React.ReactNode }) {
583 return (
584 <div className="flex gap-3">
585 <span className="hidden w-8 shrink-0 sm:block" />
586 <section className="min-w-0 grow divide-y divide-line overflow-hidden rounded-xl border border-line-strong bg-surface">
587 {children}
588 </section>
589 </div>
590 );
591}
592
593function StatusRow({
594 icon,
595 title,
596 children,
597}: {
598 icon: React.ReactNode;
599 title: string;
600 children?: React.ReactNode;
601}) {
602 return (
603 <div className="flex gap-3 px-4 py-3 text-sm">
604 <span className="mt-0.5 shrink-0">{icon}</span>
605 <div className="min-w-0">
606 <p className="font-medium">{title}</p>
607 {children && <div className="mt-0.5 text-muted">{children}</div>}
608 </div>
609 </div>
610 );
611}
612
613export default function PullPage({ loaderData, actionData, params }: Route.ComponentProps) {
614 const {
615 pull,
616 issue,
617 comments,
618 checks,
619 overlaps,
620 behind,
621 reviewPending,
622 lifecycle,
623 messages,
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs624 statuses = [],
Fast pages, required checks on the branch, self-hosted runners, honest incidents625 requiredChecks = [],
626 noChecks,
627 canAddCi,
628 canProtect,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains629 mergeable = "unknown",
630 conflicts = [],
631 earlierChecks = [],
632 workflowJobs,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md633 attempts,
Project dependencies: addresses, preview stacks, Affects, and agents who know634 preview,
Pull requests say where they are live; the project menu shows what is coming635 build,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request636 landing,
637 stalled,
638 requireUpToDate,
639 mergeQueue,
640 requiredApprovals,
641 canIgnoreChecks,
642 canUpdate,
643 agentsEnabled,
Merge branch 'worktree-agent-aca4790ea223aa235'644 agentRuns,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request645 members,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar646 teams,
647 codeOwners,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge648 rules,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request649 tab,
650 session,
651 comparison,
652 viewer,
653 canMerge,
654 canManage,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look655 canRun,
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts656 canModerate,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar657 defaultBranch: repoDefault,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request658 } = loaderData;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar659 // The branch it merges into: what every line below names.
660 const defaultBranch = pull.base ?? repoDefault;
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request661 const base = `/${params.owner}/${params.repo}`;
662 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-agent663 // g1t, on a pull request it made: the person who asked for it is a line below.
664 const opener = openedBy(pull);
Merge branch 'worktree-agent-aca4790ea223aa235'665 const checksSection = (jobs: Record<string, Job[]>, loading = false) => (
Fast pages, required checks on the branch, self-hosted runners, honest incidents666 <ChecksSection
667 run={checks}
668 required={requiredChecks}
669 statuses={statuses}
670 jobs={jobs}
671 pull={pull}
672 base={base}
673 earlier={earlierChecks}
674 canRerunWorkflows={canMerge}
675 settingsUrl={canProtect ? `${base}/settings/branches` : null}
676 error={actionData?.action === "rerun-failed" || actionData?.action === "rerun-workflow" ? actionData.error : null}
Merge branch 'worktree-agent-aca4790ea223aa235'677 loading={loading}
Fast pages, required checks on the branch, self-hosted runners, honest incidents678 />
679 );
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md680 // A closed or merged pull request's checks, as they ended: to read, with
681 // nothing to re-run and nothing that holds a merge.
682 const settledChecks = (jobs: Record<string, Job[]>, loading = false) => (
683 <ChecksSection
684 run={null}
685 required={[]}
686 statuses={statuses}
687 jobs={jobs}
688 pull={pull}
689 base={base}
690 earlier={[]}
691 canRerunWorkflows={false}
692 settled
693 loading={loading}
694 />
695 );
Merge branch 'worktree-agent-aaf03bdceac799c89'696 const addresses = useAddresses();
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request697 const remote = pull.fork
Merge branch 'worktree-agent-aaf03bdceac799c89'698 ? cloneUrl(addresses, `${pull.fork.namespace}/${pull.fork.name}`)
699 : cloneUrl(addresses, `${params.owner}/${params.repo}`);
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request700 const active = pull.status === "draft" || pull.status === "open";
701
702 // Follow an agent at work, or checks in progress, without a manual reload.
703 const working = pull.status === "draft";
Fast pages, required checks on the branch, self-hosted runners, honest incidents704 const checking = statuses.some((status) => status.state === "pending");
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request705 const reviews = verdicts(comments);
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar706 // Agents' reviews: shown, never counted.
707 const advisory = agentReviews(comments);
Fast pages, required checks on the branch, self-hosted runners, honest incidents708 // What stands between this pull request and a merge, if anything: a
709 // required check that has not passed, or the merge queue taking it out.
710 const unchecked = requiredChecks.some((check) => check.state !== "success") || checks?.status === "failed";
711 const requiredFailed = requiredChecks.some((check) => check.state === "failure") || checks?.status === "failed";
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request712 // 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.md713 // ones for the same issue are alternatives, and expected to: those are
714 // compared under their own heading.
715 const { collisions } = splitOverlaps(overlaps, pull.issue);
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request716 const review = {
717 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 rights718 // Nobody reviews their own pull request, nor one g1t made for them.
719 canJudge: active && viewer != null && viewer.id !== workOwner(pull).id,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request720 };
721 // Everyone whose review was asked for, then anyone who reviewed unasked.
722 const reviewerNames = [
723 ...new Set([...pull.reviewers, ...reviews.map(({ reviewer }) => reviewer)]),
724 ];
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar725 const teamReviewers = pull.teamReviewers ?? [];
726 // Teams to ask: the workspace's, and any asked already.
727 const teamChoices = [
728 ...teams,
729 ...teamReviewers.filter((ref) => !teams.some((team) => team.ref === ref)).map((ref) => ({ ref, name: ref })),
730 ];
731 // Branch protection wants code owners' approval it does not have yet.
732 const ownersMissing = codeOwners?.required && codeOwners.missing ? codeOwners.missing : null;
Catching up with main takes seconds when the two sides touched different files733 // A catch-up: the click shows at once; the answer says whether it is
734 // done already or a sandbox is on it.
Merge branch 'worktree-agent-a985c01f43538af01'735 const catchUpPending = usePending({ action: "update" });
Catching up with main takes seconds when the two sides touched different files736 const catchUp = actionData?.action === "update" ? actionData : null;
737 const caughtUp = catchUp && "updated" in catchUp ? catchUp.updated : null;
738 const agentCatchUp = catchUp && "agent" in catchUp ? catchUp.agent : null;
739 // Pushed already: followed until the pull request's head is the merge,
740 // so its checks show starting again, for a minute at most.
741 const settling =
742 caughtUp != null && !caughtUp.already && pull.headCommit !== caughtUp.commit && Date.now() - caughtUp.at < 60_000;
743 // Followed until it is pushed, or for so long, never longer.
744 const catchingUp =
745 agentCatchUp != null && behind && Date.now() - agentCatchUp.startedAt < CATCH_UP_TIMEOUT_MS + REFRESH_MS;
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request746 // g1t is taking a step of its own accord, so the page will change.
747 const moving =
748 lifecycle != null && lifecycle.stage !== "ready" && lifecycle.stage !== "needs_you";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains749 // Whether it merges cleanly is being worked out, so the box will change.
750 const probing = active && mergeable === "checking";
751 const conflicting = active && mergeable === "conflicting";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending752 useRefreshWhile(working || checking || reviewPending || catchingUp || settling || moving || landing || probing);
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge753 // The rules of its base it does not meet: checks show in their own
754 // section, so the rest stop the button here. Someone a ruleset lets
755 // bypass it may tick a box and merge past them.
756 const [bypassing, setBypassing] = useState(false);
757 const rulesUnmet = (rules?.unmet ?? []).filter((violation) => violation.rule !== "required_status_checks");
758 const rulesBypassable = rules?.bypassable ?? [];
Agents and memory, checks and conflicts, profiles, slug renames, custom domains759 // Why the merge button cannot be pressed, if it cannot.
760 const mergeBlocked = conflicting
761 ? "Resolve the conflicts first."
762 : probing
763 ? "Waiting to find out whether it merges cleanly."
764 : behind && requireUpToDate
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge765 ? `The rules for ${defaultBranch} require it to be up to date first.`
Fast pages, required checks on the branch, self-hosted runners, honest incidents766 : unchecked && !canIgnoreChecks
767 ? `The checks ${defaultBranch} requires have to pass first.`
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge768 : rulesUnmet.length > 0
769 ? rulesUnmet[0]!.message
770 : rulesBypassable.length > 0 && !bypassing
771 ? `Rules for ${defaultBranch} are not met. You may bypass them.`
772 : ownersMissing
773 ? "Code owners have to approve first."
774 : null;
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request775
776 return (
777 // 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 change778 <div>
779 {/* The title and the tabs run the full width; what is happening on
780 the pull request sits at the top of Conversation. */}
781 <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)782 <h2 className="text-xl font-semibold tracking-tight text-balance wrap-break-word sm:text-2xl">
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request783 {pull.title} <span className="font-normal text-faint">#{pull.number}</span>
784 </h2>
785 <div className="mt-3 flex flex-wrap items-center gap-x-3 gap-y-2 text-sm text-muted">
786 <PullState status={pull.status} />
787 <span className="flex items-center gap-2">
788 <Avatar
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent789 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request790 size={18}
791 />
792 <span>
Agents and memory, checks and conflicts, profiles, slug renames, custom domains793 <PersonLink
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent794 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Agents and memory, checks and conflicts, profiles, slug renames, custom domains795 className="font-medium text-fg hover:underline"
796 />{" "}
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request797 {pull.status === "merged" ? "merged" : "wants to merge"}
798 {pull.branch && (
799 <>
800 {" "}
801 <span className="font-mono text-fg">{pull.branch}</span>
802 </>
803 )}{" "}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar804 into{" "}
805 <BaseBranch
806 base={defaultBranch}
807 head={pull.fork ? null : pull.branch}
808 canChange={loaderData.canChangeBase && active}
809 branchesUrl={`${base}/branches.json`}
810 />
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request811 </span>
812 </span>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar813 {(pull.labels ?? []).map((name) => (
814 <LabelChip key={name} name={name} color={loaderData.labels.find((label) => label.name === name)?.color} />
815 ))}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent816 {opener.requestedBy && (
817 <span className="text-xs">
818 requested by{" "}
819 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
820 </span>
821 )}
822 {/* A pull request from a branch was made by its author, not an agent;
823 one g1t made already says so. */}
824 {!pull.branch && !opener.requestedBy && (
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request825 <span className="flex items-center gap-1.5 font-mono text-xs">
826 <Bot size={14} />
827 {pull.agent}
828 {pull.runtime === "hosted" && <span className="text-faint">on g1t</span>}
829 </span>
830 )}
831 </div>
832
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar833 {(reviews.length > 0 || advisory.length > 0) && (
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request834 <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 calendar835 {advisory.map(({ agent, verdict }) => (
836 <AgentReviewLine key={agent.id} agent={agent} verdict={verdict} base={base} />
837 ))}
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request838 {reviews.map(({ reviewer, verdict }) => (
839 <span
840 key={reviewer}
841 className={`flex items-center gap-1.5 ${
Merge branch 'worktree-agent-ad7c6d88d93adc817'842 verdict === "approve" ? "text-success" : "text-danger"
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request843 }`}
844 >
845 {verdict === "approve" ? <CircleCheck size={15} /> : <CircleSlash size={15} />}
846 {verdict === "approve" ? "Approved by" : "Changes requested by"}{" "}
847 <span className="font-medium">{reviewer}</span>
848 </span>
849 ))}
850 </p>
851 )}
852
Usage, Billing settings and prepaid AI credit; fixes from the UX audit853 <TabStrip label="Pull request" className="mt-6 items-end gap-1 border-b border-line">
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request854 <TabLink to={here} active={tab === "conversation"}>
855 <MessageSquare size={15} />
856 Conversation
Pull requests: title and tabs across the full width, with counts and the size of the change857 <TabCount n={comments.length} />
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request858 </TabLink>
859 <TabLink to={here + "?tab=session"} active={tab === "session"}>
860 <MessagesSquare size={15} />
861 Session
862 </TabLink>
863 <TabLink to={here + "?tab=changes"} active={tab === "changes"}>
864 <FileDiff size={15} />
Pull requests: title and tabs across the full width, with counts and the size of the change865 Files changed
866 <TabCount n={pull.files.length} />
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request867 </TabLink>
Pull requests: title and tabs across the full width, with counts and the size of the change868 {pull.files.length > 0 && (
869 <span className="ml-auto hidden pb-2.5 font-mono text-xs sm:inline">
Merge branch 'worktree-agent-ad7c6d88d93adc817'870 <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 change871 <span className="text-danger">−{pull.files.reduce((sum, file) => sum + file.deletions, 0).toLocaleString("en-US")}</span>
872 </span>
873 )}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit874 </TabStrip>
Pull requests: title and tabs across the full width, with counts and the size of the change875 </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)876 <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 change877 <div className="min-w-0">
878 {/* What is happening on it now: its stage, its agent, what people told
879 the agent, and what else touches the same files. On the conversation,
880 so the tabs sit right under the title. */}
881 {tab === "conversation" && (
882 <div>
883 {issue && (
884 <Link
885 to={`${base}/issues/${issue.number}`}
886 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"
887 >
888 <IssueIcon issue={issue} />
889 <span className="text-muted">
890 {pull.status === "merged" && issue.resolvedBy === pull.number
891 ? "Resolved"
892 : "For issue"}
893 </span>
894 <span className="truncate font-medium">{issue.title}</span>
895 <span className="text-faint">#{issue.number}</span>
896 {issue.pullCount > 1 && (
897 <span className="ml-auto shrink-0 text-xs text-faint">
898 1 of {issue.pullCount} pull requests
Pull requests show their tabs right under the title; agent panels move into Conversation899 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change900 )}
901 </Link>
902 )}
903 {pull.status === "merged" && (
904 <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">
905 <GitMerge size={16} className="shrink-0 text-merged" />
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request906 <span>
Pull requests: title and tabs across the full width, with counts and the size of the change907 Merged into <span className="font-mono">{defaultBranch}</span> by{" "}
908 <span className="font-medium">{pull.mergedBy}</span>{" "}
909 {/* Not an account: the repository's settings said to merge it. */}
910 {pull.mergedBy === "g1t" && "automatically, once it was ready, "}
911 {pull.mergedAt && <TimeAgo at={pull.mergedAt} />}
912 {pull.headCommit && (
913 <span className="font-mono text-muted"> · {pull.headCommit.slice(0, 7)}</span>
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request914 )}
915 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change916 </p>
917 )}
918 {pull.supersededBy != null && (
919 <p className="mt-4 rounded-xl border border-line bg-surface px-4 py-2.5 text-sm text-muted">
920 Closed because{" "}
921 <Link
922 to={`${base}/pull/${pull.supersededBy}`}
923 className="font-medium text-fg hover:underline"
924 >
925 #{pull.supersededBy}
926 </Link>{" "}
927 was merged for this issue instead.
928 </p>
929 )}
930 {lifecycle && <LifecyclePanel lifecycle={lifecycle} />}
931 {/* 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 step932 <AgentPanel
933 owner={params.owner}
934 repo={params.repo}
935 number={pull.number}
936 stage={lifecycle?.stage ?? null}
937 confidence={pull.confidence ?? null}
Merge branch 'worktree-agent-aca4790ea223aa235'938 runs={agentRuns}
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step939 />
Pull requests: title and tabs across the full width, with counts and the size of the change940
941 {/* 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 look942 {canRun &&
Pull requests: title and tabs across the full width, with counts and the size of the change943 pull.runtime === "hosted" &&
944 (working || ["working", "revising", "catching_up", "answering"].includes(lifecycle?.stage ?? "")) && (
Merge branch 'worktree-agent-a985c01f43538af01'945 // Keyed by the messages so the box empties once one shows below.
Merge branch 'worktree-agent-ad7c6d88d93adc817'946 <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 change947 <p className="flex items-center gap-2 text-sm font-medium">
Merge branch 'worktree-agent-ad7c6d88d93adc817'948 <Sparkles size={15} className="text-accent" />
Pull requests: title and tabs across the full width, with counts and the size of the change949 Message the agent
950 </p>
951 <p className="mt-1 text-xs text-muted">
952 A correction, a hint, a change of plan. It reads it at its next step, without
953 starting over.
954 </p>
955 <div className="mt-3 flex gap-2">
956 <input type="hidden" name="action" value="message" />
957 <input
958 name="body"
959 required
960 autoComplete="off"
961 data-1p-ignore
962 placeholder="Keep the old flag working too…"
Merge branch 'worktree-agent-ad7c6d88d93adc817'963 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"
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request964 />
Merge branch 'worktree-agent-a985c01f43538af01'965 <SubmitButton match={{ action: "message" }} pending="Sending…">
966 Send
967 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change968 </div>
969 </Form>
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request970 )}
Pull requests: title and tabs across the full width, with counts and the size of the change971 {messages.length > 0 && (
972 <ul className="mt-3 space-y-1.5">
973 {messages.map((message) => (
974 <li key={message.id} className="flex items-start gap-2 text-sm">
975 <Avatar name={message.author} size={18} />
976 <span className="min-w-0 grow">
977 <span className="font-medium">
978 {message.fromNumber != null ? `The agent on #${message.fromNumber}` : message.author}
979 </span>{" "}
980 <span className="text-muted">
981 {message.kind === "question"
982 ? "asked:"
983 : message.kind === "handoff"
984 ? "handed over:"
985 : message.kind === "answer"
986 ? "answered:"
987 : "to the agent:"}
988 </span>{" "}
989 {message.body}
990 {message.answer && (
Merge branch 'worktree-agent-ad7c6d88d93adc817'991 <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 change992 {message.declined ? "Declined: " : "Answer: "}
993 {message.answer}
994 </span>
995 )}
996 </span>
Merge branch 'worktree-agent-ad7c6d88d93adc817'997 <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 change998 {message.deliveredAt ? "read by the agent" : "waiting for its next step"}
999 </span>
1000 </li>
1001 ))}
1002 </ul>
1003 )}
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md1004 {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 change1005 {collisions.length > 0 && active && (
1006 <div className="mt-4 rounded-xl border border-line bg-surface px-4 py-3 text-sm">
1007 <p className="flex items-center gap-2.5 font-medium">
1008 <Radar size={16} className="shrink-0 text-info" />
1009 Other work is changing the same files
1010 </p>
1011 <ul className="mt-2 space-y-1.5">
1012 {collisions.map((other) => (
1013 <li key={other.number} className="flex flex-wrap items-baseline gap-x-2 text-muted">
1014 <Link
1015 to={`${base}/pull/${other.number}`}
1016 className="font-medium text-fg hover:underline"
1017 >
1018 {other.title} <span className="font-normal text-faint">#{other.number}</span>
1019 </Link>
1020 <span className="font-mono text-xs">{other.paths.join(", ")}</span>
1021 </li>
1022 ))}
1023 </ul>
1024 <p className="mt-2 text-xs text-faint">
1025 Whichever merges second will have to catch up, and may conflict.
1026 </p>
1027 </div>
1028 )}
1029 </div>
1030 )}
1031 <div className="mt-5">
1032 {comparison ? (
1033 <DiffView
1034 comparison={comparison}
1035 review={{
1036 comments: comments.filter((comment) => comment.path),
1037 canComment: Boolean(viewer),
1038 }}
1039 />
1040 ) : tab === "session" ? (
1041 session.length === 0 ? (
1042 <EmptyState title="Nothing recorded yet">
1043 The agent's prompts, reasoning and tool calls appear here as it
1044 works.
1045 </EmptyState>
1046 ) : (
1047 <ol className="relative space-y-5 before:absolute before:top-2 before:bottom-2 before:left-3.25 before:w-px before:bg-line">
1048 {session.map((entry) => (
1049 <Entry key={entry.seq} entry={entry} agent={pull.agent} />
1050 ))}
1051 </ol>
1052 )
1053 ) : (
1054 <div className="space-y-4">
1055 <TimelineItem
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1056 author={opener.name}
Pull requests: title and tabs across the full width, with counts and the size of the change1057 at={pull.createdAt}
1058 action={
1059 <span>
1060 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-agent1061 {opener.requestedBy ? (
Pull requests: title and tabs across the full width, with counts and the size of the change1062 <>
1063 {" "}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1064 for{" "}
1065 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
1066 </>
1067 ) : !pull.branch && (
1068 <>
1069 {" "}
Pull requests: title and tabs across the full width, with counts and the size of the change1070 with <span className="font-mono text-xs">{pull.agent}</span>
1071 </>
1072 )}
1073 </span>
1074 }
1075 >
1076 {pull.body ? (
1077 <Markdown source={pull.body} repo={{ namespace: params.owner, name: params.repo }} />
1078 ) : (
1079 <p className="text-sm text-muted">
1080 {working
1081 ? "No description yet. It is written when the pull request is marked ready for review."
1082 : "No description."}
1083 </p>
1084 )}
1085 {canManage && pull.status === "open" && (
1086 <details className="mt-3 border-t border-line pt-3 text-sm">
1087 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1088 Edit description
1089 </summary>
1090 <Form method="post" className="mt-3 space-y-2">
Merge branch 'worktree-agent-a985c01f43538af01'1091 <input type="hidden" name="action" value="describe" />
Pull requests: title and tabs across the full width, with counts and the size of the change1092 <Textarea
1093 name="summary"
1094 rows={6}
1095 placeholder="What changed and why"
1096 defaultValue={pull.body ?? ""}
1097 />
Merge branch 'worktree-agent-a985c01f43538af01'1098 <SubmitButton match={{ action: "describe" }} pending="Saving…">
1099 Save
1100 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1101 </Form>
1102 </details>
1103 )}
1104 </TimelineItem>
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1105
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1106 <CommentList
1107 comments={comments}
1108 review={review}
1109 base={base}
1110 viewerId={viewer?.id ?? null}
1111 canModerate={canModerate}
1112 failed={actionData && "comment" in actionData ? { comment: String(actionData.comment), error: actionData.error } : null}
1113 />
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1114
Projects no longer depend on each other: the dependsOn relation, its settings page (old links redirect), the overview card, a pull request's Affects panel and preview stacks, reference variables and agents' notes on what a project uses are gone; the table is dropped in a later deploy1115 {(preview || build) && <DeploymentCard preview={preview} build={build} base={base} />}
Pull requests say where they are live; the project menu shows what is coming1116
Pull requests: title and tabs across the full width, with counts and the size of the change1117 {pull.status === "draft" && (
1118 <StatusBox>
1119 <StatusRow icon={<Loader size={16} className="text-faint" />} title="This is a draft">
1120 It is still being worked on. It can be reviewed and merged once it is
1121 marked ready.
1122 </StatusRow>
1123 {canManage && (
1124 <Form method="post" className="space-y-2 px-4 py-3">
Merge branch 'worktree-agent-a985c01f43538af01'1125 <input type="hidden" name="action" value="ready" />
Pull requests: title and tabs across the full width, with counts and the size of the change1126 <Textarea name="summary" rows={3} placeholder="What changed and why" />
Merge branch 'worktree-agent-a985c01f43538af01'1127 <SubmitButton match={{ action: "ready" }} pending="Marking ready…">
1128 Mark ready for review
1129 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1130 </Form>
1131 )}
1132 </StatusBox>
1133 )}
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1134
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1135 {codeOwners && active && (
1136 <div className="flex gap-3">
1137 <span className="hidden w-8 shrink-0 sm:block" />
1138 <div className="min-w-0 grow">
1139 <PullCodeOwnersPanel
1140 owners={codeOwners}
1141 base={base}
1142 branch={defaultBranch}
1143 errorsHref={canProtect ? `${base}/settings/branches#codeowners` : `${base}/blob/${encodeURIComponent(defaultBranch)}/${codeOwners.path}`}
1144 />
1145 </div>
1146 </div>
1147 )}
1148
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md1149 {(pull.status === "merged" || pull.status === "closed") && statuses.length > 0 && (
1150 <StatusBox>
1151 <Suspense fallback={settledChecks({}, true)}>
1152 <Await resolve={workflowJobs}>{(jobs) => settledChecks(jobs)}</Await>
1153 </Suspense>
1154 </StatusBox>
1155 )}
1156
Pull requests: title and tabs across the full width, with counts and the size of the change1157 {pull.status === "open" && (
1158 <StatusBox>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1159 {/* Checks by status at once; job by job when the runs are read. */}
Merge branch 'worktree-agent-aca4790ea223aa235'1160 <Suspense fallback={checksSection({}, true)}>
1161 <Await resolve={workflowJobs}>{(jobs) => checksSection(jobs)}</Await>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1162 </Suspense>
1163 {noChecks && <AddCiPrompt owner={params.owner} repo={params.repo} canAdd={canAddCi} compact />}
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1164 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1165 icon={
1166 reviews.some(({ verdict }) => verdict === "request_changes") ? (
1167 <CircleSlash size={16} className="text-danger" />
1168 ) : reviews.length > 0 ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1169 <CircleCheck size={16} className="text-success" />
Pull requests: title and tabs across the full width, with counts and the size of the change1170 ) : (
1171 <MessageSquare size={16} className="text-faint" />
1172 )
1173 }
1174 title={
1175 reviews.length === 0
1176 ? "No reviews yet"
1177 : reviews
1178 .map(
1179 ({ reviewer, verdict }) =>
1180 `${verdict === "approve" ? "Approved by" : "Changes requested by"} ${reviewer}`,
1181 )
1182 .join(" · ")
1183 }
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1184 >
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1185 {reviewPending && "g1t is reviewing it now. "}
Pull requests: title and tabs across the full width, with counts and the size of the change1186 {requiredApprovals > 0 &&
1187 `This repository requires ${requiredApprovals} approving ${
1188 requiredApprovals === 1 ? "review" : "reviews"
1189 } before merging.`}
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1190 </StatusRow>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1191 {codeOwners?.required && (
1192 <StatusRow
1193 icon={
1194 ownersMissing ? (
1195 <CircleSlash size={16} className="text-warn" />
1196 ) : (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1197 <CircleCheck size={16} className="text-success" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1198 )
1199 }
1200 title={ownersMissing ? "Waiting for code owners" : "Code owners approved"}
1201 >
1202 {ownersMissing ?? `Every file it changes has its owners' approval.`}{" "}
1203 {ownersMissing && "This repository requires their review before merging."}
1204 </StatusRow>
1205 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1206 {landing ? (
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1207 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1208 icon={<Loader size={16} className="animate-spin text-accent" />}
1209 title="Merging"
1210 >
1211 {defaultBranch} has moved, so g1t is bringing this up to date first. It
1212 lands as soon as that is done. Watch it in the Session tab.
1213 </StatusRow>
Catching up with main takes seconds when the two sides touched different files1214 ) : agentCatchUp && behind ? (
1215 <CatchUpProgress
1216 owner={params.owner}
1217 repo={params.repo}
1218 number={pull.number}
1219 defaultBranch={defaultBranch}
1220 behind={behind}
1221 update={agentCatchUp.update}
1222 startedAt={agentCatchUp.startedAt}
1223 retrying={catchUpPending}
Merge branch 'worktree-agent-aca4790ea223aa235'1224 runs={agentRuns}
Catching up with main takes seconds when the two sides touched different files1225 />
Pull requests: title and tabs across the full width, with counts and the size of the change1226 ) : conflicting ? (
1227 <ConflictsSection
1228 conflicts={conflicts}
1229 pull={pull}
1230 owner={params.owner}
1231 repo={params.repo}
1232 defaultBranch={defaultBranch}
1233 changesUrl={here + "?tab=changes"}
1234 canResolve={canUpdate && agentsEnabled}
Catching up with main takes seconds when the two sides touched different files1235 resolving={catchUpPending}
1236 error={catchUp && "error" in catchUp ? catchUp.error : null}
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1237 />
Pull requests: title and tabs across the full width, with counts and the size of the change1238 ) : probing ? (
1239 <MergeabilityRow mergeable={mergeable} defaultBranch={defaultBranch} />
1240 ) : behind ? (
1241 <StatusRow
1242 icon={<GitPullRequestArrow size={16} className="text-info" />}
1243 title={`${defaultBranch} has moved since this was made`}
1244 >
1245 {mergeable === "clean" && "It has no conflicts with it. "}
1246 {requireUpToDate
1247 ? "This repository requires pull requests to be up to date, so it has to catch up before it can merge."
1248 : "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 files1249 {canUpdate && (
1250 <Form method="post" className="mt-2">
Pull requests: title and tabs across the full width, with counts and the size of the change1251 <input type="hidden" name="action" value="update" />
Merge branch 'worktree-agent-a985c01f43538af01'1252 <SubmitButton variant="quiet" match={{ action: "update" }} pending={`Merging ${defaultBranch} in…`}>
1253 Catch up with {defaultBranch} now
1254 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1255 </Form>
1256 )}
Catching up with main takes seconds when the two sides touched different files1257 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Pull requests: title and tabs across the full width, with counts and the size of the change1258 </StatusRow>
1259 ) : (
Merge branch 'worktree-agent-af9f0f386d52b6a73'1260 (pull.headCommit || (catchUp && "error" in catchUp)) && (
Pull requests: title and tabs across the full width, with counts and the size of the change1261 <StatusRow
Merge branch 'worktree-agent-ad7c6d88d93adc817'1262 icon={<CircleCheck size={16} className="text-success" />}
Catching up with main takes seconds when the two sides touched different files1263 title={
1264 (caughtUp && !caughtUp.already) || agentCatchUp
1265 ? `Brought up to date with ${defaultBranch}`
1266 : `Up to date with ${defaultBranch}`
1267 }
1268 >
1269 {caughtUp && !caughtUp.already && (
1270 <>
1271 Merged <span className="font-mono">{defaultBranch}</span> in as{" "}
1272 <span className="font-mono text-fg">{caughtUp.commit.slice(0, 7)}</span>. Its checks run
1273 again on the new commit.
1274 </>
1275 )}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1276 {agentCatchUp && "g1t merged it in and pushed the result. Its checks run again on the new commit."}
Merge branch 'worktree-agent-af9f0f386d52b6a73'1277 {/* A catch-up refused or failed after the pull request stopped being behind. */}
1278 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Catching up with main takes seconds when the two sides touched different files1279 </StatusRow>
Pull requests: title and tabs across the full width, with counts and the size of the change1280 )
1281 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1282 {active && rules && (rulesUnmet.length > 0 || rulesBypassable.length > 0 || rules.evaluate.length > 0) && (
1283 <StatusRow
1284 icon={
1285 <Scale
1286 size={16}
1287 className={rulesUnmet.length > 0 ? "text-danger" : rulesBypassable.length > 0 ? "text-warn" : "text-info"}
1288 />
1289 }
1290 title={
1291 rulesUnmet.length > 0
1292 ? `Rules for ${defaultBranch} are not met yet`
1293 : rulesBypassable.length > 0
1294 ? `You may bypass the rules for ${defaultBranch}`
1295 : "Rulesets being evaluated would refuse this merge"
1296 }
1297 >
1298 <div className="mt-2 space-y-3">
1299 {rulesUnmet.length > 0 && <ViolationList violations={rulesUnmet} />}
1300 {rulesBypassable.length > 0 && (
1301 <div>
1302 <p className="mb-1.5 text-xs text-muted">Not met, but a ruleset lets you bypass it:</p>
1303 <ViolationList violations={rulesBypassable} tone="warn" />
1304 </div>
1305 )}
1306 {rules.evaluate.length > 0 && (
1307 <div>
1308 <p className="mb-1.5 text-xs text-muted">Rulesets in evaluate would refuse it for this; nothing is held up:</p>
1309 <ViolationList violations={rules.evaluate} tone="info" />
1310 </div>
1311 )}
1312 {canProtect && (
1313 <Link
1314 to={`${base}/settings/rules?branch=${encodeURIComponent(defaultBranch)}`}
1315 className="inline-block text-xs text-muted underline underline-offset-2 hover:text-fg"
1316 >
1317 See every rule for {defaultBranch}
1318 </Link>
1319 )}
1320 </div>
1321 </StatusRow>
1322 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1323 {stalled && !lifecycle && (
1324 <StatusRow icon={<Hand size={16} className="text-warn" />} title="Needs you">
1325 {stalled}
1326 </StatusRow>
1327 )}
1328 {canMerge && lifecycle?.stage === "queued" && (
1329 <Form method="post" className="flex flex-wrap items-center gap-3 px-4 py-3">
1330 <ButtonLink to={`${base}/queue`}>
1331 <Layers size={15} />
1332 See the queue
1333 </ButtonLink>
Merge branch 'worktree-agent-a985c01f43538af01'1334 <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 change1335 Remove from the queue
Merge branch 'worktree-agent-a985c01f43538af01'1336 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1337 </Form>
1338 )}
1339 {canMerge && !landing && lifecycle?.stage !== "queued" && (
1340 <Form method="post" className="space-y-3 px-4 py-3">
1341 {issue?.state === "open" && (
1342 <CheckboxOption
1343 name="keepIssueOpen"
1344 label={`Keep #${issue.number} open. This is only part of the work.`}
1345 labelClassName="text-xs text-muted"
1346 />
1347 )}
1348 {unchecked && canIgnoreChecks && (
1349 <CheckboxOption
1350 name="ignoreChecks"
Fast pages, required checks on the branch, self-hosted runners, honest incidents1351 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 change1352 labelClassName="text-xs text-muted"
1353 />
1354 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1355 {rulesBypassable.length > 0 && rulesUnmet.length === 0 && (
1356 <CheckboxOption
1357 name="bypassRules"
1358 checked={bypassing}
1359 onCheckedChange={(checked) => setBypassing(checked === true)}
1360 label={`Bypass the rules for ${defaultBranch}: merge although they are not met. It is recorded.`}
1361 labelClassName="text-xs text-muted"
1362 />
1363 )}
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)1364 <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'1365 <Hint label={mergeBlocked} disabled={mergeBlocked != null}>
1366 <SubmitButton
1367 variant="accent"
1368 name="action"
1369 value="merge"
1370 disabled={mergeBlocked != null}
1371 pending={mergeQueue ? "Adding to the queue…" : "Merging…"}
1372 >
1373 {mergeQueue ? <Layers size={15} /> : <GitMerge size={15} />}
1374 {mergeQueue ? "Add to the merge queue" : `Merge into ${defaultBranch}`}
1375 </SubmitButton>
1376 </Hint>
Pull requests: title and tabs across the full width, with counts and the size of the change1377 <span className={`text-xs ${mergeBlocked ? "text-danger" : "text-muted"}`}>
1378 {mergeBlocked ?? (mergeQueue
1379 ? `Tested together with everything ahead of it, then lands on ${defaultBranch}.`
1380 : issue?.state === "open"
1381 ? `Closes issue #${issue.number}, and any other pull requests still open for it.`
1382 : `Lands its commits on ${defaultBranch}.`)}
1383 </span>
1384 </div>
1385 {actionData?.action === "merge" && (
1386 <ErrorText>{actionData.error}</ErrorText>
1387 )}
1388 </Form>
1389 )}
1390 </StatusBox>
1391 )}
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1392
Pull requests: title and tabs across the full width, with counts and the size of the change1393 <CommentForm
1394 author={viewer?.username ?? null}
1395 resetKey={comments.length}
1396 review={review}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1397 handles={teams.map((team) => `@${team.ref}`)}
Pull requests: title and tabs across the full width, with counts and the size of the change1398 />
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1399 {canManage && (active || pull.status === "closed") && (
1400 <Form method="post" className="flex flex-wrap justify-end gap-2">
1401 {pull.status === "open" && (
1402 <SubmitButton variant="quiet" name="action" value="draft" pending="Converting…">
1403 Convert to draft
1404 </SubmitButton>
1405 )}
1406 {active ? (
1407 <SubmitButton variant="quiet" name="action" value="close" pending="Closing…">
1408 Close pull request
1409 </SubmitButton>
1410 ) : (
1411 <SubmitButton variant="quiet" name="action" value="reopen" pending="Reopening…">
1412 Reopen pull request
1413 </SubmitButton>
1414 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1415 </Form>
1416 )}
1417 {actionData &&
Projects no longer depend on each other: the dependsOn relation, its settings page (old links redirect), the overview card, a pull request's Affects panel and preview stacks, reference variables and agents' notes on what a project uses are gone; the table is dropped in a later deploy1418 !["merge", "comment", "rerun-failed", "rerun-workflow", "update", "agent-review", "reviewers", "assign", "edit-comment", "delete-comment"].includes(
Pull requests: title and tabs across the full width, with counts and the size of the change1419 String(actionData.action),
1420 ) && <ErrorText>{actionData.error}</ErrorText>}
1421 </div>
1422 )}
1423 {actionData?.action === "comment" && (
1424 <div className="mt-2">
1425 <ErrorText>{actionData.error}</ErrorText>
1426 </div>
1427 )}
1428 </div>
1429 </div>
1430
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)1431 <DetailsDisclosure
1432 className={tab === "changes" ? "hidden" : "order-first lg:order-none"}
1433 summary={detailsSummary([
1434 [reviewerNames.length + teamReviewers.length, "reviewer", "reviewers"],
1435 [pull.assignees.length, "assignee", "assignees"],
1436 [(pull.labels ?? []).length, "label", "labels"],
1437 ])}
1438 >
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md1439 {/* Other than a draft, a pull request shows its checks in full in the conversation. */}
1440 {pull.status === "draft" && <WorkflowStatuses statuses={statuses} />}
Project dependencies: addresses, preview stacks, Affects, and agents who know1441 <section>
Pull requests: title and tabs across the full width, with counts and the size of the change1442 <h3 className="text-sm font-medium">Reviewers</h3>
1443 <ul className="mt-2 space-y-1.5 text-sm">
1444 {reviewerNames.map((name) => {
1445 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-agent1446 const pending = name === "g1t" && reviewPending;
Pull requests: title and tabs across the full width, with counts and the size of the change1447 return (
1448 <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-agent1449 {name === "g1t" ? (
Pull requests: title and tabs across the full width, with counts and the size of the change1450 <Sparkles size={16} className="shrink-0 text-accent" />
1451 ) : (
1452 <Avatar name={name} size={20} />
1453 )}
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)1454 <span className="grow truncate font-mono text-xs">
1455 <PersonLink name={name} className="hover:underline" />
1456 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change1457 {pending ? (
1458 <span className="flex items-center gap-1.5 text-xs text-muted">
1459 <span className="size-1.5 animate-pulse rounded-full bg-accent" />
1460 Reviewing
1461 </span>
1462 ) : verdict === "approve" ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1463 <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 change1464 <CircleCheck size={13} /> Approved
1465 </span>
1466 ) : verdict === "request_changes" ? (
1467 <span className="flex items-center gap-1 text-xs text-danger">
1468 <CircleSlash size={13} /> Changes requested
1469 </span>
1470 ) : (
1471 <span className="text-xs text-faint">Review requested</span>
1472 )}
1473 </li>
1474 );
1475 })}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1476 {teamReviewers.map((team) => (
1477 <TeamReviewer key={team} team={team} />
1478 ))}
1479 {reviewerNames.length === 0 && teamReviewers.length === 0 && (
Pull requests: title and tabs across the full width, with counts and the size of the change1480 <li className="px-1 text-xs text-faint">No reviews requested yet.</li>
1481 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1482 </ul>
Pull requests: title and tabs across the full width, with counts and the size of the change1483 {pull.status === "open" && canManage && (
1484 <div className="mt-3 space-y-2">
1485 {agentsEnabled && !reviewPending && (
1486 <Form method="post">
1487 <input type="hidden" name="action" value="agent-review" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1488 {[...pull.reviewers, ...teamReviewers].map((name) => (
Pull requests: title and tabs across the full width, with counts and the size of the change1489 <input key={name} type="hidden" name="reviewer" value={name} />
1490 ))}
1491 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1492 <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 change1493 <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-agent1494 Request review from g1t
Merge branch 'worktree-agent-a985c01f43538af01'1495 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1496 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1497 <ComputeNote note={loaderData.computeNote} />
Pull requests: title and tabs across the full width, with counts and the size of the change1498 </Form>
1499 )}
1500 <details>
1501 <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 bar1502 {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 change1503 </summary>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1504 <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 change1505 <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-agent1506 {pull.reviewers.includes("g1t") && (
1507 <input type="hidden" name="reviewer" value="g1t" />
Pull requests: title and tabs across the full width, with counts and the size of the change1508 )}
1509 <PeoplePicker
1510 name="reviewer"
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1511 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-agent1512 chosen={pull.reviewers.filter((name) => name !== "g1t")}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1513 placeholder="Other usernames or teams (acme/backend)"
Pull requests: title and tabs across the full width, with counts and the size of the change1514 />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1515 {teamChoices.length > 0 && (
1516 <div className="space-y-1.5 border-t border-line pt-2">
1517 <p className="text-xs text-faint">Teams</p>
1518 {teamChoices.map((team) => (
1519 <CheckboxOption
1520 key={team.ref}
1521 name="reviewer"
1522 value={team.ref}
1523 defaultChecked={teamReviewers.includes(team.ref)}
1524 className="items-center"
1525 labelClassName="flex min-w-0 items-center gap-2"
1526 label={
1527 <>
1528 <Users size={14} className="shrink-0 text-faint" />
1529 <span className="truncate font-mono text-xs">@{team.ref}</span>
1530 </>
1531 }
1532 />
1533 ))}
1534 </div>
1535 )}
Merge branch 'worktree-agent-a985c01f43538af01'1536 <SubmitButton variant="quiet" match={{ action: "reviewers" }} pending="Saving…">
Pull requests: title and tabs across the full width, with counts and the size of the change1537 Save reviewers
Merge branch 'worktree-agent-a985c01f43538af01'1538 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1539 </Form>
1540 </details>
1541 </div>
Project dependencies: addresses, preview stacks, Affects, and agents who know1542 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1543 {["agent-review", "reviewers"].includes(String(actionData?.action)) && (
1544 <ErrorText>{actionData?.error}</ErrorText>
1545 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1546 </section>
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1547
Pull requests: title and tabs across the full width, with counts and the size of the change1548 <section>
1549 <h3 className="text-sm font-medium">Assignees</h3>
1550 <ul className="mt-2 space-y-1.5 text-sm">
1551 {pull.assignees.map((name) => (
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1552 <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 change1553 <Avatar name={name} size={20} />
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)1554 <span className="grow truncate font-mono text-xs">
1555 <PersonLink name={name} className="hover:underline" />
1556 </span>
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1557 </li>
Pull requests: title and tabs across the full width, with counts and the size of the change1558 ))}
1559 {pull.assignees.length === 0 && (
1560 <li className="px-1 text-xs text-faint">No one yet.</li>
1561 )}
1562 </ul>
1563 {viewer && canManage && active && (
1564 <div className="mt-3 space-y-2">
1565 {!pull.assignees.includes(viewer.username) && (
1566 <Form method="post">
1567 <input type="hidden" name="action" value="assign" />
1568 {pull.assignees.map((name) => (
1569 <input key={name} type="hidden" name="assignee" value={name} />
1570 ))}
1571 <input type="hidden" name="assignee" value={viewer.username} />
1572 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1573 <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 change1574 Assign yourself
Merge branch 'worktree-agent-a985c01f43538af01'1575 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1576 </div>
1577 </Form>
1578 )}
1579 <details>
1580 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1581 Assign people
1582 </summary>
1583 <Form method="post" className="mt-2 space-y-2" key={pull.assignees.join()}>
1584 <input type="hidden" name="action" value="assign" />
1585 <PeoplePicker name="assignee" members={members} chosen={pull.assignees} />
Merge branch 'worktree-agent-a985c01f43538af01'1586 <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 change1587 Save assignees
Merge branch 'worktree-agent-a985c01f43538af01'1588 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1589 </Form>
1590 </details>
1591 </div>
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1592 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1593 {actionData?.action === "assign" && <ErrorText>{actionData.error}</ErrorText>}
1594 </section>
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1595
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1596 <LabelsBox
1597 labels={loaderData.labels}
1598 chosen={pull.labels ?? []}
1599 canEdit={canManage && active}
Merge main (membership, two-factor, GitHub repo roles) into tokens1600 canCreate={loaderData.canMakeLabels}
1601 manageUrl={loaderData.canMakeLabels ? `${base}/labels` : undefined}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1602 />
1603 <MilestoneBox
1604 milestones={loaderData.milestones}
1605 current={pull.milestone}
1606 canEdit={loaderData.canTriage}
1607 base={base}
1608 />
1609
Pull requests: title and tabs across the full width, with counts and the size of the change1610 <section>
1611 <h3 className="text-sm font-medium">Working copy</h3>
1612 {pull.branch ? (
1613 <>
1614 <p className="mt-1 text-xs text-muted">
1615 <GitBranch size={13} className="mr-1 inline align-[-2px]" />
1616 Branch <span className="font-mono text-fg">{pull.branch}</span> of this
1617 repository. Pushes to it show up here.
1618 </p>
1619 <div className="mt-2">
1620 <CopyLine text={`git clone -b ${pull.branch} ${remote}`} />
1621 </div>
1622 </>
1623 ) : (
1624 <>
1625 <p className="mt-1 text-xs text-muted">
1626 This fork belongs to the pull request. Pushes to it show up here.
1627 </p>
1628 <div className="mt-2">
1629 <CopyLine text={`git clone ${remote}`} />
1630 </div>
1631 </>
1632 )}
1633 <p className="mt-3 flex items-center gap-1.5 font-mono text-xs text-faint">
1634 <GitCommitHorizontal size={13} />
Merge checks: statuses and check runs on every commit1635 {pull.headCommit ? (
1636 <Link to={`${base}/commit/${pull.headCommit}`} className="hover:text-fg">
1637 {pull.headCommit.slice(0, 12)}
1638 </Link>
1639 ) : (
1640 "no commits pushed yet"
1641 )}
1642 {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 change1643 </p>
1644 </section>
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings1645 {loaderData.viewer && (
1646 <SubscriptionBox action={`${base}/notifications`} number={pull.number} kind="pull" subscription={loaderData.subscription} />
1647 )}
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)1648 </DetailsDisclosure>
Pull requests: title and tabs across the full width, with counts and the size of the change1649 </div>
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1650 </div>
1651 );
1652}
Pull requests say where they are live; the project menu shows what is coming1653
1654/** Where the pull request's change is live, as GitHub shows a deployment. */
1655function DeploymentCard({
1656 preview,
1657 build,
1658 base,
1659}: {
1660 preview: LiveApp | null;
1661 build: Deployment | null;
1662 base: string;
1663}) {
1664 const building = build?.status === "queued" || build?.status === "building";
1665 const failed = build?.status === "failed";
1666 return (
1667 <section className="ml-12 overflow-hidden rounded-xl border border-line bg-surface">
1668 <div className="flex flex-wrap items-center gap-3 px-4 py-3">
1669 <span
1670 className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ${
Merge branch 'worktree-agent-ad7c6d88d93adc817'1671 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 coming1672 }`}
1673 >
1674 {building ? <Loader size={15} className="animate-spin" /> : <Rocket size={15} />}
1675 </span>
1676 <div className="min-w-0 grow">
1677 <p className="text-sm font-medium">
1678 {building
1679 ? preview
1680 ? "Deploying the latest push. The last preview is still live."
1681 : "Deploying a preview"
1682 : failed
1683 ? "The preview failed to deploy"
1684 : "This branch is live"}
1685 </p>
1686 {preview ? (
Merge branch 'site-fixes' into fast-push1687 <DeployLink href={preview.url} className="mt-0.5 block truncate font-mono text-xs text-muted hover:text-accent">
Pull requests say where they are live; the project menu shows what is coming1688 {preview.url.replace(/^https?:[/][/]/, "")}
Merge branch 'site-fixes' into fast-push1689 </DeployLink>
Pull requests say where they are live; the project menu shows what is coming1690 ) : (
1691 build?.error && <p className="mt-0.5 truncate text-xs text-muted">{build.error}</p>
1692 )}
1693 </div>
1694 <div className="flex shrink-0 items-center gap-2">
1695 {build && (
1696 <Link to={`${base}/deployments/${build.id}`} className="text-xs text-muted hover:text-fg">
1697 {failed ? "See why" : "Build log"}
1698 </Link>
1699 )}
1700 {preview && (
Merge branch 'site-fixes' into fast-push1701 <DeployLink
Pull requests say where they are live; the project menu shows what is coming1702 href={preview.url}
1703 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"
1704 >
1705 Visit preview
1706 <ArrowUpRight size={13} />
Merge branch 'site-fixes' into fast-push1707 </DeployLink>
Pull requests say where they are live; the project menu shows what is coming1708 )}
1709 </div>
1710 </div>
1711 {preview && (
1712 <p className="border-t border-line px-4 py-2 text-xs text-faint">
1713 <span className="font-mono">{preview.commit.slice(0, 7)}</span> · deployed <TimeAgo at={preview.deployedAt} />
1714 {preview.branch && ` · from ${preview.branch}`}
1715 </p>
1716 )}
1717 </section>
1718 );
1719}

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