Skip to content

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

1,179 lines49,130 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.

Fast pages, required checks on the branch, self-hosted runners, honest incidents1import { ArrowDownWideNarrow, ArrowRight, ArrowUpRight, Check, ChevronDown, ChevronRight, 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";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look31import { AgentSetup } from "./agent-setup";
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all32import { InboxNeedsCard } from "./inbox";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look33import type { ShellData } from "./shell";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms34import { useLiveRefresh } from "./agents";
35import { Unavailable } from "./mission";
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix36import { TokenUsagePanel } from "./token-usage";
Merge branch 'worktree-agent-a985c01f43538af01'37import { Avatar, SubmitButton, TimeAgo } from "./ui";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms38import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger } from "./ui/dropdown-menu";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look39import type { Loaded } from "../routes/home";
40
41/*
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms42 * Mission control: the home page of someone signed in. It answers where
43 * the viewer is needed, what agents are doing, and what landed without
44 * them. It is a chunk of its own, loaded only for them, so the signed-out
45 * home page does not carry it. routes/home.tsx loads its data;
46 * lib/mission-control.ts shapes it.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look47 */
48
49/** The viewer's time zone, set by the page itself, so the greeting fits their day. */
50const TZ_COOKIE = "g1t_tz";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms51/** Rows a tab shows before "See all". */
52const ROWS = 6;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look53
54const 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 200ms55const 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 look56
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms57// --- Small pieces -----------------------------------------------------------------
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 200ms59const CHIP_TONE: Record<Reason, string> = {
60 blocking: "border-danger/35 bg-danger/10 text-danger",
61 checks_failing: "border-danger/35 bg-danger/10 text-danger",
62 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 step63 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 200ms64 stalled: "border-warn/35 bg-warn/10 text-warn",
65 asked_for_you: "border-merged/35 bg-merged/10 text-merged",
66 needs_review: "border-info/35 bg-info/10 text-info",
67 ready_to_merge: "border-accent/35 bg-accent/10 text-accent",
68};
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look69
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms70function Chip({ children, tone }: { children: ReactNode; tone: string }) {
71 return (
72 <span
73 className={cn(
74 "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",
75 tone,
76 )}
77 >
78 {children}
79 </span>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look80 );
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms81}
82
83/** A person or an agent, by name, wearing the mark that tells them apart. */
84function 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 look85 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms86 <span className={cn("inline-flex min-w-0 items-center gap-1.5", className)}>
87 <Avatar name={who.name} size={size} />
88 <span className="truncate">{who.name}</span>
89 </span>
90 );
91}
92
93function Eyebrow({ children }: { children: ReactNode }) {
94 return <p className="font-mono text-[0.625rem] font-medium tracking-[0.12em] text-faint uppercase">{children}</p>;
95}
96
97const FACT_TONE = { good: "text-accent", warn: "text-warn", bad: "text-danger" } as const;
98
99function Facts({ facts }: { facts: Fact[] }) {
100 if (facts.length === 0) return <p className="text-sm text-muted">Nothing more is known about it yet.</p>;
101 return (
102 <dl className="grid grid-cols-2 gap-x-4 gap-y-2.5">
103 {facts.map((fact) => (
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step104 <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 200ms105 <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 step106 <dd
107 className={cn(
108 "mt-0.5 text-sm font-medium tabular-nums",
109 fact.wide ? "leading-5" : "truncate",
110 fact.tone ? FACT_TONE[fact.tone] : "text-fg-soft",
111 )}
112 >
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms113 {fact.value}
114 </dd>
115 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look116 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms117 </dl>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look118 );
119}
120
Merge branch 'worktree-agent-a985c01f43538af01'121/**
122 * A form that acts on a pull request from here: working until the action
123 * has answered and the page has reloaded what it changed, then done, or
124 * the error beside it.
125 */
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms126function QuickForm({ quick, variant = "quiet" }: { quick: QuickAction; variant?: "quiet" | "accent" }) {
127 const fetcher = useFetcher<{ error?: string } | null>();
128 const [sent, setSent] = useState(false);
Merge branch 'worktree-agent-a985c01f43538af01'129 const done = sent && fetcher.state === "idle" && !fetcher.data?.error;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look130 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms131 <fetcher.Form method="post" action={quick.to} onSubmit={() => setSent(true)} className="contents">
132 {Object.entries(quick.fields).map(([name, value]) => (
133 <input key={name} type="hidden" name={name} value={value} />
134 ))}
Merge branch 'worktree-agent-a985c01f43538af01'135 <SubmitButton
136 fetcher={fetcher}
137 disabled={done}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms138 className={cn(
139 "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors disabled:cursor-default",
140 variant === "accent"
141 ? "bg-merged text-bg hover:bg-[#c9bfff]"
142 : "border border-line-strong text-fg/90 hover:bg-raised hover:text-fg",
143 done && "border-accent/40 text-accent",
144 )}
145 >
Fast pages, required checks on the branch, self-hosted runners, honest incidents146 {done ? <Check size={14} /> : null}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms147 {done ? quick.done : quick.label}
Merge branch 'worktree-agent-a985c01f43538af01'148 </SubmitButton>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms149 {fetcher.data?.error && <span className="basis-full text-xs text-danger">{fetcher.data.error}</span>}
150 </fetcher.Form>
151 );
152}
153
154function ProjectMark({ repo }: { repo: { namespace: string; name: string } | null }) {
155 return <Avatar name={repo ? repo.name : "g1t"} size={32} square />;
156}
157
158/** A row that opens to what is known about it; the first one starts open. */
159function Row({
160 open,
161 repo,
162 by,
163 title,
164 sub,
165 chip,
166 at,
167 children,
168}: {
169 open: boolean;
170 repo: { namespace: string; name: string } | null;
171 by: Who | null;
172 title: ReactNode;
173 sub: ReactNode;
174 chip: ReactNode;
175 at: number;
176 children: ReactNode;
177}) {
178 return (
179 <li>
180 <details open={open} className="group">
181 <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">
182 <ProjectMark repo={repo} />
183 <span className="min-w-0 grow">
184 <span className="block truncate text-sm">{title}</span>
185 <span className="mt-0.5 flex min-w-0 items-center gap-1.5 text-xs text-muted">
186 <span className="shrink-0 font-medium text-fg-soft">{repo ? repo.name : "Workspace"}</span>
187 {by && (
188 <>
189 <span className="text-faint">·</span>
190 <Person who={by} size={14} className={cn("shrink-0", by.agent ? "text-merged" : "")} />
191 </>
192 )}
193 {sub && (
194 <>
195 <span className="text-faint">—</span>
196 <span className="truncate">{sub}</span>
197 </>
198 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look199 </span>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms200 </span>
201 <span className="hidden sm:inline-flex">{chip}</span>
202 <span className="hidden w-16 shrink-0 text-right text-xs text-faint sm:block">
203 <TimeAgo at={at} />
204 </span>
205 <ChevronDown size={16} className="shrink-0 text-faint transition-transform group-open:rotate-180" />
206 </summary>
207 <div className="px-4 pb-4 sm:px-5 sm:pb-5">
208 <div className="mb-3 flex items-center gap-2 sm:hidden">
209 {chip}
210 <span className="text-xs text-faint">
211 <TimeAgo at={at} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look212 </span>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms213 </div>
214 {children}
215 </div>
216 </details>
217 </li>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look218 );
219}
220
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms221function NeedCard({ row, first }: { row: NeedRow; first: boolean }) {
222 const isPull = row.to.includes("/pull/");
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look223 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms224 <Row
225 open={first}
226 repo={row.repo}
227 by={row.by}
228 title={
229 <>
230 <span className="font-medium">{row.title}</span>
231 {row.ref && <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>}
232 </>
233 }
234 sub={row.ask}
235 chip={<Chip tone={CHIP_TONE[row.reason]}>{REASON_LABEL[row.reason]}</Chip>}
236 at={row.at}
237 >
238 <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)]">
239 <div className="min-w-0">
240 <Eyebrow>The ask</Eyebrow>
241 <p className="mt-2 text-sm leading-6 text-fg-soft">{row.ask}</p>
242 <p className="mt-2 text-xs leading-5 text-muted">
243 <Link to={row.to} className="font-medium text-fg hover:underline">
244 {row.repo ? `${row.repo.name}${row.ref ?? ""}` : row.title}
245 </Link>
246 {row.for && (
247 <>
248 {" "}
249 · started for <span className="text-fg-soft">{row.for}</span>
250 </>
251 )}
252 </p>
253 </div>
254 <div className="min-w-0">
255 <Eyebrow>What the agent already knows</Eyebrow>
256 <div className="mt-2">
257 <Facts facts={row.facts} />
258 </div>
259 </div>
260 <div className="rounded-lg border border-warn/20 bg-warn/[0.06] p-3.5 md:col-span-2 2xl:col-span-1">
261 <p className="text-xs font-semibold text-warn">Why this needs you</p>
262 <p className="mt-1.5 text-sm leading-6 text-fg-soft">{row.why}</p>
263 <p className="mt-2 text-xs text-faint">
264 Waiting <TimeAgo at={row.at} />
265 </p>
266 </div>
267 </div>
268 <div className="mt-3 flex flex-wrap items-center gap-2">
269 <Link
270 to={row.to}
271 prefetch="intent"
272 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"
273 >
274 Review and respond <ArrowUpRight size={14} />
275 </Link>
276 {row.quick && <QuickForm quick={row.quick} />}
277 {row.link && (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look278 <Link
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms279 to={row.link.to}
280 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 look281 >
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms282 {row.link.label}
283 </Link>
284 )}
285 {row.open !== row.to && (
286 <Link to={row.open} prefetch="intent" className="px-2 py-1.5 text-sm text-muted hover:text-fg">
287 {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 look288 </Link>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms289 )}
290 </div>
291 </Row>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look292 );
293}
294
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms295function WaitingCard({ row, first }: { row: WaitingRow; first: boolean }) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look296 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms297 <Row
298 open={first}
299 repo={row.repo}
300 by={row.by}
301 title={
302 <>
303 <span className="font-medium">{row.title}</span>
304 {row.ref && <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>}
305 </>
306 }
307 sub={row.detail}
308 chip={
309 <Chip tone="border-line-strong bg-raised text-muted">
310 {row.live && <span className="mr-1.5 size-1.5 animate-pulse rounded-full bg-accent" />}
311 {row.chip}
312 </Chip>
313 }
314 at={row.at}
315 >
316 <div className="grid gap-4 rounded-xl border border-line bg-bg/50 p-4 md:grid-cols-2 md:gap-6">
317 <div className="min-w-0">
318 <Eyebrow>{row.live ? "Doing now" : "Where it stands"}</Eyebrow>
319 <p className="mt-2 text-sm leading-6 text-fg-soft">{row.detail}</p>
320 </div>
321 <div className="min-w-0">
322 <Eyebrow>What is known</Eyebrow>
323 <div className="mt-2">
324 <Facts facts={row.facts} />
325 </div>
326 </div>
327 </div>
328 <div className="mt-3 flex flex-wrap gap-2">
329 <Link
330 to={row.to}
331 prefetch="intent"
332 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"
333 >
334 {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 look335 </Link>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms336 {row.run && row.run !== row.to && (
337 <Link to={row.run} prefetch="intent" className="px-2 py-1.5 text-sm text-muted hover:text-fg">
338 Watch the run
339 </Link>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look340 )}
341 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms342 </Row>
343 );
344}
345
346function LandedCard({ row, first }: { row: LandedRow; first: boolean }) {
347 return (
348 <Row
349 open={first}
350 repo={row.repo}
351 by={row.byAgents ? { name: row.agent, agent: isAgent(row.agent) } : row.by}
352 title={
353 <>
354 <span className="font-medium">{row.title}</span>
355 <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>
356 </>
357 }
358 sub={row.byAgents ? "landed without a person" : `merged by ${row.by?.name ?? "a person"}`}
359 chip={
360 row.byAgents ? (
361 <Chip tone="border-merged/35 bg-merged/10 text-merged">By agents</Chip>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look362 ) : (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms363 <Chip tone="border-warn/35 bg-warn/10 text-warn">Needed a person</Chip>
364 )
365 }
366 at={row.at}
367 >
368 <div className="grid gap-4 rounded-xl border border-line bg-bg/50 p-4 md:grid-cols-2 md:gap-6">
369 <div className="min-w-0">
370 <Eyebrow>How it landed</Eyebrow>
371 <p className="mt-2 text-sm leading-6 text-fg-soft">
372 {row.byAgents
373 ? `g1t merged it once everything the repository asks for was met, with no one pressing merge.`
374 : `${row.by?.name ?? "A person"} merged it.`}
375 {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 look376 </p>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms377 </div>
378 <div className="min-w-0">
379 <Eyebrow>What changed</Eyebrow>
380 <div className="mt-2">
381 <Facts facts={row.facts} />
382 </div>
383 </div>
384 </div>
385 <div className="mt-3">
386 <Link
387 to={row.to}
388 prefetch="intent"
389 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"
390 >
391 Open pull request <ArrowUpRight size={14} />
392 </Link>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look393 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms394 </Row>
395 );
396}
397
398function List({ children }: { children: ReactNode }) {
399 return <ul className="divide-y divide-line">{children}</ul>;
400}
401
402function Empty({ children, action }: { children: ReactNode; action?: ReactNode }) {
403 return (
404 <div className="px-5 py-10 text-center">
405 <div className="mx-auto max-w-md text-sm leading-6 text-muted">{children}</div>
406 {action && <div className="mt-4 flex flex-wrap justify-center gap-2">{action}</div>}
407 </div>
408 );
409}
410
411// --- The stat strip -----------------------------------------------------------------
412
413function Stat({ label, value, hint, dot, title }: { label: string; value: string; hint: ReactNode; dot?: string; title?: string }) {
414 return (
415 <div className="min-w-0 bg-surface px-4 py-3.5 sm:px-5 sm:py-4" title={title}>
416 <p className="flex items-center gap-1.5 truncate text-xs text-muted">
417 {dot && <span className={cn("size-1.5 shrink-0 rounded-full", dot)} />}
418 {label}
419 </p>
420 <p className="mt-1 font-display text-2xl font-semibold tracking-tight tabular-nums sm:text-[1.75rem]">{value}</p>
421 <p className="mt-0.5 truncate text-xs text-faint">{hint}</p>
422 </div>
423 );
424}
425
426// --- This week ----------------------------------------------------------------------
427
428/** Seven days of landed changes, each split into what agents landed alone and what a person merged. */
429function WeekChart({ week }: { week: Week }) {
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar430 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 200ms431 const height = 112;
432 const delta = change(week.total, week.previous);
433 return (
434 <div>
435 <div className="flex items-end justify-between gap-3">
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look436 <div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms437 <p className="font-display text-3xl font-semibold tracking-tight tabular-nums">{week.total.toLocaleString("en-US")}</p>
438 <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 look439 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms440 {delta != null ? (
441 <span
442 className={cn(
443 "rounded-full px-2 py-0.5 text-xs font-medium tabular-nums ring-1",
444 delta >= 0 ? "text-accent ring-accent/30" : "text-warn ring-warn/30",
445 )}
446 >
447 {signedPercent(delta)} vs last week
448 </span>
449 ) : week.previous === 0 && week.total > 0 ? (
450 <span className="text-xs text-faint">none the week before</span>
451 ) : null}
452 </div>
453 {week.total === 0 ? (
454 <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 bar455 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 200ms456 </p>
457 ) : (
458 <div className="relative mt-5" style={{ height: height + 20 }}>
459 <div className="absolute inset-x-0 border-t border-line" style={{ top: height }} />
460 <div className="absolute inset-x-0 top-0 flex items-end justify-between gap-1" style={{ height }}>
461 {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 bar462 const total = day.agents + day.assisted + day.people;
463 const bar = (n: number) => Math.max(3, Math.round((n / max) * (height - 4)));
464 // Top to bottom: agents alone, agents with a person, people.
465 const segments = [
466 { n: day.agents, tone: "bg-merged" },
467 { n: day.assisted, tone: "bg-warn" },
468 { n: day.people, tone: "bg-info" },
469 ].filter((segment) => segment.n > 0);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms470 const today = index === week.days.length - 1;
471 return (
472 <div key={day.key} className="group relative flex h-full flex-1 flex-col items-center justify-end">
473 {total > 0 && (
474 <span
475 className={cn(
476 "mb-1 text-[0.625rem] tabular-nums",
477 today ? "text-fg-soft" : "text-faint opacity-0 group-hover:opacity-100",
478 )}
479 >
480 {total}
481 </span>
482 )}
483 <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 bar484 {segments.map((segment, at) => (
485 <span key={segment.tone} className={cn("block", segment.tone, at === 0 && "rounded-t")} style={{ height: bar(segment.n) }} />
486 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms487 {total === 0 && <span className="block h-[2px] rounded-full bg-line-strong" />}
488 </div>
489 <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">
490 <p className="font-medium text-fg">{day.label}</p>
491 <p className="mt-0.5 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 bar492 <span className="size-1.5 rounded-full bg-merged" /> {day.agents} by agents on their own
493 </p>
494 <p className="flex items-center gap-1.5 text-muted">
495 <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 200ms496 </p>
497 <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 bar498 <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 200ms499 </p>
500 </div>
501 </div>
502 );
503 })}
504 </div>
505 <div className="absolute inset-x-0 bottom-0 flex justify-between gap-1">
506 {week.days.map((day, index) => (
507 <span
508 key={day.key}
509 className={cn(
510 "flex-1 text-center text-[0.6875rem]",
511 index === week.days.length - 1 ? "font-medium text-fg-soft" : "text-faint",
512 )}
513 >
514 {day.label}
515 </span>
516 ))}
517 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look518 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms519 )}
520 <div className={cn("mt-3 flex-wrap gap-x-4 gap-y-1 text-xs text-muted", week.total === 0 ? "hidden" : "flex")}>
521 <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 bar522 <span className="size-2 rounded-sm bg-merged" /> Agents, on their own
523 </span>
524 <span className="inline-flex items-center gap-1.5">
525 <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 200ms526 </span>
527 <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 bar528 <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 200ms529 </span>
530 </div>
Fixes from the page sweep531 {/* A table does not shrink to sr-only's 1px; its wrapper does. */}
532 <div className="sr-only">
533 <table>
534 <caption>Changes landed each day</caption>
535 <thead>
536 <tr>
537 <th>Day</th>
538 <th>Agents, on their own</th>
539 <th>Agents, merged by a person</th>
540 <th>People</th>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms541 </tr>
Fixes from the page sweep542 </thead>
543 <tbody>
544 {week.days.map((day) => (
545 <tr key={day.key}>
546 <td>{day.key}</td>
547 <td>{day.agents}</td>
548 <td>{day.assisted}</td>
549 <td>{day.people}</td>
550 </tr>
551 ))}
552 </tbody>
553 </table>
554 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms555 </div>
556 );
557}
558
559// --- Activity -----------------------------------------------------------------------
560
561const VERB: Record<Verb, string> = {
562 landed: "landed",
563 opened_issue: "opened",
564 closed_issue: "closed",
565 started: "started on",
566 ready: "marked ready",
567 checks_passed: "checks passed on",
568 checks_failed: "checks failed on",
569 approved: "approved",
570 changes_requested: "asked for changes on",
571 commented: "commented on",
572 asked: "was asked about",
573 deployed: "deployed production",
574 deploy_failed: "production build failed",
575 learned: "learned",
576};
577
578/** One line of the feed: who, what, and the one thing it was about, by name. */
579function FeedLine({ group, titles }: { group: ActivityGroup; titles: Record<string, string> }) {
580 const part = group.parts[0];
581 const base = `/${group.repo.namespace}/${group.repo.name}`;
582 const actor = group.actor ?? "g1t";
583 const agent = group.actor == null || isAgent(group.actor);
584 const number = part.numbers[0];
585 const title = number != null ? titles[`${group.repo.namespace}/${group.repo.name}#${number}`.toLowerCase()] : undefined;
586 const more = group.parts.length - 1 + Math.max(0, part.numbers.length - 1);
587 return (
588 <li className="flex gap-2.5 py-2">
589 <span className="mt-px">
590 <Avatar name={actor} size={22} />
591 </span>
592 <span className="min-w-0 grow text-[0.8125rem] leading-5 text-muted">
593 <span className={cn("font-medium", agent ? "text-merged" : "text-fg")}>{actor}</span> {VERB[part.verb]}{" "}
594 {part.verb === "learned" ? (
595 <Link to={part.to ?? `${base}/memory`} className="text-fg-soft hover:text-fg">
596 “{part.texts[0]}”
597 </Link>
598 ) : number != null ? (
599 <Link to={`${base}/issues/${number}`} prefetch="intent" className="text-fg-soft hover:text-fg">
600 <span className="font-mono text-xs">#{number}</span>
601 {title && <> {title}</>}
602 </Link>
603 ) : part.to ? (
604 <Link to={part.to} className="text-fg-soft hover:text-fg">
605 {group.repo.name}
606 </Link>
607 ) : null}
608 {more > 0 && <span className="text-faint"> and {more} more</span>}
609 <span className="block text-xs text-faint">
610 <span className="font-mono">{group.repo.name}</span> · <TimeAgo at={group.at} />
611 </span>
612 </span>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look613 </li>
614 );
615}
616
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms617// --- Get started --------------------------------------------------------------------
618
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look619type Step = { done: boolean; title: string; about: string; to: string | null; action: string };
620
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms621/** 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 look622function GetStarted({ steps }: { steps: Step[] }) {
623 const left = steps.filter((step) => !step.done).length;
624 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms625 <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 look626 <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 200ms627 <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 look628 <span className="text-xs text-muted">
629 {steps.length - left} of {steps.length} done
630 </span>
631 </div>
632 <ol className="mt-4 space-y-2">
633 {steps.map((step, index) => (
634 <li
635 key={step.title}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms636 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 look637 >
638 <span
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms639 className={cn(
640 "mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full text-[0.6875rem] font-medium",
641 step.done ? "bg-accent text-bg" : "text-muted ring-1 ring-line-strong",
642 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look643 >
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms644 {step.done ? <Check size={12} /> : index + 1}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look645 </span>
646 <span className="min-w-0 grow">
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms647 <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 look648 {!step.done && <span className="mt-0.5 block text-xs leading-5 text-muted">{step.about}</span>}
649 </span>
650 {!step.done && step.to && (
651 <Link
652 to={step.to}
653 className="shrink-0 rounded-md bg-fg px-2.5 py-1 text-xs font-medium text-bg transition-colors hover:bg-white"
654 >
655 {step.action}
656 </Link>
657 )}
658 </li>
659 ))}
660 </ol>
661 </section>
662 );
663}
664
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms665// --- The page -----------------------------------------------------------------------
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look666
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms667const TAB_LABEL: Record<Tab, string> = { needs: "Needs you", waiting: "Waiting on agents", landed: "Landed today" };
668const TAB_SHORT: Record<Tab, string> = { needs: "Needs you", waiting: "Waiting", landed: "Today" };
669const 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 look670
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step671export 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 look672 const shell = useRouteLoaderData("root")?.shell as ShellData | null | undefined;
673 const loaded: Loaded = loaderData;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms674 const [params] = useSearchParams();
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look675 // 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 200ms676 useLiveRefresh(loaded.changing);
677 const [greeting, setGreeting] = useState(loaded.greeting);
678 const [date, setDate] = useState(loaded.date);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look679 useEffect(() => {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms680 // The greeting and date follow the viewer's own clock, and the server
681 // learns their zone for next time.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look682 setGreeting(greetingFor(new Date().getHours()));
683 try {
684 const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms685 setDate(dateLine(Date.now(), zone ?? null));
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look686 if (zone) document.cookie = `${TZ_COOKIE}=${encodeURIComponent(zone)}; Path=/; Max-Age=31536000; SameSite=Lax`;
687 } catch {
688 // Nothing to remember.
689 }
690 }, []);
691
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms692 const { viewer, repos, waiting, landed, liveTotal, week, groups, titles, stats, canRunAgents, trial } = loaded;
693 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 look694
695 // 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 200ms696 const limit = shell?.limit && !shell.limit.comped && shell.limit.state !== "ok" && workspace ? shell.limit : null;
697 const limitRow: NeedRow[] = limit
698 ? [
699 (() => {
700 const detail =
701 limit.state === "stopped"
702 ? "No new agent starts until the limit is raised or a card is added."
703 : `${dollars(limit.exposureMicros)} of ${dollars(limit.ceilingMicros ?? 0)} used. Add a card so work keeps going.`;
704 return {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look705 key: "limit",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms706 reason: "blocking" as const,
707 repo: null,
708 ref: null,
709 title: limit.state === "stopped" ? "Agents are stopped: the usage limit was reached" : "Usage is nearing its limit",
710 ask: detail,
711 by: null,
712 for: null,
713 at: Date.now(),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look714 to: `/${workspace}/-/billing`,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms715 open: `/${workspace}/-/billing`,
716 facts: [
717 { label: "Used", value: `${dollars(limit.exposureMicros)} of ${dollars(limit.ceilingMicros ?? 0)}`, tone: "warn" as const },
718 ],
719 why: whyFor("blocking", { kind: "limit", detail }),
720 quick: null,
721 link: null,
722 };
723 })(),
724 ]
725 : [];
726 const needs = [...limitRow, ...loaded.needs];
727 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 look728
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms729 const counts: Record<Tab, number> = { needs: needs.length, waiting: waiting.length, landed: landed.length };
730 const tab = parseTab(params.get("tab")) ?? "needs";
731 const sort = parseSort(params.get("sort"));
732 const all = params.get("all") === "1";
733 const everyActivity = params.get("activity") === "all";
734 const link = (changes: Record<string, string | null>) => {
735 const next = new URLSearchParams(params);
736 for (const [key, value] of Object.entries(changes)) {
737 if (value == null) next.delete(key);
738 else next.set(key, value);
739 }
740 const text = next.toString();
741 return text ? `?${text}` : "/";
742 };
743
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look744 const steps: Step[] = [
745 {
746 done: Boolean(workspace),
747 title: "Create a workspace",
748 about: "Projects, people and agent credit live in one.",
749 to: "/workspaces/new",
750 action: "Create",
751 },
752 {
753 done: repos.length > 0,
754 title: "Add a project",
755 about: "Create a repository, or import one by its address. Push to it with git as usual.",
756 to: workspace ? `/new?workspace=${workspace}` : "/new",
757 action: "Add",
758 },
759 trial?.open
760 ? {
761 done: true,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent762 title: "Try g1t free",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look763 about: trial.granted
764 ? `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-agent765 : `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 look766 to: workspace ? `/${workspace}/-/integrations` : null,
767 action: "Connect",
768 }
769 : canRunAgents
770 ? {
771 done: Boolean(shell?.free) || shell?.limit == null || shell.limit.comped || shell.limit.state === "ok",
772 title: "Keep work running",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms773 about:
774 "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 look775 to: workspace ? `/${workspace}/-/billing` : null,
776 action: "Billing",
777 }
778 : {
779 done: false,
780 title: "Connect a model",
781 about: `${
782 trialClosed(trial, workspace ?? "This workspace") ? `${trialClosed(trial, workspace ?? "This workspace")} ` : ""
783 }Agents need a model to think with. Connect your Anthropic or OpenAI key, or any compatible endpoint.`,
784 to: workspace ? `/${workspace}/-/integrations` : null,
785 action: "Connect",
786 },
787 {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms788 done: loaded.handedOff,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look789 title: "Hand off an outcome",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms790 about:
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent791 "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 200ms792 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 look793 action: "Write one",
794 },
795 ];
796 const starting = steps.some((step) => !step.done);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms797 const noProjects = repos.length === 0;
798
799 const rows = tab === "needs" ? sortRows(needs, sort) : tab === "waiting" ? sortRows(waiting, sort) : sortRows(landed, sort);
800 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 bar801 // Of the agents' own changes only: people's work is not theirs to land.
802 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 200ms803 const delta = change(week.total, week.previous);
804 const feed = everyActivity ? groups : groups.slice(0, 8);
805
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step806 // "Put an agent on it" first, with "New issue" beside it as before: one
807 // 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 200ms808 const newIssue =
809 repos.length > 0 ? (
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step810 <div className="flex items-stretch">
811 <AgentComposer
812 repos={repos}
813 open={params.get("agent") === "new" || delegated != null}
814 result={delegated}
815 note={
816 canRunAgents ? null : (
817 <>
818 Agents need a model first.{" "}
819 {workspace && (
820 <Link to={`/${workspace}/-/integrations`} className="font-medium text-fg hover:underline">
821 Connect one
822 </Link>
823 )}
824 . The issue still opens.
825 </>
826 )
827 }
828 >
829 <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">
830 <Sparkles size={14} className="text-merged" /> Put an agent on it
831 </span>
832 </AgentComposer>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms833 <DropdownMenu>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step834 <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 200ms835 <Plus size={14} /> New issue
836 </DropdownMenuTrigger>
837 <DropdownMenuContent align="end" className="max-h-80 overflow-y-auto">
838 <DropdownMenuLabel>In which project?</DropdownMenuLabel>
839 {repos.map((repo) => (
840 <DropdownMenuItem key={repo.name} asChild>
841 <Link to={`/${repo.namespace}/${repo.name}/issues/new`}>
842 <Avatar name={repo.name} size={18} square />
843 {repo.name}
844 </Link>
845 </DropdownMenuItem>
846 ))}
847 </DropdownMenuContent>
848 </DropdownMenu>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step849 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms850 ) : (
851 <Link
852 to={workspace ? `/new?workspace=${workspace}` : "/new"}
853 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"
854 >
855 <Plus size={14} /> New project
856 </Link>
857 );
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look858
859 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms860 <main className="mx-auto max-w-7xl space-y-6 px-4 py-8 sm:px-6 sm:py-10">
861 <header className="flex flex-wrap items-end justify-between gap-x-6 gap-y-4">
862 <div className="min-w-0">
863 <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 look864 {greeting}, {loaded.name}
865 </h1>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms866 <p className="mt-1.5 text-sm text-muted" suppressHydrationWarning>
867 <span className="text-fg-soft">{date}</span> · {loaded.summary}
868 </p>
869 </div>
870 <div className="flex flex-wrap items-center gap-2">
871 {newIssue}
872 {needs.length > 0 && (
873 <Link
874 to={`${link({ tab: "needs", all: null })}#work`}
875 preventScrollReset
876 className="inline-flex items-center gap-1.5 rounded-md bg-merged px-3.5 py-2 text-sm font-semibold text-bg transition-colors hover:bg-[#c9bfff]"
877 >
878 Review {needs.length} that need{needs.length === 1 ? "s" : ""} you <ArrowRight size={14} />
879 </Link>
880 )}
881 </div>
882 </header>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look883
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms884 {starting && <GetStarted steps={steps} />}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look885
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms886 <section
887 aria-label="At a glance"
888 className="grid grid-cols-2 gap-px overflow-hidden rounded-xl border border-line bg-line lg:grid-cols-5"
889 >
890 <Stat
891 label="Projects"
892 value={stats.projects == null ? "—" : String(stats.projects)}
893 hint={
894 stats.projectsThisMonth == null
895 ? "in this workspace"
896 : stats.projectsThisMonth > 0
897 ? `+${stats.projectsThisMonth} this month`
898 : "none new this month"
899 }
900 />
901 <Stat
902 label="Agents"
903 value={loaded.runsLoaded || !workspace ? String(liveTotal) : "—"}
904 dot={liveTotal > 0 ? "bg-accent animate-pulse" : undefined}
905 hint={
906 liveTotal > 0
907 ? `live now · ${stats.agentHours < 10 ? stats.agentHours.toFixed(1) : Math.round(stats.agentHours)}h this week`
908 : stats.agentHours > 0
909 ? `none live · ${stats.agentHours < 10 ? stats.agentHours.toFixed(1) : Math.round(stats.agentHours)}h this week`
910 : "none live now"
911 }
912 />
913 <Stat
914 label="Changes this week"
915 value={loaded.perRepoLoaded ? String(week.total) : "—"}
916 hint={
917 delta != null ? `${signedPercent(delta)} vs last week` : week.previous === 0 ? "none the week before" : "merged pull requests"
918 }
919 />
920 <Stat
921 label="Landed without you"
922 dot="bg-merged"
923 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 bar924 hint={share == null ? "no agent changes yet" : `${week.byAgents} of ${week.agentChanges} agent changes`}
925 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 200ms926 />
927 <div className="col-span-2 lg:col-span-1">
928 <Stat
929 label="Need you"
930 dot="bg-warn"
931 value={String(needs.length)}
932 hint={blocking > 0 ? `${blocking} blocking` : needs.length > 0 ? "nothing blocking" : "all clear"}
933 />
934 </div>
935 </section>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look936
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms937 <div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_21rem] xl:grid-cols-[minmax(0,1fr)_23rem]">
938 <section id="work" className="min-w-0 scroll-mt-20 self-start overflow-hidden rounded-xl border border-line bg-surface">
939 <div className="flex items-center gap-2 border-b border-line px-2 sm:px-3">
940 <nav className="-mb-px flex min-w-0 grow gap-1 overflow-x-auto [scrollbar-width:none]" aria-label="Mission control">
941 {(["needs", "waiting", "landed"] as const).map((value) => (
942 <Link
943 key={value}
944 to={link({ tab: value, all: null })}
945 preventScrollReset
946 aria-current={tab === value ? "page" : undefined}
947 className={cn(
948 "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",
949 tab === value ? "border-merged font-medium text-fg" : "border-transparent text-muted hover:text-fg",
950 )}
951 >
952 <span className="sm:hidden">{TAB_SHORT[value]}</span>
953 <span className="hidden sm:inline">{TAB_LABEL[value]}</span>
954 <span
955 className={cn(
956 "rounded-full px-1.5 text-[0.6875rem] tabular-nums",
957 tab === value && value === "needs" && counts.needs > 0 ? "bg-warn/15 text-warn" : "bg-line text-muted",
958 )}
959 >
960 {counts[value]}
961 </span>
962 </Link>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look963 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms964 </nav>
965 <DropdownMenu>
966 <DropdownMenuTrigger
967 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"
968 aria-label="Sort"
969 >
970 <ArrowDownWideNarrow size={14} />
971 <span className="hidden sm:inline">{SORT_LABEL[sort]}</span>
972 </DropdownMenuTrigger>
973 <DropdownMenuContent align="end">
974 {(["impact", "newest"] as const).map((value) => (
975 <DropdownMenuItem key={value} asChild>
976 <Link to={link({ sort: value === "impact" ? null : value })} preventScrollReset>
977 {sort === value ? <Check /> : <span className="size-4" />}
978 {SORT_LABEL[value]}
979 </Link>
980 </DropdownMenuItem>
981 ))}
982 </DropdownMenuContent>
983 </DropdownMenu>
984 </div>
985
986 {!loaded.perRepoLoaded && tab !== "waiting" ? (
987 <div className="p-4">
988 <Unavailable what={tab === "needs" ? "Some of what needs you" : "What landed"} />
989 </div>
990 ) : null}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look991
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms992 {tab === "needs" &&
993 (needs.length === 0 ? (
994 noProjects ? (
995 <Empty
996 action={
997 <Link
998 to={workspace ? `/new?workspace=${workspace}` : "/new"}
999 className="rounded-md bg-fg px-3 py-1.5 text-sm font-medium text-bg hover:bg-white"
1000 >
1001 Create or import a project
1002 </Link>
1003 }
1004 >
1005 <p className="font-medium text-fg">No projects yet</p>
1006 <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-agent1007 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 200ms1008 </p>
1009 </Empty>
1010 ) : (
1011 <>
1012 <Empty>
1013 <p className="font-medium text-fg">Nothing needs you</p>
1014 <p className="mt-1">
1015 Agents are handling everything.{" "}
1016 {landed.length > 0
1017 ? "Here's what they landed today."
1018 : "Reviews, stopped work and failed deploys show up here first."}
1019 </p>
1020 </Empty>
1021 {landed.length > 0 && (
1022 <div className="border-t border-line">
1023 <List>
1024 {landed.slice(0, ROWS).map((row) => (
1025 <LandedCard key={row.key} row={row} first={false} />
1026 ))}
1027 </List>
1028 </div>
1029 )}
1030 </>
1031 )
1032 ) : (
1033 <List>
1034 {(shown as NeedRow[]).map((row, index) => (
1035 <NeedCard key={row.key} row={row} first={index === 0} />
1036 ))}
1037 </List>
1038 ))}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1039
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1040 {tab === "waiting" &&
1041 (!loaded.runsLoaded && workspace ? (
1042 <div className="p-4">
1043 <Unavailable what="Agent runs" />
1044 </div>
1045 ) : waiting.length === 0 ? (
1046 <Empty>
1047 <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-agent1048 <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 200ms1049 </Empty>
1050 ) : (
1051 <List>
1052 {(shown as WaitingRow[]).map((row, index) => (
1053 <WaitingCard key={row.key} row={row} first={index === 0} />
1054 ))}
1055 </List>
1056 ))}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1057
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1058 {tab === "landed" &&
1059 loaded.perRepoLoaded &&
1060 (landed.length === 0 ? (
1061 <Empty>
1062 <p className="font-medium text-fg">Nothing has landed today yet</p>
1063 <p className="mt-1">
1064 {week.total > 0
1065 ? `${plural(week.total, "change")} landed in the last 7 days.`
1066 : "Merged pull requests show up here the moment they land."}
1067 </p>
1068 </Empty>
1069 ) : (
1070 <List>
1071 {(shown as LandedRow[]).map((row, index) => (
1072 <LandedCard key={row.key} row={row} first={index === 0} />
1073 ))}
1074 </List>
1075 ))}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1076
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1077 {rows.length > ROWS && (
1078 <div className="flex items-center justify-between border-t border-line px-4 py-2.5 text-xs text-muted sm:px-5">
1079 <span>
1080 Showing {shown.length} of {rows.length}
1081 </span>
1082 <Link
1083 to={link({ all: all ? null : "1" })}
1084 preventScrollReset
1085 className="inline-flex items-center gap-1 font-medium text-fg-soft hover:text-fg"
1086 >
1087 {all ? "Show fewer" : "See all"} <ChevronRight size={12} />
1088 </Link>
1089 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1090 )}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1091 </section>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1092
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1093 <aside className="min-w-0 space-y-6">
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all1094 <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 200ms1095 <section className="rounded-xl border border-line bg-surface p-5">
1096 <div className="flex items-center justify-between">
1097 <h2 className="text-base font-semibold tracking-tight">This week</h2>
1098 {workspace && (
1099 <Link to={`/${workspace}/-/agents`} className="text-xs text-muted hover:text-fg">
1100 Fleet
1101 </Link>
1102 )}
1103 </div>
1104 <div className="mt-4">{loaded.perRepoLoaded ? <WeekChart week={week} /> : <Unavailable what="The week" />}</div>
1105 {workspace && stats.weekCost > 0 && (
1106 <Link
1107 to={`/${workspace}/-/usage`}
1108 className="mt-4 flex items-center justify-between border-t border-line pt-3 text-xs text-muted hover:text-fg"
1109 >
1110 <span>Agents and sandboxes cost {usd(stats.weekCost)} this week</span>
1111 <ChevronRight size={12} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1112 </Link>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1113 )}
1114 </section>
1115
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix1116 {workspace && (
1117 <TokenUsagePanel
1118 workspace={loaderData.tokens.workspace}
1119 mine={loaderData.tokens.mine}
1120 usageHref={`/${workspace}/-/usage`}
1121 />
1122 )}
1123
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1124 <section id="activity" className="scroll-mt-20 rounded-xl border border-line bg-surface p-5">
1125 <div className="flex items-center justify-between">
1126 <h2 className="text-base font-semibold tracking-tight">Activity</h2>
1127 {groups.length > 8 && (
1128 <Link
1129 to={`${link({ activity: everyActivity ? null : "all" })}#activity`}
1130 preventScrollReset
1131 className="inline-flex items-center gap-0.5 text-xs text-muted hover:text-fg"
1132 >
1133 {everyActivity ? "Less" : "All activity"} <ChevronRight size={12} />
1134 </Link>
1135 )}
1136 </div>
1137 <p className="mt-1 flex items-center gap-3 text-[0.6875rem] text-faint">
1138 <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-agent1139 <Avatar name="g1t" size={12} /> agents
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1140 </span>
1141 <span className="inline-flex items-center gap-1.5">
1142 <Avatar name={viewer.username} size={12} /> people
1143 </span>
1144 </p>
1145 {!loaded.perRepoLoaded ? (
1146 <div className="mt-3">
1147 <Unavailable what="Activity" />
1148 </div>
1149 ) : feed.length === 0 ? (
1150 <p className="mt-3 text-sm leading-6 text-muted">
1151 Nothing has moved yet. Merges, deploys, checks, reviews and what agents learn show up here.
1152 </p>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1153 ) : (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1154 <ol className="mt-2 divide-y divide-line/70">
1155 {feed.map((group) => (
1156 <FeedLine key={group.id} group={group} titles={titles} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1157 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1158 </ol>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1159 )}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1160 </section>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1161
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1162 <section className="rounded-xl border border-line bg-surface p-5">
1163 <h2 className="text-sm font-semibold">Connect your own agent</h2>
1164 <p className="mt-1.5 text-xs leading-5 text-muted">
1165 Add g1t to your coding agent. It signs in through your browser; there is no token to copy.
1166 </p>
1167 <AgentSetup className="mt-3" />
1168 <Link
1169 to="https://docs.g1t.sh/guides/bring-your-own-agent/"
1170 className="mt-3 inline-flex items-center gap-1 text-xs text-muted hover:text-fg"
1171 >
1172 How it works <ArrowRight size={12} />
1173 </Link>
1174 </section>
1175 </aside>
1176 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1177 </main>
1178 );
1179}

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