Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
1,228 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.

Mission control: a place to ask g1t, shown and not on yet, with the actions under it1import { ArrowDownWideNarrow, ArrowRight, ArrowUpRight, Check, ChevronDown, ChevronRight, ListTree, Plus, Sparkles } from "lucide-react";
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start2import { type ReactNode, useEffect, useState } from "react";
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)3import { Link, useFetcher, useLocation, useRouteLoaderData, useSearchParams } from "react-router";
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start4
5import { trialClosed } from "../lib/trial";
6import { type ActivityGroup, type Verb, greetingFor, isAgent } from "../lib/mission";
7import {
8 BLOCKING,
9 type Fact,
10 type LandedRow,
11 type NeedRow,
12 type QuickAction,
13 REASON_LABEL,
14 type Reason,
15 type Sort,
16 type Tab,
17 type WaitingRow,
18 type Week,
19 type Who,
20 change,
21 dateLine,
22 parseSort,
23 parseTab,
24 signedPercent,
25 sortRows,
26 whyFor,
27} from "../lib/mission-control";
28import { cn } from "../lib/cn";
Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means.29import { microsOf, money } from "../lib/money";
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start30import { AgentComposer, type ComposerResult } from "./agent-composer";
31import { AgentSetup } from "./agent-setup";
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all32import { InboxNeedsCard } from "./inbox";
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start33import type { ShellData } from "./shell";
34import { useLiveRefresh } from "./agents";
35import { Unavailable } from "./mission";
36import { TokenUsagePanel } from "./token-usage";
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.37import { SubmitButton, TimeAgo } from "./ui";
38import { Avatar } from "./ui/avatar";
39import { Card } from "./ui/card";
Merge branch 'worktree-agent-ae1299e92e4462012'40import { Hint } from "./ui/hint";
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start41import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger } from "./ui/dropdown-menu";
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)42import type { Loaded } from "../lib/mission-control.server";
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start43
44/*
45 * Mission control: the home page of someone signed in. It answers where
46 * the viewer is needed, what agents are doing, and what landed without
47 * them. It is a chunk of its own, loaded only for them, so the signed-out
48 * home page does not carry it. routes/home.tsx loads its data;
49 * lib/mission-control.ts shapes it.
50 */
51
52/** The viewer's time zone, set by the page itself, so the greeting fits their day. */
53const TZ_COOKIE = "g1t_tz";
54/** Rows a tab shows before "See all". */
55const ROWS = 6;
56
Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means.57const dollars = (micros: number) => money(Math.max(0, micros));
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start58const plural = (n: number, one: string, many = `${one}s`) => `${n.toLocaleString("en-US")} ${n === 1 ? one : many}`;
59
60// --- Small pieces -----------------------------------------------------------------
61
62const CHIP_TONE: Record<Reason, string> = {
63 blocking: "border-danger/35 bg-danger/10 text-danger",
64 checks_failing: "border-danger/35 bg-danger/10 text-danger",
65 outside_guardrails: "border-warn/35 bg-warn/10 text-warn",
66 low_confidence: "border-warn/35 bg-warn/10 text-warn",
67 stalled: "border-warn/35 bg-warn/10 text-warn",
Merge branch 'worktree-agent-ad7c6d88d93adc817'68 asked_for_you: "border-accent/35 bg-accent/10 text-accent",
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start69 needs_review: "border-info/35 bg-info/10 text-info",
Merge branch 'worktree-agent-ad7c6d88d93adc817'70 ready_to_merge: "border-success/35 bg-success/10 text-success",
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start71};
72
73function Chip({ children, tone }: { children: ReactNode; tone: string }) {
74 return (
75 <span
76 className={cn(
77 "inline-flex shrink-0 items-center rounded border px-1.5 py-px font-mono text-[0.625rem] font-medium tracking-wider whitespace-nowrap uppercase",
78 tone,
79 )}
80 >
81 {children}
82 </span>
83 );
84}
85
86/** A person or an agent, by name, wearing the mark that tells them apart. */
87function Person({ who, size = 16, className }: { who: Who; size?: number; className?: string }) {
88 return (
89 <span className={cn("inline-flex min-w-0 items-center gap-1.5", className)}>
90 <Avatar name={who.name} size={size} />
91 <span className="truncate">{who.name}</span>
92 </span>
93 );
94}
95
96function Eyebrow({ children }: { children: ReactNode }) {
97 return <p className="font-mono text-[0.625rem] font-medium tracking-[0.12em] text-faint uppercase">{children}</p>;
98}
99
Merge branch 'worktree-agent-ad7c6d88d93adc817'100const FACT_TONE = { good: "text-success", warn: "text-warn", bad: "text-danger" } as const;
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start101
102function Facts({ facts }: { facts: Fact[] }) {
103 if (facts.length === 0) return <p className="text-sm text-muted">Nothing more is known about it yet.</p>;
104 return (
105 <dl className="grid grid-cols-2 gap-x-4 gap-y-2.5">
106 {facts.map((fact) => (
107 <div key={fact.label} className={cn("min-w-0", fact.wide && "col-span-2")}>
108 <dt className="text-xs text-faint">{fact.label}</dt>
109 <dd
110 className={cn(
111 "mt-0.5 text-sm font-medium tabular-nums",
112 fact.wide ? "leading-5" : "truncate",
113 fact.tone ? FACT_TONE[fact.tone] : "text-fg-soft",
114 )}
115 >
116 {fact.value}
117 </dd>
118 </div>
119 ))}
120 </dl>
121 );
122}
123
Merge branch 'worktree-agent-a985c01f43538af01'124/**
125 * A form that acts on a pull request from here: working until the action
126 * has answered and the page has reloaded what it changed, then done, or
127 * the error beside it.
128 */
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.129function QuickForm({ quick, variant = "outline" }: { quick: QuickAction; variant?: "outline" | "accent" }) {
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start130 const fetcher = useFetcher<{ error?: string } | null>();
131 const [sent, setSent] = useState(false);
Merge branch 'worktree-agent-a985c01f43538af01'132 const done = sent && fetcher.state === "idle" && !fetcher.data?.error;
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start133 return (
134 <fetcher.Form method="post" action={quick.to} onSubmit={() => setSent(true)} className="contents">
135 {Object.entries(quick.fields).map(([name, value]) => (
136 <input key={name} type="hidden" name={name} value={value} />
137 ))}
Merge branch 'worktree-agent-a985c01f43538af01'138 <SubmitButton
139 fetcher={fetcher}
140 disabled={done}
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start141 className={cn(
142 "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors disabled:cursor-default",
143 variant === "accent"
Merge branch 'worktree-agent-ad7c6d88d93adc817'144 ? "bg-accent text-bg hover:bg-accent-hover"
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start145 : "border border-line-strong text-fg/90 hover:bg-raised hover:text-fg",
Merge branch 'worktree-agent-ad7c6d88d93adc817'146 done && "border-success/40 text-success",
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start147 )}
148 >
149 {done ? <Check size={14} /> : null}
150 {done ? quick.done : quick.label}
Merge branch 'worktree-agent-a985c01f43538af01'151 </SubmitButton>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start152 {fetcher.data?.error && <span className="basis-full text-xs text-danger">{fetcher.data.error}</span>}
153 </fetcher.Form>
154 );
155}
156
157function ProjectMark({ repo }: { repo: { namespace: string; name: string } | null }) {
158 return <Avatar name={repo ? repo.name : "g1t"} size={32} square />;
159}
160
161/** A row that opens to what is known about it; the first one starts open. */
162function Row({
163 open,
164 repo,
165 by,
166 title,
167 sub,
168 chip,
169 at,
170 children,
171}: {
172 open: boolean;
173 repo: { namespace: string; name: string } | null;
174 by: Who | null;
175 title: ReactNode;
176 sub: ReactNode;
177 chip: ReactNode;
178 at: number;
179 children: ReactNode;
180}) {
181 return (
182 <li>
183 <details open={open} className="group">
184 <summary className="flex cursor-pointer list-none items-center gap-3 px-4 py-3 transition-colors select-none hover:bg-raised/60 sm:px-5 [&::-webkit-details-marker]:hidden">
185 <ProjectMark repo={repo} />
186 <span className="min-w-0 grow">
187 <span className="block truncate text-sm">{title}</span>
188 <span className="mt-0.5 flex min-w-0 items-center gap-1.5 text-xs text-muted">
189 <span className="shrink-0 font-medium text-fg-soft">{repo ? repo.name : "Workspace"}</span>
190 {by && (
191 <>
192 <span className="text-faint">·</span>
Merge branch 'worktree-agent-ad7c6d88d93adc817'193 <Person who={by} size={14} className={cn("shrink-0", by.agent ? "text-accent" : "")} />
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start194 </>
195 )}
196 {sub && (
197 <>
198 <span className="text-faint">—</span>
199 <span className="truncate">{sub}</span>
200 </>
201 )}
202 </span>
203 </span>
204 <span className="hidden sm:inline-flex">{chip}</span>
205 <span className="hidden w-16 shrink-0 text-right text-xs text-faint sm:block">
206 <TimeAgo at={at} />
207 </span>
208 <ChevronDown size={16} className="shrink-0 text-faint transition-transform group-open:rotate-180" />
209 </summary>
210 <div className="px-4 pb-4 sm:px-5 sm:pb-5">
211 <div className="mb-3 flex items-center gap-2 sm:hidden">
212 {chip}
213 <span className="text-xs text-faint">
214 <TimeAgo at={at} />
215 </span>
216 </div>
217 {children}
218 </div>
219 </details>
220 </li>
221 );
222}
223
224function NeedCard({ row, first }: { row: NeedRow; first: boolean }) {
225 const isPull = row.to.includes("/pull/");
226 return (
227 <Row
228 open={first}
229 repo={row.repo}
230 by={row.by}
231 title={
232 <>
233 <span className="font-medium">{row.title}</span>
234 {row.ref && <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>}
235 </>
236 }
237 sub={row.ask}
238 chip={<Chip tone={CHIP_TONE[row.reason]}>{REASON_LABEL[row.reason]}</Chip>}
239 at={row.at}
240 >
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.241 <Card tone="plain" className="grid gap-4 bg-bg/50 p-4 md:grid-cols-2 md:gap-x-6 2xl:grid-cols-[minmax(0,1.1fr)_minmax(0,1fr)_minmax(0,0.95fr)]">
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start242 <div className="min-w-0">
243 <Eyebrow>The ask</Eyebrow>
244 <p className="mt-2 text-sm leading-6 text-fg-soft">{row.ask}</p>
245 <p className="mt-2 text-xs leading-5 text-muted">
246 <Link to={row.to} className="font-medium text-fg hover:underline">
247 {row.repo ? `${row.repo.name}${row.ref ?? ""}` : row.title}
248 </Link>
249 {row.for && (
250 <>
251 {" "}
252 · started for <span className="text-fg-soft">{row.for}</span>
253 </>
254 )}
255 </p>
256 </div>
257 <div className="min-w-0">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit258 <Eyebrow>{row.by?.agent ? "What the agent already knows" : "What is known"}</Eyebrow>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start259 <div className="mt-2">
260 <Facts facts={row.facts} />
261 </div>
262 </div>
263 <div className="rounded-lg border border-warn/20 bg-warn/[0.06] p-3.5 md:col-span-2 2xl:col-span-1">
264 <p className="text-xs font-semibold text-warn">Why this needs you</p>
265 <p className="mt-1.5 text-sm leading-6 text-fg-soft">{row.why}</p>
266 <p className="mt-2 text-xs text-faint">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit267 Started waiting <TimeAgo at={row.at} />
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start268 </p>
269 </div>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.270 </Card>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start271 <div className="mt-3 flex flex-wrap items-center gap-2">
272 <Link
273 to={row.to}
274 prefetch="intent"
You choose how g1t looks: Auto follows your system, Light and Dark stay put, set from the account menu, the phone's You and help sheet, Settings → Account or the command palette; the page is drawn in your choice from the first byte, every colour has a light value that reads at 4.5:1 or better, code and diffs are highlighted for both, diagrams and the document editor follow along, the product tour and the g1t mark's avatar stay dark, and Dark is what you see until you choose; the workspaces guide says how.275 className="inline-flex items-center gap-1.5 rounded-md bg-fg px-3 py-1.5 text-sm font-medium text-bg transition-colors hover:bg-fg-hover"
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start276 >
277 Review and respond <ArrowUpRight size={14} />
278 </Link>
279 {row.quick && <QuickForm quick={row.quick} />}
280 {row.link && (
281 <Link
282 to={row.link.to}
283 className="inline-flex items-center rounded-md border border-line-strong px-3 py-1.5 text-sm font-medium text-fg/90 hover:bg-raised hover:text-fg"
284 >
285 {row.link.label}
286 </Link>
287 )}
288 {row.open !== row.to && (
289 <Link to={row.open} prefetch="intent" className="px-2 py-1.5 text-sm text-muted hover:text-fg">
290 {row.open.includes("/issues/") ? "Open issue" : isPull ? "See the changes" : "Open pull request"}
291 </Link>
292 )}
293 </div>
294 </Row>
295 );
296}
297
298function WaitingCard({ row, first }: { row: WaitingRow; first: boolean }) {
299 return (
300 <Row
301 open={first}
302 repo={row.repo}
303 by={row.by}
304 title={
305 <>
306 <span className="font-medium">{row.title}</span>
307 {row.ref && <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>}
308 </>
309 }
310 sub={row.detail}
311 chip={
312 <Chip tone="border-line-strong bg-raised text-muted">
Merge branch 'worktree-agent-ad7c6d88d93adc817'313 {row.live && <span className="mr-1.5 size-1.5 animate-pulse rounded-full bg-success" />}
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start314 {row.chip}
315 </Chip>
316 }
317 at={row.at}
318 >
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.319 <Card tone="plain" className="grid gap-4 bg-bg/50 p-4 md:grid-cols-2 md:gap-6">
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start320 <div className="min-w-0">
321 <Eyebrow>{row.live ? "Doing now" : "Where it stands"}</Eyebrow>
322 <p className="mt-2 text-sm leading-6 text-fg-soft">{row.detail}</p>
323 </div>
324 <div className="min-w-0">
325 <Eyebrow>What is known</Eyebrow>
326 <div className="mt-2">
327 <Facts facts={row.facts} />
328 </div>
329 </div>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.330 </Card>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start331 <div className="mt-3 flex flex-wrap gap-2">
332 <Link
333 to={row.to}
334 prefetch="intent"
335 className="inline-flex items-center gap-1.5 rounded-md border border-line-strong px-3 py-1.5 text-sm font-medium text-fg/90 hover:bg-raised hover:text-fg"
336 >
337 {row.to.includes("/runs/") ? "Watch the run" : "Open pull request"} <ArrowUpRight size={14} />
338 </Link>
339 {row.run && row.run !== row.to && (
340 <Link to={row.run} prefetch="intent" className="px-2 py-1.5 text-sm text-muted hover:text-fg">
341 Watch the run
342 </Link>
343 )}
344 </div>
345 </Row>
346 );
347}
348
349function LandedCard({ row, first }: { row: LandedRow; first: boolean }) {
350 return (
351 <Row
352 open={first}
353 repo={row.repo}
354 by={row.byAgents ? { name: row.agent, agent: isAgent(row.agent) } : row.by}
355 title={
356 <>
357 <span className="font-medium">{row.title}</span>
358 <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>
359 </>
360 }
361 sub={row.byAgents ? "landed without a person" : `merged by ${row.by?.name ?? "a person"}`}
362 chip={
363 row.byAgents ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'364 <Chip tone="border-accent/35 bg-accent/10 text-accent">By agents</Chip>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start365 ) : (
366 <Chip tone="border-warn/35 bg-warn/10 text-warn">Needed a person</Chip>
367 )
368 }
369 at={row.at}
370 >
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.371 <Card tone="plain" className="grid gap-4 bg-bg/50 p-4 md:grid-cols-2 md:gap-6">
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start372 <div className="min-w-0">
373 <Eyebrow>How it landed</Eyebrow>
374 <p className="mt-2 text-sm leading-6 text-fg-soft">
375 {row.byAgents
376 ? `g1t merged it once everything the repository asks for was met, with no one pressing merge.`
377 : `${row.by?.name ?? "A person"} merged it.`}
378 {row.agent && isAgent(row.agent) && ` ${row.agent} made the change.`}
379 </p>
380 </div>
381 <div className="min-w-0">
382 <Eyebrow>What changed</Eyebrow>
383 <div className="mt-2">
384 <Facts facts={row.facts} />
385 </div>
386 </div>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.387 </Card>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start388 <div className="mt-3">
389 <Link
390 to={row.to}
391 prefetch="intent"
392 className="inline-flex items-center gap-1.5 rounded-md border border-line-strong px-3 py-1.5 text-sm font-medium text-fg/90 hover:bg-raised hover:text-fg"
393 >
394 Open pull request <ArrowUpRight size={14} />
395 </Link>
396 </div>
397 </Row>
398 );
399}
400
401function List({ children }: { children: ReactNode }) {
402 return <ul className="divide-y divide-line">{children}</ul>;
403}
404
405function Empty({ children, action }: { children: ReactNode; action?: ReactNode }) {
406 return (
407 <div className="px-5 py-10 text-center">
408 <div className="mx-auto max-w-md text-sm leading-6 text-muted">{children}</div>
409 {action && <div className="mt-4 flex flex-wrap justify-center gap-2">{action}</div>}
410 </div>
411 );
412}
413
414// --- The stat strip -----------------------------------------------------------------
415
416function Stat({ label, value, hint, dot, title }: { label: string; value: string; hint: ReactNode; dot?: string; title?: string }) {
417 return (
Merge branch 'worktree-agent-ae1299e92e4462012'418 <Hint label={title}>
419 <div className="min-w-0 bg-surface px-4 py-3.5 sm:px-5 sm:py-4">
420 <p className="flex items-center gap-1.5 truncate text-xs text-muted">
421 {dot && <span className={cn("size-1.5 shrink-0 rounded-full", dot)} />}
422 {label}
423 </p>
424 <p className="mt-1 font-display text-2xl font-semibold tracking-tight tabular-nums sm:text-[1.75rem]">{value}</p>
425 <p className="mt-0.5 truncate text-xs text-faint">{hint}</p>
426 </div>
427 </Hint>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start428 );
429}
430
431// --- This week ----------------------------------------------------------------------
432
433/** Seven days of landed changes, each split into what agents landed alone and what a person merged. */
434function WeekChart({ week }: { week: Week }) {
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar435 const max = Math.max(1, ...week.days.map((d) => d.agents + d.assisted + d.people));
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start436 const height = 112;
437 const delta = change(week.total, week.previous);
438 return (
439 <div>
440 <div className="flex items-end justify-between gap-3">
441 <div>
442 <p className="font-display text-3xl font-semibold tracking-tight tabular-nums">{week.total.toLocaleString("en-US")}</p>
443 <p className="text-xs text-muted">changes landed in 7 days</p>
444 </div>
445 {delta != null ? (
446 <span
447 className={cn(
448 "rounded-full px-2 py-0.5 text-xs font-medium tabular-nums ring-1",
Merge branch 'worktree-agent-ad7c6d88d93adc817'449 delta >= 0 ? "text-success ring-success/30" : "text-warn ring-warn/30",
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start450 )}
451 >
452 {signedPercent(delta)} vs last week
453 </span>
454 ) : week.previous === 0 && week.total > 0 ? (
455 <span className="text-xs text-faint">none the week before</span>
456 ) : null}
457 </div>
458 {week.total === 0 ? (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.459 <Card asChild tone="plain" radius="lg" className="mt-4 border-dashed px-4 py-6 text-center text-sm leading-6 text-muted">
460 <p>
461 Nothing landed in the last 7 days. Each change that does shows here, by day: agents' changes that landed on their own, agents' that a person merged, and people's own.
462 </p>
463 </Card>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start464 ) : (
465 <div className="relative mt-5" style={{ height: height + 20 }}>
466 <div className="absolute inset-x-0 border-t border-line" style={{ top: height }} />
467 <div className="absolute inset-x-0 top-0 flex items-end justify-between gap-1" style={{ height }}>
468 {week.days.map((day, index) => {
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar469 const total = day.agents + day.assisted + day.people;
470 const bar = (n: number) => Math.max(3, Math.round((n / max) * (height - 4)));
471 // Top to bottom: agents alone, agents with a person, people.
472 const segments = [
Merge branch 'worktree-agent-ad7c6d88d93adc817'473 { n: day.agents, tone: "bg-accent" },
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar474 { n: day.assisted, tone: "bg-warn" },
475 { n: day.people, tone: "bg-info" },
476 ].filter((segment) => segment.n > 0);
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start477 const today = index === week.days.length - 1;
478 return (
479 <div key={day.key} className="group relative flex h-full flex-1 flex-col items-center justify-end">
480 {total > 0 && (
481 <span
482 className={cn(
483 "mb-1 text-[0.625rem] tabular-nums",
Phones reach every page: each mode's menu from the top bar or by tapping the tab you're on, Docs first in the avatar sheet, a project's pages as a swipeable strip; dialogs as bottom sheets above the keyboard, safe areas, menus kept on screen, 40px targets, touch-visible controls, save bars above the tab bar484 today ? "text-fg-soft" : "text-faint opacity-0 group-hover:opacity-100 max-md:hidden",
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start485 )}
486 >
487 {total}
488 </span>
489 )}
490 <div className="flex w-full max-w-6 flex-col items-stretch gap-[2px]">
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar491 {segments.map((segment, at) => (
492 <span key={segment.tone} className={cn("block", segment.tone, at === 0 && "rounded-t")} style={{ height: bar(segment.n) }} />
493 ))}
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start494 {total === 0 && <span className="block h-[2px] rounded-full bg-line-strong" />}
495 </div>
Phones reach every page: each mode's menu from the top bar or by tapping the tab you're on, Docs first in the avatar sheet, a project's pages as a swipeable strip; dialogs as bottom sheets above the keyboard, safe areas, menus kept on screen, 40px targets, touch-visible controls, save bars above the tab bar496 <div className="pointer-events-none absolute bottom-full left-1/2 z-10 mb-1 hidden -translate-x-1/2 rounded-md border border-line-strong bg-raised px-2.5 py-1.5 text-xs whitespace-nowrap shadow-lg shadow-black/40 md:group-hover:block">
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start497 <p className="font-medium text-fg">{day.label}</p>
498 <p className="mt-0.5 flex items-center gap-1.5 text-muted">
Merge branch 'worktree-agent-ad7c6d88d93adc817'499 <span className="size-1.5 rounded-full bg-accent" /> {day.agents} by agents on their own
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar500 </p>
501 <p className="flex items-center gap-1.5 text-muted">
502 <span className="size-1.5 rounded-full bg-warn" /> {day.assisted} by agents, merged by a person
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start503 </p>
504 <p className="flex items-center gap-1.5 text-muted">
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar505 <span className="size-1.5 rounded-full bg-info" /> {day.people} by people
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start506 </p>
507 </div>
508 </div>
509 );
510 })}
511 </div>
512 <div className="absolute inset-x-0 bottom-0 flex justify-between gap-1">
513 {week.days.map((day, index) => (
514 <span
515 key={day.key}
516 className={cn(
517 "flex-1 text-center text-[0.6875rem]",
518 index === week.days.length - 1 ? "font-medium text-fg-soft" : "text-faint",
519 )}
520 >
521 {day.label}
522 </span>
523 ))}
524 </div>
525 </div>
526 )}
527 <div className={cn("mt-3 flex-wrap gap-x-4 gap-y-1 text-xs text-muted", week.total === 0 ? "hidden" : "flex")}>
528 <span className="inline-flex items-center gap-1.5">
Merge branch 'worktree-agent-ad7c6d88d93adc817'529 <span className="size-2 rounded-sm bg-accent" /> Agents, on their own
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar530 </span>
531 <span className="inline-flex items-center gap-1.5">
532 <span className="size-2 rounded-sm bg-warn" /> Agents, merged by a person
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start533 </span>
534 <span className="inline-flex items-center gap-1.5">
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar535 <span className="size-2 rounded-sm bg-info" /> People
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start536 </span>
537 </div>
Fixes from the page sweep538 {/* A table does not shrink to sr-only's 1px; its wrapper does. */}
539 <div className="sr-only">
540 <table>
541 <caption>Changes landed each day</caption>
542 <thead>
543 <tr>
544 <th>Day</th>
545 <th>Agents, on their own</th>
546 <th>Agents, merged by a person</th>
547 <th>People</th>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start548 </tr>
Fixes from the page sweep549 </thead>
550 <tbody>
551 {week.days.map((day) => (
552 <tr key={day.key}>
553 <td>{day.key}</td>
554 <td>{day.agents}</td>
555 <td>{day.assisted}</td>
556 <td>{day.people}</td>
557 </tr>
558 ))}
559 </tbody>
560 </table>
561 </div>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start562 </div>
563 );
564}
565
566// --- Activity -----------------------------------------------------------------------
567
568const VERB: Record<Verb, string> = {
569 landed: "landed",
570 opened_issue: "opened",
571 closed_issue: "closed",
572 started: "started on",
573 ready: "marked ready",
574 checks_passed: "checks passed on",
575 checks_failed: "checks failed on",
576 approved: "approved",
577 changes_requested: "asked for changes on",
578 commented: "commented on",
579 asked: "was asked about",
580 deployed: "deployed production",
581 deploy_failed: "production build failed",
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md582 pushed: "pushed to the default branch",
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start583 learned: "learned",
584};
585
586/** One line of the feed: who, what, and the one thing it was about, by name. */
587function FeedLine({ group, titles }: { group: ActivityGroup; titles: Record<string, string> }) {
588 const part = group.parts[0];
589 const base = `/${group.repo.namespace}/${group.repo.name}`;
590 const actor = group.actor ?? "g1t";
591 const agent = group.actor == null || isAgent(group.actor);
592 const number = part.numbers[0];
593 const title = number != null ? titles[`${group.repo.namespace}/${group.repo.name}#${number}`.toLowerCase()] : undefined;
594 const more = group.parts.length - 1 + Math.max(0, part.numbers.length - 1);
595 return (
596 <li className="flex gap-2.5 py-2">
597 <span className="mt-px">
598 <Avatar name={actor} size={22} />
599 </span>
600 <span className="min-w-0 grow text-[0.8125rem] leading-5 text-muted">
Merge branch 'worktree-agent-ad7c6d88d93adc817'601 <span className={cn("font-medium", agent ? "text-accent" : "text-fg")}>{actor}</span> {VERB[part.verb]}{" "}
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start602 {part.verb === "learned" ? (
603 <Link to={part.to ?? `${base}/memory`} className="text-fg-soft hover:text-fg">
604 “{part.texts[0]}”
605 </Link>
606 ) : number != null ? (
607 <Link to={`${base}/issues/${number}`} prefetch="intent" className="text-fg-soft hover:text-fg">
608 <span className="font-mono text-xs">#{number}</span>
609 {title && <> {title}</>}
610 </Link>
611 ) : part.to ? (
612 <Link to={part.to} className="text-fg-soft hover:text-fg">
613 {group.repo.name}
614 </Link>
615 ) : null}
616 {more > 0 && <span className="text-faint"> and {more} more</span>}
617 <span className="block text-xs text-faint">
618 <span className="font-mono">{group.repo.name}</span> · <TimeAgo at={group.at} />
619 </span>
620 </span>
621 </li>
622 );
623}
624
625// --- Get started --------------------------------------------------------------------
626
627type Step = { done: boolean; title: string; about: string; to: string | null; action: string };
628
629/** The first things to do, ticked off as they are done, until work has been handed to agents. */
630function GetStarted({ steps }: { steps: Step[] }) {
631 const left = steps.filter((step) => !step.done).length;
632 return (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.633 <Card asChild className="p-5">
634 <section>
635 <div className="flex items-baseline justify-between gap-3">
636 <h2 className="text-base font-semibold tracking-tight">Get started</h2>
637 <span className="text-xs text-muted">
638 {steps.length - left} of {steps.length} done
639 </span>
640 </div>
641 <ol className="mt-4 space-y-2">
642 {steps.map((step, index) => (
643 <li
644 key={step.title}
645 className={cn("flex items-start gap-3 rounded-lg px-3 py-2.5", step.done ? "" : "bg-bg/50 ring-1 ring-line")}
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start646 >
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.647 <span
648 className={cn(
649 "mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full text-[0.6875rem] font-medium",
650 step.done ? "bg-success text-bg" : "text-muted ring-1 ring-line-strong",
651 )}
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start652 >
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.653 {step.done ? <Check size={12} /> : index + 1}
654 </span>
655 <span className="min-w-0 grow">
656 <span className={cn("block text-sm font-medium", step.done && "text-muted line-through decoration-faint")}>{step.title}</span>
657 {!step.done && <span className="mt-0.5 block text-xs leading-5 text-muted">{step.about}</span>}
658 </span>
659 {!step.done && step.to && (
660 <Link
661 to={step.to}
662 className="shrink-0 rounded-md bg-fg px-2.5 py-1 text-xs font-medium text-bg transition-colors hover:bg-fg-hover"
663 >
664 {step.action}
665 </Link>
666 )}
667 </li>
668 ))}
669 </ol>
670 </section>
671 </Card>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start672 );
673}
674
675// --- The page -----------------------------------------------------------------------
676
677const TAB_LABEL: Record<Tab, string> = { needs: "Needs you", waiting: "Waiting on agents", landed: "Landed today" };
678const TAB_SHORT: Record<Tab, string> = { needs: "Needs you", waiting: "Waiting", landed: "Today" };
679const SORT_LABEL: Record<Sort, string> = { impact: "By impact", newest: "Newest" };
680
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)681export default function MissionControl({
682 loaderData,
683 delegated = null,
684 variant = "code",
685}: {
686 loaderData: Loaded;
687 delegated?: ComposerResult;
688 /** Code's Overview: its own title in place of the greeting, and the actions on their own (Home has the Agent box). */
689 variant?: "code";
690}) {
691 const { pathname } = useLocation();
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start692 const shell = useRouteLoaderData("root")?.shell as ShellData | null | undefined;
693 const loaded: Loaded = loaderData;
694 const [params] = useSearchParams();
695 // Agents are at work, so the page changes without anyone touching it.
696 useLiveRefresh(loaded.changing);
697 const [greeting, setGreeting] = useState(loaded.greeting);
698 const [date, setDate] = useState(loaded.date);
699 useEffect(() => {
700 // The greeting and date follow the viewer's own clock, and the server
701 // learns their zone for next time.
702 setGreeting(greetingFor(new Date().getHours()));
703 try {
704 const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
705 setDate(dateLine(Date.now(), zone ?? null));
706 if (zone) document.cookie = `${TZ_COOKIE}=${encodeURIComponent(zone)}; Path=/; Max-Age=31536000; SameSite=Lax`;
707 } catch {
708 // Nothing to remember.
709 }
710 }, []);
711
712 const { viewer, repos, waiting, landed, liveTotal, week, groups, titles, stats, canRunAgents, trial } = loaded;
713 const workspace = shell?.workspace?.slug ?? loaded.workspace ?? viewer.workspaces?.[0]?.slug ?? null;
714
715 // The usage limit, as the sidebar knows it, heads the list when it bites.
716 const limit = shell?.limit && !shell.limit.comped && shell.limit.state !== "ok" && workspace ? shell.limit : null;
717 const limitRow: NeedRow[] = limit
718 ? [
719 (() => {
720 const detail =
721 limit.state === "stopped"
722 ? "No new agent starts until the limit is raised or a card is added."
723 : `${dollars(limit.exposureMicros)} of ${dollars(limit.ceilingMicros ?? 0)} used. Add a card so work keeps going.`;
724 return {
725 key: "limit",
726 reason: "blocking" as const,
727 repo: null,
728 ref: null,
729 title: limit.state === "stopped" ? "Agents are stopped: the usage limit was reached" : "Usage is nearing its limit",
730 ask: detail,
731 by: null,
732 for: null,
733 at: Date.now(),
734 to: `/${workspace}/-/billing`,
735 open: `/${workspace}/-/billing`,
736 facts: [
737 { label: "Used", value: `${dollars(limit.exposureMicros)} of ${dollars(limit.ceilingMicros ?? 0)}`, tone: "warn" as const },
738 ],
739 why: whyFor("blocking", { kind: "limit", detail }),
740 quick: null,
741 link: null,
742 };
743 })(),
744 ]
745 : [];
746 const needs = [...limitRow, ...loaded.needs];
747 const blocking = needs.filter((row) => BLOCKING.has(row.reason)).length;
748
749 const counts: Record<Tab, number> = { needs: needs.length, waiting: waiting.length, landed: landed.length };
750 const tab = parseTab(params.get("tab")) ?? "needs";
751 const sort = parseSort(params.get("sort"));
752 const all = params.get("all") === "1";
753 const everyActivity = params.get("activity") === "all";
754 const link = (changes: Record<string, string | null>) => {
755 const next = new URLSearchParams(params);
756 for (const [key, value] of Object.entries(changes)) {
757 if (value == null) next.delete(key);
758 else next.set(key, value);
759 }
760 const text = next.toString();
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)761 return text ? `?${text}` : pathname;
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start762 };
763
764 const steps: Step[] = [
765 {
766 done: Boolean(workspace),
767 title: "Create a workspace",
768 about: "Projects, people and agent credit live in one.",
769 to: "/workspaces/new",
770 action: "Create",
771 },
772 {
773 done: repos.length > 0,
774 title: "Add a project",
775 about: "Create a repository, or import one by its address. Push to it with git as usual.",
New project and Import from GitHub work in the workspace you're in, as the sidebar shows it, with no workspace to pick on the page; a link that names another of your workspaces switches you to it first. The projects guide says so.776 to: "/new",
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start777 action: "Add",
778 },
779 trial?.open
780 ? {
781 done: true,
782 title: "Try g1t free",
783 about: trial.granted
784 ? `This workspace has ${dollars(trial.limitMicros - trial.usedMicros)} of its ${dollars(trial.limitMicros)} trial credit left, for g1t's models and sandboxes.`
785 : `This workspace gets ${dollars(trial.limitMicros)} of trial credit, for g1t's models and sandboxes, the first time g1t works here.`,
Integrations are a directory of connectors, for a workspace and for each person786 to: workspace ? `/${workspace}/-/integrations/models` : null,
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start787 action: "Connect",
788 }
789 : canRunAgents
790 ? {
791 done: Boolean(shell?.free) || shell?.limit == null || shell.limit.comped || shell.limit.state === "ok",
792 title: "Keep work running",
793 about:
794 "Usage is charged after it runs. Add a card under Billing, so g1t charges it as you near your limit instead of stopping work.",
795 to: workspace ? `/${workspace}/-/billing` : null,
796 action: "Billing",
797 }
798 : {
799 done: false,
800 title: "Connect a model",
801 about: `${
802 trialClosed(trial, workspace ?? "This workspace") ? `${trialClosed(trial, workspace ?? "This workspace")} ` : ""
803 }Agents need a model to think with. Connect your Anthropic or OpenAI key, or any compatible endpoint.`,
Integrations are a directory of connectors, for a workspace and for each person804 to: workspace ? `/${workspace}/-/integrations/models` : null,
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start805 action: "Connect",
806 },
807 {
808 done: loaded.handedOff,
809 title: "Hand off an outcome",
810 about:
811 "Open an issue and assign it to g1t, or write an outcome on a project's Plans page and let a planner split it into issues.",
812 to: repos[0] ? `/${repos[0].namespace}/${repos[0].name}/plans` : null,
813 action: "Write one",
814 },
815 ];
816 const starting = steps.some((step) => !step.done);
817 const noProjects = repos.length === 0;
818
819 const rows = tab === "needs" ? sortRows(needs, sort) : tab === "waiting" ? sortRows(waiting, sort) : sortRows(landed, sort);
820 const shown = all ? rows : rows.slice(0, ROWS);
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar821 // Of the agents' own changes only: people's work is not theirs to land.
822 const share = week.agentChanges > 0 ? week.byAgents / week.agentChanges : null;
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start823 const delta = change(week.total, week.previous);
824 const feed = everyActivity ? groups : groups.slice(0, 8);
825
826 // "Put an agent on it" first, with "New issue" beside it as before: one
827 // split control, the agent the main way in.
828 const newIssue =
829 repos.length > 0 ? (
830 <div className="flex items-stretch">
831 <AgentComposer
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)832 action={pathname}
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start833 repos={repos}
834 open={params.get("agent") === "new" || delegated != null}
835 result={delegated}
836 note={
837 canRunAgents ? null : (
838 <>
839 Agents need a model first.{" "}
840 {workspace && (
Integrations are a directory of connectors, for a workspace and for each person841 <Link to={`/${workspace}/-/integrations/models`} className="font-medium text-fg hover:underline">
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start842 Connect one
843 </Link>
844 )}
845 . The issue still opens.
846 </>
847 )
848 }
849 >
850 <span className="inline-flex cursor-pointer items-center gap-1.5 rounded-l-md border border-line-strong bg-raised px-3 py-2 text-sm font-medium text-fg transition-colors hover:bg-line/60 group-open/composer:bg-line/60">
Merge branch 'worktree-agent-ad7c6d88d93adc817'851 <Sparkles size={14} className="text-accent" /> Put an agent on it
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start852 </span>
853 </AgentComposer>
854 <DropdownMenu>
855 <DropdownMenuTrigger className="-ml-px inline-flex items-center gap-1.5 rounded-r-md border border-line-strong px-3 py-2 text-sm font-medium text-fg/90 transition-colors hover:bg-raised hover:text-fg">
856 <Plus size={14} /> New issue
857 </DropdownMenuTrigger>
858 <DropdownMenuContent align="end" className="max-h-80 overflow-y-auto">
859 <DropdownMenuLabel>In which project?</DropdownMenuLabel>
860 {repos.map((repo) => (
861 <DropdownMenuItem key={repo.name} asChild>
862 <Link to={`/${repo.namespace}/${repo.name}/issues/new`}>
863 <Avatar name={repo.name} size={18} square />
864 {repo.name}
865 </Link>
866 </DropdownMenuItem>
867 ))}
868 </DropdownMenuContent>
869 </DropdownMenu>
870 </div>
871 ) : (
872 <Link
New project and Import from GitHub work in the workspace you're in, as the sidebar shows it, with no workspace to pick on the page; a link that names another of your workspaces switches you to it first. The projects guide says so.873 to={"/new"}
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start874 className="inline-flex items-center gap-1.5 rounded-md border border-line-strong px-3 py-2 text-sm font-medium text-fg/90 hover:bg-raised hover:text-fg"
875 >
876 <Plus size={14} /> New project
877 </Link>
878 );
879
880 return (
881 <main className="mx-auto max-w-7xl space-y-6 px-4 py-8 sm:px-6 sm:py-10">
882 <header className="flex flex-wrap items-end justify-between gap-x-6 gap-y-4">
883 <div className="min-w-0">
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)884 <h1 className="text-2xl leading-tight font-semibold tracking-tight" suppressHydrationWarning>
885 {variant === "code" ? "Overview" : `${greeting}, ${loaded.name}`}
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start886 </h1>
887 <p className="mt-1.5 text-sm text-muted" suppressHydrationWarning>
888 <span className="text-fg-soft">{date}</span> · {loaded.summary}
889 </p>
890 </div>
891 </header>
892
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)893 {/* The ways to start work. The Agent box itself is on Home. */}
894 <section aria-label="Start work" className="flex flex-wrap items-center gap-2">
Mission control: a place to ask g1t, shown and not on yet, with the actions under it895 {newIssue}
896 {repos.length > 0 && (
897 <DropdownMenu>
898 <DropdownMenuTrigger className="inline-flex items-center gap-1.5 rounded-md border border-line-strong px-3 py-2 text-sm font-medium text-fg/90 transition-colors hover:bg-raised hover:text-fg">
899 <ListTree size={14} /> Plan work
900 </DropdownMenuTrigger>
901 <DropdownMenuContent align="start" className="max-h-80 overflow-y-auto">
902 <DropdownMenuLabel>Plan in which project?</DropdownMenuLabel>
903 {repos.map((repo) => (
904 <DropdownMenuItem key={repo.name} asChild>
905 <Link to={`/${repo.namespace}/${repo.name}/plans`}>
906 <Avatar name={repo.name} size={18} square />
907 {repo.name}
908 </Link>
909 </DropdownMenuItem>
910 ))}
911 </DropdownMenuContent>
912 </DropdownMenu>
913 )}
914 {needs.length > 0 && (
915 <Link
916 to={`${link({ tab: "needs", all: null })}#work`}
917 preventScrollReset
Merge branch 'worktree-agent-ad7c6d88d93adc817'918 className="inline-flex items-center gap-1.5 rounded-md bg-accent px-3.5 py-2 text-sm font-semibold text-bg transition-colors hover:bg-accent-hover"
Mission control: a place to ask g1t, shown and not on yet, with the actions under it919 >
920 Review {needs.length} that need{needs.length === 1 ? "s" : ""} you <ArrowRight size={14} />
921 </Link>
922 )}
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)923 </section>
Mission control: a place to ask g1t, shown and not on yet, with the actions under it924
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start925 {starting && <GetStarted steps={steps} />}
926
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.927 <Card asChild tone="plain" className="grid grid-cols-2 gap-px overflow-hidden bg-line lg:grid-cols-5">
928 <section
929 aria-label="At a glance">
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start930 <Stat
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.931 label="Projects"
932 value={stats.projects == null ? "—" : String(stats.projects)}
933 hint={
934 stats.projectsThisMonth == null
935 ? "in this workspace"
936 : stats.projectsThisMonth > 0
937 ? `+${stats.projectsThisMonth} this month`
938 : "none new this month"
939 }
940 />
941 <Stat
942 label="Agents"
943 value={loaded.runsLoaded || !workspace ? String(liveTotal) : "—"}
944 dot={liveTotal > 0 ? "bg-success animate-pulse" : undefined}
945 hint={
946 liveTotal > 0
947 ? `live now · ${stats.agentHours < 10 ? stats.agentHours.toFixed(1) : Math.round(stats.agentHours)}h this week`
948 : stats.agentHours > 0
949 ? `none live · ${stats.agentHours < 10 ? stats.agentHours.toFixed(1) : Math.round(stats.agentHours)}h this week`
950 : "none live now"
951 }
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start952 />
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.953 <Stat
954 label="Changes this week"
955 value={loaded.perRepoLoaded ? String(week.total) : "—"}
956 hint={
957 delta != null ? `${signedPercent(delta)} vs last week` : week.previous === 0 ? "none the week before" : "merged pull requests"
958 }
959 />
960 <Stat
961 label="Landed without you"
962 dot="bg-accent"
963 value={share == null ? "—" : `${Math.round(share * 100)}%`}
964 hint={share == null ? "no agent changes yet" : `${week.byAgents} of ${week.agentChanges} agent changes`}
965 title="Of the changes agents wrote, those g1t merged by auto-merge or the merge queue, with no person pressing merge. People's own changes are not counted."
966 />
967 <div className="col-span-2 lg:col-span-1">
968 <Stat
969 label="Needs you"
970 dot="bg-warn"
971 value={String(needs.length)}
972 hint={blocking > 0 ? `${blocking} blocking` : needs.length > 0 ? "nothing blocking" : "all clear"}
973 />
974 </div>
975 </section>
976 </Card>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start977
978 <div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_21rem] xl:grid-cols-[minmax(0,1fr)_23rem]">
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.979 <Card asChild className="min-w-0 scroll-mt-20 self-start overflow-hidden">
980 <section id="work">
981 <div className="flex items-center gap-2 border-b border-line px-2 sm:px-3">
982 <nav className="-mb-px flex min-w-0 grow gap-1 overflow-x-auto [scrollbar-width:none]" aria-label="Overview">
983 {(["needs", "waiting", "landed"] as const).map((value) => (
984 <Link
985 key={value}
986 to={link({ tab: value, all: null })}
987 preventScrollReset
988 aria-current={tab === value ? "page" : undefined}
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start989 className={cn(
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.990 "flex shrink-0 items-center gap-1.5 border-b-2 px-2 py-3 text-sm whitespace-nowrap transition-colors sm:px-2.5",
991 tab === value ? "border-accent font-medium text-fg" : "border-transparent text-muted hover:text-fg",
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start992 )}
993 >
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.994 <span className="sm:hidden">{TAB_SHORT[value]}</span>
995 <span className="hidden sm:inline">{TAB_LABEL[value]}</span>
996 <span
997 className={cn(
998 "rounded-full px-1.5 text-[0.6875rem] tabular-nums",
999 tab === value && value === "needs" && counts.needs > 0 ? "bg-warn/15 text-warn" : "bg-line text-muted",
1000 )}
1001 >
1002 {counts[value]}
1003 </span>
1004 </Link>
1005 ))}
1006 </nav>
1007 <DropdownMenu>
1008 <DropdownMenuTrigger
1009 className="inline-flex shrink-0 items-center gap-1.5 rounded-md px-2 py-1.5 text-xs text-muted hover:bg-raised hover:text-fg"
1010 aria-label="Sort"
1011 >
1012 <ArrowDownWideNarrow size={14} />
1013 <span className="hidden sm:inline">{SORT_LABEL[sort]}</span>
1014 </DropdownMenuTrigger>
1015 <DropdownMenuContent align="end">
1016 {(["impact", "newest"] as const).map((value) => (
1017 <DropdownMenuItem key={value} asChild>
1018 <Link to={link({ sort: value === "impact" ? null : value })} preventScrollReset>
1019 {sort === value ? <Check /> : <span className="size-4" />}
1020 {SORT_LABEL[value]}
1021 </Link>
1022 </DropdownMenuItem>
1023 ))}
1024 </DropdownMenuContent>
1025 </DropdownMenu>
1026 </div>
1027
1028 {!loaded.perRepoLoaded && tab !== "waiting" ? (
1029 <div className="p-4">
1030 <Unavailable what={tab === "needs" ? "Some of what needs you" : "What landed"} />
1031 </div>
1032 ) : null}
1033
1034 {tab === "needs" &&
1035 (needs.length === 0 ? (
1036 noProjects ? (
1037 <Empty
1038 action={
1039 <Link
1040 to={"/new"}
1041 className="rounded-md bg-fg px-3 py-1.5 text-sm font-medium text-bg hover:bg-fg-hover"
1042 >
1043 Create or import a project
1044 </Link>
1045 }
1046 >
1047 <p className="font-medium text-fg">No projects yet</p>
1048 <p className="mt-1">
1049 Create a repository or import one. Assign its issues to g1t, and what needs you shows up here.
1050 </p>
1051 </Empty>
1052 ) : (
1053 <>
1054 <Empty>
1055 <p className="font-medium text-fg">Nothing needs you</p>
1056 <p className="mt-1">
1057 Agents are handling everything.{" "}
1058 {landed.length > 0
1059 ? "Here's what they landed today."
1060 : "Reviews, stopped work and failed deploys show up here first."}
1061 </p>
1062 </Empty>
1063 {landed.length > 0 && (
1064 <div className="border-t border-line">
1065 <List>
1066 {landed.slice(0, ROWS).map((row) => (
1067 <LandedCard key={row.key} row={row} first={false} />
1068 ))}
1069 </List>
1070 </div>
1071 )}
1072 </>
1073 )
1074 ) : (
1075 <List>
1076 {(shown as NeedRow[]).map((row, index) => (
1077 <NeedCard key={row.key} row={row} first={index === 0} />
1078 ))}
1079 </List>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start1080 ))}
1081
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.1082 {tab === "waiting" &&
1083 (!loaded.runsLoaded && workspace ? (
1084 <div className="p-4">
1085 <Unavailable what="Agent runs" />
1086 </div>
1087 ) : waiting.length === 0 ? (
1088 <Empty>
1089 <p className="font-medium text-fg">No agent is at work right now</p>
1090 <p className="mt-1">Assign an issue to g1t and one starts on it in seconds. Its run shows here while it works.</p>
1091 </Empty>
1092 ) : (
1093 <List>
1094 {(shown as WaitingRow[]).map((row, index) => (
1095 <WaitingCard key={row.key} row={row} first={index === 0} />
1096 ))}
1097 </List>
1098 ))}
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start1099
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.1100 {tab === "landed" &&
1101 loaded.perRepoLoaded &&
1102 (landed.length === 0 ? (
1103 <Empty>
1104 <p className="font-medium text-fg">Nothing has landed today yet</p>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start1105 <p className="mt-1">
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.1106 {week.total > 0
1107 ? `${plural(week.total, "change")} landed in the last 7 days.`
1108 : "Merged pull requests show up here the moment they land."}
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start1109 </p>
1110 </Empty>
1111 ) : (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.1112 <List>
1113 {(shown as LandedRow[]).map((row, index) => (
1114 <LandedCard key={row.key} row={row} first={index === 0} />
1115 ))}
1116 </List>
1117 ))}
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start1118
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.1119 {rows.length > ROWS && (
1120 <div className="flex items-center justify-between border-t border-line px-4 py-2.5 text-xs text-muted sm:px-5">
1121 <span>
1122 Showing {shown.length} of {rows.length}
1123 </span>
1124 <Link
1125 to={link({ all: all ? null : "1" })}
1126 preventScrollReset
1127 className="inline-flex items-center gap-1 font-medium text-fg-soft hover:text-fg"
1128 >
1129 {all ? "Show fewer" : "See all"} <ChevronRight size={12} />
1130 </Link>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start1131 </div>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.1132 )}
1133 </section>
1134 </Card>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start1135
1136 <aside className="min-w-0 space-y-6">
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all1137 <InboxNeedsCard items={loaderData.inboxNeeds.items} total={loaderData.inboxNeeds.total} />
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.1138 <Card asChild className="p-5">
1139 <section>
1140 <div className="flex items-center justify-between">
1141 <h2 className="text-base font-semibold tracking-tight">This week</h2>
1142 {workspace && (
1143 <Link to={`/${workspace}/-/agents`} className="text-xs text-muted hover:text-fg">
1144 Fleet
1145 </Link>
1146 )}
1147 </div>
1148 <div className="mt-4">{loaded.perRepoLoaded ? <WeekChart week={week} /> : <Unavailable what="The week" />}</div>
1149 {workspace && stats.weekCost > 0 && (
1150 <Link
1151 to={`/${workspace}/-/usage`}
1152 className="mt-4 flex items-center justify-between border-t border-line pt-3 text-xs text-muted hover:text-fg"
1153 >
Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means.1154 <span>Usage at price this week: {money(microsOf(stats.weekCost))}</span>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.1155 <ChevronRight size={12} />
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start1156 </Link>
1157 )}
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.1158 </section>
1159 </Card>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start1160
1161 {workspace && (
1162 <TokenUsagePanel
1163 workspace={loaderData.tokens.workspace}
1164 mine={loaderData.tokens.mine}
1165 usageHref={`/${workspace}/-/usage`}
1166 />
1167 )}
1168
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.1169 <Card asChild className="scroll-mt-20 p-5">
1170 <section id="activity">
1171 <div className="flex items-center justify-between">
1172 <h2 className="text-base font-semibold tracking-tight">Activity</h2>
1173 {groups.length > 8 && (
1174 <Link
1175 to={`${link({ activity: everyActivity ? null : "all" })}#activity`}
1176 preventScrollReset
1177 className="inline-flex items-center gap-0.5 text-xs text-muted hover:text-fg"
1178 >
1179 {everyActivity ? "Less" : "All activity"} <ChevronRight size={12} />
1180 </Link>
1181 )}
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start1182 </div>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.1183 <p className="mt-1 flex items-center gap-3 text-[0.6875rem] text-faint">
1184 <span className="inline-flex items-center gap-1.5">
1185 <Avatar name="g1t" size={12} /> agents
1186 </span>
1187 <span className="inline-flex items-center gap-1.5">
1188 <Avatar name={viewer.username} size={12} /> people
1189 </span>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start1190 </p>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.1191 {!loaded.perRepoLoaded ? (
1192 <div className="mt-3">
1193 <Unavailable what="Activity" />
1194 </div>
1195 ) : feed.length === 0 ? (
1196 <p className="mt-3 text-sm leading-6 text-muted">
1197 Nothing has moved yet. Merges, deploys, checks, reviews and what agents learn show up here.
1198 </p>
1199 ) : (
1200 <ol className="mt-2 divide-y divide-line/70">
1201 {feed.map((group) => (
1202 <FeedLine key={group.id} group={group} titles={titles} />
1203 ))}
1204 </ol>
1205 )}
1206 </section>
1207 </Card>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start1208
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.1209 <Card asChild className="p-5">
1210 <section>
1211 <h2 className="text-sm font-semibold">Connect your own agent</h2>
1212 <p className="mt-1.5 text-xs leading-5 text-muted">
1213 Add g1t to your coding agent. It signs in through your browser; there is no token to copy.
1214 </p>
1215 <AgentSetup className="mt-3" />
1216 <Link
1217 to="https://docs.g1t.sh/guides/bring-your-own-agent/"
1218 className="mt-3 inline-flex items-center gap-1 text-xs text-muted hover:text-fg"
1219 >
1220 How it works <ArrowRight size={12} />
1221 </Link>
1222 </section>
1223 </Card>
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start1224 </aside>
1225 </div>
1226 </main>
1227 );
1228}

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