Skip to content

g1t/apps/web/app/components/mission-control.tsx

1,203 lines50,258 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.

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";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look2import { type ReactNode, useEffect, useState } from "react";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms3import { Link, useFetcher, useRouteLoaderData, useSearchParams } from "react-router";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look4
5import { trialClosed } from "../lib/trial";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms6import { 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 usd,
27 whyFor,
28} from "../lib/mission-control";
29import { cn } from "../lib/cn";
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step30import { AgentComposer, type ComposerResult } from "./agent-composer";
Mission control: a place to ask g1t, shown and not on yet, with the actions under it31import { AskComposer } from "./ask-composer";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look32import { AgentSetup } from "./agent-setup";
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all33import { InboxNeedsCard } from "./inbox";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look34import type { ShellData } from "./shell";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms35import { useLiveRefresh } from "./agents";
36import { Unavailable } from "./mission";
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix37import { TokenUsagePanel } from "./token-usage";
Merge branch 'worktree-agent-a985c01f43538af01'38import { Avatar, SubmitButton, TimeAgo } from "./ui";
Merge branch 'worktree-agent-ae1299e92e4462012'39import { Hint } from "./ui/hint";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms40import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger } from "./ui/dropdown-menu";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look41import type { Loaded } from "../routes/home";
42
43/*
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms44 * Mission control: the home page of someone signed in. It answers where
45 * the viewer is needed, what agents are doing, and what landed without
46 * them. It is a chunk of its own, loaded only for them, so the signed-out
47 * home page does not carry it. routes/home.tsx loads its data;
48 * lib/mission-control.ts shapes it.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look49 */
50
51/** The viewer's time zone, set by the page itself, so the greeting fits their day. */
52const TZ_COOKIE = "g1t_tz";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms53/** Rows a tab shows before "See all". */
54const ROWS = 6;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look55
56const dollars = (micros: number) => `$${(Math.max(0, micros) / 1_000_000).toFixed(2)}`;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms57const plural = (n: number, one: string, many = `${one}s`) => `${n.toLocaleString("en-US")} ${n === 1 ? one : many}`;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look58
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms59// --- Small pieces -----------------------------------------------------------------
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look60
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms61const CHIP_TONE: Record<Reason, string> = {
62 blocking: "border-danger/35 bg-danger/10 text-danger",
63 checks_failing: "border-danger/35 bg-danger/10 text-danger",
64 outside_guardrails: "border-warn/35 bg-warn/10 text-warn",
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step65 low_confidence: "border-warn/35 bg-warn/10 text-warn",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms66 stalled: "border-warn/35 bg-warn/10 text-warn",
Merge branch 'worktree-agent-ad7c6d88d93adc817'67 asked_for_you: "border-accent/35 bg-accent/10 text-accent",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms68 needs_review: "border-info/35 bg-info/10 text-info",
Merge branch 'worktree-agent-ad7c6d88d93adc817'69 ready_to_merge: "border-success/35 bg-success/10 text-success",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms70};
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look71
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms72function Chip({ children, tone }: { children: ReactNode; tone: string }) {
73 return (
74 <span
75 className={cn(
76 "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",
77 tone,
78 )}
79 >
80 {children}
81 </span>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look82 );
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms83}
84
85/** A person or an agent, by name, wearing the mark that tells them apart. */
86function Person({ who, size = 16, className }: { who: Who; size?: number; className?: string }) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look87 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms88 <span className={cn("inline-flex min-w-0 items-center gap-1.5", className)}>
89 <Avatar name={who.name} size={size} />
90 <span className="truncate">{who.name}</span>
91 </span>
92 );
93}
94
95function Eyebrow({ children }: { children: ReactNode }) {
96 return <p className="font-mono text-[0.625rem] font-medium tracking-[0.12em] text-faint uppercase">{children}</p>;
97}
98
Merge branch 'worktree-agent-ad7c6d88d93adc817'99const FACT_TONE = { good: "text-success", warn: "text-warn", bad: "text-danger" } as const;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms100
101function Facts({ facts }: { facts: Fact[] }) {
102 if (facts.length === 0) return <p className="text-sm text-muted">Nothing more is known about it yet.</p>;
103 return (
104 <dl className="grid grid-cols-2 gap-x-4 gap-y-2.5">
105 {facts.map((fact) => (
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step106 <div key={fact.label} className={cn("min-w-0", fact.wide && "col-span-2")}>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms107 <dt className="text-xs text-faint">{fact.label}</dt>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step108 <dd
109 className={cn(
110 "mt-0.5 text-sm font-medium tabular-nums",
111 fact.wide ? "leading-5" : "truncate",
112 fact.tone ? FACT_TONE[fact.tone] : "text-fg-soft",
113 )}
114 >
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms115 {fact.value}
116 </dd>
117 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look118 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms119 </dl>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look120 );
121}
122
Merge branch 'worktree-agent-a985c01f43538af01'123/**
124 * A form that acts on a pull request from here: working until the action
125 * has answered and the page has reloaded what it changed, then done, or
126 * the error beside it.
127 */
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms128function QuickForm({ quick, variant = "quiet" }: { quick: QuickAction; variant?: "quiet" | "accent" }) {
129 const fetcher = useFetcher<{ error?: string } | null>();
130 const [sent, setSent] = useState(false);
Merge branch 'worktree-agent-a985c01f43538af01'131 const done = sent && fetcher.state === "idle" && !fetcher.data?.error;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look132 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms133 <fetcher.Form method="post" action={quick.to} onSubmit={() => setSent(true)} className="contents">
134 {Object.entries(quick.fields).map(([name, value]) => (
135 <input key={name} type="hidden" name={name} value={value} />
136 ))}
Merge branch 'worktree-agent-a985c01f43538af01'137 <SubmitButton
138 fetcher={fetcher}
139 disabled={done}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms140 className={cn(
141 "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors disabled:cursor-default",
142 variant === "accent"
Merge branch 'worktree-agent-ad7c6d88d93adc817'143 ? "bg-accent text-bg hover:bg-accent-hover"
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms144 : "border border-line-strong text-fg/90 hover:bg-raised hover:text-fg",
Merge branch 'worktree-agent-ad7c6d88d93adc817'145 done && "border-success/40 text-success",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms146 )}
147 >
Fast pages, required checks on the branch, self-hosted runners, honest incidents148 {done ? <Check size={14} /> : null}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms149 {done ? quick.done : quick.label}
Merge branch 'worktree-agent-a985c01f43538af01'150 </SubmitButton>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms151 {fetcher.data?.error && <span className="basis-full text-xs text-danger">{fetcher.data.error}</span>}
152 </fetcher.Form>
153 );
154}
155
156function ProjectMark({ repo }: { repo: { namespace: string; name: string } | null }) {
157 return <Avatar name={repo ? repo.name : "g1t"} size={32} square />;
158}
159
160/** A row that opens to what is known about it; the first one starts open. */
161function Row({
162 open,
163 repo,
164 by,
165 title,
166 sub,
167 chip,
168 at,
169 children,
170}: {
171 open: boolean;
172 repo: { namespace: string; name: string } | null;
173 by: Who | null;
174 title: ReactNode;
175 sub: ReactNode;
176 chip: ReactNode;
177 at: number;
178 children: ReactNode;
179}) {
180 return (
181 <li>
182 <details open={open} className="group">
183 <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">
184 <ProjectMark repo={repo} />
185 <span className="min-w-0 grow">
186 <span className="block truncate text-sm">{title}</span>
187 <span className="mt-0.5 flex min-w-0 items-center gap-1.5 text-xs text-muted">
188 <span className="shrink-0 font-medium text-fg-soft">{repo ? repo.name : "Workspace"}</span>
189 {by && (
190 <>
191 <span className="text-faint">·</span>
Merge branch 'worktree-agent-ad7c6d88d93adc817'192 <Person who={by} size={14} className={cn("shrink-0", by.agent ? "text-accent" : "")} />
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms193 </>
194 )}
195 {sub && (
196 <>
197 <span className="text-faint">—</span>
198 <span className="truncate">{sub}</span>
199 </>
200 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look201 </span>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms202 </span>
203 <span className="hidden sm:inline-flex">{chip}</span>
204 <span className="hidden w-16 shrink-0 text-right text-xs text-faint sm:block">
205 <TimeAgo at={at} />
206 </span>
207 <ChevronDown size={16} className="shrink-0 text-faint transition-transform group-open:rotate-180" />
208 </summary>
209 <div className="px-4 pb-4 sm:px-5 sm:pb-5">
210 <div className="mb-3 flex items-center gap-2 sm:hidden">
211 {chip}
212 <span className="text-xs text-faint">
213 <TimeAgo at={at} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look214 </span>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms215 </div>
216 {children}
217 </div>
218 </details>
219 </li>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look220 );
221}
222
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms223function NeedCard({ row, first }: { row: NeedRow; first: boolean }) {
224 const isPull = row.to.includes("/pull/");
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look225 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms226 <Row
227 open={first}
228 repo={row.repo}
229 by={row.by}
230 title={
231 <>
232 <span className="font-medium">{row.title}</span>
233 {row.ref && <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>}
234 </>
235 }
236 sub={row.ask}
237 chip={<Chip tone={CHIP_TONE[row.reason]}>{REASON_LABEL[row.reason]}</Chip>}
238 at={row.at}
239 >
240 <div className="grid gap-4 rounded-xl border border-line 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)]">
241 <div className="min-w-0">
242 <Eyebrow>The ask</Eyebrow>
243 <p className="mt-2 text-sm leading-6 text-fg-soft">{row.ask}</p>
244 <p className="mt-2 text-xs leading-5 text-muted">
245 <Link to={row.to} className="font-medium text-fg hover:underline">
246 {row.repo ? `${row.repo.name}${row.ref ?? ""}` : row.title}
247 </Link>
248 {row.for && (
249 <>
250 {" "}
251 · started for <span className="text-fg-soft">{row.for}</span>
252 </>
253 )}
254 </p>
255 </div>
256 <div className="min-w-0">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit257 <Eyebrow>{row.by?.agent ? "What the agent already knows" : "What is known"}</Eyebrow>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms258 <div className="mt-2">
259 <Facts facts={row.facts} />
260 </div>
261 </div>
262 <div className="rounded-lg border border-warn/20 bg-warn/[0.06] p-3.5 md:col-span-2 2xl:col-span-1">
263 <p className="text-xs font-semibold text-warn">Why this needs you</p>
264 <p className="mt-1.5 text-sm leading-6 text-fg-soft">{row.why}</p>
265 <p className="mt-2 text-xs text-faint">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit266 Started waiting <TimeAgo at={row.at} />
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms267 </p>
268 </div>
269 </div>
270 <div className="mt-3 flex flex-wrap items-center gap-2">
271 <Link
272 to={row.to}
273 prefetch="intent"
274 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-white"
275 >
276 Review and respond <ArrowUpRight size={14} />
277 </Link>
278 {row.quick && <QuickForm quick={row.quick} />}
279 {row.link && (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look280 <Link
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms281 to={row.link.to}
282 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"
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look283 >
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms284 {row.link.label}
285 </Link>
286 )}
287 {row.open !== row.to && (
288 <Link to={row.open} prefetch="intent" className="px-2 py-1.5 text-sm text-muted hover:text-fg">
289 {row.open.includes("/issues/") ? "Open issue" : isPull ? "See the changes" : "Open pull request"}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look290 </Link>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms291 )}
292 </div>
293 </Row>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look294 );
295}
296
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms297function WaitingCard({ row, first }: { row: WaitingRow; first: boolean }) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look298 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms299 <Row
300 open={first}
301 repo={row.repo}
302 by={row.by}
303 title={
304 <>
305 <span className="font-medium">{row.title}</span>
306 {row.ref && <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>}
307 </>
308 }
309 sub={row.detail}
310 chip={
311 <Chip tone="border-line-strong bg-raised text-muted">
Merge branch 'worktree-agent-ad7c6d88d93adc817'312 {row.live && <span className="mr-1.5 size-1.5 animate-pulse rounded-full bg-success" />}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms313 {row.chip}
314 </Chip>
315 }
316 at={row.at}
317 >
318 <div className="grid gap-4 rounded-xl border border-line bg-bg/50 p-4 md:grid-cols-2 md:gap-6">
319 <div className="min-w-0">
320 <Eyebrow>{row.live ? "Doing now" : "Where it stands"}</Eyebrow>
321 <p className="mt-2 text-sm leading-6 text-fg-soft">{row.detail}</p>
322 </div>
323 <div className="min-w-0">
324 <Eyebrow>What is known</Eyebrow>
325 <div className="mt-2">
326 <Facts facts={row.facts} />
327 </div>
328 </div>
329 </div>
330 <div className="mt-3 flex flex-wrap gap-2">
331 <Link
332 to={row.to}
333 prefetch="intent"
334 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"
335 >
336 {row.to.includes("/runs/") ? "Watch the run" : "Open pull request"} <ArrowUpRight size={14} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look337 </Link>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms338 {row.run && row.run !== row.to && (
339 <Link to={row.run} prefetch="intent" className="px-2 py-1.5 text-sm text-muted hover:text-fg">
340 Watch the run
341 </Link>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look342 )}
343 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms344 </Row>
345 );
346}
347
348function LandedCard({ row, first }: { row: LandedRow; first: boolean }) {
349 return (
350 <Row
351 open={first}
352 repo={row.repo}
353 by={row.byAgents ? { name: row.agent, agent: isAgent(row.agent) } : row.by}
354 title={
355 <>
356 <span className="font-medium">{row.title}</span>
357 <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>
358 </>
359 }
360 sub={row.byAgents ? "landed without a person" : `merged by ${row.by?.name ?? "a person"}`}
361 chip={
362 row.byAgents ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'363 <Chip tone="border-accent/35 bg-accent/10 text-accent">By agents</Chip>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look364 ) : (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms365 <Chip tone="border-warn/35 bg-warn/10 text-warn">Needed a person</Chip>
366 )
367 }
368 at={row.at}
369 >
370 <div className="grid gap-4 rounded-xl border border-line bg-bg/50 p-4 md:grid-cols-2 md:gap-6">
371 <div className="min-w-0">
372 <Eyebrow>How it landed</Eyebrow>
373 <p className="mt-2 text-sm leading-6 text-fg-soft">
374 {row.byAgents
375 ? `g1t merged it once everything the repository asks for was met, with no one pressing merge.`
376 : `${row.by?.name ?? "A person"} merged it.`}
377 {row.agent && isAgent(row.agent) && ` ${row.agent} made the change.`}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look378 </p>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms379 </div>
380 <div className="min-w-0">
381 <Eyebrow>What changed</Eyebrow>
382 <div className="mt-2">
383 <Facts facts={row.facts} />
384 </div>
385 </div>
386 </div>
387 <div className="mt-3">
388 <Link
389 to={row.to}
390 prefetch="intent"
391 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"
392 >
393 Open pull request <ArrowUpRight size={14} />
394 </Link>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look395 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms396 </Row>
397 );
398}
399
400function List({ children }: { children: ReactNode }) {
401 return <ul className="divide-y divide-line">{children}</ul>;
402}
403
404function Empty({ children, action }: { children: ReactNode; action?: ReactNode }) {
405 return (
406 <div className="px-5 py-10 text-center">
407 <div className="mx-auto max-w-md text-sm leading-6 text-muted">{children}</div>
408 {action && <div className="mt-4 flex flex-wrap justify-center gap-2">{action}</div>}
409 </div>
410 );
411}
412
413// --- The stat strip -----------------------------------------------------------------
414
415function Stat({ label, value, hint, dot, title }: { label: string; value: string; hint: ReactNode; dot?: string; title?: string }) {
416 return (
Merge branch 'worktree-agent-ae1299e92e4462012'417 <Hint label={title}>
418 <div className="min-w-0 bg-surface px-4 py-3.5 sm:px-5 sm:py-4">
419 <p className="flex items-center gap-1.5 truncate text-xs text-muted">
420 {dot && <span className={cn("size-1.5 shrink-0 rounded-full", dot)} />}
421 {label}
422 </p>
423 <p className="mt-1 font-display text-2xl font-semibold tracking-tight tabular-nums sm:text-[1.75rem]">{value}</p>
424 <p className="mt-0.5 truncate text-xs text-faint">{hint}</p>
425 </div>
426 </Hint>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms427 );
428}
429
430// --- This week ----------------------------------------------------------------------
431
432/** Seven days of landed changes, each split into what agents landed alone and what a person merged. */
433function WeekChart({ week }: { week: Week }) {
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar434 const max = Math.max(1, ...week.days.map((d) => d.agents + d.assisted + d.people));
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms435 const height = 112;
436 const delta = change(week.total, week.previous);
437 return (
438 <div>
439 <div className="flex items-end justify-between gap-3">
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look440 <div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms441 <p className="font-display text-3xl font-semibold tracking-tight tabular-nums">{week.total.toLocaleString("en-US")}</p>
442 <p className="text-xs text-muted">changes landed in 7 days</p>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look443 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms444 {delta != null ? (
445 <span
446 className={cn(
447 "rounded-full px-2 py-0.5 text-xs font-medium tabular-nums ring-1",
Merge branch 'worktree-agent-ad7c6d88d93adc817'448 delta >= 0 ? "text-success ring-success/30" : "text-warn ring-warn/30",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms449 )}
450 >
451 {signedPercent(delta)} vs last week
452 </span>
453 ) : week.previous === 0 && week.total > 0 ? (
454 <span className="text-xs text-faint">none the week before</span>
455 ) : null}
456 </div>
457 {week.total === 0 ? (
458 <p className="mt-4 rounded-lg border border-dashed border-line px-4 py-6 text-center text-sm leading-6 text-muted">
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar459 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.
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms460 </p>
461 ) : (
462 <div className="relative mt-5" style={{ height: height + 20 }}>
463 <div className="absolute inset-x-0 border-t border-line" style={{ top: height }} />
464 <div className="absolute inset-x-0 top-0 flex items-end justify-between gap-1" style={{ height }}>
465 {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 bar466 const total = day.agents + day.assisted + day.people;
467 const bar = (n: number) => Math.max(3, Math.round((n / max) * (height - 4)));
468 // Top to bottom: agents alone, agents with a person, people.
469 const segments = [
Merge branch 'worktree-agent-ad7c6d88d93adc817'470 { 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 bar471 { n: day.assisted, tone: "bg-warn" },
472 { n: day.people, tone: "bg-info" },
473 ].filter((segment) => segment.n > 0);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms474 const today = index === week.days.length - 1;
475 return (
476 <div key={day.key} className="group relative flex h-full flex-1 flex-col items-center justify-end">
477 {total > 0 && (
478 <span
479 className={cn(
480 "mb-1 text-[0.625rem] tabular-nums",
481 today ? "text-fg-soft" : "text-faint opacity-0 group-hover:opacity-100",
482 )}
483 >
484 {total}
485 </span>
486 )}
487 <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 bar488 {segments.map((segment, at) => (
489 <span key={segment.tone} className={cn("block", segment.tone, at === 0 && "rounded-t")} style={{ height: bar(segment.n) }} />
490 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms491 {total === 0 && <span className="block h-[2px] rounded-full bg-line-strong" />}
492 </div>
493 <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 group-hover:block">
494 <p className="font-medium text-fg">{day.label}</p>
495 <p className="mt-0.5 flex items-center gap-1.5 text-muted">
Merge branch 'worktree-agent-ad7c6d88d93adc817'496 <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 bar497 </p>
498 <p className="flex items-center gap-1.5 text-muted">
499 <span className="size-1.5 rounded-full bg-warn" /> {day.assisted} by agents, merged by a person
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms500 </p>
501 <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 bar502 <span className="size-1.5 rounded-full bg-info" /> {day.people} by people
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms503 </p>
504 </div>
505 </div>
506 );
507 })}
508 </div>
509 <div className="absolute inset-x-0 bottom-0 flex justify-between gap-1">
510 {week.days.map((day, index) => (
511 <span
512 key={day.key}
513 className={cn(
514 "flex-1 text-center text-[0.6875rem]",
515 index === week.days.length - 1 ? "font-medium text-fg-soft" : "text-faint",
516 )}
517 >
518 {day.label}
519 </span>
520 ))}
521 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look522 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms523 )}
524 <div className={cn("mt-3 flex-wrap gap-x-4 gap-y-1 text-xs text-muted", week.total === 0 ? "hidden" : "flex")}>
525 <span className="inline-flex items-center gap-1.5">
Merge branch 'worktree-agent-ad7c6d88d93adc817'526 <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 bar527 </span>
528 <span className="inline-flex items-center gap-1.5">
529 <span className="size-2 rounded-sm bg-warn" /> Agents, merged by a person
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms530 </span>
531 <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 bar532 <span className="size-2 rounded-sm bg-info" /> People
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms533 </span>
534 </div>
Fixes from the page sweep535 {/* A table does not shrink to sr-only's 1px; its wrapper does. */}
536 <div className="sr-only">
537 <table>
538 <caption>Changes landed each day</caption>
539 <thead>
540 <tr>
541 <th>Day</th>
542 <th>Agents, on their own</th>
543 <th>Agents, merged by a person</th>
544 <th>People</th>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms545 </tr>
Fixes from the page sweep546 </thead>
547 <tbody>
548 {week.days.map((day) => (
549 <tr key={day.key}>
550 <td>{day.key}</td>
551 <td>{day.agents}</td>
552 <td>{day.assisted}</td>
553 <td>{day.people}</td>
554 </tr>
555 ))}
556 </tbody>
557 </table>
558 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms559 </div>
560 );
561}
562
563// --- Activity -----------------------------------------------------------------------
564
565const VERB: Record<Verb, string> = {
566 landed: "landed",
567 opened_issue: "opened",
568 closed_issue: "closed",
569 started: "started on",
570 ready: "marked ready",
571 checks_passed: "checks passed on",
572 checks_failed: "checks failed on",
573 approved: "approved",
574 changes_requested: "asked for changes on",
575 commented: "commented on",
576 asked: "was asked about",
577 deployed: "deployed production",
578 deploy_failed: "production build failed",
579 learned: "learned",
580};
581
582/** One line of the feed: who, what, and the one thing it was about, by name. */
583function FeedLine({ group, titles }: { group: ActivityGroup; titles: Record<string, string> }) {
584 const part = group.parts[0];
585 const base = `/${group.repo.namespace}/${group.repo.name}`;
586 const actor = group.actor ?? "g1t";
587 const agent = group.actor == null || isAgent(group.actor);
588 const number = part.numbers[0];
589 const title = number != null ? titles[`${group.repo.namespace}/${group.repo.name}#${number}`.toLowerCase()] : undefined;
590 const more = group.parts.length - 1 + Math.max(0, part.numbers.length - 1);
591 return (
592 <li className="flex gap-2.5 py-2">
593 <span className="mt-px">
594 <Avatar name={actor} size={22} />
595 </span>
596 <span className="min-w-0 grow text-[0.8125rem] leading-5 text-muted">
Merge branch 'worktree-agent-ad7c6d88d93adc817'597 <span className={cn("font-medium", agent ? "text-accent" : "text-fg")}>{actor}</span> {VERB[part.verb]}{" "}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms598 {part.verb === "learned" ? (
599 <Link to={part.to ?? `${base}/memory`} className="text-fg-soft hover:text-fg">
600 “{part.texts[0]}”
601 </Link>
602 ) : number != null ? (
603 <Link to={`${base}/issues/${number}`} prefetch="intent" className="text-fg-soft hover:text-fg">
604 <span className="font-mono text-xs">#{number}</span>
605 {title && <> {title}</>}
606 </Link>
607 ) : part.to ? (
608 <Link to={part.to} className="text-fg-soft hover:text-fg">
609 {group.repo.name}
610 </Link>
611 ) : null}
612 {more > 0 && <span className="text-faint"> and {more} more</span>}
613 <span className="block text-xs text-faint">
614 <span className="font-mono">{group.repo.name}</span> · <TimeAgo at={group.at} />
615 </span>
616 </span>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look617 </li>
618 );
619}
620
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms621// --- Get started --------------------------------------------------------------------
622
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look623type Step = { done: boolean; title: string; about: string; to: string | null; action: string };
624
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms625/** The first things to do, ticked off as they are done, until work has been handed to agents. */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look626function GetStarted({ steps }: { steps: Step[] }) {
627 const left = steps.filter((step) => !step.done).length;
628 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms629 <section className="rounded-xl border border-line bg-surface p-5">
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look630 <div className="flex items-baseline justify-between gap-3">
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms631 <h2 className="text-base font-semibold tracking-tight">Get started</h2>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look632 <span className="text-xs text-muted">
633 {steps.length - left} of {steps.length} done
634 </span>
635 </div>
636 <ol className="mt-4 space-y-2">
637 {steps.map((step, index) => (
638 <li
639 key={step.title}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms640 className={cn("flex items-start gap-3 rounded-lg px-3 py-2.5", step.done ? "" : "bg-bg/50 ring-1 ring-line")}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look641 >
642 <span
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms643 className={cn(
644 "mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full text-[0.6875rem] font-medium",
Merge branch 'worktree-agent-ad7c6d88d93adc817'645 step.done ? "bg-success text-bg" : "text-muted ring-1 ring-line-strong",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms646 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look647 >
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms648 {step.done ? <Check size={12} /> : index + 1}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look649 </span>
650 <span className="min-w-0 grow">
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms651 <span className={cn("block text-sm font-medium", step.done && "text-muted line-through decoration-faint")}>{step.title}</span>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look652 {!step.done && <span className="mt-0.5 block text-xs leading-5 text-muted">{step.about}</span>}
653 </span>
654 {!step.done && step.to && (
655 <Link
656 to={step.to}
657 className="shrink-0 rounded-md bg-fg px-2.5 py-1 text-xs font-medium text-bg transition-colors hover:bg-white"
658 >
659 {step.action}
660 </Link>
661 )}
662 </li>
663 ))}
664 </ol>
665 </section>
666 );
667}
668
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms669// --- The page -----------------------------------------------------------------------
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look670
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms671const TAB_LABEL: Record<Tab, string> = { needs: "Needs you", waiting: "Waiting on agents", landed: "Landed today" };
672const TAB_SHORT: Record<Tab, string> = { needs: "Needs you", waiting: "Waiting", landed: "Today" };
673const SORT_LABEL: Record<Sort, string> = { impact: "By impact", newest: "Newest" };
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look674
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step675export default function MissionControl({ loaderData, delegated = null }: { loaderData: Loaded; delegated?: ComposerResult }) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look676 const shell = useRouteLoaderData("root")?.shell as ShellData | null | undefined;
677 const loaded: Loaded = loaderData;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms678 const [params] = useSearchParams();
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look679 // Agents are at work, so the page changes without anyone touching it.
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms680 useLiveRefresh(loaded.changing);
681 const [greeting, setGreeting] = useState(loaded.greeting);
682 const [date, setDate] = useState(loaded.date);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look683 useEffect(() => {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms684 // The greeting and date follow the viewer's own clock, and the server
685 // learns their zone for next time.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look686 setGreeting(greetingFor(new Date().getHours()));
687 try {
688 const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms689 setDate(dateLine(Date.now(), zone ?? null));
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look690 if (zone) document.cookie = `${TZ_COOKIE}=${encodeURIComponent(zone)}; Path=/; Max-Age=31536000; SameSite=Lax`;
691 } catch {
692 // Nothing to remember.
693 }
694 }, []);
695
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms696 const { viewer, repos, waiting, landed, liveTotal, week, groups, titles, stats, canRunAgents, trial } = loaded;
697 const workspace = shell?.workspace?.slug ?? loaded.workspace ?? viewer.workspaces?.[0]?.slug ?? null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look698
699 // The usage limit, as the sidebar knows it, heads the list when it bites.
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms700 const limit = shell?.limit && !shell.limit.comped && shell.limit.state !== "ok" && workspace ? shell.limit : null;
701 const limitRow: NeedRow[] = limit
702 ? [
703 (() => {
704 const detail =
705 limit.state === "stopped"
706 ? "No new agent starts until the limit is raised or a card is added."
707 : `${dollars(limit.exposureMicros)} of ${dollars(limit.ceilingMicros ?? 0)} used. Add a card so work keeps going.`;
708 return {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look709 key: "limit",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms710 reason: "blocking" as const,
711 repo: null,
712 ref: null,
713 title: limit.state === "stopped" ? "Agents are stopped: the usage limit was reached" : "Usage is nearing its limit",
714 ask: detail,
715 by: null,
716 for: null,
717 at: Date.now(),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look718 to: `/${workspace}/-/billing`,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms719 open: `/${workspace}/-/billing`,
720 facts: [
721 { label: "Used", value: `${dollars(limit.exposureMicros)} of ${dollars(limit.ceilingMicros ?? 0)}`, tone: "warn" as const },
722 ],
723 why: whyFor("blocking", { kind: "limit", detail }),
724 quick: null,
725 link: null,
726 };
727 })(),
728 ]
729 : [];
730 const needs = [...limitRow, ...loaded.needs];
731 const blocking = needs.filter((row) => BLOCKING.has(row.reason)).length;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look732
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms733 const counts: Record<Tab, number> = { needs: needs.length, waiting: waiting.length, landed: landed.length };
734 const tab = parseTab(params.get("tab")) ?? "needs";
735 const sort = parseSort(params.get("sort"));
736 const all = params.get("all") === "1";
737 const everyActivity = params.get("activity") === "all";
738 const link = (changes: Record<string, string | null>) => {
739 const next = new URLSearchParams(params);
740 for (const [key, value] of Object.entries(changes)) {
741 if (value == null) next.delete(key);
742 else next.set(key, value);
743 }
744 const text = next.toString();
745 return text ? `?${text}` : "/";
746 };
747
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look748 const steps: Step[] = [
749 {
750 done: Boolean(workspace),
751 title: "Create a workspace",
752 about: "Projects, people and agent credit live in one.",
753 to: "/workspaces/new",
754 action: "Create",
755 },
756 {
757 done: repos.length > 0,
758 title: "Add a project",
759 about: "Create a repository, or import one by its address. Push to it with git as usual.",
760 to: workspace ? `/new?workspace=${workspace}` : "/new",
761 action: "Add",
762 },
763 trial?.open
764 ? {
765 done: true,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent766 title: "Try g1t free",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look767 about: trial.granted
768 ? `This workspace has ${dollars(trial.limitMicros - trial.usedMicros)} of its ${dollars(trial.limitMicros)} trial credit left, for g1t's models and sandboxes.`
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent769 : `This workspace gets ${dollars(trial.limitMicros)} of trial credit, for g1t's models and sandboxes, the first time g1t works here.`,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look770 to: workspace ? `/${workspace}/-/integrations` : null,
771 action: "Connect",
772 }
773 : canRunAgents
774 ? {
775 done: Boolean(shell?.free) || shell?.limit == null || shell.limit.comped || shell.limit.state === "ok",
776 title: "Keep work running",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms777 about:
778 "Usage is charged after it runs. Add a card under Billing, so g1t charges it as you near your limit instead of stopping work.",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look779 to: workspace ? `/${workspace}/-/billing` : null,
780 action: "Billing",
781 }
782 : {
783 done: false,
784 title: "Connect a model",
785 about: `${
786 trialClosed(trial, workspace ?? "This workspace") ? `${trialClosed(trial, workspace ?? "This workspace")} ` : ""
787 }Agents need a model to think with. Connect your Anthropic or OpenAI key, or any compatible endpoint.`,
788 to: workspace ? `/${workspace}/-/integrations` : null,
789 action: "Connect",
790 },
791 {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms792 done: loaded.handedOff,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look793 title: "Hand off an outcome",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms794 about:
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent795 "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.",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms796 to: repos[0] ? `/${repos[0].namespace}/${repos[0].name}/plans` : null,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look797 action: "Write one",
798 },
799 ];
800 const starting = steps.some((step) => !step.done);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms801 const noProjects = repos.length === 0;
802
803 const rows = tab === "needs" ? sortRows(needs, sort) : tab === "waiting" ? sortRows(waiting, sort) : sortRows(landed, sort);
804 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 bar805 // Of the agents' own changes only: people's work is not theirs to land.
806 const share = week.agentChanges > 0 ? week.byAgents / week.agentChanges : null;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms807 const delta = change(week.total, week.previous);
808 const feed = everyActivity ? groups : groups.slice(0, 8);
809
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step810 // "Put an agent on it" first, with "New issue" beside it as before: one
811 // split control, the agent the main way in.
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms812 const newIssue =
813 repos.length > 0 ? (
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step814 <div className="flex items-stretch">
815 <AgentComposer
816 repos={repos}
817 open={params.get("agent") === "new" || delegated != null}
818 result={delegated}
819 note={
820 canRunAgents ? null : (
821 <>
822 Agents need a model first.{" "}
823 {workspace && (
824 <Link to={`/${workspace}/-/integrations`} className="font-medium text-fg hover:underline">
825 Connect one
826 </Link>
827 )}
828 . The issue still opens.
829 </>
830 )
831 }
832 >
833 <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'834 <Sparkles size={14} className="text-accent" /> Put an agent on it
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step835 </span>
836 </AgentComposer>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms837 <DropdownMenu>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step838 <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">
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms839 <Plus size={14} /> New issue
840 </DropdownMenuTrigger>
841 <DropdownMenuContent align="end" className="max-h-80 overflow-y-auto">
842 <DropdownMenuLabel>In which project?</DropdownMenuLabel>
843 {repos.map((repo) => (
844 <DropdownMenuItem key={repo.name} asChild>
845 <Link to={`/${repo.namespace}/${repo.name}/issues/new`}>
846 <Avatar name={repo.name} size={18} square />
847 {repo.name}
848 </Link>
849 </DropdownMenuItem>
850 ))}
851 </DropdownMenuContent>
852 </DropdownMenu>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step853 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms854 ) : (
855 <Link
856 to={workspace ? `/new?workspace=${workspace}` : "/new"}
857 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"
858 >
859 <Plus size={14} /> New project
860 </Link>
861 );
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look862
863 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms864 <main className="mx-auto max-w-7xl space-y-6 px-4 py-8 sm:px-6 sm:py-10">
865 <header className="flex flex-wrap items-end justify-between gap-x-6 gap-y-4">
866 <div className="min-w-0">
867 <h1 className="text-[1.75rem] leading-tight font-semibold tracking-tight sm:text-[2rem]" suppressHydrationWarning>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look868 {greeting}, {loaded.name}
869 </h1>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms870 <p className="mt-1.5 text-sm text-muted" suppressHydrationWarning>
871 <span className="text-fg-soft">{date}</span> · {loaded.summary}
872 </p>
873 </div>
874 </header>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look875
Previews build from any branch; Agent in place of Ask AI; pin from the sidebar876 {/* The agent is not on yet; the actions under it do the work today. */}
Mission control: a place to ask g1t, shown and not on yet, with the actions under it877 <AskComposer>
878 {newIssue}
879 {repos.length > 0 && (
880 <DropdownMenu>
881 <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">
882 <ListTree size={14} /> Plan work
883 </DropdownMenuTrigger>
884 <DropdownMenuContent align="start" className="max-h-80 overflow-y-auto">
885 <DropdownMenuLabel>Plan in which project?</DropdownMenuLabel>
886 {repos.map((repo) => (
887 <DropdownMenuItem key={repo.name} asChild>
888 <Link to={`/${repo.namespace}/${repo.name}/plans`}>
889 <Avatar name={repo.name} size={18} square />
890 {repo.name}
891 </Link>
892 </DropdownMenuItem>
893 ))}
894 </DropdownMenuContent>
895 </DropdownMenu>
896 )}
897 {needs.length > 0 && (
898 <Link
899 to={`${link({ tab: "needs", all: null })}#work`}
900 preventScrollReset
Merge branch 'worktree-agent-ad7c6d88d93adc817'901 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 it902 >
903 Review {needs.length} that need{needs.length === 1 ? "s" : ""} you <ArrowRight size={14} />
904 </Link>
905 )}
906 </AskComposer>
907
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms908 {starting && <GetStarted steps={steps} />}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look909
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms910 <section
911 aria-label="At a glance"
912 className="grid grid-cols-2 gap-px overflow-hidden rounded-xl border border-line bg-line lg:grid-cols-5"
913 >
914 <Stat
915 label="Projects"
916 value={stats.projects == null ? "—" : String(stats.projects)}
917 hint={
918 stats.projectsThisMonth == null
919 ? "in this workspace"
920 : stats.projectsThisMonth > 0
921 ? `+${stats.projectsThisMonth} this month`
922 : "none new this month"
923 }
924 />
925 <Stat
926 label="Agents"
927 value={loaded.runsLoaded || !workspace ? String(liveTotal) : "—"}
Merge branch 'worktree-agent-ad7c6d88d93adc817'928 dot={liveTotal > 0 ? "bg-success animate-pulse" : undefined}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms929 hint={
930 liveTotal > 0
931 ? `live now · ${stats.agentHours < 10 ? stats.agentHours.toFixed(1) : Math.round(stats.agentHours)}h this week`
932 : stats.agentHours > 0
933 ? `none live · ${stats.agentHours < 10 ? stats.agentHours.toFixed(1) : Math.round(stats.agentHours)}h this week`
934 : "none live now"
935 }
936 />
937 <Stat
938 label="Changes this week"
939 value={loaded.perRepoLoaded ? String(week.total) : "—"}
940 hint={
941 delta != null ? `${signedPercent(delta)} vs last week` : week.previous === 0 ? "none the week before" : "merged pull requests"
942 }
943 />
944 <Stat
945 label="Landed without you"
Merge branch 'worktree-agent-ad7c6d88d93adc817'946 dot="bg-accent"
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms947 value={share == null ? "—" : `${Math.round(share * 100)}%`}
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar948 hint={share == null ? "no agent changes yet" : `${week.byAgents} of ${week.agentChanges} agent changes`}
949 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."
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms950 />
951 <div className="col-span-2 lg:col-span-1">
952 <Stat
Usage, Billing settings and prepaid AI credit; fixes from the UX audit953 label="Needs you"
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms954 dot="bg-warn"
955 value={String(needs.length)}
956 hint={blocking > 0 ? `${blocking} blocking` : needs.length > 0 ? "nothing blocking" : "all clear"}
957 />
958 </div>
959 </section>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look960
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms961 <div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_21rem] xl:grid-cols-[minmax(0,1fr)_23rem]">
962 <section id="work" className="min-w-0 scroll-mt-20 self-start overflow-hidden rounded-xl border border-line bg-surface">
963 <div className="flex items-center gap-2 border-b border-line px-2 sm:px-3">
964 <nav className="-mb-px flex min-w-0 grow gap-1 overflow-x-auto [scrollbar-width:none]" aria-label="Mission control">
965 {(["needs", "waiting", "landed"] as const).map((value) => (
966 <Link
967 key={value}
968 to={link({ tab: value, all: null })}
969 preventScrollReset
970 aria-current={tab === value ? "page" : undefined}
971 className={cn(
972 "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",
Merge branch 'worktree-agent-ad7c6d88d93adc817'973 tab === value ? "border-accent font-medium text-fg" : "border-transparent text-muted hover:text-fg",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms974 )}
975 >
976 <span className="sm:hidden">{TAB_SHORT[value]}</span>
977 <span className="hidden sm:inline">{TAB_LABEL[value]}</span>
978 <span
979 className={cn(
980 "rounded-full px-1.5 text-[0.6875rem] tabular-nums",
981 tab === value && value === "needs" && counts.needs > 0 ? "bg-warn/15 text-warn" : "bg-line text-muted",
982 )}
983 >
984 {counts[value]}
985 </span>
986 </Link>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look987 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms988 </nav>
989 <DropdownMenu>
990 <DropdownMenuTrigger
991 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"
992 aria-label="Sort"
993 >
994 <ArrowDownWideNarrow size={14} />
995 <span className="hidden sm:inline">{SORT_LABEL[sort]}</span>
996 </DropdownMenuTrigger>
997 <DropdownMenuContent align="end">
998 {(["impact", "newest"] as const).map((value) => (
999 <DropdownMenuItem key={value} asChild>
1000 <Link to={link({ sort: value === "impact" ? null : value })} preventScrollReset>
1001 {sort === value ? <Check /> : <span className="size-4" />}
1002 {SORT_LABEL[value]}
1003 </Link>
1004 </DropdownMenuItem>
1005 ))}
1006 </DropdownMenuContent>
1007 </DropdownMenu>
1008 </div>
1009
1010 {!loaded.perRepoLoaded && tab !== "waiting" ? (
1011 <div className="p-4">
1012 <Unavailable what={tab === "needs" ? "Some of what needs you" : "What landed"} />
1013 </div>
1014 ) : null}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1015
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1016 {tab === "needs" &&
1017 (needs.length === 0 ? (
1018 noProjects ? (
1019 <Empty
1020 action={
1021 <Link
1022 to={workspace ? `/new?workspace=${workspace}` : "/new"}
1023 className="rounded-md bg-fg px-3 py-1.5 text-sm font-medium text-bg hover:bg-white"
1024 >
1025 Create or import a project
1026 </Link>
1027 }
1028 >
1029 <p className="font-medium text-fg">No projects yet</p>
1030 <p className="mt-1">
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1031 Create a repository or import one. Assign its issues to g1t, and what needs you shows up here.
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1032 </p>
1033 </Empty>
1034 ) : (
1035 <>
1036 <Empty>
1037 <p className="font-medium text-fg">Nothing needs you</p>
1038 <p className="mt-1">
1039 Agents are handling everything.{" "}
1040 {landed.length > 0
1041 ? "Here's what they landed today."
1042 : "Reviews, stopped work and failed deploys show up here first."}
1043 </p>
1044 </Empty>
1045 {landed.length > 0 && (
1046 <div className="border-t border-line">
1047 <List>
1048 {landed.slice(0, ROWS).map((row) => (
1049 <LandedCard key={row.key} row={row} first={false} />
1050 ))}
1051 </List>
1052 </div>
1053 )}
1054 </>
1055 )
1056 ) : (
1057 <List>
1058 {(shown as NeedRow[]).map((row, index) => (
1059 <NeedCard key={row.key} row={row} first={index === 0} />
1060 ))}
1061 </List>
1062 ))}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1063
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1064 {tab === "waiting" &&
1065 (!loaded.runsLoaded && workspace ? (
1066 <div className="p-4">
1067 <Unavailable what="Agent runs" />
1068 </div>
1069 ) : waiting.length === 0 ? (
1070 <Empty>
1071 <p className="font-medium text-fg">No agent is at work right now</p>
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1072 <p className="mt-1">Assign an issue to g1t and one starts on it in seconds. Its run shows here while it works.</p>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1073 </Empty>
1074 ) : (
1075 <List>
1076 {(shown as WaitingRow[]).map((row, index) => (
1077 <WaitingCard key={row.key} row={row} first={index === 0} />
1078 ))}
1079 </List>
1080 ))}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1081
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1082 {tab === "landed" &&
1083 loaded.perRepoLoaded &&
1084 (landed.length === 0 ? (
1085 <Empty>
1086 <p className="font-medium text-fg">Nothing has landed today yet</p>
1087 <p className="mt-1">
1088 {week.total > 0
1089 ? `${plural(week.total, "change")} landed in the last 7 days.`
1090 : "Merged pull requests show up here the moment they land."}
1091 </p>
1092 </Empty>
1093 ) : (
1094 <List>
1095 {(shown as LandedRow[]).map((row, index) => (
1096 <LandedCard key={row.key} row={row} first={index === 0} />
1097 ))}
1098 </List>
1099 ))}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1100
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1101 {rows.length > ROWS && (
1102 <div className="flex items-center justify-between border-t border-line px-4 py-2.5 text-xs text-muted sm:px-5">
1103 <span>
1104 Showing {shown.length} of {rows.length}
1105 </span>
1106 <Link
1107 to={link({ all: all ? null : "1" })}
1108 preventScrollReset
1109 className="inline-flex items-center gap-1 font-medium text-fg-soft hover:text-fg"
1110 >
1111 {all ? "Show fewer" : "See all"} <ChevronRight size={12} />
1112 </Link>
1113 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1114 )}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1115 </section>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1116
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1117 <aside className="min-w-0 space-y-6">
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all1118 <InboxNeedsCard items={loaderData.inboxNeeds.items} total={loaderData.inboxNeeds.total} />
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1119 <section className="rounded-xl border border-line bg-surface p-5">
1120 <div className="flex items-center justify-between">
1121 <h2 className="text-base font-semibold tracking-tight">This week</h2>
1122 {workspace && (
1123 <Link to={`/${workspace}/-/agents`} className="text-xs text-muted hover:text-fg">
1124 Fleet
1125 </Link>
1126 )}
1127 </div>
1128 <div className="mt-4">{loaded.perRepoLoaded ? <WeekChart week={week} /> : <Unavailable what="The week" />}</div>
1129 {workspace && stats.weekCost > 0 && (
1130 <Link
1131 to={`/${workspace}/-/usage`}
1132 className="mt-4 flex items-center justify-between border-t border-line pt-3 text-xs text-muted hover:text-fg"
1133 >
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1134 <span>Usage at price this week: {usd(stats.weekCost)}</span>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1135 <ChevronRight size={12} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1136 </Link>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1137 )}
1138 </section>
1139
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix1140 {workspace && (
1141 <TokenUsagePanel
1142 workspace={loaderData.tokens.workspace}
1143 mine={loaderData.tokens.mine}
1144 usageHref={`/${workspace}/-/usage`}
1145 />
1146 )}
1147
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1148 <section id="activity" className="scroll-mt-20 rounded-xl border border-line bg-surface p-5">
1149 <div className="flex items-center justify-between">
1150 <h2 className="text-base font-semibold tracking-tight">Activity</h2>
1151 {groups.length > 8 && (
1152 <Link
1153 to={`${link({ activity: everyActivity ? null : "all" })}#activity`}
1154 preventScrollReset
1155 className="inline-flex items-center gap-0.5 text-xs text-muted hover:text-fg"
1156 >
1157 {everyActivity ? "Less" : "All activity"} <ChevronRight size={12} />
1158 </Link>
1159 )}
1160 </div>
1161 <p className="mt-1 flex items-center gap-3 text-[0.6875rem] text-faint">
1162 <span className="inline-flex items-center gap-1.5">
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1163 <Avatar name="g1t" size={12} /> agents
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1164 </span>
1165 <span className="inline-flex items-center gap-1.5">
1166 <Avatar name={viewer.username} size={12} /> people
1167 </span>
1168 </p>
1169 {!loaded.perRepoLoaded ? (
1170 <div className="mt-3">
1171 <Unavailable what="Activity" />
1172 </div>
1173 ) : feed.length === 0 ? (
1174 <p className="mt-3 text-sm leading-6 text-muted">
1175 Nothing has moved yet. Merges, deploys, checks, reviews and what agents learn show up here.
1176 </p>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1177 ) : (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1178 <ol className="mt-2 divide-y divide-line/70">
1179 {feed.map((group) => (
1180 <FeedLine key={group.id} group={group} titles={titles} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1181 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1182 </ol>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1183 )}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1184 </section>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1185
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1186 <section className="rounded-xl border border-line bg-surface p-5">
1187 <h2 className="text-sm font-semibold">Connect your own agent</h2>
1188 <p className="mt-1.5 text-xs leading-5 text-muted">
1189 Add g1t to your coding agent. It signs in through your browser; there is no token to copy.
1190 </p>
1191 <AgentSetup className="mt-3" />
1192 <Link
1193 to="https://docs.g1t.sh/guides/bring-your-own-agent/"
1194 className="mt-3 inline-flex items-center gap-1 text-xs text-muted hover:text-fg"
1195 >
1196 How it works <ArrowRight size={12} />
1197 </Link>
1198 </section>
1199 </aside>
1200 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1201 </main>
1202 );
1203}

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