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

Get started on mission control1import { 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,
Get started on mission control13 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,
Get started on mission control20 MessagesSquare,
Pull requests say where they are live; the project menu shows what is coming21 Rocket,
Get started on mission control22 StickyNote,
23 User,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar24 Users,
Get started on mission control25 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";
Get started on mission control29
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";
Get started on mission control44
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";
Get started on mission control50import { 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";
Get started on mission control52import { 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";
Get started on mission control56import { 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,
Get started on mission control63 ErrorText,
Merge branch 'worktree-agent-a985c01f43538af01'64 SubmitButton,
Get started on mission control65 Textarea,
66 TimeAgo,
Merge branch 'worktree-agent-a985c01f43538af01'67 usePending,
Get started on mission control68} 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";
Get started on mission control76import {
77 CommentForm,
78 CommentList,
79 IssueIcon,
80 PeoplePicker,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains81 PersonLink,
Get started on mission control82 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,
Get started on mission control86 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";
Get started on mission control98import { 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";
Merge branch 'mirroring' into artifacts-mode101import { useMirrorReason } from "../../components/mirror";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending102import { REFRESH_MS, useRefreshWhile } from "../../lib/refresh";
Get started on mission control103
104const EMPTY_COMPARISON: Comparison = { base: null, head: "", files: [], truncated: false };
105const TABS = ["conversation", "session", "changes"] as const;
106type Tab = (typeof TABS)[number];
107
Social cards for every page: og.g1t.sh108export function meta({ loaderData, params, ...args }: Route.MetaArgs) {
109 const pull = loaderData?.pull;
110 const title = pull ? `${pull.title} · Pull request #${pull.number} · ` : "";
111 const state = { draft: "Draft", open: "Open", merged: "Merged", closed: "Closed" }[pull?.status ?? "open"];
112 const body = excerpt(pull?.body);
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent113 const opener = pull ? openedBy(pull) : null;
Social cards for every page: og.g1t.sh114 return page(args, {
115 title: `${title}${params.owner}/${params.repo} · g1t`,
116 description: pull
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent117 ? `${state} pull request #${pull.number} on ${params.owner}/${params.repo} by ${opener!.name}${opener!.requestedBy ? `, requested by ${opener!.requestedBy}` : ""}.${body ? ` ${body}` : ""}`
Social cards for every page: og.g1t.sh118 : null,
119 // The card shows the title and the state.
120 version: pull ? [pull.title, pull.status] : undefined,
121 type: "article",
122 });
Get started on mission control123}
124
125export async function loader({ params, context, request }: Route.LoaderArgs) {
126 const viewer = getViewer(context);
127 const path = { namespace: params.owner, name: params.repo };
128 const number = Number(params.number);
129 const asked = new URL(request.url).searchParams.get("tab");
130 const tab: Tab = TABS.find((name) => name === asked) ?? "conversation";
131
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look132 // Members of the workspace pick reviewers and assignees from its people;
133 // what the viewer may do here goes by their role on the repository.
Get started on mission control134 const member = (viewer?.workspaces ?? []).some(
135 (membership) => membership.slug === params.owner,
136 );
Fast pages, required checks on the branch, self-hosted runners, honest incidents137 // Everything starts at once. Only what the viewer's role decides waits
138 // for the repository, which the layout is looking up in this request too
139 // (lib/access.server.ts), and only what the pull request decides waits
140 // for it.
Project dependencies: addresses, preview stacks, Affects, and agents who know141 const ref = { workspace: params.owner, slug: params.repo };
Fast pages, required checks on the branch, self-hosted runners, honest incidents142 const access = accessTo(context, params);
143 const pullFound = work.getPull(path, number, viewer);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar144 // Its labels and milestone, with the repository's to choose from.
145 const labelsFound = work.listLabels(path, viewer);
146 const milestonesFound = work.listMilestones(path, viewer);
147 labelsFound.catch(() => null);
148 milestonesFound.catch(() => null);
149 const [{ can }, found, repo, settings, agentsEnabled, members, computeNote, session, deployed, subscription, runs, teamList] = await Promise.all([
Fast pages, required checks on the branch, self-hosted runners, honest incidents150 access,
151 pullFound,
Get started on mission control152 repos.get(path, viewer),
153 work.getSettings(path, viewer),
g1t's agents only for listed workspaces, whatever the state of billing154 env.RUNNER.enabled(viewer, path),
Get started on mission control155 // A member picks reviewers and assignees from the workspace's people.
156 member ? identity.listMembers(params.owner, viewer) : null,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent157 // Before someone asks g1t for something: whether the plan lets it start.
Fast pages, required checks on the branch, self-hosted runners, honest incidents158 access.then(({ can }) => (can.run ? computeNoteFor(params.owner, "agent") : null)),
159 tab === "session" ? work.readSession(path, number, viewer) : null,
160 // Its preview, for people with a role here: beside the rest, not after.
161 access.then(({ insider }) => (insider ? deployments.list(ref, viewer).catch(() => null) : null)),
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings162 // Whether the viewer hears of it, for the sidebar's Notifications.
163 viewer
164 ? repoFor(context, params).then((found) =>
165 found.ok ? inbox.subscription(viewer, { repoId: found.value.id, number }).catch(() => null) : null,
166 )
167 : null,
Merge branch 'worktree-agent-aca4790ea223aa235'168 // Its agent's latest runs, for the Agent panel: with the page, not after it.
169 agents.listRuns(viewer, { repo: path, number, limit: 5 }).catch(() => null),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar170 // A member asks the workspace's teams to review, and mentions them.
171 member ? identity.listTeams(viewer, params.owner).catch(() => null) : null,
Get started on mission control172 ]);
173 if (!found.ok) {
174 // Issues and pull requests share numbers; this one may be an issue.
175 const issue = await work.getIssue(path, number, viewer);
176 if (issue.ok) throw redirect(`/${params.owner}/${params.repo}/issues/${number}`);
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put177 throw notFound("pull");
Get started on mission control178 }
179 const { pull } = found.value;
180 const range = pullComparison(pull);
Agents and memory, checks and conflicts, profiles, slug renames, custom domains181 // The jobs of each workflow run on its head, to list checks job by job.
182 // Read as the viewer: a run they cannot see is listed by its status alone.
Fast pages, required checks on the branch, self-hosted runners, honest incidents183 // Streamed: the checks show by status first, then job by job.
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md184 // A closed or merged one lists its head's checks too, to read only.
Agents and memory, checks and conflicts, profiles, slug renames, custom domains185 const runIds = [...new Set((found.value.statuses ?? []).map(runIdOf).filter((id) => id != null))].slice(0, 10);
Fast pages, required checks on the branch, self-hosted runners, honest incidents186 const workflowJobs =
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md187 tab === "conversation" && pull.status !== "draft" && runIds.length > 0
Fast pages, required checks on the branch, self-hosted runners, honest incidents188 ? Promise.all(runIds.map((id) => actions.run(path, viewer, id).catch(() => null))).then((runs) => {
189 const jobs: Record<string, Job[]> = {};
190 for (const run of runs) if (run?.ok) jobs[run.value.run.id] = run.value.jobs;
191 return jobs;
192 })
193 .catch(() => ({}) as Record<string, Job[]>)
194 : Promise.resolve({} as Record<string, Job[]>);
195 // No checks at all on an open pull request: whether that is because the
196 // repository has no workflows, to offer a starter one.
197 const unchecked =
198 tab === "conversation" &&
199 pull.status === "open" &&
200 (found.value.statuses ?? []).length === 0 &&
201 (found.value.requiredChecks ?? []).length === 0;
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 deploy202 const [comparison, noChecks] = await Promise.all([
Usage, Billing settings and prepaid AI credit; fixes from the UX audit203 // The first screens of it highlighted here, so their colours do not pop in.
204 tab === "changes"
205 ? repos
206 .compare(range.repoId, viewer, range.base, range.head, range.baseBranch)
207 .then(async (found) => (found.ok ? { ok: true as const, value: await highlightFirstFiles(found.value) } : found))
208 : null,
Fast pages, required checks on the branch, self-hosted runners, honest incidents209 unchecked
210 ? actions
211 .workflows(path, viewer)
212 .then((found) => found.ok && found.value.length === 0)
213 .catch(() => false)
214 : false,
Get started on mission control215 ]);
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md216 // Every pull request for its issue, to compare: streamed, and read only
217 // when there is more than this one.
218 const attempts =
219 tab === "conversation" && pull.issue != null && (found.value.issue?.pullCount ?? 0) > 1
220 ? attemptsFor(path, viewer, pull, found.value.comments).catch(() => [] as Attempt[])
221 : null;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar222 const [labels, milestones] = await Promise.all([labelsFound, milestonesFound]);
223 const repoDefault = repo.ok ? repo.value.defaultBranch : "main";
224 // The default branch's protection covers pull requests into it only.
225 const protectedBase = (pull.base ?? repoDefault) === repoDefault;
Get started on mission control226 return {
227 ...found.value,
Merge checks: statuses and check runs on every commit228 // Every check on its head, as the commit's own list shows them: streamed.
229 headChecks: commitChecksFor(path, viewer, [pull.headCommit]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar230 labels: labels.ok ? labels.value : [],
231 milestones: milestones.ok ? milestones.value : [],
232 // Labelling and milestones: Triage and up; its author, its labels.
233 canTriage: can.triage,
Merge main (membership, two-factor, GitHub repo roles) into tokens234 // Making labels needs Write; applying them, Triage.
235 canMakeLabels: can.manage_labels,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar236 // The branch it merges into: Write and up.
237 canChangeBase: can.push,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains238 workflowJobs,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md239 attempts,
Get started on mission control240 tab,
241 session: session?.ok ? session.value : [],
242 // An empty comparison if it could not be made.
243 comparison: comparison && (comparison.ok ? comparison.value : EMPTY_COMPARISON),
244 viewer,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look245 // Write and up can merge.
246 canMerge: can.merge,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights247 // Triage and up manage anyone's pull request; its author, their own,
248 // and whoever asked g1t for one, that one.
249 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 look250 // Telling its agent things, and re-running checks, spend compute: Write and up.
251 canRun: can.run,
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts252 // Maintain and up edit and delete anyone's comments; everyone, their own.
253 canModerate: can.manage_settings,
Get started on mission control254 // 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 rights255 // whoever it is for (who asked g1t for it, or its author), a branch
256 // from anyone who can push.
257 canUpdate: pull.fork ? viewer?.id === workOwner(pull).id : can.push,
Get started on mission control258 agentsEnabled,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look259 computeNote,
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings260 subscription,
Merge branch 'worktree-agent-aca4790ea223aa235'261 // As the project's agents.json has them; left out, the panel fetches them.
262 agentRuns: runs?.ok ? { runs: runs.value, member: can.run } : undefined,
Get started on mission control263 members: members?.ok ? members.value.map((person) => person.username) : [],
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar264 // `workspace/slug` and name of each team the viewer can see.
265 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 merge266 // What the rules of the branch it merges into ask, as they stack; the
267 // default branch's settings where the rules are not known.
268 requireUpToDate: found.value.rules ? found.value.rules.strict : protectedBase && settings.ok && settings.value.requireUpToDate,
269 mergeQueue: found.value.rules ? found.value.rules.merge_queue : protectedBase && settings.ok && settings.value.mergeQueue,
270 requiredApprovals: found.value.rules
271 ? found.value.rules.required_approvals
272 : protectedBase && settings.ok
273 ? settings.value.requiredApprovals
274 : 0,
275 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 incidents276 noChecks,
277 // Who may open the pull request that adds CI: anyone who can push.
278 canAddCi: can.push,
279 // Who may choose the required checks.
280 canProtect: can.manage_protection,
Get started on mission control281 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 deploy282 ...deploymentOf(deployed?.ok ? deployed.value : null, number),
Get started on mission control283 };
284}
285
Pull requests say where they are live; the project menu shows what is coming286/**
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md287 * This pull request and the latest few others for its issue, each with its
288 * head's checks and where its review stands, this one first.
289 */
290async function attemptsFor(path: RepoPath, viewer: Viewer, pull: Pull, comments: Comment[]): Promise<Attempt[]> {
291 const found = await work.getIssue(path, pull.issue!, viewer);
292 if (!found.ok) return [];
293 const others = otherAttempts(found.value.pulls, pull.number).flatMap(
294 (number) => found.value.pulls.find((other) => other.number === number) ?? [],
295 );
296 const [details, checks] = await Promise.all([
297 Promise.all(others.map((other) => work.getPull(path, other.number, viewer).catch(() => null))),
298 commitChecksFor(path, viewer, [pull.headCommit, ...others.map((other) => other.headCommit)]),
299 ]);
300 const attempt = (one: Pull, said: Comment[], current: boolean): Attempt => ({
301 number: one.number,
302 title: one.title,
303 status: one.status,
304 supersededBy: one.supersededBy,
305 author: openedBy(one).name,
306 headCommit: one.headCommit,
307 files: one.files,
308 updatedAt: one.updatedAt,
309 current,
310 shared: current ? [] : sharedPaths(one.files, pull.files),
311 checks: (one.headCommit && checks?.[one.headCommit]) || null,
312 review: attemptReview(said, one.reviewers),
313 });
314 return [
315 attempt(pull, comments, true),
316 ...others.map((other, index) => {
317 const detail = details[index];
318 return attempt(other, detail?.ok ? detail.value.comments : [], false);
319 }),
320 ];
321}
322
323/**
Pull requests say where they are live; the project menu shows what is coming324 * 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 deploy325 * one is going or after one failed).
Pull requests say where they are live; the project menu shows what is coming326 */
Fast pages, required checks on the branch, self-hosted runners, honest incidents327function deploymentOf(
Pull requests say where they are live; the project menu shows what is coming328 list: { deployments: Deployment[]; live: LiveApp[] } | null,
329 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 deploy330): { preview: LiveApp | null; build: Deployment | null } {
Fast pages, required checks on the branch, self-hosted runners, honest incidents331 const preview = list?.live.find((app) => app.kind === "preview" && app.number === number) ?? null;
332 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 deploy333 return { preview, build };
Pull requests say where they are live; the project menu shows what is coming334}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look335const PULL_NEEDS: Record<string, Capability> = {
336 "agent-review": "run",
337 "rerun-workflow": "run",
Fast pages, required checks on the branch, self-hosted runners, honest incidents338 "rerun-failed": "run",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look339 message: "run",
340 merge: "merge",
341 unqueue: "merge",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar342 milestone: "triage",
343 base: "push",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look344};
345
Get started on mission control346export async function action({ request, params, context }: Route.ActionArgs) {
347 assertSameOrigin(request);
348 const user = requireUser(context, request);
349 const form = await request.formData();
350 const path = { namespace: params.owner, name: params.repo };
351 const number = Number(params.number);
352 const action = form.get("action");
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look353 // What each form needs beyond reading; work checks the rest.
354 const needed = typeof action === "string" ? PULL_NEEDS[action] : undefined;
355 const refused = needed ? await refusal(context, params, needed) : null;
356 if (refused) return { error: refused, action: String(action) };
Get started on mission control357 const verdict = form.get("verdict");
358 const line = Number(form.get("line"));
359 /** Names ticked in a people picker, plus those typed beside it. */
360 const picked = (field: string) => [
361 ...form.getAll(field).map(String),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar362 // `@acme/backend` is asked as `acme/backend`.
363 ...String(form.get("others") ?? "").split(/[\s,]+/).map((name) => name.replace(/^@/, "")),
Get started on mission control364 ];
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent365 // Asking g1t for its review records the request, then starts it.
Get started on mission control366 if (action === "agent-review") {
367 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-agent368 reviewers: [...form.getAll("reviewer").map(String), "g1t"],
Get started on mission control369 });
370 if (!asked.ok) return { error: asked.error.message, action };
371 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar372 // Its labels, milestone and base, from the sidebar's menus and the header.
373 if (action === "labels") {
374 const set = await work.setLabels(user, path, number, form.getAll("label").map(String));
375 return set.ok ? null : { error: set.error.message, action };
376 }
377 if (action === "milestone" || action === "base") {
378 const changed = await work.updatePull(
379 user,
380 path,
381 number,
382 action === "milestone"
383 ? { milestone: Number(form.get("milestone")) || 0 }
384 : { base: String(form.get("base") ?? "") },
385 );
386 return changed.ok ? null : { error: changed.error.message, action };
387 }
Catching up with main takes seconds when the two sides touched different files388 // Catching up: merged and pushed in seconds when the two sides changed
389 // different files; otherwise handed to a sandbox, which takes a minute.
390 if (action === "update") {
391 const caught = await work.catchUpPull(user, path, number);
392 if (!caught.ok) return { action, error: caught.error.message };
393 const update = caught.value;
394 if (update.outcome !== "needs_agent") {
395 return {
396 action,
397 updated: { commit: update.commit, already: update.outcome === "up_to_date", at: Date.now() },
398 };
399 }
400 const started = await env.RUNNER.update(user, path, number);
401 if (!started.ok) return { action, error: started.error.message };
402 return { action, agent: { update, startedAt: Date.now() } };
403 }
Agents and memory, checks and conflicts, profiles, slug renames, custom domains404 // A workflow run's failed jobs, run again. Who may is the actions service's call.
405 if (action === "rerun-workflow") {
406 const rerun = await actions.rerun(user, path, String(form.get("run") ?? ""), true);
407 return rerun.ok ? null : { error: rerun.error.message, action };
408 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents409 // Every failed run on its head, run again: Mission control's quick action.
410 if (action === "rerun-failed") {
411 const found = await work.getPull(path, number, user);
412 if (!found.ok) return { error: found.error.message, action };
413 const failed = [
414 ...new Set(
415 (found.value.statuses ?? [])
416 .filter((status) => status.state === "failure" || status.state === "error")
417 .map(runIdOf)
418 .filter((id) => id != null),
419 ),
420 ];
421 if (failed.length === 0) return { error: "No workflow run failed on its latest commit.", action };
422 const reruns = await Promise.all(failed.map((id) => actions.rerun(user, path, id, true)));
423 const refused = reruns.find((rerun) => !rerun.ok);
424 return refused && !refused.ok ? { error: refused.error.message, action } : null;
425 }
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts426 // A comment's own text, changed or taken away: its author's to do, or a
427 // maintainer's, which work checks.
428 if (action === "edit-comment" || action === "delete-comment") {
429 const id = String(form.get("comment") ?? "");
430 const changed =
431 action === "edit-comment"
432 ? await work.editComment(user, path, id, String(form.get("body") ?? ""))
433 : await work.deleteComment(user, path, id);
434 return changed.ok ? null : { error: changed.error.message, action, comment: id };
435 }
Get started on mission control436 const result =
437 action === "merge"
438 ? await work.mergePull(user, path, number, {
439 keepIssueOpen: form.get("keepIssueOpen") === "on",
440 ignoreChecks: form.get("ignoreChecks") === "on",
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge441 bypassRules: form.get("bypassRules") === "on",
Get started on mission control442 })
443 : action === "unqueue"
444 ? await work.removeFromQueue(user, path, number)
445 : action === "message"
446 ? await work.messageAgent(user, path, number, String(form.get("body") ?? ""))
447 : action === "close"
448 ? await work.closePull(user, path, number)
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts449 : action === "reopen"
450 ? await work.reopenPull(user, path, number)
451 : action === "draft"
452 ? await work.convertPullToDraft(user, path, number)
Fast pages, required checks on the branch, self-hosted runners, honest incidents453 : action === "agent-review"
Get started on mission control454 ? await env.RUNNER.review(user, path, number)
455 : action === "reviewers"
456 ? await work.updatePull(user, path, number, { reviewers: picked("reviewer") })
457 : action === "assign"
458 ? await work.updatePull(user, path, number, { assignees: picked("assignee") })
459 : action === "comment"
460 ? await work.addComment(user, path, number, {
461 body: String(form.get("body") ?? ""),
462 path: String(form.get("path") ?? "") || undefined,
463 line: line > 0 ? line : undefined,
464 verdict:
465 verdict === "approve" || verdict === "request_changes" ? verdict : undefined,
466 })
467 : await work.readyPull(user, path, number, String(form.get("summary") ?? ""));
468 return result.ok ? null : { error: result.error.message, action };
469}
470
Pull requests: title and tabs across the full width, with counts and the size of the change471/** How many, beside a tab's name, when there are any. */
472function TabCount({ n }: { n: number }) {
473 if (n <= 0) return null;
474 return (
475 <span className="rounded-full bg-raised px-1.5 py-px text-xs tabular-nums text-muted ring-1 ring-line">
476 {n.toLocaleString("en-US")}
477 </span>
478 );
479}
480
Get started on mission control481function TabLink({
482 to,
483 active,
484 children,
485}: {
486 to: string;
487 active: boolean;
488 children: React.ReactNode;
489}) {
490 return (
491 <Link
492 to={to}
493 preventScrollReset
Usage, Billing settings and prepaid AI credit; fixes from the UX audit494 aria-current={active ? "page" : undefined}
Get started on mission control495 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)496 "-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 " +
Get started on mission control497 (active
498 ? "border-accent font-medium text-fg"
499 : "border-transparent text-muted hover:text-fg")
500 }
501 >
502 {children}
503 </Link>
504 );
505}
506
507/** One step of the session, on the timeline's rail. */
508function Entry({ entry, agent }: { entry: SessionEntry; agent: string }) {
509 const isTool = entry.kind === "tool_call" || entry.kind === "tool_result";
510 const Icon =
511 entry.kind === "prompt"
512 ? User
513 : entry.kind === "note"
514 ? StickyNote
515 : isTool
516 ? Wrench
517 : Bot;
518 return (
519 <li className="relative pl-10">
520 <span
521 className={`absolute top-0.5 left-0 flex size-7 items-center justify-center rounded-full border bg-bg ${
522 entry.kind === "prompt"
523 ? "border-accent/50 text-accent"
524 : "border-line text-faint"
525 }`}
526 >
527 <Icon size={14} />
528 </span>
529 {isTool ? (
530 <details className="group rounded-lg border border-line bg-surface">
531 <summary className="flex cursor-pointer list-none items-center gap-2 px-3 py-1.5 text-sm">
532 <ChevronRight
533 size={14}
534 className="text-faint transition-transform group-open:rotate-90"
535 />
536 <span className="font-mono text-xs text-accent">
537 {entry.tool ?? "tool"}
538 </span>
539 <span className="truncate font-mono text-xs text-muted">
540 {entry.kind === "tool_result" ? "→ " : ""}
541 {entry.text.split("\n")[0]}
542 </span>
543 </summary>
544 <pre className="overflow-x-auto border-t border-line p-3 font-mono text-xs whitespace-pre-wrap text-muted">
545 <code>{entry.text}</code>
546 </pre>
547 </details>
548 ) : (
549 <div>
550 <p className="text-xs font-medium text-faint">
551 {entry.kind === "prompt"
552 ? "Prompt"
553 : entry.kind === "note"
554 ? "Note"
555 : agent}
556 </p>
557 {entry.kind === "message" ? (
558 <div className="mt-1">
559 <Markdown source={entry.text} />
560 </div>
561 ) : entry.kind === "prompt" ? (
562 <div className="mt-1.5 max-h-[32rem] overflow-y-auto rounded-xl border border-line bg-surface p-4">
563 <Markdown source={entry.text} />
564 </div>
565 ) : (
566 <p className="mt-1 text-[0.9375rem] leading-relaxed wrap-break-word whitespace-pre-wrap">
567 {entry.text}
568 </p>
569 )}
570 </div>
571 )}
572 {entry.commit && (
573 <p className="mt-1.5 flex items-center gap-1 font-mono text-xs text-faint">
574 <GitCommitHorizontal size={12} />
575 {entry.commit.slice(0, 7)}
576 </p>
577 )}
578 </li>
579 );
580}
581
582/** The box at the foot of the conversation saying what stands before a merge. */
583function StatusBox({ children }: { children: React.ReactNode }) {
584 return (
585 <div className="flex gap-3">
586 <span className="hidden w-8 shrink-0 sm:block" />
587 <section className="min-w-0 grow divide-y divide-line overflow-hidden rounded-xl border border-line-strong bg-surface">
588 {children}
589 </section>
590 </div>
591 );
592}
593
594function StatusRow({
595 icon,
596 title,
597 children,
598}: {
599 icon: React.ReactNode;
600 title: string;
601 children?: React.ReactNode;
602}) {
603 return (
604 <div className="flex gap-3 px-4 py-3 text-sm">
605 <span className="mt-0.5 shrink-0">{icon}</span>
606 <div className="min-w-0">
607 <p className="font-medium">{title}</p>
608 {children && <div className="mt-0.5 text-muted">{children}</div>}
609 </div>
610 </div>
611 );
612}
613
614export default function PullPage({ loaderData, actionData, params }: Route.ComponentProps) {
615 const {
616 pull,
617 issue,
618 comments,
619 checks,
620 overlaps,
621 behind,
622 reviewPending,
623 lifecycle,
624 messages,
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs625 statuses = [],
Fast pages, required checks on the branch, self-hosted runners, honest incidents626 requiredChecks = [],
627 noChecks,
628 canAddCi,
629 canProtect,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains630 mergeable = "unknown",
631 conflicts = [],
632 earlierChecks = [],
633 workflowJobs,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md634 attempts,
Project dependencies: addresses, preview stacks, Affects, and agents who know635 preview,
Pull requests say where they are live; the project menu shows what is coming636 build,
Get started on mission control637 landing,
638 stalled,
639 requireUpToDate,
640 mergeQueue,
641 requiredApprovals,
642 canIgnoreChecks,
643 canUpdate,
644 agentsEnabled,
Merge branch 'worktree-agent-aca4790ea223aa235'645 agentRuns,
Get started on mission control646 members,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar647 teams,
648 codeOwners,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge649 rules,
Get started on mission control650 tab,
651 session,
652 comparison,
653 viewer,
654 canMerge,
655 canManage,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look656 canRun,
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts657 canModerate,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar658 defaultBranch: repoDefault,
Get started on mission control659 } = loaderData;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar660 // The branch it merges into: what every line below names.
661 const defaultBranch = pull.base ?? repoDefault;
Get started on mission control662 const base = `/${params.owner}/${params.repo}`;
663 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-agent664 // g1t, on a pull request it made: the person who asked for it is a line below.
665 const opener = openedBy(pull);
Merge branch 'worktree-agent-aca4790ea223aa235'666 const checksSection = (jobs: Record<string, Job[]>, loading = false) => (
Fast pages, required checks on the branch, self-hosted runners, honest incidents667 <ChecksSection
668 run={checks}
669 required={requiredChecks}
670 statuses={statuses}
671 jobs={jobs}
672 pull={pull}
673 base={base}
674 earlier={earlierChecks}
675 canRerunWorkflows={canMerge}
676 settingsUrl={canProtect ? `${base}/settings/branches` : null}
677 error={actionData?.action === "rerun-failed" || actionData?.action === "rerun-workflow" ? actionData.error : null}
Merge branch 'worktree-agent-aca4790ea223aa235'678 loading={loading}
Fast pages, required checks on the branch, self-hosted runners, honest incidents679 />
680 );
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md681 // A closed or merged pull request's checks, as they ended: to read, with
682 // nothing to re-run and nothing that holds a merge.
683 const settledChecks = (jobs: Record<string, Job[]>, loading = false) => (
684 <ChecksSection
685 run={null}
686 required={[]}
687 statuses={statuses}
688 jobs={jobs}
689 pull={pull}
690 base={base}
691 earlier={[]}
692 canRerunWorkflows={false}
693 settled
694 loading={loading}
695 />
696 );
Merge branch 'worktree-agent-aaf03bdceac799c89'697 const addresses = useAddresses();
Get started on mission control698 const remote = pull.fork
Merge branch 'worktree-agent-aaf03bdceac799c89'699 ? cloneUrl(addresses, `${pull.fork.namespace}/${pull.fork.name}`)
700 : cloneUrl(addresses, `${params.owner}/${params.repo}`);
Get started on mission control701 const active = pull.status === "draft" || pull.status === "open";
702
703 // Follow an agent at work, or checks in progress, without a manual reload.
704 const working = pull.status === "draft";
Fast pages, required checks on the branch, self-hosted runners, honest incidents705 const checking = statuses.some((status) => status.state === "pending");
Get started on mission control706 const reviews = verdicts(comments);
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar707 // Agents' reviews: shown, never counted.
708 const advisory = agentReviews(comments);
Fast pages, required checks on the branch, self-hosted runners, honest incidents709 // What stands between this pull request and a merge, if anything: a
710 // required check that has not passed, or the merge queue taking it out.
711 const unchecked = requiredChecks.some((check) => check.state !== "success") || checks?.status === "failed";
712 const requiredFailed = requiredChecks.some((check) => check.state === "failure") || checks?.status === "failed";
Get started on mission control713 // 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.md714 // ones for the same issue are alternatives, and expected to: those are
715 // compared under their own heading.
716 const { collisions } = splitOverlaps(overlaps, pull.issue);
Get started on mission control717 const review = {
718 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 rights719 // Nobody reviews their own pull request, nor one g1t made for them.
720 canJudge: active && viewer != null && viewer.id !== workOwner(pull).id,
Get started on mission control721 };
722 // Everyone whose review was asked for, then anyone who reviewed unasked.
723 const reviewerNames = [
724 ...new Set([...pull.reviewers, ...reviews.map(({ reviewer }) => reviewer)]),
725 ];
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar726 const teamReviewers = pull.teamReviewers ?? [];
727 // Teams to ask: the workspace's, and any asked already.
728 const teamChoices = [
729 ...teams,
730 ...teamReviewers.filter((ref) => !teams.some((team) => team.ref === ref)).map((ref) => ({ ref, name: ref })),
731 ];
732 // Branch protection wants code owners' approval it does not have yet.
733 const ownersMissing = codeOwners?.required && codeOwners.missing ? codeOwners.missing : null;
Catching up with main takes seconds when the two sides touched different files734 // A catch-up: the click shows at once; the answer says whether it is
735 // done already or a sandbox is on it.
Merge branch 'worktree-agent-a985c01f43538af01'736 const catchUpPending = usePending({ action: "update" });
Catching up with main takes seconds when the two sides touched different files737 const catchUp = actionData?.action === "update" ? actionData : null;
738 const caughtUp = catchUp && "updated" in catchUp ? catchUp.updated : null;
739 const agentCatchUp = catchUp && "agent" in catchUp ? catchUp.agent : null;
740 // Pushed already: followed until the pull request's head is the merge,
741 // so its checks show starting again, for a minute at most.
742 const settling =
743 caughtUp != null && !caughtUp.already && pull.headCommit !== caughtUp.commit && Date.now() - caughtUp.at < 60_000;
744 // Followed until it is pushed, or for so long, never longer.
745 const catchingUp =
746 agentCatchUp != null && behind && Date.now() - agentCatchUp.startedAt < CATCH_UP_TIMEOUT_MS + REFRESH_MS;
Get started on mission control747 // g1t is taking a step of its own accord, so the page will change.
748 const moving =
749 lifecycle != null && lifecycle.stage !== "ready" && lifecycle.stage !== "needs_you";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains750 // Whether it merges cleanly is being worked out, so the box will change.
751 const probing = active && mergeable === "checking";
752 const conflicting = active && mergeable === "conflicting";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending753 useRefreshWhile(working || checking || reviewPending || catchingUp || settling || moving || landing || probing);
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge754 // The rules of its base it does not meet: checks show in their own
755 // section, so the rest stop the button here. Someone a ruleset lets
756 // bypass it may tick a box and merge past them.
757 const [bypassing, setBypassing] = useState(false);
758 const rulesUnmet = (rules?.unmet ?? []).filter((violation) => violation.rule !== "required_status_checks");
759 const rulesBypassable = rules?.bypassable ?? [];
Merge branch 'mirroring' into artifacts-mode760 // A mirror takes no merges until someone takes over.
761 const mirrorBlocked = useMirrorReason();
Agents and memory, checks and conflicts, profiles, slug renames, custom domains762 // Why the merge button cannot be pressed, if it cannot.
Merge branch 'mirroring' into artifacts-mode763 const mergeBlocked = mirrorBlocked ?? (conflicting
Agents and memory, checks and conflicts, profiles, slug renames, custom domains764 ? "Resolve the conflicts first."
765 : probing
766 ? "Waiting to find out whether it merges cleanly."
767 : behind && requireUpToDate
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge768 ? `The rules for ${defaultBranch} require it to be up to date first.`
Fast pages, required checks on the branch, self-hosted runners, honest incidents769 : unchecked && !canIgnoreChecks
770 ? `The checks ${defaultBranch} requires have to pass first.`
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge771 : rulesUnmet.length > 0
772 ? rulesUnmet[0]!.message
773 : rulesBypassable.length > 0 && !bypassing
774 ? `Rules for ${defaultBranch} are not met. You may bypass them.`
775 : ownersMissing
776 ? "Code owners have to approve first."
Merge branch 'mirroring' into artifacts-mode777 : null);
Get started on mission control778
779 return (
780 // 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 change781 <div>
782 {/* The title and the tabs run the full width; what is happening on
783 the pull request sits at the top of Conversation. */}
784 <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)785 <h2 className="text-xl font-semibold tracking-tight text-balance wrap-break-word sm:text-2xl">
Get started on mission control786 {pull.title} <span className="font-normal text-faint">#{pull.number}</span>
787 </h2>
788 <div className="mt-3 flex flex-wrap items-center gap-x-3 gap-y-2 text-sm text-muted">
789 <PullState status={pull.status} />
790 <span className="flex items-center gap-2">
791 <Avatar
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent792 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Get started on mission control793 size={18}
794 />
795 <span>
Agents and memory, checks and conflicts, profiles, slug renames, custom domains796 <PersonLink
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent797 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Agents and memory, checks and conflicts, profiles, slug renames, custom domains798 className="font-medium text-fg hover:underline"
799 />{" "}
Get started on mission control800 {pull.status === "merged" ? "merged" : "wants to merge"}
801 {pull.branch && (
802 <>
803 {" "}
804 <span className="font-mono text-fg">{pull.branch}</span>
805 </>
806 )}{" "}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar807 into{" "}
808 <BaseBranch
809 base={defaultBranch}
810 head={pull.fork ? null : pull.branch}
811 canChange={loaderData.canChangeBase && active}
812 branchesUrl={`${base}/branches.json`}
813 />
Get started on mission control814 </span>
815 </span>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar816 {(pull.labels ?? []).map((name) => (
817 <LabelChip key={name} name={name} color={loaderData.labels.find((label) => label.name === name)?.color} />
818 ))}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent819 {opener.requestedBy && (
820 <span className="text-xs">
821 requested by{" "}
822 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
823 </span>
824 )}
825 {/* A pull request from a branch was made by its author, not an agent;
826 one g1t made already says so. */}
827 {!pull.branch && !opener.requestedBy && (
Get started on mission control828 <span className="flex items-center gap-1.5 font-mono text-xs">
829 <Bot size={14} />
830 {pull.agent}
831 {pull.runtime === "hosted" && <span className="text-faint">on g1t</span>}
832 </span>
833 )}
834 </div>
835
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar836 {(reviews.length > 0 || advisory.length > 0) && (
Get started on mission control837 <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 calendar838 {advisory.map(({ agent, verdict }) => (
839 <AgentReviewLine key={agent.id} agent={agent} verdict={verdict} base={base} />
840 ))}
Get started on mission control841 {reviews.map(({ reviewer, verdict }) => (
842 <span
843 key={reviewer}
844 className={`flex items-center gap-1.5 ${
Merge branch 'worktree-agent-ad7c6d88d93adc817'845 verdict === "approve" ? "text-success" : "text-danger"
Get started on mission control846 }`}
847 >
848 {verdict === "approve" ? <CircleCheck size={15} /> : <CircleSlash size={15} />}
849 {verdict === "approve" ? "Approved by" : "Changes requested by"}{" "}
850 <span className="font-medium">{reviewer}</span>
851 </span>
852 ))}
853 </p>
854 )}
855
Usage, Billing settings and prepaid AI credit; fixes from the UX audit856 <TabStrip label="Pull request" className="mt-6 items-end gap-1 border-b border-line">
Get started on mission control857 <TabLink to={here} active={tab === "conversation"}>
858 <MessageSquare size={15} />
859 Conversation
Pull requests: title and tabs across the full width, with counts and the size of the change860 <TabCount n={comments.length} />
Get started on mission control861 </TabLink>
862 <TabLink to={here + "?tab=session"} active={tab === "session"}>
863 <MessagesSquare size={15} />
864 Session
865 </TabLink>
866 <TabLink to={here + "?tab=changes"} active={tab === "changes"}>
867 <FileDiff size={15} />
Pull requests: title and tabs across the full width, with counts and the size of the change868 Files changed
869 <TabCount n={pull.files.length} />
Get started on mission control870 </TabLink>
Pull requests: title and tabs across the full width, with counts and the size of the change871 {pull.files.length > 0 && (
872 <span className="ml-auto hidden pb-2.5 font-mono text-xs sm:inline">
Merge branch 'worktree-agent-ad7c6d88d93adc817'873 <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 change874 <span className="text-danger">−{pull.files.reduce((sum, file) => sum + file.deletions, 0).toLocaleString("en-US")}</span>
875 </span>
876 )}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit877 </TabStrip>
Pull requests: title and tabs across the full width, with counts and the size of the change878 </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)879 <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 change880 <div className="min-w-0">
881 {/* What is happening on it now: its stage, its agent, what people told
882 the agent, and what else touches the same files. On the conversation,
883 so the tabs sit right under the title. */}
884 {tab === "conversation" && (
885 <div>
886 {issue && (
887 <Link
888 to={`${base}/issues/${issue.number}`}
889 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"
890 >
891 <IssueIcon issue={issue} />
892 <span className="text-muted">
893 {pull.status === "merged" && issue.resolvedBy === pull.number
894 ? "Resolved"
895 : "For issue"}
896 </span>
897 <span className="truncate font-medium">{issue.title}</span>
898 <span className="text-faint">#{issue.number}</span>
899 {issue.pullCount > 1 && (
900 <span className="ml-auto shrink-0 text-xs text-faint">
901 1 of {issue.pullCount} pull requests
Pull requests show their tabs right under the title; agent panels move into Conversation902 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change903 )}
904 </Link>
905 )}
906 {pull.status === "merged" && (
907 <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">
908 <GitMerge size={16} className="shrink-0 text-merged" />
Get started on mission control909 <span>
Pull requests: title and tabs across the full width, with counts and the size of the change910 Merged into <span className="font-mono">{defaultBranch}</span> by{" "}
911 <span className="font-medium">{pull.mergedBy}</span>{" "}
912 {/* Not an account: the repository's settings said to merge it. */}
913 {pull.mergedBy === "g1t" && "automatically, once it was ready, "}
914 {pull.mergedAt && <TimeAgo at={pull.mergedAt} />}
915 {pull.headCommit && (
916 <span className="font-mono text-muted"> · {pull.headCommit.slice(0, 7)}</span>
Get started on mission control917 )}
918 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change919 </p>
920 )}
921 {pull.supersededBy != null && (
922 <p className="mt-4 rounded-xl border border-line bg-surface px-4 py-2.5 text-sm text-muted">
923 Closed because{" "}
924 <Link
925 to={`${base}/pull/${pull.supersededBy}`}
926 className="font-medium text-fg hover:underline"
927 >
928 #{pull.supersededBy}
929 </Link>{" "}
930 was merged for this issue instead.
931 </p>
932 )}
933 {lifecycle && <LifecyclePanel lifecycle={lifecycle} />}
934 {/* 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 step935 <AgentPanel
936 owner={params.owner}
937 repo={params.repo}
938 number={pull.number}
939 stage={lifecycle?.stage ?? null}
940 confidence={pull.confidence ?? null}
Merge branch 'worktree-agent-aca4790ea223aa235'941 runs={agentRuns}
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step942 />
Pull requests: title and tabs across the full width, with counts and the size of the change943
944 {/* 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 look945 {canRun &&
Pull requests: title and tabs across the full width, with counts and the size of the change946 pull.runtime === "hosted" &&
947 (working || ["working", "revising", "catching_up", "answering"].includes(lifecycle?.stage ?? "")) && (
Merge branch 'worktree-agent-a985c01f43538af01'948 // Keyed by the messages so the box empties once one shows below.
Merge branch 'worktree-agent-ad7c6d88d93adc817'949 <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 change950 <p className="flex items-center gap-2 text-sm font-medium">
Merge branch 'worktree-agent-ad7c6d88d93adc817'951 <Sparkles size={15} className="text-accent" />
Pull requests: title and tabs across the full width, with counts and the size of the change952 Message the agent
953 </p>
954 <p className="mt-1 text-xs text-muted">
955 A correction, a hint, a change of plan. It reads it at its next step, without
956 starting over.
957 </p>
958 <div className="mt-3 flex gap-2">
959 <input type="hidden" name="action" value="message" />
960 <input
961 name="body"
962 required
963 autoComplete="off"
964 data-1p-ignore
965 placeholder="Keep the old flag working too…"
Merge branch 'worktree-agent-ad7c6d88d93adc817'966 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"
Get started on mission control967 />
Merge branch 'worktree-agent-a985c01f43538af01'968 <SubmitButton match={{ action: "message" }} pending="Sending…">
969 Send
970 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change971 </div>
972 </Form>
Get started on mission control973 )}
Pull requests: title and tabs across the full width, with counts and the size of the change974 {messages.length > 0 && (
975 <ul className="mt-3 space-y-1.5">
976 {messages.map((message) => (
977 <li key={message.id} className="flex items-start gap-2 text-sm">
978 <Avatar name={message.author} size={18} />
979 <span className="min-w-0 grow">
980 <span className="font-medium">
981 {message.fromNumber != null ? `The agent on #${message.fromNumber}` : message.author}
982 </span>{" "}
983 <span className="text-muted">
984 {message.kind === "question"
985 ? "asked:"
986 : message.kind === "handoff"
987 ? "handed over:"
988 : message.kind === "answer"
989 ? "answered:"
990 : "to the agent:"}
991 </span>{" "}
992 {message.body}
993 {message.answer && (
Merge branch 'worktree-agent-ad7c6d88d93adc817'994 <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 change995 {message.declined ? "Declined: " : "Answer: "}
996 {message.answer}
997 </span>
998 )}
999 </span>
Merge branch 'worktree-agent-ad7c6d88d93adc817'1000 <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 change1001 {message.deliveredAt ? "read by the agent" : "waiting for its next step"}
1002 </span>
1003 </li>
1004 ))}
1005 </ul>
1006 )}
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md1007 {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 change1008 {collisions.length > 0 && active && (
1009 <div className="mt-4 rounded-xl border border-line bg-surface px-4 py-3 text-sm">
1010 <p className="flex items-center gap-2.5 font-medium">
1011 <Radar size={16} className="shrink-0 text-info" />
1012 Other work is changing the same files
1013 </p>
1014 <ul className="mt-2 space-y-1.5">
1015 {collisions.map((other) => (
1016 <li key={other.number} className="flex flex-wrap items-baseline gap-x-2 text-muted">
1017 <Link
1018 to={`${base}/pull/${other.number}`}
1019 className="font-medium text-fg hover:underline"
1020 >
1021 {other.title} <span className="font-normal text-faint">#{other.number}</span>
1022 </Link>
1023 <span className="font-mono text-xs">{other.paths.join(", ")}</span>
1024 </li>
1025 ))}
1026 </ul>
1027 <p className="mt-2 text-xs text-faint">
1028 Whichever merges second will have to catch up, and may conflict.
1029 </p>
1030 </div>
1031 )}
1032 </div>
1033 )}
1034 <div className="mt-5">
1035 {comparison ? (
1036 <DiffView
1037 comparison={comparison}
1038 review={{
1039 comments: comments.filter((comment) => comment.path),
1040 canComment: Boolean(viewer),
1041 }}
1042 />
1043 ) : tab === "session" ? (
1044 session.length === 0 ? (
1045 <EmptyState title="Nothing recorded yet">
1046 The agent's prompts, reasoning and tool calls appear here as it
1047 works.
1048 </EmptyState>
1049 ) : (
1050 <ol className="relative space-y-5 before:absolute before:top-2 before:bottom-2 before:left-3.25 before:w-px before:bg-line">
1051 {session.map((entry) => (
1052 <Entry key={entry.seq} entry={entry} agent={pull.agent} />
1053 ))}
1054 </ol>
1055 )
1056 ) : (
1057 <div className="space-y-4">
1058 <TimelineItem
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1059 author={opener.name}
Pull requests: title and tabs across the full width, with counts and the size of the change1060 at={pull.createdAt}
1061 action={
1062 <span>
1063 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-agent1064 {opener.requestedBy ? (
Pull requests: title and tabs across the full width, with counts and the size of the change1065 <>
1066 {" "}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1067 for{" "}
1068 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
1069 </>
1070 ) : !pull.branch && (
1071 <>
1072 {" "}
Pull requests: title and tabs across the full width, with counts and the size of the change1073 with <span className="font-mono text-xs">{pull.agent}</span>
1074 </>
1075 )}
1076 </span>
1077 }
1078 >
1079 {pull.body ? (
1080 <Markdown source={pull.body} repo={{ namespace: params.owner, name: params.repo }} />
1081 ) : (
1082 <p className="text-sm text-muted">
1083 {working
1084 ? "No description yet. It is written when the pull request is marked ready for review."
1085 : "No description."}
1086 </p>
1087 )}
1088 {canManage && pull.status === "open" && (
1089 <details className="mt-3 border-t border-line pt-3 text-sm">
1090 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1091 Edit description
1092 </summary>
1093 <Form method="post" className="mt-3 space-y-2">
Merge branch 'worktree-agent-a985c01f43538af01'1094 <input type="hidden" name="action" value="describe" />
Pull requests: title and tabs across the full width, with counts and the size of the change1095 <Textarea
1096 name="summary"
1097 rows={6}
1098 placeholder="What changed and why"
1099 defaultValue={pull.body ?? ""}
1100 />
Merge branch 'worktree-agent-a985c01f43538af01'1101 <SubmitButton match={{ action: "describe" }} pending="Saving…">
1102 Save
1103 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1104 </Form>
1105 </details>
1106 )}
1107 </TimelineItem>
Get started on mission control1108
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1109 <CommentList
1110 comments={comments}
1111 review={review}
1112 base={base}
1113 viewerId={viewer?.id ?? null}
1114 canModerate={canModerate}
1115 failed={actionData && "comment" in actionData ? { comment: String(actionData.comment), error: actionData.error } : null}
1116 />
Get started on mission control1117
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 deploy1118 {(preview || build) && <DeploymentCard preview={preview} build={build} base={base} />}
Pull requests say where they are live; the project menu shows what is coming1119
Pull requests: title and tabs across the full width, with counts and the size of the change1120 {pull.status === "draft" && (
1121 <StatusBox>
1122 <StatusRow icon={<Loader size={16} className="text-faint" />} title="This is a draft">
1123 It is still being worked on. It can be reviewed and merged once it is
1124 marked ready.
1125 </StatusRow>
1126 {canManage && (
1127 <Form method="post" className="space-y-2 px-4 py-3">
Merge branch 'worktree-agent-a985c01f43538af01'1128 <input type="hidden" name="action" value="ready" />
Pull requests: title and tabs across the full width, with counts and the size of the change1129 <Textarea name="summary" rows={3} placeholder="What changed and why" />
Merge branch 'worktree-agent-a985c01f43538af01'1130 <SubmitButton match={{ action: "ready" }} pending="Marking ready…">
1131 Mark ready for review
1132 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1133 </Form>
1134 )}
1135 </StatusBox>
1136 )}
Get started on mission control1137
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1138 {codeOwners && active && (
1139 <div className="flex gap-3">
1140 <span className="hidden w-8 shrink-0 sm:block" />
1141 <div className="min-w-0 grow">
1142 <PullCodeOwnersPanel
1143 owners={codeOwners}
1144 base={base}
1145 branch={defaultBranch}
1146 errorsHref={canProtect ? `${base}/settings/branches#codeowners` : `${base}/blob/${encodeURIComponent(defaultBranch)}/${codeOwners.path}`}
1147 />
1148 </div>
1149 </div>
1150 )}
1151
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md1152 {(pull.status === "merged" || pull.status === "closed") && statuses.length > 0 && (
1153 <StatusBox>
1154 <Suspense fallback={settledChecks({}, true)}>
1155 <Await resolve={workflowJobs}>{(jobs) => settledChecks(jobs)}</Await>
1156 </Suspense>
1157 </StatusBox>
1158 )}
1159
Pull requests: title and tabs across the full width, with counts and the size of the change1160 {pull.status === "open" && (
1161 <StatusBox>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1162 {/* Checks by status at once; job by job when the runs are read. */}
Merge branch 'worktree-agent-aca4790ea223aa235'1163 <Suspense fallback={checksSection({}, true)}>
1164 <Await resolve={workflowJobs}>{(jobs) => checksSection(jobs)}</Await>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1165 </Suspense>
1166 {noChecks && <AddCiPrompt owner={params.owner} repo={params.repo} canAdd={canAddCi} compact />}
Get started on mission control1167 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1168 icon={
1169 reviews.some(({ verdict }) => verdict === "request_changes") ? (
1170 <CircleSlash size={16} className="text-danger" />
1171 ) : reviews.length > 0 ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1172 <CircleCheck size={16} className="text-success" />
Pull requests: title and tabs across the full width, with counts and the size of the change1173 ) : (
1174 <MessageSquare size={16} className="text-faint" />
1175 )
1176 }
1177 title={
1178 reviews.length === 0
1179 ? "No reviews yet"
1180 : reviews
1181 .map(
1182 ({ reviewer, verdict }) =>
1183 `${verdict === "approve" ? "Approved by" : "Changes requested by"} ${reviewer}`,
1184 )
1185 .join(" · ")
1186 }
Get started on mission control1187 >
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1188 {reviewPending && "g1t is reviewing it now. "}
Pull requests: title and tabs across the full width, with counts and the size of the change1189 {requiredApprovals > 0 &&
1190 `This repository requires ${requiredApprovals} approving ${
1191 requiredApprovals === 1 ? "review" : "reviews"
1192 } before merging.`}
Get started on mission control1193 </StatusRow>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1194 {codeOwners?.required && (
1195 <StatusRow
1196 icon={
1197 ownersMissing ? (
1198 <CircleSlash size={16} className="text-warn" />
1199 ) : (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1200 <CircleCheck size={16} className="text-success" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1201 )
1202 }
1203 title={ownersMissing ? "Waiting for code owners" : "Code owners approved"}
1204 >
1205 {ownersMissing ?? `Every file it changes has its owners' approval.`}{" "}
1206 {ownersMissing && "This repository requires their review before merging."}
1207 </StatusRow>
1208 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1209 {landing ? (
Get started on mission control1210 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1211 icon={<Loader size={16} className="animate-spin text-accent" />}
1212 title="Merging"
1213 >
1214 {defaultBranch} has moved, so g1t is bringing this up to date first. It
1215 lands as soon as that is done. Watch it in the Session tab.
1216 </StatusRow>
Catching up with main takes seconds when the two sides touched different files1217 ) : agentCatchUp && behind ? (
1218 <CatchUpProgress
1219 owner={params.owner}
1220 repo={params.repo}
1221 number={pull.number}
1222 defaultBranch={defaultBranch}
1223 behind={behind}
1224 update={agentCatchUp.update}
1225 startedAt={agentCatchUp.startedAt}
1226 retrying={catchUpPending}
Merge branch 'worktree-agent-aca4790ea223aa235'1227 runs={agentRuns}
Catching up with main takes seconds when the two sides touched different files1228 />
Pull requests: title and tabs across the full width, with counts and the size of the change1229 ) : conflicting ? (
1230 <ConflictsSection
1231 conflicts={conflicts}
1232 pull={pull}
1233 owner={params.owner}
1234 repo={params.repo}
1235 defaultBranch={defaultBranch}
1236 changesUrl={here + "?tab=changes"}
1237 canResolve={canUpdate && agentsEnabled}
Catching up with main takes seconds when the two sides touched different files1238 resolving={catchUpPending}
1239 error={catchUp && "error" in catchUp ? catchUp.error : null}
Get started on mission control1240 />
Pull requests: title and tabs across the full width, with counts and the size of the change1241 ) : probing ? (
1242 <MergeabilityRow mergeable={mergeable} defaultBranch={defaultBranch} />
1243 ) : behind ? (
1244 <StatusRow
1245 icon={<GitPullRequestArrow size={16} className="text-info" />}
1246 title={`${defaultBranch} has moved since this was made`}
1247 >
1248 {mergeable === "clean" && "It has no conflicts with it. "}
1249 {requireUpToDate
1250 ? "This repository requires pull requests to be up to date, so it has to catch up before it can merge."
1251 : "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 files1252 {canUpdate && (
1253 <Form method="post" className="mt-2">
Pull requests: title and tabs across the full width, with counts and the size of the change1254 <input type="hidden" name="action" value="update" />
Merge branch 'worktree-agent-a985c01f43538af01'1255 <SubmitButton variant="quiet" match={{ action: "update" }} pending={`Merging ${defaultBranch} in…`}>
1256 Catch up with {defaultBranch} now
1257 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1258 </Form>
1259 )}
Catching up with main takes seconds when the two sides touched different files1260 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Pull requests: title and tabs across the full width, with counts and the size of the change1261 </StatusRow>
1262 ) : (
Merge branch 'worktree-agent-af9f0f386d52b6a73'1263 (pull.headCommit || (catchUp && "error" in catchUp)) && (
Pull requests: title and tabs across the full width, with counts and the size of the change1264 <StatusRow
Merge branch 'worktree-agent-ad7c6d88d93adc817'1265 icon={<CircleCheck size={16} className="text-success" />}
Catching up with main takes seconds when the two sides touched different files1266 title={
1267 (caughtUp && !caughtUp.already) || agentCatchUp
1268 ? `Brought up to date with ${defaultBranch}`
1269 : `Up to date with ${defaultBranch}`
1270 }
1271 >
1272 {caughtUp && !caughtUp.already && (
1273 <>
1274 Merged <span className="font-mono">{defaultBranch}</span> in as{" "}
1275 <span className="font-mono text-fg">{caughtUp.commit.slice(0, 7)}</span>. Its checks run
1276 again on the new commit.
1277 </>
1278 )}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1279 {agentCatchUp && "g1t merged it in and pushed the result. Its checks run again on the new commit."}
Merge branch 'worktree-agent-af9f0f386d52b6a73'1280 {/* A catch-up refused or failed after the pull request stopped being behind. */}
1281 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Catching up with main takes seconds when the two sides touched different files1282 </StatusRow>
Pull requests: title and tabs across the full width, with counts and the size of the change1283 )
1284 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1285 {active && rules && (rulesUnmet.length > 0 || rulesBypassable.length > 0 || rules.evaluate.length > 0) && (
1286 <StatusRow
1287 icon={
1288 <Scale
1289 size={16}
1290 className={rulesUnmet.length > 0 ? "text-danger" : rulesBypassable.length > 0 ? "text-warn" : "text-info"}
1291 />
1292 }
1293 title={
1294 rulesUnmet.length > 0
1295 ? `Rules for ${defaultBranch} are not met yet`
1296 : rulesBypassable.length > 0
1297 ? `You may bypass the rules for ${defaultBranch}`
1298 : "Rulesets being evaluated would refuse this merge"
1299 }
1300 >
1301 <div className="mt-2 space-y-3">
1302 {rulesUnmet.length > 0 && <ViolationList violations={rulesUnmet} />}
1303 {rulesBypassable.length > 0 && (
1304 <div>
1305 <p className="mb-1.5 text-xs text-muted">Not met, but a ruleset lets you bypass it:</p>
1306 <ViolationList violations={rulesBypassable} tone="warn" />
1307 </div>
1308 )}
1309 {rules.evaluate.length > 0 && (
1310 <div>
1311 <p className="mb-1.5 text-xs text-muted">Rulesets in evaluate would refuse it for this; nothing is held up:</p>
1312 <ViolationList violations={rules.evaluate} tone="info" />
1313 </div>
1314 )}
1315 {canProtect && (
1316 <Link
1317 to={`${base}/settings/rules?branch=${encodeURIComponent(defaultBranch)}`}
1318 className="inline-block text-xs text-muted underline underline-offset-2 hover:text-fg"
1319 >
1320 See every rule for {defaultBranch}
1321 </Link>
1322 )}
1323 </div>
1324 </StatusRow>
1325 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1326 {stalled && !lifecycle && (
1327 <StatusRow icon={<Hand size={16} className="text-warn" />} title="Needs you">
1328 {stalled}
1329 </StatusRow>
1330 )}
1331 {canMerge && lifecycle?.stage === "queued" && (
1332 <Form method="post" className="flex flex-wrap items-center gap-3 px-4 py-3">
1333 <ButtonLink to={`${base}/queue`}>
1334 <Layers size={15} />
1335 See the queue
1336 </ButtonLink>
Merge branch 'worktree-agent-a985c01f43538af01'1337 <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 change1338 Remove from the queue
Merge branch 'worktree-agent-a985c01f43538af01'1339 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1340 </Form>
1341 )}
1342 {canMerge && !landing && lifecycle?.stage !== "queued" && (
1343 <Form method="post" className="space-y-3 px-4 py-3">
1344 {issue?.state === "open" && (
1345 <CheckboxOption
1346 name="keepIssueOpen"
1347 label={`Keep #${issue.number} open. This is only part of the work.`}
1348 labelClassName="text-xs text-muted"
1349 />
1350 )}
1351 {unchecked && canIgnoreChecks && (
1352 <CheckboxOption
1353 name="ignoreChecks"
Fast pages, required checks on the branch, self-hosted runners, honest incidents1354 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 change1355 labelClassName="text-xs text-muted"
1356 />
1357 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1358 {rulesBypassable.length > 0 && rulesUnmet.length === 0 && (
1359 <CheckboxOption
1360 name="bypassRules"
1361 checked={bypassing}
1362 onCheckedChange={(checked) => setBypassing(checked === true)}
1363 label={`Bypass the rules for ${defaultBranch}: merge although they are not met. It is recorded.`}
1364 labelClassName="text-xs text-muted"
1365 />
1366 )}
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)1367 <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'1368 <Hint label={mergeBlocked} disabled={mergeBlocked != null}>
1369 <SubmitButton
1370 variant="accent"
1371 name="action"
1372 value="merge"
1373 disabled={mergeBlocked != null}
1374 pending={mergeQueue ? "Adding to the queue…" : "Merging…"}
1375 >
1376 {mergeQueue ? <Layers size={15} /> : <GitMerge size={15} />}
1377 {mergeQueue ? "Add to the merge queue" : `Merge into ${defaultBranch}`}
1378 </SubmitButton>
1379 </Hint>
Pull requests: title and tabs across the full width, with counts and the size of the change1380 <span className={`text-xs ${mergeBlocked ? "text-danger" : "text-muted"}`}>
1381 {mergeBlocked ?? (mergeQueue
1382 ? `Tested together with everything ahead of it, then lands on ${defaultBranch}.`
1383 : issue?.state === "open"
1384 ? `Closes issue #${issue.number}, and any other pull requests still open for it.`
1385 : `Lands its commits on ${defaultBranch}.`)}
1386 </span>
1387 </div>
1388 {actionData?.action === "merge" && (
1389 <ErrorText>{actionData.error}</ErrorText>
1390 )}
1391 </Form>
1392 )}
1393 </StatusBox>
1394 )}
Get started on mission control1395
Pull requests: title and tabs across the full width, with counts and the size of the change1396 <CommentForm
1397 author={viewer?.username ?? null}
1398 resetKey={comments.length}
1399 review={review}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1400 handles={teams.map((team) => `@${team.ref}`)}
Pull requests: title and tabs across the full width, with counts and the size of the change1401 />
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1402 {canManage && (active || pull.status === "closed") && (
1403 <Form method="post" className="flex flex-wrap justify-end gap-2">
1404 {pull.status === "open" && (
1405 <SubmitButton variant="quiet" name="action" value="draft" pending="Converting…">
1406 Convert to draft
1407 </SubmitButton>
1408 )}
1409 {active ? (
1410 <SubmitButton variant="quiet" name="action" value="close" pending="Closing…">
1411 Close pull request
1412 </SubmitButton>
1413 ) : (
1414 <SubmitButton variant="quiet" name="action" value="reopen" pending="Reopening…">
1415 Reopen pull request
1416 </SubmitButton>
1417 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1418 </Form>
1419 )}
1420 {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 deploy1421 !["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 change1422 String(actionData.action),
1423 ) && <ErrorText>{actionData.error}</ErrorText>}
1424 </div>
1425 )}
1426 {actionData?.action === "comment" && (
1427 <div className="mt-2">
1428 <ErrorText>{actionData.error}</ErrorText>
1429 </div>
1430 )}
1431 </div>
1432 </div>
1433
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)1434 <DetailsDisclosure
1435 className={tab === "changes" ? "hidden" : "order-first lg:order-none"}
1436 summary={detailsSummary([
1437 [reviewerNames.length + teamReviewers.length, "reviewer", "reviewers"],
1438 [pull.assignees.length, "assignee", "assignees"],
1439 [(pull.labels ?? []).length, "label", "labels"],
1440 ])}
1441 >
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md1442 {/* Other than a draft, a pull request shows its checks in full in the conversation. */}
1443 {pull.status === "draft" && <WorkflowStatuses statuses={statuses} />}
Project dependencies: addresses, preview stacks, Affects, and agents who know1444 <section>
Pull requests: title and tabs across the full width, with counts and the size of the change1445 <h3 className="text-sm font-medium">Reviewers</h3>
1446 <ul className="mt-2 space-y-1.5 text-sm">
1447 {reviewerNames.map((name) => {
1448 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-agent1449 const pending = name === "g1t" && reviewPending;
Pull requests: title and tabs across the full width, with counts and the size of the change1450 return (
1451 <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-agent1452 {name === "g1t" ? (
Pull requests: title and tabs across the full width, with counts and the size of the change1453 <Sparkles size={16} className="shrink-0 text-accent" />
1454 ) : (
1455 <Avatar name={name} size={20} />
1456 )}
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)1457 <span className="grow truncate font-mono text-xs">
1458 <PersonLink name={name} className="hover:underline" />
1459 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change1460 {pending ? (
1461 <span className="flex items-center gap-1.5 text-xs text-muted">
1462 <span className="size-1.5 animate-pulse rounded-full bg-accent" />
1463 Reviewing
1464 </span>
1465 ) : verdict === "approve" ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1466 <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 change1467 <CircleCheck size={13} /> Approved
1468 </span>
1469 ) : verdict === "request_changes" ? (
1470 <span className="flex items-center gap-1 text-xs text-danger">
1471 <CircleSlash size={13} /> Changes requested
1472 </span>
1473 ) : (
1474 <span className="text-xs text-faint">Review requested</span>
1475 )}
1476 </li>
1477 );
1478 })}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1479 {teamReviewers.map((team) => (
1480 <TeamReviewer key={team} team={team} />
1481 ))}
1482 {reviewerNames.length === 0 && teamReviewers.length === 0 && (
Pull requests: title and tabs across the full width, with counts and the size of the change1483 <li className="px-1 text-xs text-faint">No reviews requested yet.</li>
1484 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1485 </ul>
Pull requests: title and tabs across the full width, with counts and the size of the change1486 {pull.status === "open" && canManage && (
1487 <div className="mt-3 space-y-2">
1488 {agentsEnabled && !reviewPending && (
1489 <Form method="post">
1490 <input type="hidden" name="action" value="agent-review" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1491 {[...pull.reviewers, ...teamReviewers].map((name) => (
Pull requests: title and tabs across the full width, with counts and the size of the change1492 <input key={name} type="hidden" name="reviewer" value={name} />
1493 ))}
1494 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1495 <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 change1496 <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-agent1497 Request review from g1t
Merge branch 'worktree-agent-a985c01f43538af01'1498 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1499 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1500 <ComputeNote note={loaderData.computeNote} />
Pull requests: title and tabs across the full width, with counts and the size of the change1501 </Form>
1502 )}
1503 <details>
1504 <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 bar1505 {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 change1506 </summary>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1507 <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 change1508 <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-agent1509 {pull.reviewers.includes("g1t") && (
1510 <input type="hidden" name="reviewer" value="g1t" />
Pull requests: title and tabs across the full width, with counts and the size of the change1511 )}
1512 <PeoplePicker
1513 name="reviewer"
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1514 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-agent1515 chosen={pull.reviewers.filter((name) => name !== "g1t")}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1516 placeholder="Other usernames or teams (acme/backend)"
Pull requests: title and tabs across the full width, with counts and the size of the change1517 />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1518 {teamChoices.length > 0 && (
1519 <div className="space-y-1.5 border-t border-line pt-2">
1520 <p className="text-xs text-faint">Teams</p>
1521 {teamChoices.map((team) => (
1522 <CheckboxOption
1523 key={team.ref}
1524 name="reviewer"
1525 value={team.ref}
1526 defaultChecked={teamReviewers.includes(team.ref)}
1527 className="items-center"
1528 labelClassName="flex min-w-0 items-center gap-2"
1529 label={
1530 <>
1531 <Users size={14} className="shrink-0 text-faint" />
1532 <span className="truncate font-mono text-xs">@{team.ref}</span>
1533 </>
1534 }
1535 />
1536 ))}
1537 </div>
1538 )}
Merge branch 'worktree-agent-a985c01f43538af01'1539 <SubmitButton variant="quiet" match={{ action: "reviewers" }} pending="Saving…">
Pull requests: title and tabs across the full width, with counts and the size of the change1540 Save reviewers
Merge branch 'worktree-agent-a985c01f43538af01'1541 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1542 </Form>
1543 </details>
1544 </div>
Project dependencies: addresses, preview stacks, Affects, and agents who know1545 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1546 {["agent-review", "reviewers"].includes(String(actionData?.action)) && (
1547 <ErrorText>{actionData?.error}</ErrorText>
1548 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1549 </section>
Get started on mission control1550
Pull requests: title and tabs across the full width, with counts and the size of the change1551 <section>
1552 <h3 className="text-sm font-medium">Assignees</h3>
1553 <ul className="mt-2 space-y-1.5 text-sm">
1554 {pull.assignees.map((name) => (
Get started on mission control1555 <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 change1556 <Avatar name={name} size={20} />
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)1557 <span className="grow truncate font-mono text-xs">
1558 <PersonLink name={name} className="hover:underline" />
1559 </span>
Get started on mission control1560 </li>
Pull requests: title and tabs across the full width, with counts and the size of the change1561 ))}
1562 {pull.assignees.length === 0 && (
1563 <li className="px-1 text-xs text-faint">No one yet.</li>
1564 )}
1565 </ul>
1566 {viewer && canManage && active && (
1567 <div className="mt-3 space-y-2">
1568 {!pull.assignees.includes(viewer.username) && (
1569 <Form method="post">
1570 <input type="hidden" name="action" value="assign" />
1571 {pull.assignees.map((name) => (
1572 <input key={name} type="hidden" name="assignee" value={name} />
1573 ))}
1574 <input type="hidden" name="assignee" value={viewer.username} />
1575 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1576 <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 change1577 Assign yourself
Merge branch 'worktree-agent-a985c01f43538af01'1578 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1579 </div>
1580 </Form>
1581 )}
1582 <details>
1583 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1584 Assign people
1585 </summary>
1586 <Form method="post" className="mt-2 space-y-2" key={pull.assignees.join()}>
1587 <input type="hidden" name="action" value="assign" />
1588 <PeoplePicker name="assignee" members={members} chosen={pull.assignees} />
Merge branch 'worktree-agent-a985c01f43538af01'1589 <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 change1590 Save assignees
Merge branch 'worktree-agent-a985c01f43538af01'1591 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1592 </Form>
1593 </details>
1594 </div>
Get started on mission control1595 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1596 {actionData?.action === "assign" && <ErrorText>{actionData.error}</ErrorText>}
1597 </section>
Get started on mission control1598
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1599 <LabelsBox
1600 labels={loaderData.labels}
1601 chosen={pull.labels ?? []}
1602 canEdit={canManage && active}
Merge main (membership, two-factor, GitHub repo roles) into tokens1603 canCreate={loaderData.canMakeLabels}
1604 manageUrl={loaderData.canMakeLabels ? `${base}/labels` : undefined}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1605 />
1606 <MilestoneBox
1607 milestones={loaderData.milestones}
1608 current={pull.milestone}
1609 canEdit={loaderData.canTriage}
1610 base={base}
1611 />
1612
Pull requests: title and tabs across the full width, with counts and the size of the change1613 <section>
1614 <h3 className="text-sm font-medium">Working copy</h3>
1615 {pull.branch ? (
1616 <>
1617 <p className="mt-1 text-xs text-muted">
1618 <GitBranch size={13} className="mr-1 inline align-[-2px]" />
1619 Branch <span className="font-mono text-fg">{pull.branch}</span> of this
1620 repository. Pushes to it show up here.
1621 </p>
1622 <div className="mt-2">
1623 <CopyLine text={`git clone -b ${pull.branch} ${remote}`} />
1624 </div>
1625 </>
1626 ) : (
1627 <>
1628 <p className="mt-1 text-xs text-muted">
1629 This fork belongs to the pull request. Pushes to it show up here.
1630 </p>
1631 <div className="mt-2">
1632 <CopyLine text={`git clone ${remote}`} />
1633 </div>
1634 </>
1635 )}
1636 <p className="mt-3 flex items-center gap-1.5 font-mono text-xs text-faint">
1637 <GitCommitHorizontal size={13} />
Merge checks: statuses and check runs on every commit1638 {pull.headCommit ? (
1639 <Link to={`${base}/commit/${pull.headCommit}`} className="hover:text-fg">
1640 {pull.headCommit.slice(0, 12)}
1641 </Link>
1642 ) : (
1643 "no commits pushed yet"
1644 )}
1645 {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 change1646 </p>
1647 </section>
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings1648 {loaderData.viewer && (
1649 <SubscriptionBox action={`${base}/notifications`} number={pull.number} kind="pull" subscription={loaderData.subscription} />
1650 )}
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)1651 </DetailsDisclosure>
Pull requests: title and tabs across the full width, with counts and the size of the change1652 </div>
Get started on mission control1653 </div>
1654 );
1655}
Pull requests say where they are live; the project menu shows what is coming1656
1657/** Where the pull request's change is live, as GitHub shows a deployment. */
1658function DeploymentCard({
1659 preview,
1660 build,
1661 base,
1662}: {
1663 preview: LiveApp | null;
1664 build: Deployment | null;
1665 base: string;
1666}) {
1667 const building = build?.status === "queued" || build?.status === "building";
1668 const failed = build?.status === "failed";
1669 return (
1670 <section className="ml-12 overflow-hidden rounded-xl border border-line bg-surface">
1671 <div className="flex flex-wrap items-center gap-3 px-4 py-3">
1672 <span
1673 className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ${
Merge branch 'worktree-agent-ad7c6d88d93adc817'1674 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 coming1675 }`}
1676 >
1677 {building ? <Loader size={15} className="animate-spin" /> : <Rocket size={15} />}
1678 </span>
1679 <div className="min-w-0 grow">
1680 <p className="text-sm font-medium">
1681 {building
1682 ? preview
1683 ? "Deploying the latest push. The last preview is still live."
1684 : "Deploying a preview"
1685 : failed
1686 ? "The preview failed to deploy"
1687 : "This branch is live"}
1688 </p>
1689 {preview ? (
Merge branch 'site-fixes' into fast-push1690 <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 coming1691 {preview.url.replace(/^https?:[/][/]/, "")}
Merge branch 'site-fixes' into fast-push1692 </DeployLink>
Pull requests say where they are live; the project menu shows what is coming1693 ) : (
1694 build?.error && <p className="mt-0.5 truncate text-xs text-muted">{build.error}</p>
1695 )}
1696 </div>
1697 <div className="flex shrink-0 items-center gap-2">
1698 {build && (
1699 <Link to={`${base}/deployments/${build.id}`} className="text-xs text-muted hover:text-fg">
1700 {failed ? "See why" : "Build log"}
1701 </Link>
1702 )}
1703 {preview && (
Merge branch 'site-fixes' into fast-push1704 <DeployLink
Pull requests say where they are live; the project menu shows what is coming1705 href={preview.url}
1706 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"
1707 >
1708 Visit preview
1709 <ArrowUpRight size={13} />
Merge branch 'site-fixes' into fast-push1710 </DeployLink>
Pull requests say where they are live; the project menu shows what is coming1711 )}
1712 </div>
1713 </div>
1714 {preview && (
1715 <p className="border-t border-line px-4 py-2 text-xs text-faint">
1716 <span className="font-mono">{preview.commit.slice(0, 7)}</span> · deployed <TimeAgo at={preview.deployedAt} />
1717 {preview.branch && ` · from ${preview.branch}`}
1718 </p>
1719 )}
1720 </section>
1721 );
1722}

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