Skip to content

g1t/apps/web/app/routes/repo/pulls.tsx

228 lines9,909 bytesCodeBlame

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.

Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1import { ArrowLeft, Bot, GitBranch, Milestone as MilestoneIcon, Plus, Tag } from "lucide-react";
2import { Link, useLocation } from "react-router";
Issues and pull requests replace intents and attempts3
4import type { Route } from "./+types/pulls";
Social cards for every page: og.g1t.sh5import { page } from "../../lib/meta";
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights6import { openedBy } from "../../lib/opened-by";
Pull requests from branches7import { ButtonLink, EmptyState, TimeAgo } from "../../components/ui";
Merge branch 'worktree-agent-ae1299e92e4462012'8import { Hint } from "../../components/ui/hint";
Acceptance checks in sandboxes, line comments and review verdicts9import { CheckBadge } from "../../components/checks";
Agents as a team: lifecycle, merge queue, billing and a new shell10import { ChangeSize, PullIcon, StateTabs } from "../../components/work";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains11import { AgentBadge, useActiveRuns } from "../../components/agents";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar12import { FilterMenu, LabelChip, Swatch } from "../../components/labels";
13import { colorsOf, listFilters, withFilter } from "../../lib/labels";
14import { accessTo, repoFor } from "../../lib/access.server";
Merge branch 'worktree-agent-aca4790ea223aa235'15import { agents, work } from "../../lib/services.server";
Issues and pull requests replace intents and attempts16import { getViewer, unwrap } from "../../lib/session.server";
17
Social cards for every page: og.g1t.sh18export function meta({ params, ...args }: Route.MetaArgs) {
19 return page(args, { title: `Pull requests · ${params.owner}/${params.repo} · g1t` });
Issues and pull requests replace intents and attempts20}
21
22export async function loader({ params, context, request }: Route.LoaderArgs) {
23 const path = { namespace: params.owner, name: params.repo };
Merge branch 'worktree-agent-aca4790ea223aa235'24 const viewer = getViewer(context);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar25 // `?label=bug&milestone=3&base=release`, or the same written in `q`.
26 const { state, label, milestone, base } = listFilters(new URL(request.url).searchParams);
Merge branch 'worktree-agent-aca4790ea223aa235'27 // Which an agent is working on, beside the list, so its badges come with it.
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar28 const [pulls, active, { can }, labels, milestones, repo] = await Promise.all([
29 work.listPulls(path, viewer, state, {
30 label: label || undefined,
31 milestone: milestone ?? undefined,
32 base: base || undefined,
33 }),
Merge branch 'worktree-agent-aca4790ea223aa235'34 agents.listRuns(viewer, { repo: path, active: true, limit: 100 }).catch(() => null),
35 accessTo(context, params),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar36 work.listLabels(path, viewer),
37 work.listMilestones(path, viewer, "open"),
38 // The layout looks it up in this request too.
39 repoFor(context, params),
Merge branch 'worktree-agent-aca4790ea223aa235'40 ]);
Issues and pull requests replace intents and attempts41 return {
Merge branch 'worktree-agent-aca4790ea223aa235'42 pulls: unwrap(pulls),
Issues and pull requests replace intents and attempts43 state,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar44 label,
45 milestone,
46 base,
47 labels: labels.ok ? labels.value : [],
48 milestones: milestones.ok ? milestones.value : [],
49 defaultBranch: repo.ok ? repo.value.defaultBranch : null,
Merge branch 'worktree-agent-aca4790ea223aa235'50 // As the project's agents.json has them; left out, the list fetches them.
51 active: active?.ok ? { runs: active.value, member: can.run } : undefined,
Issues and pull requests replace intents and attempts52 } as const;
53}
54
55export default function Pulls({ loaderData, params }: Route.ComponentProps) {
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar56 const { pulls, state, active, labels, milestones, label, milestone, defaultBranch } = loaderData;
Issues and pull requests replace intents and attempts57 const base = `/${params.owner}/${params.repo}`;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar58 const list = `${base}/pulls`;
Agents and memory, checks and conflicts, profiles, slug renames, custom domains59 // Which pull requests an agent is working on this minute, and at what.
Merge branch 'worktree-agent-aca4790ea223aa235'60 const working = useActiveRuns(params.owner, params.repo, active);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar61 const current = new URLSearchParams(useLocation().search);
62 const colors = colorsOf(labels);
63 const milestoneTitle = milestones.find((m) => m.number === milestone)?.title ?? (milestone ? `#${milestone}` : null);
64 const filtered = [label, milestone, loaderData.base].some(Boolean);
65 const kept = new URLSearchParams(current);
66 kept.delete("state");
67 kept.delete("q");
Issues and pull requests replace intents and attempts68 return (
69 <div>
Pull requests from branches70 <StateTabs
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar71 to={list}
Pull requests from branches72 state={state}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar73 query={kept.toString()}
Pull requests from branches74 action={
75 <ButtonLink to={`${base}/pulls/new`}>
76 <Plus size={15} />
77 New pull request
78 </ButtonLink>
79 }
80 />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar81 <div className="mt-3 flex flex-wrap items-center gap-2">
82 <FilterMenu
83 label="Label"
84 active={label || undefined}
85 clearTo={label ? withFilter(list, current, "label", null) : undefined}
86 searchPlaceholder="Filter labels"
87 emptyText="No label matches."
88 options={labels.map((each) => ({
89 key: each.name,
90 to: withFilter(list, current, "label", each.name),
91 keywords: `${each.name} ${each.description}`,
92 selected: each.name === label,
93 label: (
94 <>
95 <Swatch color={each.color} />
96 <span className="truncate">{each.name}</span>
97 </>
98 ),
99 }))}
100 />
101 <FilterMenu
102 label="Milestone"
103 active={milestoneTitle ?? undefined}
104 clearTo={milestone ? withFilter(list, current, "milestone", null) : undefined}
105 searchPlaceholder="Filter milestones"
106 emptyText="No open milestone matches."
107 options={milestones.map((each) => ({
108 key: String(each.number),
109 to: withFilter(list, current, "milestone", String(each.number)),
110 keywords: `${each.title} ${each.number}`,
111 selected: each.number === milestone,
112 label: <span className="truncate">{each.title}</span>,
113 }))}
114 />
115 {loaderData.base && (
Merge branch 'worktree-agent-ae1299e92e4462012'116 <Hint label="Show pull requests into every branch">
117 <Link
118 to={withFilter(list, current, "base", null)}
119 className="inline-flex h-8 items-center gap-1.5 rounded-md border border-accent/40 bg-accent/5 px-2.5 text-sm"
120 >
121 Into <span className="font-mono">{loaderData.base}</span>
122 <span className="sr-only">: show pull requests into every branch</span>
123 </Link>
124 </Hint>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar125 )}
126 {filtered && (
127 <Link to={state === "closed" ? `${list}?state=closed` : list} className="text-xs text-muted hover:text-fg">
128 Clear filters
129 </Link>
130 )}
131 <span className="ml-auto flex items-center gap-1">
132 <Link
133 to={`${base}/labels`}
134 className="inline-flex h-8 items-center gap-1.5 rounded-md px-2.5 text-sm text-muted transition-colors hover:bg-surface hover:text-fg"
135 >
136 <Tag size={14} />
137 Labels
138 </Link>
139 <Link
140 to={`${base}/milestones`}
141 className="inline-flex h-8 items-center gap-1.5 rounded-md px-2.5 text-sm text-muted transition-colors hover:bg-surface hover:text-fg"
142 >
143 <MilestoneIcon size={14} />
144 Milestones
145 </Link>
146 </span>
147 </div>
Issues and pull requests replace intents and attempts148 <div className="mt-4">
149 {pulls.length === 0 ? (
150 <EmptyState
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar151 title={
152 filtered
153 ? `No ${state} pull requests match these filters`
154 : state === "open"
155 ? "No open pull requests"
156 : "No closed pull requests"
157 }
Issues and pull requests replace intents and attempts158 >
Pull requests from branches159 A pull request proposes a change. Assign agents to an issue and
160 each opens one in its own fork, or push a branch and open one
161 yourself.
Issues and pull requests replace intents and attempts162 </EmptyState>
163 ) : (
164 <ul className="divide-y divide-line overflow-hidden rounded-xl border border-line">
165 {pulls.map((pull) => (
166 <li key={pull.id}>
167 <Link
Agents as a team: lifecycle, merge queue, billing and a new shell168 prefetch="intent"
Issues and pull requests replace intents and attempts169 to={`${base}/pull/${pull.number}`}
170 className="flex items-start gap-3 px-4 py-3 transition-colors hover:bg-surface"
171 >
172 <span className="mt-0.5">
173 <PullIcon status={pull.status} />
174 </span>
175 <span className="min-w-0 grow">
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar176 <span className="flex flex-wrap items-center gap-x-2 gap-y-1">
177 <span className="min-w-0 truncate font-medium">{pull.title}</span>
Issues and pull requests replace intents and attempts178 {pull.status === "draft" && (
179 <span className="shrink-0 rounded-full border border-line px-1.5 py-px text-[0.6875rem] text-faint">
180 draft
181 </span>
182 )}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar183 {(pull.labels ?? []).map((name) => (
184 <LabelChip key={name} name={name} color={colors[name]} />
185 ))}
Agents and memory, checks and conflicts, profiles, slug renames, custom domains186 <AgentBadge run={working.get(pull.number)} />
Issues and pull requests replace intents and attempts187 </span>
188 <span className="mt-0.5 block text-xs text-faint">
189 #{pull.number} opened <TimeAgo at={pull.createdAt} /> by{" "}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent190 {openedBy(pull).name}
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights191 {pull.requestedBy && <> for {pull.requestedBy.username}</>}
Issues and pull requests replace intents and attempts192 {pull.issue != null && <> · for #{pull.issue}</>}
193 {pull.supersededBy != null && <> · superseded by #{pull.supersededBy}</>}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar194 {pull.milestone && (
195 <>
196 {" "}
197 · <MilestoneIcon size={11} className="inline" /> {pull.milestone.title}
198 </>
199 )}
200 {/* Into a branch other than the default one: said. */}
201 {pull.base && defaultBranch && pull.base !== defaultBranch && (
202 <>
203 {" "}
204 · <ArrowLeft size={11} className="inline" /> into{" "}
205 <span className="font-mono">{pull.base}</span>
206 </>
207 )}
Issues and pull requests replace intents and attempts208 </span>
209 </span>
Agents as a team: lifecycle, merge queue, billing and a new shell210 <span className="mt-0.5 hidden sm:block">
211 <ChangeSize files={pull.files} />
212 </span>
Acceptance checks in sandboxes, line comments and review verdicts213 <span className="mt-0.5">
214 <CheckBadge status={pull.checkStatus} />
215 </span>
Issues and pull requests replace intents and attempts216 <span className="mt-0.5 flex shrink-0 items-center gap-1 font-mono text-xs text-muted">
Pull requests from branches217 {pull.branch ? <GitBranch size={13} /> : <Bot size={13} />}
218 {pull.branch ?? pull.agent}
Issues and pull requests replace intents and attempts219 </span>
220 </Link>
221 </li>
222 ))}
223 </ul>
224 )}
225 </div>
226 </div>
227 );
228}

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