Skip to content
1,739 linesCodeBlameRaw

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

Acceptance checks in sandboxes, line comments and review verdicts1import { env } from "cloudflare:workers";
Issues and pull requests replace intents and attempts2import {
3 Bot,
4 ChevronRight,
5 FileDiff,
Pull requests from branches6 GitBranch,
Issues and pull requests replace intents and attempts7 GitCommitHorizontal,
Acceptance checks in sandboxes, line comments and review verdicts8 CircleCheck,
9 CircleSlash,
Issues and pull requests replace intents and attempts10 GitMerge,
Agents as a team: lifecycle, merge queue, billing and a new shell11 Layers,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge12 Scale,
Agents as a team: lifecycle, merge queue, billing and a new shell13 GitPullRequestArrow,
14 Hand,
15 Loader,
16 Radar,
17 Sparkles,
Issues and pull requests replace intents and attempts18 MessageSquare,
Pull requests say where they are live; the project menu shows what is coming19 ArrowUpRight,
Issues and pull requests replace intents and attempts20 MessagesSquare,
Pull requests say where they are live; the project menu shows what is coming21 Rocket,
Project dependencies: addresses, preview stacks, Affects, and agents who know22 Network,
Issues and pull requests replace intents and attempts23 StickyNote,
24 User,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar25 Users,
Issues and pull requests replace intents and attempts26 Wrench,
27} from "lucide-react";
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge28import { Suspense, useState } from "react";
Merge branch 'worktree-agent-a985c01f43538af01'29import { Await, Form, Link, redirect } from "react-router";
Issues and pull requests replace intents and attempts30
Pull requests say where they are live; the project menu shows what is coming31import {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look32 type Capability,
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,
37 type SessionEntry,
38 type Viewer,
39 pullComparison,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights40 workOwner,
Pull requests say where they are live; the project menu shows what is coming41} from "@g1t/contracts";
Issues and pull requests replace intents and attempts42
43import type { Route } from "./+types/pull";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit44import { highlightFirstFiles } from "../../lib/highlight.server";
Social cards for every page: og.g1t.sh45import { 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-agent46import { openedBy } from "../../lib/opened-by";
Merge branch 'worktree-agent-aaf03bdceac799c89'47import { cloneUrl, useAddresses } from "../../lib/addresses";
Issues and pull requests replace intents and attempts48import { DiffView } from "../../components/diff-view";
Agents as a team: lifecycle, merge queue, billing and a new shell49import { LifecyclePanel } from "../../components/lifecycle";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains50import { AgentPanel } from "../../components/agents";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar51import { BaseBranch } from "../../components/base-branch";
52import { LabelChip, LabelsBox, MilestoneBox } from "../../components/labels";
Issues and pull requests replace intents and attempts53import { Markdown } from "../../components/markdown";
54import {
55 Avatar,
Agents as a team: lifecycle, merge queue, billing and a new shell56 ButtonLink,
Issues and pull requests replace intents and attempts57 CopyLine,
58 EmptyState,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look59 ComputeNote,
Issues and pull requests replace intents and attempts60 ErrorText,
Merge branch 'worktree-agent-a985c01f43538af01'61 SubmitButton,
Issues and pull requests replace intents and attempts62 Textarea,
63 TimeAgo,
Merge branch 'worktree-agent-a985c01f43538af01'64 usePending,
Issues and pull requests replace intents and attempts65} from "../../components/ui";
Workspace names and icons, and a component kit for every control66import { CheckboxOption } from "../../components/ui/checkbox";
Merge branch 'worktree-agent-ae1299e92e4462012'67import { Hint } from "../../components/ui/hint";
Merge branch 'worktree-agent-aca4790ea223aa235'68import { Loading, SkeletonLine } from "../../components/ui/skeleton";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit69import { TabStrip } from "../../components/ui/tab-strip";
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs70import { WorkflowStatuses } from "../../components/actions";
Fast pages, required checks on the branch, self-hosted runners, honest incidents71import { AddCiPrompt } from "../../components/add-ci";
Agents as a team: lifecycle, merge queue, billing and a new shell72import {
73 CommentForm,
74 CommentList,
75 IssueIcon,
76 PeoplePicker,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains77 PersonLink,
Agents as a team: lifecycle, merge queue, billing and a new shell78 PullState,
79 TimelineItem,
80 verdicts,
81} from "../../components/work";
Catching up with main takes seconds when the two sides touched different files82import { CatchUpProgress, ChecksSection, ConflictsSection, MergeabilityRow, runIdOf } from "../../components/merge-box";
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge83import { ViolationList } from "../../components/rules";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar84import { PullCodeOwnersPanel, TeamReviewer } from "../../components/codeowners";
Catching up with main takes seconds when the two sides touched different files85import { 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 put86import { notFound } from "../../lib/not-found.server";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look87import { computeNoteFor } from "../../lib/compute.server";
Merge branch 'worktree-agent-aca4790ea223aa235'88import { actions, agents, deployments, identity, inbox, projects, repos, work } from "../../lib/services.server";
Merge checks: statuses and check runs on every commit89import { commitChecksFor } from "../../lib/commit-checks.server";
90import { CommitChecksBadge } from "../../components/commit-checks";
Issues and pull requests replace intents and attempts91import { assertSameOrigin, getViewer, requireUser } from "../../lib/session.server";
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings92import { accessTo, refusal, repoFor } from "../../lib/access.server";
93import { SubscriptionBox } from "../../components/notifications";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending94import { REFRESH_MS, useRefreshWhile } from "../../lib/refresh";
Issues and pull requests replace intents and attempts95
96const EMPTY_COMPARISON: Comparison = { base: null, head: "", files: [], truncated: false };
97const TABS = ["conversation", "session", "changes"] as const;
98type Tab = (typeof TABS)[number];
99
Social cards for every page: og.g1t.sh100export function meta({ loaderData, params, ...args }: Route.MetaArgs) {
101 const pull = loaderData?.pull;
102 const title = pull ? `${pull.title} · Pull request #${pull.number} · ` : "";
103 const state = { draft: "Draft", open: "Open", merged: "Merged", closed: "Closed" }[pull?.status ?? "open"];
104 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-agent105 const opener = pull ? openedBy(pull) : null;
Social cards for every page: og.g1t.sh106 return page(args, {
107 title: `${title}${params.owner}/${params.repo} · g1t`,
108 description: pull
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent109 ? `${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.sh110 : null,
111 // The card shows the title and the state.
112 version: pull ? [pull.title, pull.status] : undefined,
113 type: "article",
114 });
Issues and pull requests replace intents and attempts115}
116
117export async function loader({ params, context, request }: Route.LoaderArgs) {
118 const viewer = getViewer(context);
119 const path = { namespace: params.owner, name: params.repo };
120 const number = Number(params.number);
121 const asked = new URL(request.url).searchParams.get("tab");
122 const tab: Tab = TABS.find((name) => name === asked) ?? "conversation";
123
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look124 // Members of the workspace pick reviewers and assignees from its people;
125 // what the viewer may do here goes by their role on the repository.
Agents as a team: lifecycle, merge queue, billing and a new shell126 const member = (viewer?.workspaces ?? []).some(
127 (membership) => membership.slug === params.owner,
128 );
Fast pages, required checks on the branch, self-hosted runners, honest incidents129 // Everything starts at once. Only what the viewer's role decides waits
130 // for the repository, which the layout is looking up in this request too
131 // (lib/access.server.ts), and only what the pull request decides waits
132 // for it.
Project dependencies: addresses, preview stacks, Affects, and agents who know133 const ref = { workspace: params.owner, slug: params.repo };
Fast pages, required checks on the branch, self-hosted runners, honest incidents134 const access = accessTo(context, params);
135 const pullFound = work.getPull(path, number, viewer);
136 const deps = projects.dependencies(params.owner, params.repo, viewer);
137 // Awaited below, unless the pull request is missing first.
138 deps.catch(() => null);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar139 // Its labels and milestone, with the repository's to choose from.
140 const labelsFound = work.listLabels(path, viewer);
141 const milestonesFound = work.listMilestones(path, viewer);
142 labelsFound.catch(() => null);
143 milestonesFound.catch(() => null);
144 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 incidents145 access,
146 pullFound,
Issues and pull requests replace intents and attempts147 repos.get(path, viewer),
Agents as a team: lifecycle, merge queue, billing and a new shell148 work.getSettings(path, viewer),
g1t's agents only for listed workspaces, whatever the state of billing149 env.RUNNER.enabled(viewer, path),
Agents as a team: lifecycle, merge queue, billing and a new shell150 // A member picks reviewers and assignees from the workspace's people.
151 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-agent152 // Before someone asks g1t for something: whether the plan lets it start.
Fast pages, required checks on the branch, self-hosted runners, honest incidents153 access.then(({ can }) => (can.run ? computeNoteFor(params.owner, "agent") : null)),
154 tab === "session" ? work.readSession(path, number, viewer) : null,
155 // Its preview, for people with a role here: beside the rest, not after.
156 access.then(({ insider }) => (insider ? deployments.list(ref, viewer).catch(() => null) : null)),
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings157 // Whether the viewer hears of it, for the sidebar's Notifications.
158 viewer
159 ? repoFor(context, params).then((found) =>
160 found.ok ? inbox.subscription(viewer, { repoId: found.value.id, number }).catch(() => null) : null,
161 )
162 : null,
Merge branch 'worktree-agent-aca4790ea223aa235'163 // Its agent's latest runs, for the Agent panel: with the page, not after it.
164 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 bar165 // A member asks the workspace's teams to review, and mentions them.
166 member ? identity.listTeams(viewer, params.owner).catch(() => null) : null,
Issues and pull requests replace intents and attempts167 ]);
168 if (!found.ok) {
169 // Issues and pull requests share numbers; this one may be an issue.
170 const issue = await work.getIssue(path, number, viewer);
171 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 put172 throw notFound("pull");
Issues and pull requests replace intents and attempts173 }
174 const { pull } = found.value;
Pull requests from branches175 const range = pullComparison(pull);
Agents and memory, checks and conflicts, profiles, slug renames, custom domains176 // The jobs of each workflow run on its head, to list checks job by job.
177 // 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 incidents178 // Streamed: the checks show by status first, then job by job.
Agents and memory, checks and conflicts, profiles, slug renames, custom domains179 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 incidents180 const workflowJobs =
181 tab === "conversation" && pull.status === "open" && runIds.length > 0
182 ? Promise.all(runIds.map((id) => actions.run(path, viewer, id).catch(() => null))).then((runs) => {
183 const jobs: Record<string, Job[]> = {};
184 for (const run of runs) if (run?.ok) jobs[run.value.run.id] = run.value.jobs;
185 return jobs;
186 })
187 .catch(() => ({}) as Record<string, Job[]>)
188 : Promise.resolve({} as Record<string, Job[]>);
189 // No checks at all on an open pull request: whether that is because the
190 // repository has no workflows, to offer a starter one.
191 const unchecked =
192 tab === "conversation" &&
193 pull.status === "open" &&
194 (found.value.statuses ?? []).length === 0 &&
195 (found.value.requiredChecks ?? []).length === 0;
196 const [comparison, used, noChecks] = await Promise.all([
Usage, Billing settings and prepaid AI credit; fixes from the UX audit197 // The first screens of it highlighted here, so their colours do not pop in.
198 tab === "changes"
199 ? repos
200 .compare(range.repoId, viewer, range.base, range.head, range.baseBranch)
201 .then(async (found) => (found.ok ? { ok: true as const, value: await highlightFirstFiles(found.value) } : found))
202 : null,
Fast pages, required checks on the branch, self-hosted runners, honest incidents203 deps,
204 unchecked
205 ? actions
206 .workflows(path, viewer)
207 .then((found) => found.ok && found.value.length === 0)
208 .catch(() => false)
209 : false,
Issues and pull requests replace intents and attempts210 ]);
Fast pages, required checks on the branch, self-hosted runners, honest incidents211 const affects = used.ok ? used.value.usedBy : [];
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar212 const [labels, milestones] = await Promise.all([labelsFound, milestonesFound]);
213 const repoDefault = repo.ok ? repo.value.defaultBranch : "main";
214 // The default branch's protection covers pull requests into it only.
215 const protectedBase = (pull.base ?? repoDefault) === repoDefault;
Issues and pull requests replace intents and attempts216 return {
217 ...found.value,
Merge checks: statuses and check runs on every commit218 // Every check on its head, as the commit's own list shows them: streamed.
219 headChecks: commitChecksFor(path, viewer, [pull.headCommit]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar220 labels: labels.ok ? labels.value : [],
221 milestones: milestones.ok ? milestones.value : [],
222 // Labelling and milestones: Triage and up; its author, its labels.
223 canTriage: can.triage,
Merge main (membership, two-factor, GitHub repo roles) into tokens224 // Making labels needs Write; applying them, Triage.
225 canMakeLabels: can.manage_labels,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar226 // The branch it merges into: Write and up.
227 canChangeBase: can.push,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains228 workflowJobs,
Issues and pull requests replace intents and attempts229 tab,
230 session: session?.ok ? session.value : [],
231 // An empty comparison if it could not be made.
232 comparison: comparison && (comparison.ok ? comparison.value : EMPTY_COMPARISON),
233 viewer,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look234 // Write and up can merge.
235 canMerge: can.merge,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights236 // Triage and up manage anyone's pull request; its author, their own,
237 // and whoever asked g1t for one, that one.
238 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 look239 // Telling its agent things, and re-running checks, spend compute: Write and up.
240 canRun: can.run,
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts241 // Maintain and up edit and delete anyone's comments; everyone, their own.
242 canModerate: can.manage_settings,
Agents as a team: lifecycle, merge queue, billing and a new shell243 // 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 rights244 // whoever it is for (who asked g1t for it, or its author), a branch
245 // from anyone who can push.
246 canUpdate: pull.fork ? viewer?.id === workOwner(pull).id : can.push,
Agents as a team: lifecycle, merge queue, billing and a new shell247 agentsEnabled,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look248 computeNote,
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings249 subscription,
Merge branch 'worktree-agent-aca4790ea223aa235'250 // As the project's agents.json has them; left out, the panel fetches them.
251 agentRuns: runs?.ok ? { runs: runs.value, member: can.run } : undefined,
Agents as a team: lifecycle, merge queue, billing and a new shell252 members: members?.ok ? members.value.map((person) => person.username) : [],
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar253 // `workspace/slug` and name of each team the viewer can see.
254 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 merge255 // What the rules of the branch it merges into ask, as they stack; the
256 // default branch's settings where the rules are not known.
257 requireUpToDate: found.value.rules ? found.value.rules.strict : protectedBase && settings.ok && settings.value.requireUpToDate,
258 mergeQueue: found.value.rules ? found.value.rules.merge_queue : protectedBase && settings.ok && settings.value.mergeQueue,
259 requiredApprovals: found.value.rules
260 ? found.value.rules.required_approvals
261 : protectedBase && settings.ok
262 ? settings.value.requiredApprovals
263 : 0,
264 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 incidents265 noChecks,
266 // Who may open the pull request that adds CI: anyone who can push.
267 canAddCi: can.push,
268 // Who may choose the required checks.
269 canProtect: can.manage_protection,
Issues and pull requests replace intents and attempts270 defaultBranch: repo.ok ? repo.value.defaultBranch : "main",
Fast pages, required checks on the branch, self-hosted runners, honest incidents271 affects,
272 // Streamed: the conversation shows first, the preview card after.
273 ...deploymentOf(deployed?.ok ? deployed.value : null, number, params.owner, affects, viewer),
Issues and pull requests replace intents and attempts274 };
275}
276
Pull requests say where they are live; the project menu shows what is coming277/**
278 * Where this pull request is live: its preview (or its latest build, while
279 * one is going or after one failed), and the previews of the projects
280 * that use this one, built against it.
281 */
Fast pages, required checks on the branch, self-hosted runners, honest incidents282function deploymentOf(
Pull requests say where they are live; the project menu shows what is coming283 list: { deployments: Deployment[]; live: LiveApp[] } | null,
284 number: number,
285 owner: string,
286 affects: { slug: string; name: string }[],
287 viewer: Viewer,
Fast pages, required checks on the branch, self-hosted runners, honest incidents288): { preview: LiveApp | null; build: Deployment | null; stacked: Promise<Stacked[]> } {
289 const preview = list?.live.find((app) => app.kind === "preview" && app.number === number) ?? null;
290 const build = list?.deployments.find((d) => d.kind === "preview" && d.number === number) ?? null;
Pull requests say where they are live; the project menu shows what is coming291 const branch = preview?.branch ?? build?.branch ?? null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents292 // Streamed: another lookup per project, shown in the card once known.
293 const stacked: Promise<Stacked[]> = branch
294 ? Promise.all(
295 affects.slice(0, 5).map(async (project) => {
296 const theirs = await deployments.list({ workspace: owner, slug: project.slug }, viewer).catch(() => null);
297 const app = theirs?.ok ? theirs.value.live.find((a) => a.kind === "preview" && a.branch === branch) : undefined;
298 return app ? { name: project.name, slug: project.slug, url: app.url } : null;
299 }),
300 ).then((found) => found.filter((entry) => entry != null))
301 : Promise.resolve([]);
Pull requests say where they are live; the project menu shows what is coming302 return { preview, build, stacked };
303}
304
Fast pages, required checks on the branch, self-hosted runners, honest incidents305/** A project that uses this one, with its preview built against this change. */
306type Stacked = { name: string; slug: string; url: string };
307
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look308const PULL_NEEDS: Record<string, Capability> = {
309 stack: "run",
310 "agent-review": "run",
311 "rerun-workflow": "run",
Fast pages, required checks on the branch, self-hosted runners, honest incidents312 "rerun-failed": "run",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look313 message: "run",
314 merge: "merge",
315 unqueue: "merge",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar316 milestone: "triage",
317 base: "push",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look318};
319
Issues and pull requests replace intents and attempts320export async function action({ request, params, context }: Route.ActionArgs) {
321 assertSameOrigin(request);
322 const user = requireUser(context, request);
323 const form = await request.formData();
324 const path = { namespace: params.owner, name: params.repo };
325 const number = Number(params.number);
326 const action = form.get("action");
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look327 // What each form needs beyond reading; work checks the rest.
328 const needed = typeof action === "string" ? PULL_NEEDS[action] : undefined;
329 const refused = needed ? await refusal(context, params, needed) : null;
330 if (refused) return { error: refused, action: String(action) };
Acceptance checks in sandboxes, line comments and review verdicts331 const verdict = form.get("verdict");
332 const line = Number(form.get("line"));
Agents as a team: lifecycle, merge queue, billing and a new shell333 /** Names ticked in a people picker, plus those typed beside it. */
334 const picked = (field: string) => [
335 ...form.getAll(field).map(String),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar336 // `@acme/backend` is asked as `acme/backend`.
337 ...String(form.get("others") ?? "").split(/[\s,]+/).map((name) => name.replace(/^@/, "")),
Agents as a team: lifecycle, merge queue, billing and a new shell338 ];
Project dependencies: addresses, preview stacks, Affects, and agents who know339 // The projects that use this one, built against this pull request's preview.
340 if (action === "stack") {
341 const built = await deployments.stack(user, { workspace: params.owner, slug: params.repo }, String(form.get("branch") ?? ""));
342 return built.ok
343 ? { action, notice: `Building ${built.value.join(", ")} against this preview. They appear on their Deployments pages.` }
344 : { action, error: built.error.message };
345 }
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent346 // Asking g1t for its review records the request, then starts it.
Agents as a team: lifecycle, merge queue, billing and a new shell347 if (action === "agent-review") {
348 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-agent349 reviewers: [...form.getAll("reviewer").map(String), "g1t"],
Agents as a team: lifecycle, merge queue, billing and a new shell350 });
351 if (!asked.ok) return { error: asked.error.message, action };
352 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar353 // Its labels, milestone and base, from the sidebar's menus and the header.
354 if (action === "labels") {
355 const set = await work.setLabels(user, path, number, form.getAll("label").map(String));
356 return set.ok ? null : { error: set.error.message, action };
357 }
358 if (action === "milestone" || action === "base") {
359 const changed = await work.updatePull(
360 user,
361 path,
362 number,
363 action === "milestone"
364 ? { milestone: Number(form.get("milestone")) || 0 }
365 : { base: String(form.get("base") ?? "") },
366 );
367 return changed.ok ? null : { error: changed.error.message, action };
368 }
Catching up with main takes seconds when the two sides touched different files369 // Catching up: merged and pushed in seconds when the two sides changed
370 // different files; otherwise handed to a sandbox, which takes a minute.
371 if (action === "update") {
372 const caught = await work.catchUpPull(user, path, number);
373 if (!caught.ok) return { action, error: caught.error.message };
374 const update = caught.value;
375 if (update.outcome !== "needs_agent") {
376 return {
377 action,
378 updated: { commit: update.commit, already: update.outcome === "up_to_date", at: Date.now() },
379 };
380 }
381 const started = await env.RUNNER.update(user, path, number);
382 if (!started.ok) return { action, error: started.error.message };
383 return { action, agent: { update, startedAt: Date.now() } };
384 }
Agents and memory, checks and conflicts, profiles, slug renames, custom domains385 // A workflow run's failed jobs, run again. Who may is the actions service's call.
386 if (action === "rerun-workflow") {
387 const rerun = await actions.rerun(user, path, String(form.get("run") ?? ""), true);
388 return rerun.ok ? null : { error: rerun.error.message, action };
389 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents390 // Every failed run on its head, run again: Mission control's quick action.
391 if (action === "rerun-failed") {
392 const found = await work.getPull(path, number, user);
393 if (!found.ok) return { error: found.error.message, action };
394 const failed = [
395 ...new Set(
396 (found.value.statuses ?? [])
397 .filter((status) => status.state === "failure" || status.state === "error")
398 .map(runIdOf)
399 .filter((id) => id != null),
400 ),
401 ];
402 if (failed.length === 0) return { error: "No workflow run failed on its latest commit.", action };
403 const reruns = await Promise.all(failed.map((id) => actions.rerun(user, path, id, true)));
404 const refused = reruns.find((rerun) => !rerun.ok);
405 return refused && !refused.ok ? { error: refused.error.message, action } : null;
406 }
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts407 // A comment's own text, changed or taken away: its author's to do, or a
408 // maintainer's, which work checks.
409 if (action === "edit-comment" || action === "delete-comment") {
410 const id = String(form.get("comment") ?? "");
411 const changed =
412 action === "edit-comment"
413 ? await work.editComment(user, path, id, String(form.get("body") ?? ""))
414 : await work.deleteComment(user, path, id);
415 return changed.ok ? null : { error: changed.error.message, action, comment: id };
416 }
Issues and pull requests replace intents and attempts417 const result =
418 action === "merge"
Acceptance checks in sandboxes, line comments and review verdicts419 ? await work.mergePull(user, path, number, {
420 keepIssueOpen: form.get("keepIssueOpen") === "on",
421 ignoreChecks: form.get("ignoreChecks") === "on",
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge422 bypassRules: form.get("bypassRules") === "on",
Acceptance checks in sandboxes, line comments and review verdicts423 })
Agents as a team: lifecycle, merge queue, billing and a new shell424 : action === "unqueue"
425 ? await work.removeFromQueue(user, path, number)
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request426 : action === "message"
427 ? await work.messageAgent(user, path, number, String(form.get("body") ?? ""))
Issues and pull requests replace intents and attempts428 : action === "close"
429 ? await work.closePull(user, path, number)
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts430 : action === "reopen"
431 ? await work.reopenPull(user, path, number)
432 : action === "draft"
433 ? await work.convertPullToDraft(user, path, number)
Fast pages, required checks on the branch, self-hosted runners, honest incidents434 : action === "agent-review"
Agents as a team: lifecycle, merge queue, billing and a new shell435 ? await env.RUNNER.review(user, path, number)
436 : action === "reviewers"
437 ? await work.updatePull(user, path, number, { reviewers: picked("reviewer") })
438 : action === "assign"
439 ? await work.updatePull(user, path, number, { assignees: picked("assignee") })
Acceptance checks in sandboxes, line comments and review verdicts440 : action === "comment"
441 ? await work.addComment(user, path, number, {
442 body: String(form.get("body") ?? ""),
443 path: String(form.get("path") ?? "") || undefined,
444 line: line > 0 ? line : undefined,
445 verdict:
446 verdict === "approve" || verdict === "request_changes" ? verdict : undefined,
447 })
448 : await work.readyPull(user, path, number, String(form.get("summary") ?? ""));
Issues and pull requests replace intents and attempts449 return result.ok ? null : { error: result.error.message, action };
450}
451
Pull requests: title and tabs across the full width, with counts and the size of the change452/** How many, beside a tab's name, when there are any. */
453function TabCount({ n }: { n: number }) {
454 if (n <= 0) return null;
455 return (
456 <span className="rounded-full bg-raised px-1.5 py-px text-xs tabular-nums text-muted ring-1 ring-line">
457 {n.toLocaleString("en-US")}
458 </span>
459 );
460}
461
Issues and pull requests replace intents and attempts462function TabLink({
463 to,
464 active,
465 children,
466}: {
467 to: string;
468 active: boolean;
469 children: React.ReactNode;
470}) {
471 return (
472 <Link
473 to={to}
474 preventScrollReset
Usage, Billing settings and prepaid AI credit; fixes from the UX audit475 aria-current={active ? "page" : undefined}
Issues and pull requests replace intents and attempts476 className={
Usage, Billing settings and prepaid AI credit; fixes from the UX audit477 "-mb-px flex items-center gap-2 border-b-2 px-3 pb-2.5 text-sm whitespace-nowrap transition-colors " +
Issues and pull requests replace intents and attempts478 (active
479 ? "border-accent font-medium text-fg"
480 : "border-transparent text-muted hover:text-fg")
481 }
482 >
483 {children}
484 </Link>
485 );
486}
487
488/** One step of the session, on the timeline's rail. */
489function Entry({ entry, agent }: { entry: SessionEntry; agent: string }) {
490 const isTool = entry.kind === "tool_call" || entry.kind === "tool_result";
491 const Icon =
492 entry.kind === "prompt"
493 ? User
494 : entry.kind === "note"
495 ? StickyNote
496 : isTool
497 ? Wrench
498 : Bot;
499 return (
500 <li className="relative pl-10">
501 <span
502 className={`absolute top-0.5 left-0 flex size-7 items-center justify-center rounded-full border bg-bg ${
503 entry.kind === "prompt"
504 ? "border-accent/50 text-accent"
505 : "border-line text-faint"
506 }`}
507 >
508 <Icon size={14} />
509 </span>
510 {isTool ? (
511 <details className="group rounded-lg border border-line bg-surface">
512 <summary className="flex cursor-pointer list-none items-center gap-2 px-3 py-1.5 text-sm">
513 <ChevronRight
514 size={14}
515 className="text-faint transition-transform group-open:rotate-90"
516 />
517 <span className="font-mono text-xs text-accent">
518 {entry.tool ?? "tool"}
519 </span>
520 <span className="truncate font-mono text-xs text-muted">
521 {entry.kind === "tool_result" ? "→ " : ""}
522 {entry.text.split("\n")[0]}
523 </span>
524 </summary>
525 <pre className="overflow-x-auto border-t border-line p-3 font-mono text-xs whitespace-pre-wrap text-muted">
Fix a hydration mismatch in output that starts with a blank line526 <code>{entry.text}</code>
Issues and pull requests replace intents and attempts527 </pre>
528 </details>
529 ) : (
530 <div>
531 <p className="text-xs font-medium text-faint">
532 {entry.kind === "prompt"
533 ? "Prompt"
534 : entry.kind === "note"
535 ? "Note"
536 : agent}
537 </p>
538 {entry.kind === "message" ? (
539 <div className="mt-1">
540 <Markdown source={entry.text} />
541 </div>
Agents as a team: lifecycle, merge queue, billing and a new shell542 ) : entry.kind === "prompt" ? (
543 <div className="mt-1.5 max-h-[32rem] overflow-y-auto rounded-xl border border-line bg-surface p-4">
544 <Markdown source={entry.text} />
545 </div>
Issues and pull requests replace intents and attempts546 ) : (
Agents as a team: lifecycle, merge queue, billing and a new shell547 <p className="mt-1 text-[0.9375rem] leading-relaxed wrap-break-word whitespace-pre-wrap">
Issues and pull requests replace intents and attempts548 {entry.text}
549 </p>
550 )}
551 </div>
552 )}
553 {entry.commit && (
554 <p className="mt-1.5 flex items-center gap-1 font-mono text-xs text-faint">
555 <GitCommitHorizontal size={12} />
556 {entry.commit.slice(0, 7)}
557 </p>
558 )}
559 </li>
560 );
561}
562
Agents as a team: lifecycle, merge queue, billing and a new shell563/** The box at the foot of the conversation saying what stands before a merge. */
564function StatusBox({ children }: { children: React.ReactNode }) {
565 return (
566 <div className="flex gap-3">
567 <span className="hidden w-8 shrink-0 sm:block" />
568 <section className="min-w-0 grow divide-y divide-line overflow-hidden rounded-xl border border-line-strong bg-surface">
569 {children}
570 </section>
571 </div>
572 );
573}
574
575function StatusRow({
576 icon,
577 title,
578 children,
579}: {
580 icon: React.ReactNode;
581 title: string;
582 children?: React.ReactNode;
583}) {
584 return (
585 <div className="flex gap-3 px-4 py-3 text-sm">
586 <span className="mt-0.5 shrink-0">{icon}</span>
587 <div className="min-w-0">
588 <p className="font-medium">{title}</p>
589 {children && <div className="mt-0.5 text-muted">{children}</div>}
590 </div>
591 </div>
592 );
593}
594
Issues and pull requests replace intents and attempts595export default function PullPage({ loaderData, actionData, params }: Route.ComponentProps) {
596 const {
597 pull,
598 issue,
599 comments,
Acceptance checks in sandboxes, line comments and review verdicts600 checks,
Agents as a team: lifecycle, merge queue, billing and a new shell601 overlaps,
602 behind,
603 reviewPending,
604 lifecycle,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request605 messages,
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs606 statuses = [],
Fast pages, required checks on the branch, self-hosted runners, honest incidents607 requiredChecks = [],
608 noChecks,
609 canAddCi,
610 canProtect,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains611 mergeable = "unknown",
612 conflicts = [],
613 earlierChecks = [],
614 workflowJobs,
Project dependencies: addresses, preview stacks, Affects, and agents who know615 affects,
616 preview,
Pull requests say where they are live; the project menu shows what is coming617 build,
618 stacked,
Agents as a team: lifecycle, merge queue, billing and a new shell619 landing,
620 stalled,
621 requireUpToDate,
622 mergeQueue,
623 requiredApprovals,
624 canIgnoreChecks,
625 canUpdate,
626 agentsEnabled,
Merge branch 'worktree-agent-aca4790ea223aa235'627 agentRuns,
Agents as a team: lifecycle, merge queue, billing and a new shell628 members,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar629 teams,
630 codeOwners,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge631 rules,
Issues and pull requests replace intents and attempts632 tab,
633 session,
634 comparison,
635 viewer,
636 canMerge,
637 canManage,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look638 canRun,
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts639 canModerate,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar640 defaultBranch: repoDefault,
Issues and pull requests replace intents and attempts641 } = loaderData;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar642 // The branch it merges into: what every line below names.
643 const defaultBranch = pull.base ?? repoDefault;
Issues and pull requests replace intents and attempts644 const base = `/${params.owner}/${params.repo}`;
645 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-agent646 // g1t, on a pull request it made: the person who asked for it is a line below.
647 const opener = openedBy(pull);
Merge branch 'worktree-agent-aca4790ea223aa235'648 const checksSection = (jobs: Record<string, Job[]>, loading = false) => (
Fast pages, required checks on the branch, self-hosted runners, honest incidents649 <ChecksSection
650 run={checks}
651 required={requiredChecks}
652 statuses={statuses}
653 jobs={jobs}
654 pull={pull}
655 base={base}
656 earlier={earlierChecks}
657 canRerunWorkflows={canMerge}
658 settingsUrl={canProtect ? `${base}/settings/branches` : null}
659 error={actionData?.action === "rerun-failed" || actionData?.action === "rerun-workflow" ? actionData.error : null}
Merge branch 'worktree-agent-aca4790ea223aa235'660 loading={loading}
Fast pages, required checks on the branch, self-hosted runners, honest incidents661 />
662 );
Merge branch 'worktree-agent-aaf03bdceac799c89'663 const addresses = useAddresses();
Pull requests from branches664 const remote = pull.fork
Merge branch 'worktree-agent-aaf03bdceac799c89'665 ? cloneUrl(addresses, `${pull.fork.namespace}/${pull.fork.name}`)
666 : cloneUrl(addresses, `${params.owner}/${params.repo}`);
Issues and pull requests replace intents and attempts667 const active = pull.status === "draft" || pull.status === "open";
668
Acceptance checks in sandboxes, line comments and review verdicts669 // Follow an agent at work, or checks in progress, without a manual reload.
Issues and pull requests replace intents and attempts670 const working = pull.status === "draft";
Fast pages, required checks on the branch, self-hosted runners, honest incidents671 const checking = statuses.some((status) => status.state === "pending");
Acceptance checks in sandboxes, line comments and review verdicts672 const reviews = verdicts(comments);
Fast pages, required checks on the branch, self-hosted runners, honest incidents673 // What stands between this pull request and a merge, if anything: a
674 // required check that has not passed, or the merge queue taking it out.
675 const unchecked = requiredChecks.some((check) => check.state !== "success") || checks?.status === "failed";
676 const requiredFailed = requiredChecks.some((check) => check.state === "failure") || checks?.status === "failed";
Agents as a team: lifecycle, merge queue, billing and a new shell677 // Pull requests for other issues changing the same files will conflict;
678 // ones for the same issue are alternatives, and expected to.
679 const collisions = overlaps.filter((other) => other.issue == null || other.issue !== pull.issue);
680 const review = {
681 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 rights682 // Nobody reviews their own pull request, nor one g1t made for them.
683 canJudge: active && viewer != null && viewer.id !== workOwner(pull).id,
Agents as a team: lifecycle, merge queue, billing and a new shell684 };
685 // Everyone whose review was asked for, then anyone who reviewed unasked.
686 const reviewerNames = [
687 ...new Set([...pull.reviewers, ...reviews.map(({ reviewer }) => reviewer)]),
688 ];
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar689 const teamReviewers = pull.teamReviewers ?? [];
690 // Teams to ask: the workspace's, and any asked already.
691 const teamChoices = [
692 ...teams,
693 ...teamReviewers.filter((ref) => !teams.some((team) => team.ref === ref)).map((ref) => ({ ref, name: ref })),
694 ];
695 // Branch protection wants code owners' approval it does not have yet.
696 const ownersMissing = codeOwners?.required && codeOwners.missing ? codeOwners.missing : null;
Catching up with main takes seconds when the two sides touched different files697 // A catch-up: the click shows at once; the answer says whether it is
698 // done already or a sandbox is on it.
Merge branch 'worktree-agent-a985c01f43538af01'699 const catchUpPending = usePending({ action: "update" });
Catching up with main takes seconds when the two sides touched different files700 const catchUp = actionData?.action === "update" ? actionData : null;
701 const caughtUp = catchUp && "updated" in catchUp ? catchUp.updated : null;
702 const agentCatchUp = catchUp && "agent" in catchUp ? catchUp.agent : null;
703 // Pushed already: followed until the pull request's head is the merge,
704 // so its checks show starting again, for a minute at most.
705 const settling =
706 caughtUp != null && !caughtUp.already && pull.headCommit !== caughtUp.commit && Date.now() - caughtUp.at < 60_000;
707 // Followed until it is pushed, or for so long, never longer.
708 const catchingUp =
709 agentCatchUp != null && behind && Date.now() - agentCatchUp.startedAt < CATCH_UP_TIMEOUT_MS + REFRESH_MS;
Agents as a team: lifecycle, merge queue, billing and a new shell710 // g1t is taking a step of its own accord, so the page will change.
711 const moving =
712 lifecycle != null && lifecycle.stage !== "ready" && lifecycle.stage !== "needs_you";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains713 // Whether it merges cleanly is being worked out, so the box will change.
714 const probing = active && mergeable === "checking";
715 const conflicting = active && mergeable === "conflicting";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending716 useRefreshWhile(working || checking || reviewPending || catchingUp || settling || moving || landing || probing);
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge717 // The rules of its base it does not meet: checks show in their own
718 // section, so the rest stop the button here. Someone a ruleset lets
719 // bypass it may tick a box and merge past them.
720 const [bypassing, setBypassing] = useState(false);
721 const rulesUnmet = (rules?.unmet ?? []).filter((violation) => violation.rule !== "required_status_checks");
722 const rulesBypassable = rules?.bypassable ?? [];
Agents and memory, checks and conflicts, profiles, slug renames, custom domains723 // Why the merge button cannot be pressed, if it cannot.
724 const mergeBlocked = conflicting
725 ? "Resolve the conflicts first."
726 : probing
727 ? "Waiting to find out whether it merges cleanly."
728 : behind && requireUpToDate
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge729 ? `The rules for ${defaultBranch} require it to be up to date first.`
Fast pages, required checks on the branch, self-hosted runners, honest incidents730 : unchecked && !canIgnoreChecks
731 ? `The checks ${defaultBranch} requires have to pass first.`
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge732 : rulesUnmet.length > 0
733 ? rulesUnmet[0]!.message
734 : rulesBypassable.length > 0 && !bypassing
735 ? `Rules for ${defaultBranch} are not met. You may bypass them.`
736 : ownersMissing
737 ? "Code owners have to approve first."
738 : null;
Issues and pull requests replace intents and attempts739
740 return (
Agents as a team: lifecycle, merge queue, billing and a new shell741 // 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 change742 <div>
743 {/* The title and the tabs run the full width; what is happening on
744 the pull request sits at the top of Conversation. */}
745 <header>
Issues and pull requests replace intents and attempts746 <h2 className="text-2xl font-semibold tracking-tight text-balance">
747 {pull.title} <span className="font-normal text-faint">#{pull.number}</span>
748 </h2>
749 <div className="mt-3 flex flex-wrap items-center gap-x-3 gap-y-2 text-sm text-muted">
750 <PullState status={pull.status} />
751 <span className="flex items-center gap-2">
Agents as a team: lifecycle, merge queue, billing and a new shell752 <Avatar
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent753 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Agents as a team: lifecycle, merge queue, billing and a new shell754 size={18}
755 />
Issues and pull requests replace intents and attempts756 <span>
Agents and memory, checks and conflicts, profiles, slug renames, custom domains757 <PersonLink
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent758 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Agents and memory, checks and conflicts, profiles, slug renames, custom domains759 className="font-medium text-fg hover:underline"
760 />{" "}
Pull requests from branches761 {pull.status === "merged" ? "merged" : "wants to merge"}
762 {pull.branch && (
763 <>
764 {" "}
765 <span className="font-mono text-fg">{pull.branch}</span>
766 </>
767 )}{" "}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar768 into{" "}
769 <BaseBranch
770 base={defaultBranch}
771 head={pull.fork ? null : pull.branch}
772 canChange={loaderData.canChangeBase && active}
773 branchesUrl={`${base}/branches.json`}
774 />
Issues and pull requests replace intents and attempts775 </span>
776 </span>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar777 {(pull.labels ?? []).map((name) => (
778 <LabelChip key={name} name={name} color={loaderData.labels.find((label) => label.name === name)?.color} />
779 ))}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent780 {opener.requestedBy && (
781 <span className="text-xs">
782 requested by{" "}
783 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
784 </span>
785 )}
786 {/* A pull request from a branch was made by its author, not an agent;
787 one g1t made already says so. */}
788 {!pull.branch && !opener.requestedBy && (
Pull requests from branches789 <span className="flex items-center gap-1.5 font-mono text-xs">
790 <Bot size={14} />
791 {pull.agent}
792 {pull.runtime === "hosted" && <span className="text-faint">on g1t</span>}
793 </span>
794 )}
Issues and pull requests replace intents and attempts795 </div>
796
Acceptance checks in sandboxes, line comments and review verdicts797 {reviews.length > 0 && (
798 <p className="mt-3 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm">
799 {reviews.map(({ reviewer, verdict }) => (
800 <span
801 key={reviewer}
802 className={`flex items-center gap-1.5 ${
Merge branch 'worktree-agent-ad7c6d88d93adc817'803 verdict === "approve" ? "text-success" : "text-danger"
Acceptance checks in sandboxes, line comments and review verdicts804 }`}
805 >
806 {verdict === "approve" ? <CircleCheck size={15} /> : <CircleSlash size={15} />}
807 {verdict === "approve" ? "Approved by" : "Changes requested by"}{" "}
808 <span className="font-medium">{reviewer}</span>
809 </span>
810 ))}
811 </p>
812 )}
813
Usage, Billing settings and prepaid AI credit; fixes from the UX audit814 <TabStrip label="Pull request" className="mt-6 items-end gap-1 border-b border-line">
Issues and pull requests replace intents and attempts815 <TabLink to={here} active={tab === "conversation"}>
816 <MessageSquare size={15} />
817 Conversation
Pull requests: title and tabs across the full width, with counts and the size of the change818 <TabCount n={comments.length} />
Issues and pull requests replace intents and attempts819 </TabLink>
820 <TabLink to={here + "?tab=session"} active={tab === "session"}>
821 <MessagesSquare size={15} />
822 Session
823 </TabLink>
824 <TabLink to={here + "?tab=changes"} active={tab === "changes"}>
825 <FileDiff size={15} />
Pull requests: title and tabs across the full width, with counts and the size of the change826 Files changed
827 <TabCount n={pull.files.length} />
Issues and pull requests replace intents and attempts828 </TabLink>
Pull requests: title and tabs across the full width, with counts and the size of the change829 {pull.files.length > 0 && (
830 <span className="ml-auto hidden pb-2.5 font-mono text-xs sm:inline">
Merge branch 'worktree-agent-ad7c6d88d93adc817'831 <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 change832 <span className="text-danger">−{pull.files.reduce((sum, file) => sum + file.deletions, 0).toLocaleString("en-US")}</span>
833 </span>
834 )}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit835 </TabStrip>
Pull requests: title and tabs across the full width, with counts and the size of the change836 </header>
837 <div className={`mt-6 grid gap-8 ${tab === "changes" ? "" : "lg:grid-cols-[1fr_19rem]"}`}>
838 <div className="min-w-0">
839 {/* What is happening on it now: its stage, its agent, what people told
840 the agent, and what else touches the same files. On the conversation,
841 so the tabs sit right under the title. */}
842 {tab === "conversation" && (
843 <div>
844 {issue && (
845 <Link
846 to={`${base}/issues/${issue.number}`}
847 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"
848 >
849 <IssueIcon issue={issue} />
850 <span className="text-muted">
851 {pull.status === "merged" && issue.resolvedBy === pull.number
852 ? "Resolved"
853 : "For issue"}
854 </span>
855 <span className="truncate font-medium">{issue.title}</span>
856 <span className="text-faint">#{issue.number}</span>
857 {issue.pullCount > 1 && (
858 <span className="ml-auto shrink-0 text-xs text-faint">
859 1 of {issue.pullCount} pull requests
Pull requests show their tabs right under the title; agent panels move into Conversation860 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change861 )}
862 </Link>
863 )}
864 {pull.status === "merged" && (
865 <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">
866 <GitMerge size={16} className="shrink-0 text-merged" />
Agents as a team: lifecycle, merge queue, billing and a new shell867 <span>
Pull requests: title and tabs across the full width, with counts and the size of the change868 Merged into <span className="font-mono">{defaultBranch}</span> by{" "}
869 <span className="font-medium">{pull.mergedBy}</span>{" "}
870 {/* Not an account: the repository's settings said to merge it. */}
871 {pull.mergedBy === "g1t" && "automatically, once it was ready, "}
872 {pull.mergedAt && <TimeAgo at={pull.mergedAt} />}
873 {pull.headCommit && (
874 <span className="font-mono text-muted"> · {pull.headCommit.slice(0, 7)}</span>
Agents as a team: lifecycle, merge queue, billing and a new shell875 )}
876 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change877 </p>
878 )}
879 {pull.supersededBy != null && (
880 <p className="mt-4 rounded-xl border border-line bg-surface px-4 py-2.5 text-sm text-muted">
881 Closed because{" "}
882 <Link
883 to={`${base}/pull/${pull.supersededBy}`}
884 className="font-medium text-fg hover:underline"
885 >
886 #{pull.supersededBy}
887 </Link>{" "}
888 was merged for this issue instead.
889 </p>
890 )}
891 {lifecycle && <LifecyclePanel lifecycle={lifecycle} />}
892 {/* 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 step893 <AgentPanel
894 owner={params.owner}
895 repo={params.repo}
896 number={pull.number}
897 stage={lifecycle?.stage ?? null}
898 confidence={pull.confidence ?? null}
Merge branch 'worktree-agent-aca4790ea223aa235'899 runs={agentRuns}
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step900 />
Pull requests: title and tabs across the full width, with counts and the size of the change901
902 {/* 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 look903 {canRun &&
Pull requests: title and tabs across the full width, with counts and the size of the change904 pull.runtime === "hosted" &&
905 (working || ["working", "revising", "catching_up", "answering"].includes(lifecycle?.stage ?? "")) && (
Merge branch 'worktree-agent-a985c01f43538af01'906 // Keyed by the messages so the box empties once one shows below.
Merge branch 'worktree-agent-ad7c6d88d93adc817'907 <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 change908 <p className="flex items-center gap-2 text-sm font-medium">
Merge branch 'worktree-agent-ad7c6d88d93adc817'909 <Sparkles size={15} className="text-accent" />
Pull requests: title and tabs across the full width, with counts and the size of the change910 Message the agent
911 </p>
912 <p className="mt-1 text-xs text-muted">
913 A correction, a hint, a change of plan. It reads it at its next step, without
914 starting over.
915 </p>
916 <div className="mt-3 flex gap-2">
917 <input type="hidden" name="action" value="message" />
918 <input
919 name="body"
920 required
921 autoComplete="off"
922 data-1p-ignore
923 placeholder="Keep the old flag working too…"
Merge branch 'worktree-agent-ad7c6d88d93adc817'924 className="h-9 min-w-0 grow rounded-md bg-bg px-3 text-sm ring-1 ring-line outline-none placeholder:text-faint focus:ring-accent/60"
Agents as a team: lifecycle, merge queue, billing and a new shell925 />
Merge branch 'worktree-agent-a985c01f43538af01'926 <SubmitButton match={{ action: "message" }} pending="Sending…">
927 Send
928 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change929 </div>
930 </Form>
Agents as a team: lifecycle, merge queue, billing and a new shell931 )}
Pull requests: title and tabs across the full width, with counts and the size of the change932 {messages.length > 0 && (
933 <ul className="mt-3 space-y-1.5">
934 {messages.map((message) => (
935 <li key={message.id} className="flex items-start gap-2 text-sm">
936 <Avatar name={message.author} size={18} />
937 <span className="min-w-0 grow">
938 <span className="font-medium">
939 {message.fromNumber != null ? `The agent on #${message.fromNumber}` : message.author}
940 </span>{" "}
941 <span className="text-muted">
942 {message.kind === "question"
943 ? "asked:"
944 : message.kind === "handoff"
945 ? "handed over:"
946 : message.kind === "answer"
947 ? "answered:"
948 : "to the agent:"}
949 </span>{" "}
950 {message.body}
951 {message.answer && (
Merge branch 'worktree-agent-ad7c6d88d93adc817'952 <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 change953 {message.declined ? "Declined: " : "Answer: "}
954 {message.answer}
955 </span>
956 )}
957 </span>
Merge branch 'worktree-agent-ad7c6d88d93adc817'958 <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 change959 {message.deliveredAt ? "read by the agent" : "waiting for its next step"}
960 </span>
961 </li>
962 ))}
963 </ul>
964 )}
965 {collisions.length > 0 && active && (
966 <div className="mt-4 rounded-xl border border-line bg-surface px-4 py-3 text-sm">
967 <p className="flex items-center gap-2.5 font-medium">
968 <Radar size={16} className="shrink-0 text-info" />
969 Other work is changing the same files
970 </p>
971 <ul className="mt-2 space-y-1.5">
972 {collisions.map((other) => (
973 <li key={other.number} className="flex flex-wrap items-baseline gap-x-2 text-muted">
974 <Link
975 to={`${base}/pull/${other.number}`}
976 className="font-medium text-fg hover:underline"
977 >
978 {other.title} <span className="font-normal text-faint">#{other.number}</span>
979 </Link>
980 <span className="font-mono text-xs">{other.paths.join(", ")}</span>
981 </li>
982 ))}
983 </ul>
984 <p className="mt-2 text-xs text-faint">
985 Whichever merges second will have to catch up, and may conflict.
986 </p>
987 </div>
988 )}
989 </div>
990 )}
991 <div className="mt-5">
992 {comparison ? (
993 <DiffView
994 comparison={comparison}
995 review={{
996 comments: comments.filter((comment) => comment.path),
997 canComment: Boolean(viewer),
998 }}
999 />
1000 ) : tab === "session" ? (
1001 session.length === 0 ? (
1002 <EmptyState title="Nothing recorded yet">
1003 The agent's prompts, reasoning and tool calls appear here as it
1004 works.
1005 </EmptyState>
1006 ) : (
1007 <ol className="relative space-y-5 before:absolute before:top-2 before:bottom-2 before:left-3.25 before:w-px before:bg-line">
1008 {session.map((entry) => (
1009 <Entry key={entry.seq} entry={entry} agent={pull.agent} />
1010 ))}
1011 </ol>
1012 )
1013 ) : (
1014 <div className="space-y-4">
1015 <TimelineItem
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1016 author={opener.name}
Pull requests: title and tabs across the full width, with counts and the size of the change1017 at={pull.createdAt}
1018 action={
1019 <span>
1020 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-agent1021 {opener.requestedBy ? (
Pull requests: title and tabs across the full width, with counts and the size of the change1022 <>
1023 {" "}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1024 for{" "}
1025 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
1026 </>
1027 ) : !pull.branch && (
1028 <>
1029 {" "}
Pull requests: title and tabs across the full width, with counts and the size of the change1030 with <span className="font-mono text-xs">{pull.agent}</span>
1031 </>
1032 )}
1033 </span>
1034 }
1035 >
1036 {pull.body ? (
1037 <Markdown source={pull.body} repo={{ namespace: params.owner, name: params.repo }} />
1038 ) : (
1039 <p className="text-sm text-muted">
1040 {working
1041 ? "No description yet. It is written when the pull request is marked ready for review."
1042 : "No description."}
1043 </p>
1044 )}
1045 {canManage && pull.status === "open" && (
1046 <details className="mt-3 border-t border-line pt-3 text-sm">
1047 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1048 Edit description
1049 </summary>
1050 <Form method="post" className="mt-3 space-y-2">
Merge branch 'worktree-agent-a985c01f43538af01'1051 <input type="hidden" name="action" value="describe" />
Pull requests: title and tabs across the full width, with counts and the size of the change1052 <Textarea
1053 name="summary"
1054 rows={6}
1055 placeholder="What changed and why"
1056 defaultValue={pull.body ?? ""}
1057 />
Merge branch 'worktree-agent-a985c01f43538af01'1058 <SubmitButton match={{ action: "describe" }} pending="Saving…">
1059 Save
1060 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1061 </Form>
1062 </details>
1063 )}
1064 </TimelineItem>
Agents as a team: lifecycle, merge queue, billing and a new shell1065
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1066 <CommentList
1067 comments={comments}
1068 review={review}
1069 base={base}
1070 viewerId={viewer?.id ?? null}
1071 canModerate={canModerate}
1072 failed={actionData && "comment" in actionData ? { comment: String(actionData.comment), error: actionData.error } : null}
1073 />
Agents as a team: lifecycle, merge queue, billing and a new shell1074
Merge branch 'worktree-agent-aca4790ea223aa235'1075 {(preview || build) && <DeploymentCard preview={preview} build={build} stacked={stacked} stacking={affects.length > 0} base={base} />}
Pull requests say where they are live; the project menu shows what is coming1076
Pull requests: title and tabs across the full width, with counts and the size of the change1077 {pull.status === "draft" && (
1078 <StatusBox>
1079 <StatusRow icon={<Loader size={16} className="text-faint" />} title="This is a draft">
1080 It is still being worked on. It can be reviewed and merged once it is
1081 marked ready.
1082 </StatusRow>
1083 {canManage && (
1084 <Form method="post" className="space-y-2 px-4 py-3">
Merge branch 'worktree-agent-a985c01f43538af01'1085 <input type="hidden" name="action" value="ready" />
Pull requests: title and tabs across the full width, with counts and the size of the change1086 <Textarea name="summary" rows={3} placeholder="What changed and why" />
Merge branch 'worktree-agent-a985c01f43538af01'1087 <SubmitButton match={{ action: "ready" }} pending="Marking ready…">
1088 Mark ready for review
1089 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1090 </Form>
1091 )}
1092 </StatusBox>
1093 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1094
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1095 {codeOwners && active && (
1096 <div className="flex gap-3">
1097 <span className="hidden w-8 shrink-0 sm:block" />
1098 <div className="min-w-0 grow">
1099 <PullCodeOwnersPanel
1100 owners={codeOwners}
1101 base={base}
1102 branch={defaultBranch}
1103 errorsHref={canProtect ? `${base}/settings/branches#codeowners` : `${base}/blob/${encodeURIComponent(defaultBranch)}/${codeOwners.path}`}
1104 />
1105 </div>
1106 </div>
1107 )}
1108
Pull requests: title and tabs across the full width, with counts and the size of the change1109 {pull.status === "open" && (
1110 <StatusBox>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1111 {/* Checks by status at once; job by job when the runs are read. */}
Merge branch 'worktree-agent-aca4790ea223aa235'1112 <Suspense fallback={checksSection({}, true)}>
1113 <Await resolve={workflowJobs}>{(jobs) => checksSection(jobs)}</Await>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1114 </Suspense>
1115 {noChecks && <AddCiPrompt owner={params.owner} repo={params.repo} canAdd={canAddCi} compact />}
Agents as a team: lifecycle, merge queue, billing and a new shell1116 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1117 icon={
1118 reviews.some(({ verdict }) => verdict === "request_changes") ? (
1119 <CircleSlash size={16} className="text-danger" />
1120 ) : reviews.length > 0 ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1121 <CircleCheck size={16} className="text-success" />
Pull requests: title and tabs across the full width, with counts and the size of the change1122 ) : (
1123 <MessageSquare size={16} className="text-faint" />
1124 )
1125 }
1126 title={
1127 reviews.length === 0
1128 ? "No reviews yet"
1129 : reviews
1130 .map(
1131 ({ reviewer, verdict }) =>
1132 `${verdict === "approve" ? "Approved by" : "Changes requested by"} ${reviewer}`,
1133 )
1134 .join(" · ")
1135 }
Agents as a team: lifecycle, merge queue, billing and a new shell1136 >
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1137 {reviewPending && "g1t is reviewing it now. "}
Pull requests: title and tabs across the full width, with counts and the size of the change1138 {requiredApprovals > 0 &&
1139 `This repository requires ${requiredApprovals} approving ${
1140 requiredApprovals === 1 ? "review" : "reviews"
1141 } before merging.`}
Agents as a team: lifecycle, merge queue, billing and a new shell1142 </StatusRow>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1143 {codeOwners?.required && (
1144 <StatusRow
1145 icon={
1146 ownersMissing ? (
1147 <CircleSlash size={16} className="text-warn" />
1148 ) : (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1149 <CircleCheck size={16} className="text-success" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1150 )
1151 }
1152 title={ownersMissing ? "Waiting for code owners" : "Code owners approved"}
1153 >
1154 {ownersMissing ?? `Every file it changes has its owners' approval.`}{" "}
1155 {ownersMissing && "This repository requires their review before merging."}
1156 </StatusRow>
1157 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1158 {landing ? (
Agents as a team: lifecycle, merge queue, billing and a new shell1159 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1160 icon={<Loader size={16} className="animate-spin text-accent" />}
1161 title="Merging"
1162 >
1163 {defaultBranch} has moved, so g1t is bringing this up to date first. It
1164 lands as soon as that is done. Watch it in the Session tab.
1165 </StatusRow>
Catching up with main takes seconds when the two sides touched different files1166 ) : agentCatchUp && behind ? (
1167 <CatchUpProgress
1168 owner={params.owner}
1169 repo={params.repo}
1170 number={pull.number}
1171 defaultBranch={defaultBranch}
1172 behind={behind}
1173 update={agentCatchUp.update}
1174 startedAt={agentCatchUp.startedAt}
1175 retrying={catchUpPending}
Merge branch 'worktree-agent-aca4790ea223aa235'1176 runs={agentRuns}
Catching up with main takes seconds when the two sides touched different files1177 />
Pull requests: title and tabs across the full width, with counts and the size of the change1178 ) : conflicting ? (
1179 <ConflictsSection
1180 conflicts={conflicts}
1181 pull={pull}
1182 owner={params.owner}
1183 repo={params.repo}
1184 defaultBranch={defaultBranch}
1185 changesUrl={here + "?tab=changes"}
1186 canResolve={canUpdate && agentsEnabled}
Catching up with main takes seconds when the two sides touched different files1187 resolving={catchUpPending}
1188 error={catchUp && "error" in catchUp ? catchUp.error : null}
Agents as a team: lifecycle, merge queue, billing and a new shell1189 />
Pull requests: title and tabs across the full width, with counts and the size of the change1190 ) : probing ? (
1191 <MergeabilityRow mergeable={mergeable} defaultBranch={defaultBranch} />
1192 ) : behind ? (
1193 <StatusRow
1194 icon={<GitPullRequestArrow size={16} className="text-info" />}
1195 title={`${defaultBranch} has moved since this was made`}
1196 >
1197 {mergeable === "clean" && "It has no conflicts with it. "}
1198 {requireUpToDate
1199 ? "This repository requires pull requests to be up to date, so it has to catch up before it can merge."
1200 : "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 files1201 {canUpdate && (
1202 <Form method="post" className="mt-2">
Pull requests: title and tabs across the full width, with counts and the size of the change1203 <input type="hidden" name="action" value="update" />
Merge branch 'worktree-agent-a985c01f43538af01'1204 <SubmitButton variant="quiet" match={{ action: "update" }} pending={`Merging ${defaultBranch} in…`}>
1205 Catch up with {defaultBranch} now
1206 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1207 </Form>
1208 )}
Catching up with main takes seconds when the two sides touched different files1209 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Pull requests: title and tabs across the full width, with counts and the size of the change1210 </StatusRow>
1211 ) : (
Merge branch 'worktree-agent-af9f0f386d52b6a73'1212 (pull.headCommit || (catchUp && "error" in catchUp)) && (
Pull requests: title and tabs across the full width, with counts and the size of the change1213 <StatusRow
Merge branch 'worktree-agent-ad7c6d88d93adc817'1214 icon={<CircleCheck size={16} className="text-success" />}
Catching up with main takes seconds when the two sides touched different files1215 title={
1216 (caughtUp && !caughtUp.already) || agentCatchUp
1217 ? `Brought up to date with ${defaultBranch}`
1218 : `Up to date with ${defaultBranch}`
1219 }
1220 >
1221 {caughtUp && !caughtUp.already && (
1222 <>
1223 Merged <span className="font-mono">{defaultBranch}</span> in as{" "}
1224 <span className="font-mono text-fg">{caughtUp.commit.slice(0, 7)}</span>. Its checks run
1225 again on the new commit.
1226 </>
1227 )}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1228 {agentCatchUp && "g1t merged it in and pushed the result. Its checks run again on the new commit."}
Merge branch 'worktree-agent-af9f0f386d52b6a73'1229 {/* A catch-up refused or failed after the pull request stopped being behind. */}
1230 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Catching up with main takes seconds when the two sides touched different files1231 </StatusRow>
Pull requests: title and tabs across the full width, with counts and the size of the change1232 )
1233 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1234 {active && rules && (rulesUnmet.length > 0 || rulesBypassable.length > 0 || rules.evaluate.length > 0) && (
1235 <StatusRow
1236 icon={
1237 <Scale
1238 size={16}
1239 className={rulesUnmet.length > 0 ? "text-danger" : rulesBypassable.length > 0 ? "text-warn" : "text-info"}
1240 />
1241 }
1242 title={
1243 rulesUnmet.length > 0
1244 ? `Rules for ${defaultBranch} are not met yet`
1245 : rulesBypassable.length > 0
1246 ? `You may bypass the rules for ${defaultBranch}`
1247 : "Rulesets being evaluated would refuse this merge"
1248 }
1249 >
1250 <div className="mt-2 space-y-3">
1251 {rulesUnmet.length > 0 && <ViolationList violations={rulesUnmet} />}
1252 {rulesBypassable.length > 0 && (
1253 <div>
1254 <p className="mb-1.5 text-xs text-muted">Not met, but a ruleset lets you bypass it:</p>
1255 <ViolationList violations={rulesBypassable} tone="warn" />
1256 </div>
1257 )}
1258 {rules.evaluate.length > 0 && (
1259 <div>
1260 <p className="mb-1.5 text-xs text-muted">Rulesets in evaluate would refuse it for this; nothing is held up:</p>
1261 <ViolationList violations={rules.evaluate} tone="info" />
1262 </div>
1263 )}
1264 {canProtect && (
1265 <Link
1266 to={`${base}/settings/rules?branch=${encodeURIComponent(defaultBranch)}`}
1267 className="inline-block text-xs text-muted underline underline-offset-2 hover:text-fg"
1268 >
1269 See every rule for {defaultBranch}
1270 </Link>
1271 )}
1272 </div>
1273 </StatusRow>
1274 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1275 {stalled && !lifecycle && (
1276 <StatusRow icon={<Hand size={16} className="text-warn" />} title="Needs you">
1277 {stalled}
1278 </StatusRow>
1279 )}
1280 {canMerge && lifecycle?.stage === "queued" && (
1281 <Form method="post" className="flex flex-wrap items-center gap-3 px-4 py-3">
1282 <ButtonLink to={`${base}/queue`}>
1283 <Layers size={15} />
1284 See the queue
1285 </ButtonLink>
Merge branch 'worktree-agent-a985c01f43538af01'1286 <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 change1287 Remove from the queue
Merge branch 'worktree-agent-a985c01f43538af01'1288 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1289 </Form>
1290 )}
1291 {canMerge && !landing && lifecycle?.stage !== "queued" && (
1292 <Form method="post" className="space-y-3 px-4 py-3">
1293 {issue?.state === "open" && (
1294 <CheckboxOption
1295 name="keepIssueOpen"
1296 label={`Keep #${issue.number} open. This is only part of the work.`}
1297 labelClassName="text-xs text-muted"
1298 />
1299 )}
1300 {unchecked && canIgnoreChecks && (
1301 <CheckboxOption
1302 name="ignoreChecks"
Fast pages, required checks on the branch, self-hosted runners, honest incidents1303 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 change1304 labelClassName="text-xs text-muted"
1305 />
1306 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1307 {rulesBypassable.length > 0 && rulesUnmet.length === 0 && (
1308 <CheckboxOption
1309 name="bypassRules"
1310 checked={bypassing}
1311 onCheckedChange={(checked) => setBypassing(checked === true)}
1312 label={`Bypass the rules for ${defaultBranch}: merge although they are not met. It is recorded.`}
1313 labelClassName="text-xs text-muted"
1314 />
1315 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1316 <div className="flex flex-wrap items-center gap-3">
Merge branch 'worktree-agent-ae1299e92e4462012'1317 <Hint label={mergeBlocked} disabled={mergeBlocked != null}>
1318 <SubmitButton
1319 variant="accent"
1320 name="action"
1321 value="merge"
1322 disabled={mergeBlocked != null}
1323 pending={mergeQueue ? "Adding to the queue…" : "Merging…"}
1324 >
1325 {mergeQueue ? <Layers size={15} /> : <GitMerge size={15} />}
1326 {mergeQueue ? "Add to the merge queue" : `Merge into ${defaultBranch}`}
1327 </SubmitButton>
1328 </Hint>
Pull requests: title and tabs across the full width, with counts and the size of the change1329 <span className={`text-xs ${mergeBlocked ? "text-danger" : "text-muted"}`}>
1330 {mergeBlocked ?? (mergeQueue
1331 ? `Tested together with everything ahead of it, then lands on ${defaultBranch}.`
1332 : issue?.state === "open"
1333 ? `Closes issue #${issue.number}, and any other pull requests still open for it.`
1334 : `Lands its commits on ${defaultBranch}.`)}
1335 </span>
1336 </div>
1337 {actionData?.action === "merge" && (
1338 <ErrorText>{actionData.error}</ErrorText>
1339 )}
1340 </Form>
1341 )}
1342 </StatusBox>
1343 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1344
Pull requests: title and tabs across the full width, with counts and the size of the change1345 <CommentForm
1346 author={viewer?.username ?? null}
1347 resetKey={comments.length}
1348 review={review}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1349 handles={teams.map((team) => `@${team.ref}`)}
Pull requests: title and tabs across the full width, with counts and the size of the change1350 />
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1351 {canManage && (active || pull.status === "closed") && (
1352 <Form method="post" className="flex flex-wrap justify-end gap-2">
1353 {pull.status === "open" && (
1354 <SubmitButton variant="quiet" name="action" value="draft" pending="Converting…">
1355 Convert to draft
1356 </SubmitButton>
1357 )}
1358 {active ? (
1359 <SubmitButton variant="quiet" name="action" value="close" pending="Closing…">
1360 Close pull request
1361 </SubmitButton>
1362 ) : (
1363 <SubmitButton variant="quiet" name="action" value="reopen" pending="Reopening…">
1364 Reopen pull request
1365 </SubmitButton>
1366 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1367 </Form>
1368 )}
1369 {actionData &&
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1370 !["merge", "comment", "stack", "rerun-failed", "rerun-workflow", "update", "agent-review", "reviewers", "assign", "edit-comment", "delete-comment"].includes(
Pull requests: title and tabs across the full width, with counts and the size of the change1371 String(actionData.action),
1372 ) && <ErrorText>{actionData.error}</ErrorText>}
1373 </div>
1374 )}
1375 {actionData?.action === "comment" && (
1376 <div className="mt-2">
1377 <ErrorText>{actionData.error}</ErrorText>
1378 </div>
1379 )}
1380 </div>
1381 </div>
1382
1383 <aside className={tab === "changes" ? "hidden" : "space-y-6"}>
1384 {/* An open pull request shows its checks in full in the merge box. */}
Fast pages, required checks on the branch, self-hosted runners, honest incidents1385 {pull.status !== "open" && <WorkflowStatuses statuses={statuses} />}
Pull requests: title and tabs across the full width, with counts and the size of the change1386 {affects.length > 0 && (
1387 <section>
1388 <h3 className="flex items-center gap-1.5 text-sm font-medium">
1389 <Network size={14} className="text-faint" />
1390 Affects
1391 </h3>
1392 <p className="mt-1 text-xs text-muted">Projects that use this one, and so may feel this change:</p>
1393 <ul className="mt-2 space-y-1 text-sm">
1394 {affects.map((project) => (
1395 <li key={project.slug} className="flex items-center justify-between gap-2">
1396 <Link to={`/${params.owner}/${project.slug}`} className="hover:underline">
1397 {project.name}
1398 </Link>
1399 {project.as && <code className="font-mono text-xs text-faint">{project.as}</code>}
1400 </li>
1401 ))}
1402 </ul>
1403 {preview?.branch && canMerge && (
1404 <Form method="post" className="mt-3">
1405 <input type="hidden" name="action" value="stack" />
1406 <input type="hidden" name="branch" value={preview.branch} />
Merge branch 'worktree-agent-a985c01f43538af01'1407 <SubmitButton
1408 match={{ action: "stack" }}
1409 pending="Starting the builds…"
1410 className="flex w-full items-center justify-center gap-1.5 rounded-md border border-line px-3 py-1.5 text-xs text-muted transition-colors hover:border-line-strong hover:text-fg disabled:opacity-50"
Pull requests: title and tabs across the full width, with counts and the size of the change1411 >
1412 Preview them against this change
Merge branch 'worktree-agent-a985c01f43538af01'1413 </SubmitButton>
Agents as a team: lifecycle, merge queue, billing and a new shell1414 </Form>
1415 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1416 {actionData?.action === "stack" &&
1417 ("notice" in actionData ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1418 <p className="mt-2 text-xs text-success">{String(actionData.notice)}</p>
Pull requests: title and tabs across the full width, with counts and the size of the change1419 ) : (
1420 <ErrorText>{actionData.error}</ErrorText>
1421 ))}
1422 </section>
Acceptance checks in sandboxes, line comments and review verdicts1423 )}
Issues and pull requests replace intents and attempts1424
Project dependencies: addresses, preview stacks, Affects, and agents who know1425 <section>
Pull requests: title and tabs across the full width, with counts and the size of the change1426 <h3 className="text-sm font-medium">Reviewers</h3>
1427 <ul className="mt-2 space-y-1.5 text-sm">
1428 {reviewerNames.map((name) => {
1429 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-agent1430 const pending = name === "g1t" && reviewPending;
Pull requests: title and tabs across the full width, with counts and the size of the change1431 return (
1432 <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-agent1433 {name === "g1t" ? (
Pull requests: title and tabs across the full width, with counts and the size of the change1434 <Sparkles size={16} className="shrink-0 text-accent" />
1435 ) : (
1436 <Avatar name={name} size={20} />
1437 )}
Commits show their accounts, and every name opens a card (repos 0016)1438 <span className="grow truncate font-mono text-xs">
1439 <PersonLink name={name} className="hover:underline" />
1440 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change1441 {pending ? (
1442 <span className="flex items-center gap-1.5 text-xs text-muted">
1443 <span className="size-1.5 animate-pulse rounded-full bg-accent" />
1444 Reviewing
1445 </span>
1446 ) : verdict === "approve" ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1447 <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 change1448 <CircleCheck size={13} /> Approved
1449 </span>
1450 ) : verdict === "request_changes" ? (
1451 <span className="flex items-center gap-1 text-xs text-danger">
1452 <CircleSlash size={13} /> Changes requested
1453 </span>
1454 ) : (
1455 <span className="text-xs text-faint">Review requested</span>
1456 )}
1457 </li>
1458 );
1459 })}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1460 {teamReviewers.map((team) => (
1461 <TeamReviewer key={team} team={team} />
1462 ))}
1463 {reviewerNames.length === 0 && teamReviewers.length === 0 && (
Pull requests: title and tabs across the full width, with counts and the size of the change1464 <li className="px-1 text-xs text-faint">No reviews requested yet.</li>
1465 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1466 </ul>
Pull requests: title and tabs across the full width, with counts and the size of the change1467 {pull.status === "open" && canManage && (
1468 <div className="mt-3 space-y-2">
1469 {agentsEnabled && !reviewPending && (
1470 <Form method="post">
1471 <input type="hidden" name="action" value="agent-review" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1472 {[...pull.reviewers, ...teamReviewers].map((name) => (
Pull requests: title and tabs across the full width, with counts and the size of the change1473 <input key={name} type="hidden" name="reviewer" value={name} />
1474 ))}
1475 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1476 <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 change1477 <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-agent1478 Request review from g1t
Merge branch 'worktree-agent-a985c01f43538af01'1479 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1480 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1481 <ComputeNote note={loaderData.computeNote} />
Pull requests: title and tabs across the full width, with counts and the size of the change1482 </Form>
1483 )}
1484 <details>
1485 <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 bar1486 {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 change1487 </summary>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1488 <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 change1489 <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-agent1490 {pull.reviewers.includes("g1t") && (
1491 <input type="hidden" name="reviewer" value="g1t" />
Pull requests: title and tabs across the full width, with counts and the size of the change1492 )}
1493 <PeoplePicker
1494 name="reviewer"
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1495 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-agent1496 chosen={pull.reviewers.filter((name) => name !== "g1t")}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1497 placeholder="Other usernames or teams (acme/backend)"
Pull requests: title and tabs across the full width, with counts and the size of the change1498 />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1499 {teamChoices.length > 0 && (
1500 <div className="space-y-1.5 border-t border-line pt-2">
1501 <p className="text-xs text-faint">Teams</p>
1502 {teamChoices.map((team) => (
1503 <CheckboxOption
1504 key={team.ref}
1505 name="reviewer"
1506 value={team.ref}
1507 defaultChecked={teamReviewers.includes(team.ref)}
1508 className="items-center"
1509 labelClassName="flex min-w-0 items-center gap-2"
1510 label={
1511 <>
1512 <Users size={14} className="shrink-0 text-faint" />
1513 <span className="truncate font-mono text-xs">@{team.ref}</span>
1514 </>
1515 }
1516 />
1517 ))}
1518 </div>
1519 )}
Merge branch 'worktree-agent-a985c01f43538af01'1520 <SubmitButton variant="quiet" match={{ action: "reviewers" }} pending="Saving…">
Pull requests: title and tabs across the full width, with counts and the size of the change1521 Save reviewers
Merge branch 'worktree-agent-a985c01f43538af01'1522 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1523 </Form>
1524 </details>
1525 </div>
Project dependencies: addresses, preview stacks, Affects, and agents who know1526 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1527 {["agent-review", "reviewers"].includes(String(actionData?.action)) && (
1528 <ErrorText>{actionData?.error}</ErrorText>
1529 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1530 </section>
Acceptance checks in sandboxes, line comments and review verdicts1531
Pull requests: title and tabs across the full width, with counts and the size of the change1532 <section>
1533 <h3 className="text-sm font-medium">Assignees</h3>
1534 <ul className="mt-2 space-y-1.5 text-sm">
1535 {pull.assignees.map((name) => (
Agents as a team: lifecycle, merge queue, billing and a new shell1536 <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 change1537 <Avatar name={name} size={20} />
Commits show their accounts, and every name opens a card (repos 0016)1538 <span className="grow truncate font-mono text-xs">
1539 <PersonLink name={name} className="hover:underline" />
1540 </span>
Agents as a team: lifecycle, merge queue, billing and a new shell1541 </li>
Pull requests: title and tabs across the full width, with counts and the size of the change1542 ))}
1543 {pull.assignees.length === 0 && (
1544 <li className="px-1 text-xs text-faint">No one yet.</li>
1545 )}
1546 </ul>
1547 {viewer && canManage && active && (
1548 <div className="mt-3 space-y-2">
1549 {!pull.assignees.includes(viewer.username) && (
1550 <Form method="post">
1551 <input type="hidden" name="action" value="assign" />
1552 {pull.assignees.map((name) => (
1553 <input key={name} type="hidden" name="assignee" value={name} />
1554 ))}
1555 <input type="hidden" name="assignee" value={viewer.username} />
1556 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1557 <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 change1558 Assign yourself
Merge branch 'worktree-agent-a985c01f43538af01'1559 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1560 </div>
1561 </Form>
1562 )}
1563 <details>
1564 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1565 Assign people
1566 </summary>
1567 <Form method="post" className="mt-2 space-y-2" key={pull.assignees.join()}>
1568 <input type="hidden" name="action" value="assign" />
1569 <PeoplePicker name="assignee" members={members} chosen={pull.assignees} />
Merge branch 'worktree-agent-a985c01f43538af01'1570 <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 change1571 Save assignees
Merge branch 'worktree-agent-a985c01f43538af01'1572 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1573 </Form>
1574 </details>
1575 </div>
Issues and pull requests replace intents and attempts1576 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1577 {actionData?.action === "assign" && <ErrorText>{actionData.error}</ErrorText>}
1578 </section>
Issues and pull requests replace intents and attempts1579
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1580 <LabelsBox
1581 labels={loaderData.labels}
1582 chosen={pull.labels ?? []}
1583 canEdit={canManage && active}
Merge main (membership, two-factor, GitHub repo roles) into tokens1584 canCreate={loaderData.canMakeLabels}
1585 manageUrl={loaderData.canMakeLabels ? `${base}/labels` : undefined}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1586 />
1587 <MilestoneBox
1588 milestones={loaderData.milestones}
1589 current={pull.milestone}
1590 canEdit={loaderData.canTriage}
1591 base={base}
1592 />
1593
Pull requests: title and tabs across the full width, with counts and the size of the change1594 <section>
1595 <h3 className="text-sm font-medium">Working copy</h3>
1596 {pull.branch ? (
1597 <>
1598 <p className="mt-1 text-xs text-muted">
1599 <GitBranch size={13} className="mr-1 inline align-[-2px]" />
1600 Branch <span className="font-mono text-fg">{pull.branch}</span> of this
1601 repository. Pushes to it show up here.
1602 </p>
1603 <div className="mt-2">
1604 <CopyLine text={`git clone -b ${pull.branch} ${remote}`} />
1605 </div>
1606 </>
1607 ) : (
1608 <>
1609 <p className="mt-1 text-xs text-muted">
1610 This fork belongs to the pull request. Pushes to it show up here.
1611 </p>
1612 <div className="mt-2">
1613 <CopyLine text={`git clone ${remote}`} />
1614 </div>
1615 </>
1616 )}
1617 <p className="mt-3 flex items-center gap-1.5 font-mono text-xs text-faint">
1618 <GitCommitHorizontal size={13} />
Merge checks: statuses and check runs on every commit1619 {pull.headCommit ? (
1620 <Link to={`${base}/commit/${pull.headCommit}`} className="hover:text-fg">
1621 {pull.headCommit.slice(0, 12)}
1622 </Link>
1623 ) : (
1624 "no commits pushed yet"
1625 )}
1626 {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 change1627 </p>
1628 </section>
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings1629 {loaderData.viewer && (
1630 <SubscriptionBox action={`${base}/notifications`} number={pull.number} kind="pull" subscription={loaderData.subscription} />
1631 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1632 </aside>
1633 </div>
Issues and pull requests replace intents and attempts1634 </div>
1635 );
1636}
Pull requests say where they are live; the project menu shows what is coming1637
1638/** Where the pull request's change is live, as GitHub shows a deployment. */
1639function DeploymentCard({
1640 preview,
1641 build,
1642 stacked,
Merge branch 'worktree-agent-aca4790ea223aa235'1643 stacking,
Pull requests say where they are live; the project menu shows what is coming1644 base,
1645}: {
1646 preview: LiveApp | null;
1647 build: Deployment | null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents1648 stacked: Promise<Stacked[]>;
Merge branch 'worktree-agent-aca4790ea223aa235'1649 /** Projects use this one, so previews built against it may be on their way. */
1650 stacking: boolean;
Pull requests say where they are live; the project menu shows what is coming1651 base: string;
1652}) {
1653 const building = build?.status === "queued" || build?.status === "building";
1654 const failed = build?.status === "failed";
1655 return (
1656 <section className="ml-12 overflow-hidden rounded-xl border border-line bg-surface">
1657 <div className="flex flex-wrap items-center gap-3 px-4 py-3">
1658 <span
1659 className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ${
Merge branch 'worktree-agent-ad7c6d88d93adc817'1660 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 coming1661 }`}
1662 >
1663 {building ? <Loader size={15} className="animate-spin" /> : <Rocket size={15} />}
1664 </span>
1665 <div className="min-w-0 grow">
1666 <p className="text-sm font-medium">
1667 {building
1668 ? preview
1669 ? "Deploying the latest push. The last preview is still live."
1670 : "Deploying a preview"
1671 : failed
1672 ? "The preview failed to deploy"
1673 : "This branch is live"}
1674 </p>
1675 {preview ? (
1676 <a href={preview.url} className="mt-0.5 block truncate font-mono text-xs text-muted hover:text-accent">
1677 {preview.url.replace(/^https?:[/][/]/, "")}
1678 </a>
1679 ) : (
1680 build?.error && <p className="mt-0.5 truncate text-xs text-muted">{build.error}</p>
1681 )}
1682 </div>
1683 <div className="flex shrink-0 items-center gap-2">
1684 {build && (
1685 <Link to={`${base}/deployments/${build.id}`} className="text-xs text-muted hover:text-fg">
1686 {failed ? "See why" : "Build log"}
1687 </Link>
1688 )}
1689 {preview && (
1690 <a
1691 href={preview.url}
1692 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"
1693 >
1694 Visit preview
1695 <ArrowUpRight size={13} />
1696 </a>
1697 )}
1698 </div>
1699 </div>
1700 {preview && (
1701 <p className="border-t border-line px-4 py-2 text-xs text-faint">
1702 <span className="font-mono">{preview.commit.slice(0, 7)}</span> · deployed <TimeAgo at={preview.deployedAt} />
1703 {preview.branch && ` · from ${preview.branch}`}
1704 </p>
1705 )}
Fast pages, required checks on the branch, self-hosted runners, honest incidents1706 {/* Read after the page: the card's own part shows at once. */}
Merge branch 'worktree-agent-aca4790ea223aa235'1707 <Suspense
1708 fallback={
1709 stacking && (
1710 <Loading className="border-t border-line px-4 py-2.5 text-xs">
1711 <SkeletonLine className="w-40" />
1712 <SkeletonLine className="mt-1.5 w-64 max-w-full" />
1713 </Loading>
1714 )
1715 }
1716 >
Fast pages, required checks on the branch, self-hosted runners, honest incidents1717 <Await resolve={stacked}>
1718 {(stacked) =>
1719 stacked.length > 0 && (
Merge branch 'worktree-agent-aca4790ea223aa235'1720 <div className="animate-fade-in border-t border-line px-4 py-2.5">
Fast pages, required checks on the branch, self-hosted runners, honest incidents1721 <p className="text-xs text-muted">Built against this change:</p>
1722 <ul className="mt-1.5 space-y-1">
1723 {stacked.map((entry) => (
1724 <li key={entry.slug} className="flex items-center gap-2 text-xs">
1725 <span className="font-medium">{entry.name}</span>
1726 <a href={entry.url} className="truncate font-mono text-muted hover:text-accent">
1727 {entry.url.replace(/^https?:[/][/]/, "")}
1728 </a>
1729 </li>
1730 ))}
1731 </ul>
1732 </div>
1733 )
1734 }
1735 </Await>
1736 </Suspense>
Pull requests say where they are live; the project menu shows what is coming1737 </section>
1738 );
1739}

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