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

1,176 lines49,035 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";
32import type { ShellData } from "./shell";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms33import { useLiveRefresh } from "./agents";
34import { Unavailable } from "./mission";
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix35import { TokenUsagePanel } from "./token-usage";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look36import { Avatar, TimeAgo } from "./ui";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms37import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger } from "./ui/dropdown-menu";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look38import type { Loaded } from "../routes/home";
39
40/*
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms41 * Mission control: the home page of someone signed in. It answers where
42 * the viewer is needed, what agents are doing, and what landed without
43 * them. It is a chunk of its own, loaded only for them, so the signed-out
44 * home page does not carry it. routes/home.tsx loads its data;
45 * lib/mission-control.ts shapes it.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look46 */
47
48/** The viewer's time zone, set by the page itself, so the greeting fits their day. */
49const TZ_COOKIE = "g1t_tz";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms50/** Rows a tab shows before "See all". */
51const ROWS = 6;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look52
53const 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 200ms54const 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 look55
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms56// --- Small pieces -----------------------------------------------------------------
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look57
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms58const CHIP_TONE: Record<Reason, string> = {
59 blocking: "border-danger/35 bg-danger/10 text-danger",
60 checks_failing: "border-danger/35 bg-danger/10 text-danger",
61 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 step62 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 200ms63 stalled: "border-warn/35 bg-warn/10 text-warn",
64 asked_for_you: "border-merged/35 bg-merged/10 text-merged",
65 needs_review: "border-info/35 bg-info/10 text-info",
66 ready_to_merge: "border-accent/35 bg-accent/10 text-accent",
67};
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look68
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms69function Chip({ children, tone }: { children: ReactNode; tone: string }) {
70 return (
71 <span
72 className={cn(
73 "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",
74 tone,
75 )}
76 >
77 {children}
78 </span>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look79 );
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms80}
81
82/** A person or an agent, by name, wearing the mark that tells them apart. */
83function 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 look84 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms85 <span className={cn("inline-flex min-w-0 items-center gap-1.5", className)}>
86 <Avatar name={who.name} size={size} />
87 <span className="truncate">{who.name}</span>
88 </span>
89 );
90}
91
92function Eyebrow({ children }: { children: ReactNode }) {
93 return <p className="font-mono text-[0.625rem] font-medium tracking-[0.12em] text-faint uppercase">{children}</p>;
94}
95
96const FACT_TONE = { good: "text-accent", warn: "text-warn", bad: "text-danger" } as const;
97
98function Facts({ facts }: { facts: Fact[] }) {
99 if (facts.length === 0) return <p className="text-sm text-muted">Nothing more is known about it yet.</p>;
100 return (
101 <dl className="grid grid-cols-2 gap-x-4 gap-y-2.5">
102 {facts.map((fact) => (
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step103 <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 200ms104 <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 step105 <dd
106 className={cn(
107 "mt-0.5 text-sm font-medium tabular-nums",
108 fact.wide ? "leading-5" : "truncate",
109 fact.tone ? FACT_TONE[fact.tone] : "text-fg-soft",
110 )}
111 >
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms112 {fact.value}
113 </dd>
114 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look115 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms116 </dl>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look117 );
118}
119
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms120/** A form that acts on a pull request from here, saying so before the page catches up. */
121function QuickForm({ quick, variant = "quiet" }: { quick: QuickAction; variant?: "quiet" | "accent" }) {
122 const fetcher = useFetcher<{ error?: string } | null>();
123 const [sent, setSent] = useState(false);
124 const busy = fetcher.state !== "idle";
Fast pages, required checks on the branch, self-hosted runners, honest incidents125 // Shown as done the moment it is sent, and undone if it fails: the page
126 // reloads behind it, which takes longer than the action.
127 const done = sent && (busy || !fetcher.data?.error);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look128 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms129 <fetcher.Form method="post" action={quick.to} onSubmit={() => setSent(true)} className="contents">
130 {Object.entries(quick.fields).map(([name, value]) => (
131 <input key={name} type="hidden" name={name} value={value} />
132 ))}
133 <button
134 type="submit"
135 disabled={busy || done}
136 className={cn(
137 "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors disabled:cursor-default",
138 variant === "accent"
139 ? "bg-merged text-bg hover:bg-[#c9bfff]"
140 : "border border-line-strong text-fg/90 hover:bg-raised hover:text-fg",
141 done && "border-accent/40 text-accent",
142 )}
143 >
Fast pages, required checks on the branch, self-hosted runners, honest incidents144 {done ? <Check size={14} /> : null}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms145 {done ? quick.done : quick.label}
146 </button>
147 {fetcher.data?.error && <span className="basis-full text-xs text-danger">{fetcher.data.error}</span>}
148 </fetcher.Form>
149 );
150}
151
152function ProjectMark({ repo }: { repo: { namespace: string; name: string } | null }) {
153 return <Avatar name={repo ? repo.name : "g1t"} size={32} square />;
154}
155
156/** A row that opens to what is known about it; the first one starts open. */
157function Row({
158 open,
159 repo,
160 by,
161 title,
162 sub,
163 chip,
164 at,
165 children,
166}: {
167 open: boolean;
168 repo: { namespace: string; name: string } | null;
169 by: Who | null;
170 title: ReactNode;
171 sub: ReactNode;
172 chip: ReactNode;
173 at: number;
174 children: ReactNode;
175}) {
176 return (
177 <li>
178 <details open={open} className="group">
179 <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">
180 <ProjectMark repo={repo} />
181 <span className="min-w-0 grow">
182 <span className="block truncate text-sm">{title}</span>
183 <span className="mt-0.5 flex min-w-0 items-center gap-1.5 text-xs text-muted">
184 <span className="shrink-0 font-medium text-fg-soft">{repo ? repo.name : "Workspace"}</span>
185 {by && (
186 <>
187 <span className="text-faint">·</span>
188 <Person who={by} size={14} className={cn("shrink-0", by.agent ? "text-merged" : "")} />
189 </>
190 )}
191 {sub && (
192 <>
193 <span className="text-faint">—</span>
194 <span className="truncate">{sub}</span>
195 </>
196 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look197 </span>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms198 </span>
199 <span className="hidden sm:inline-flex">{chip}</span>
200 <span className="hidden w-16 shrink-0 text-right text-xs text-faint sm:block">
201 <TimeAgo at={at} />
202 </span>
203 <ChevronDown size={16} className="shrink-0 text-faint transition-transform group-open:rotate-180" />
204 </summary>
205 <div className="px-4 pb-4 sm:px-5 sm:pb-5">
206 <div className="mb-3 flex items-center gap-2 sm:hidden">
207 {chip}
208 <span className="text-xs text-faint">
209 <TimeAgo at={at} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look210 </span>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms211 </div>
212 {children}
213 </div>
214 </details>
215 </li>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look216 );
217}
218
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms219function NeedCard({ row, first }: { row: NeedRow; first: boolean }) {
220 const isPull = row.to.includes("/pull/");
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look221 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms222 <Row
223 open={first}
224 repo={row.repo}
225 by={row.by}
226 title={
227 <>
228 <span className="font-medium">{row.title}</span>
229 {row.ref && <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>}
230 </>
231 }
232 sub={row.ask}
233 chip={<Chip tone={CHIP_TONE[row.reason]}>{REASON_LABEL[row.reason]}</Chip>}
234 at={row.at}
235 >
236 <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)]">
237 <div className="min-w-0">
238 <Eyebrow>The ask</Eyebrow>
239 <p className="mt-2 text-sm leading-6 text-fg-soft">{row.ask}</p>
240 <p className="mt-2 text-xs leading-5 text-muted">
241 <Link to={row.to} className="font-medium text-fg hover:underline">
242 {row.repo ? `${row.repo.name}${row.ref ?? ""}` : row.title}
243 </Link>
244 {row.for && (
245 <>
246 {" "}
247 · started for <span className="text-fg-soft">{row.for}</span>
248 </>
249 )}
250 </p>
251 </div>
252 <div className="min-w-0">
253 <Eyebrow>What the agent already knows</Eyebrow>
254 <div className="mt-2">
255 <Facts facts={row.facts} />
256 </div>
257 </div>
258 <div className="rounded-lg border border-warn/20 bg-warn/[0.06] p-3.5 md:col-span-2 2xl:col-span-1">
259 <p className="text-xs font-semibold text-warn">Why this needs you</p>
260 <p className="mt-1.5 text-sm leading-6 text-fg-soft">{row.why}</p>
261 <p className="mt-2 text-xs text-faint">
262 Waiting <TimeAgo at={row.at} />
263 </p>
264 </div>
265 </div>
266 <div className="mt-3 flex flex-wrap items-center gap-2">
267 <Link
268 to={row.to}
269 prefetch="intent"
270 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"
271 >
272 Review and respond <ArrowUpRight size={14} />
273 </Link>
274 {row.quick && <QuickForm quick={row.quick} />}
275 {row.link && (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look276 <Link
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms277 to={row.link.to}
278 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 look279 >
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms280 {row.link.label}
281 </Link>
282 )}
283 {row.open !== row.to && (
284 <Link to={row.open} prefetch="intent" className="px-2 py-1.5 text-sm text-muted hover:text-fg">
285 {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 look286 </Link>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms287 )}
288 </div>
289 </Row>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look290 );
291}
292
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms293function WaitingCard({ row, first }: { row: WaitingRow; first: boolean }) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look294 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms295 <Row
296 open={first}
297 repo={row.repo}
298 by={row.by}
299 title={
300 <>
301 <span className="font-medium">{row.title}</span>
302 {row.ref && <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>}
303 </>
304 }
305 sub={row.detail}
306 chip={
307 <Chip tone="border-line-strong bg-raised text-muted">
308 {row.live && <span className="mr-1.5 size-1.5 animate-pulse rounded-full bg-accent" />}
309 {row.chip}
310 </Chip>
311 }
312 at={row.at}
313 >
314 <div className="grid gap-4 rounded-xl border border-line bg-bg/50 p-4 md:grid-cols-2 md:gap-6">
315 <div className="min-w-0">
316 <Eyebrow>{row.live ? "Doing now" : "Where it stands"}</Eyebrow>
317 <p className="mt-2 text-sm leading-6 text-fg-soft">{row.detail}</p>
318 </div>
319 <div className="min-w-0">
320 <Eyebrow>What is known</Eyebrow>
321 <div className="mt-2">
322 <Facts facts={row.facts} />
323 </div>
324 </div>
325 </div>
326 <div className="mt-3 flex flex-wrap gap-2">
327 <Link
328 to={row.to}
329 prefetch="intent"
330 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"
331 >
332 {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 look333 </Link>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms334 {row.run && row.run !== row.to && (
335 <Link to={row.run} prefetch="intent" className="px-2 py-1.5 text-sm text-muted hover:text-fg">
336 Watch the run
337 </Link>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look338 )}
339 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms340 </Row>
341 );
342}
343
344function LandedCard({ row, first }: { row: LandedRow; first: boolean }) {
345 return (
346 <Row
347 open={first}
348 repo={row.repo}
349 by={row.byAgents ? { name: row.agent, agent: isAgent(row.agent) } : row.by}
350 title={
351 <>
352 <span className="font-medium">{row.title}</span>
353 <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>
354 </>
355 }
356 sub={row.byAgents ? "landed without a person" : `merged by ${row.by?.name ?? "a person"}`}
357 chip={
358 row.byAgents ? (
359 <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 look360 ) : (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms361 <Chip tone="border-warn/35 bg-warn/10 text-warn">Needed a person</Chip>
362 )
363 }
364 at={row.at}
365 >
366 <div className="grid gap-4 rounded-xl border border-line bg-bg/50 p-4 md:grid-cols-2 md:gap-6">
367 <div className="min-w-0">
368 <Eyebrow>How it landed</Eyebrow>
369 <p className="mt-2 text-sm leading-6 text-fg-soft">
370 {row.byAgents
371 ? `g1t merged it once everything the repository asks for was met, with no one pressing merge.`
372 : `${row.by?.name ?? "A person"} merged it.`}
373 {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 look374 </p>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms375 </div>
376 <div className="min-w-0">
377 <Eyebrow>What changed</Eyebrow>
378 <div className="mt-2">
379 <Facts facts={row.facts} />
380 </div>
381 </div>
382 </div>
383 <div className="mt-3">
384 <Link
385 to={row.to}
386 prefetch="intent"
387 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"
388 >
389 Open pull request <ArrowUpRight size={14} />
390 </Link>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look391 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms392 </Row>
393 );
394}
395
396function List({ children }: { children: ReactNode }) {
397 return <ul className="divide-y divide-line">{children}</ul>;
398}
399
400function Empty({ children, action }: { children: ReactNode; action?: ReactNode }) {
401 return (
402 <div className="px-5 py-10 text-center">
403 <div className="mx-auto max-w-md text-sm leading-6 text-muted">{children}</div>
404 {action && <div className="mt-4 flex flex-wrap justify-center gap-2">{action}</div>}
405 </div>
406 );
407}
408
409// --- The stat strip -----------------------------------------------------------------
410
411function Stat({ label, value, hint, dot, title }: { label: string; value: string; hint: ReactNode; dot?: string; title?: string }) {
412 return (
413 <div className="min-w-0 bg-surface px-4 py-3.5 sm:px-5 sm:py-4" title={title}>
414 <p className="flex items-center gap-1.5 truncate text-xs text-muted">
415 {dot && <span className={cn("size-1.5 shrink-0 rounded-full", dot)} />}
416 {label}
417 </p>
418 <p className="mt-1 font-display text-2xl font-semibold tracking-tight tabular-nums sm:text-[1.75rem]">{value}</p>
419 <p className="mt-0.5 truncate text-xs text-faint">{hint}</p>
420 </div>
421 );
422}
423
424// --- This week ----------------------------------------------------------------------
425
426/** Seven days of landed changes, each split into what agents landed alone and what a person merged. */
427function WeekChart({ week }: { week: Week }) {
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar428 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 200ms429 const height = 112;
430 const delta = change(week.total, week.previous);
431 return (
432 <div>
433 <div className="flex items-end justify-between gap-3">
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look434 <div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms435 <p className="font-display text-3xl font-semibold tracking-tight tabular-nums">{week.total.toLocaleString("en-US")}</p>
436 <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 look437 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms438 {delta != null ? (
439 <span
440 className={cn(
441 "rounded-full px-2 py-0.5 text-xs font-medium tabular-nums ring-1",
442 delta >= 0 ? "text-accent ring-accent/30" : "text-warn ring-warn/30",
443 )}
444 >
445 {signedPercent(delta)} vs last week
446 </span>
447 ) : week.previous === 0 && week.total > 0 ? (
448 <span className="text-xs text-faint">none the week before</span>
449 ) : null}
450 </div>
451 {week.total === 0 ? (
452 <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 bar453 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 200ms454 </p>
455 ) : (
456 <div className="relative mt-5" style={{ height: height + 20 }}>
457 <div className="absolute inset-x-0 border-t border-line" style={{ top: height }} />
458 <div className="absolute inset-x-0 top-0 flex items-end justify-between gap-1" style={{ height }}>
459 {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 bar460 const total = day.agents + day.assisted + day.people;
461 const bar = (n: number) => Math.max(3, Math.round((n / max) * (height - 4)));
462 // Top to bottom: agents alone, agents with a person, people.
463 const segments = [
464 { n: day.agents, tone: "bg-merged" },
465 { n: day.assisted, tone: "bg-warn" },
466 { n: day.people, tone: "bg-info" },
467 ].filter((segment) => segment.n > 0);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms468 const today = index === week.days.length - 1;
469 return (
470 <div key={day.key} className="group relative flex h-full flex-1 flex-col items-center justify-end">
471 {total > 0 && (
472 <span
473 className={cn(
474 "mb-1 text-[0.625rem] tabular-nums",
475 today ? "text-fg-soft" : "text-faint opacity-0 group-hover:opacity-100",
476 )}
477 >
478 {total}
479 </span>
480 )}
481 <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 bar482 {segments.map((segment, at) => (
483 <span key={segment.tone} className={cn("block", segment.tone, at === 0 && "rounded-t")} style={{ height: bar(segment.n) }} />
484 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms485 {total === 0 && <span className="block h-[2px] rounded-full bg-line-strong" />}
486 </div>
487 <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">
488 <p className="font-medium text-fg">{day.label}</p>
489 <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 bar490 <span className="size-1.5 rounded-full bg-merged" /> {day.agents} by agents on their own
491 </p>
492 <p className="flex items-center gap-1.5 text-muted">
493 <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 200ms494 </p>
495 <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 bar496 <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 200ms497 </p>
498 </div>
499 </div>
500 );
501 })}
502 </div>
503 <div className="absolute inset-x-0 bottom-0 flex justify-between gap-1">
504 {week.days.map((day, index) => (
505 <span
506 key={day.key}
507 className={cn(
508 "flex-1 text-center text-[0.6875rem]",
509 index === week.days.length - 1 ? "font-medium text-fg-soft" : "text-faint",
510 )}
511 >
512 {day.label}
513 </span>
514 ))}
515 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look516 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms517 )}
518 <div className={cn("mt-3 flex-wrap gap-x-4 gap-y-1 text-xs text-muted", week.total === 0 ? "hidden" : "flex")}>
519 <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 bar520 <span className="size-2 rounded-sm bg-merged" /> Agents, on their own
521 </span>
522 <span className="inline-flex items-center gap-1.5">
523 <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 200ms524 </span>
525 <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 bar526 <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 200ms527 </span>
528 </div>
Fixes from the page sweep529 {/* A table does not shrink to sr-only's 1px; its wrapper does. */}
530 <div className="sr-only">
531 <table>
532 <caption>Changes landed each day</caption>
533 <thead>
534 <tr>
535 <th>Day</th>
536 <th>Agents, on their own</th>
537 <th>Agents, merged by a person</th>
538 <th>People</th>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms539 </tr>
Fixes from the page sweep540 </thead>
541 <tbody>
542 {week.days.map((day) => (
543 <tr key={day.key}>
544 <td>{day.key}</td>
545 <td>{day.agents}</td>
546 <td>{day.assisted}</td>
547 <td>{day.people}</td>
548 </tr>
549 ))}
550 </tbody>
551 </table>
552 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms553 </div>
554 );
555}
556
557// --- Activity -----------------------------------------------------------------------
558
559const VERB: Record<Verb, string> = {
560 landed: "landed",
561 opened_issue: "opened",
562 closed_issue: "closed",
563 started: "started on",
564 ready: "marked ready",
565 checks_passed: "checks passed on",
566 checks_failed: "checks failed on",
567 approved: "approved",
568 changes_requested: "asked for changes on",
569 commented: "commented on",
570 asked: "was asked about",
571 deployed: "deployed production",
572 deploy_failed: "production build failed",
573 learned: "learned",
574};
575
576/** One line of the feed: who, what, and the one thing it was about, by name. */
577function FeedLine({ group, titles }: { group: ActivityGroup; titles: Record<string, string> }) {
578 const part = group.parts[0];
579 const base = `/${group.repo.namespace}/${group.repo.name}`;
580 const actor = group.actor ?? "g1t";
581 const agent = group.actor == null || isAgent(group.actor);
582 const number = part.numbers[0];
583 const title = number != null ? titles[`${group.repo.namespace}/${group.repo.name}#${number}`.toLowerCase()] : undefined;
584 const more = group.parts.length - 1 + Math.max(0, part.numbers.length - 1);
585 return (
586 <li className="flex gap-2.5 py-2">
587 <span className="mt-px">
588 <Avatar name={actor} size={22} />
589 </span>
590 <span className="min-w-0 grow text-[0.8125rem] leading-5 text-muted">
591 <span className={cn("font-medium", agent ? "text-merged" : "text-fg")}>{actor}</span> {VERB[part.verb]}{" "}
592 {part.verb === "learned" ? (
593 <Link to={part.to ?? `${base}/memory`} className="text-fg-soft hover:text-fg">
594 “{part.texts[0]}”
595 </Link>
596 ) : number != null ? (
597 <Link to={`${base}/issues/${number}`} prefetch="intent" className="text-fg-soft hover:text-fg">
598 <span className="font-mono text-xs">#{number}</span>
599 {title && <> {title}</>}
600 </Link>
601 ) : part.to ? (
602 <Link to={part.to} className="text-fg-soft hover:text-fg">
603 {group.repo.name}
604 </Link>
605 ) : null}
606 {more > 0 && <span className="text-faint"> and {more} more</span>}
607 <span className="block text-xs text-faint">
608 <span className="font-mono">{group.repo.name}</span> · <TimeAgo at={group.at} />
609 </span>
610 </span>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look611 </li>
612 );
613}
614
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms615// --- Get started --------------------------------------------------------------------
616
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look617type Step = { done: boolean; title: string; about: string; to: string | null; action: string };
618
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms619/** 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 look620function GetStarted({ steps }: { steps: Step[] }) {
621 const left = steps.filter((step) => !step.done).length;
622 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms623 <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 look624 <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 200ms625 <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 look626 <span className="text-xs text-muted">
627 {steps.length - left} of {steps.length} done
628 </span>
629 </div>
630 <ol className="mt-4 space-y-2">
631 {steps.map((step, index) => (
632 <li
633 key={step.title}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms634 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 look635 >
636 <span
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms637 className={cn(
638 "mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full text-[0.6875rem] font-medium",
639 step.done ? "bg-accent text-bg" : "text-muted ring-1 ring-line-strong",
640 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look641 >
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms642 {step.done ? <Check size={12} /> : index + 1}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look643 </span>
644 <span className="min-w-0 grow">
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms645 <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 look646 {!step.done && <span className="mt-0.5 block text-xs leading-5 text-muted">{step.about}</span>}
647 </span>
648 {!step.done && step.to && (
649 <Link
650 to={step.to}
651 className="shrink-0 rounded-md bg-fg px-2.5 py-1 text-xs font-medium text-bg transition-colors hover:bg-white"
652 >
653 {step.action}
654 </Link>
655 )}
656 </li>
657 ))}
658 </ol>
659 </section>
660 );
661}
662
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms663// --- The page -----------------------------------------------------------------------
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look664
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms665const TAB_LABEL: Record<Tab, string> = { needs: "Needs you", waiting: "Waiting on agents", landed: "Landed today" };
666const TAB_SHORT: Record<Tab, string> = { needs: "Needs you", waiting: "Waiting", landed: "Today" };
667const 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 look668
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step669export 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 look670 const shell = useRouteLoaderData("root")?.shell as ShellData | null | undefined;
671 const loaded: Loaded = loaderData;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms672 const [params] = useSearchParams();
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look673 // 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 200ms674 useLiveRefresh(loaded.changing);
675 const [greeting, setGreeting] = useState(loaded.greeting);
676 const [date, setDate] = useState(loaded.date);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look677 useEffect(() => {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms678 // The greeting and date follow the viewer's own clock, and the server
679 // learns their zone for next time.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look680 setGreeting(greetingFor(new Date().getHours()));
681 try {
682 const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms683 setDate(dateLine(Date.now(), zone ?? null));
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look684 if (zone) document.cookie = `${TZ_COOKIE}=${encodeURIComponent(zone)}; Path=/; Max-Age=31536000; SameSite=Lax`;
685 } catch {
686 // Nothing to remember.
687 }
688 }, []);
689
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms690 const { viewer, repos, waiting, landed, liveTotal, week, groups, titles, stats, canRunAgents, trial } = loaded;
691 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 look692
693 // 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 200ms694 const limit = shell?.limit && !shell.limit.comped && shell.limit.state !== "ok" && workspace ? shell.limit : null;
695 const limitRow: NeedRow[] = limit
696 ? [
697 (() => {
698 const detail =
699 limit.state === "stopped"
700 ? "No new agent starts until the limit is raised or a card is added."
701 : `${dollars(limit.exposureMicros)} of ${dollars(limit.ceilingMicros ?? 0)} used. Add a card so work keeps going.`;
702 return {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look703 key: "limit",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms704 reason: "blocking" as const,
705 repo: null,
706 ref: null,
707 title: limit.state === "stopped" ? "Agents are stopped: the usage limit was reached" : "Usage is nearing its limit",
708 ask: detail,
709 by: null,
710 for: null,
711 at: Date.now(),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look712 to: `/${workspace}/-/billing`,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms713 open: `/${workspace}/-/billing`,
714 facts: [
715 { label: "Used", value: `${dollars(limit.exposureMicros)} of ${dollars(limit.ceilingMicros ?? 0)}`, tone: "warn" as const },
716 ],
717 why: whyFor("blocking", { kind: "limit", detail }),
718 quick: null,
719 link: null,
720 };
721 })(),
722 ]
723 : [];
724 const needs = [...limitRow, ...loaded.needs];
725 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 look726
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms727 const counts: Record<Tab, number> = { needs: needs.length, waiting: waiting.length, landed: landed.length };
728 const tab = parseTab(params.get("tab")) ?? "needs";
729 const sort = parseSort(params.get("sort"));
730 const all = params.get("all") === "1";
731 const everyActivity = params.get("activity") === "all";
732 const link = (changes: Record<string, string | null>) => {
733 const next = new URLSearchParams(params);
734 for (const [key, value] of Object.entries(changes)) {
735 if (value == null) next.delete(key);
736 else next.set(key, value);
737 }
738 const text = next.toString();
739 return text ? `?${text}` : "/";
740 };
741
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look742 const steps: Step[] = [
743 {
744 done: Boolean(workspace),
745 title: "Create a workspace",
746 about: "Projects, people and agent credit live in one.",
747 to: "/workspaces/new",
748 action: "Create",
749 },
750 {
751 done: repos.length > 0,
752 title: "Add a project",
753 about: "Create a repository, or import one by its address. Push to it with git as usual.",
754 to: workspace ? `/new?workspace=${workspace}` : "/new",
755 action: "Add",
756 },
757 trial?.open
758 ? {
759 done: true,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent760 title: "Try g1t free",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look761 about: trial.granted
762 ? `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-agent763 : `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 look764 to: workspace ? `/${workspace}/-/integrations` : null,
765 action: "Connect",
766 }
767 : canRunAgents
768 ? {
769 done: Boolean(shell?.free) || shell?.limit == null || shell.limit.comped || shell.limit.state === "ok",
770 title: "Keep work running",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms771 about:
772 "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 look773 to: workspace ? `/${workspace}/-/billing` : null,
774 action: "Billing",
775 }
776 : {
777 done: false,
778 title: "Connect a model",
779 about: `${
780 trialClosed(trial, workspace ?? "This workspace") ? `${trialClosed(trial, workspace ?? "This workspace")} ` : ""
781 }Agents need a model to think with. Connect your Anthropic or OpenAI key, or any compatible endpoint.`,
782 to: workspace ? `/${workspace}/-/integrations` : null,
783 action: "Connect",
784 },
785 {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms786 done: loaded.handedOff,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look787 title: "Hand off an outcome",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms788 about:
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent789 "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 200ms790 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 look791 action: "Write one",
792 },
793 ];
794 const starting = steps.some((step) => !step.done);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms795 const noProjects = repos.length === 0;
796
797 const rows = tab === "needs" ? sortRows(needs, sort) : tab === "waiting" ? sortRows(waiting, sort) : sortRows(landed, sort);
798 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 bar799 // Of the agents' own changes only: people's work is not theirs to land.
800 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 200ms801 const delta = change(week.total, week.previous);
802 const feed = everyActivity ? groups : groups.slice(0, 8);
803
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step804 // "Put an agent on it" first, with "New issue" beside it as before: one
805 // 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 200ms806 const newIssue =
807 repos.length > 0 ? (
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step808 <div className="flex items-stretch">
809 <AgentComposer
810 repos={repos}
811 open={params.get("agent") === "new" || delegated != null}
812 result={delegated}
813 note={
814 canRunAgents ? null : (
815 <>
816 Agents need a model first.{" "}
817 {workspace && (
818 <Link to={`/${workspace}/-/integrations`} className="font-medium text-fg hover:underline">
819 Connect one
820 </Link>
821 )}
822 . The issue still opens.
823 </>
824 )
825 }
826 >
827 <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">
828 <Sparkles size={14} className="text-merged" /> Put an agent on it
829 </span>
830 </AgentComposer>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms831 <DropdownMenu>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step832 <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 200ms833 <Plus size={14} /> New issue
834 </DropdownMenuTrigger>
835 <DropdownMenuContent align="end" className="max-h-80 overflow-y-auto">
836 <DropdownMenuLabel>In which project?</DropdownMenuLabel>
837 {repos.map((repo) => (
838 <DropdownMenuItem key={repo.name} asChild>
839 <Link to={`/${repo.namespace}/${repo.name}/issues/new`}>
840 <Avatar name={repo.name} size={18} square />
841 {repo.name}
842 </Link>
843 </DropdownMenuItem>
844 ))}
845 </DropdownMenuContent>
846 </DropdownMenu>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step847 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms848 ) : (
849 <Link
850 to={workspace ? `/new?workspace=${workspace}` : "/new"}
851 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"
852 >
853 <Plus size={14} /> New project
854 </Link>
855 );
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look856
857 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms858 <main className="mx-auto max-w-7xl space-y-6 px-4 py-8 sm:px-6 sm:py-10">
859 <header className="flex flex-wrap items-end justify-between gap-x-6 gap-y-4">
860 <div className="min-w-0">
861 <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 look862 {greeting}, {loaded.name}
863 </h1>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms864 <p className="mt-1.5 text-sm text-muted" suppressHydrationWarning>
865 <span className="text-fg-soft">{date}</span> · {loaded.summary}
866 </p>
867 </div>
868 <div className="flex flex-wrap items-center gap-2">
869 {newIssue}
870 {needs.length > 0 && (
871 <Link
872 to={`${link({ tab: "needs", all: null })}#work`}
873 preventScrollReset
874 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]"
875 >
876 Review {needs.length} that need{needs.length === 1 ? "s" : ""} you <ArrowRight size={14} />
877 </Link>
878 )}
879 </div>
880 </header>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look881
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms882 {starting && <GetStarted steps={steps} />}
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 <section
885 aria-label="At a glance"
886 className="grid grid-cols-2 gap-px overflow-hidden rounded-xl border border-line bg-line lg:grid-cols-5"
887 >
888 <Stat
889 label="Projects"
890 value={stats.projects == null ? "—" : String(stats.projects)}
891 hint={
892 stats.projectsThisMonth == null
893 ? "in this workspace"
894 : stats.projectsThisMonth > 0
895 ? `+${stats.projectsThisMonth} this month`
896 : "none new this month"
897 }
898 />
899 <Stat
900 label="Agents"
901 value={loaded.runsLoaded || !workspace ? String(liveTotal) : "—"}
902 dot={liveTotal > 0 ? "bg-accent animate-pulse" : undefined}
903 hint={
904 liveTotal > 0
905 ? `live now · ${stats.agentHours < 10 ? stats.agentHours.toFixed(1) : Math.round(stats.agentHours)}h this week`
906 : stats.agentHours > 0
907 ? `none live · ${stats.agentHours < 10 ? stats.agentHours.toFixed(1) : Math.round(stats.agentHours)}h this week`
908 : "none live now"
909 }
910 />
911 <Stat
912 label="Changes this week"
913 value={loaded.perRepoLoaded ? String(week.total) : "—"}
914 hint={
915 delta != null ? `${signedPercent(delta)} vs last week` : week.previous === 0 ? "none the week before" : "merged pull requests"
916 }
917 />
918 <Stat
919 label="Landed without you"
920 dot="bg-merged"
921 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 bar922 hint={share == null ? "no agent changes yet" : `${week.byAgents} of ${week.agentChanges} agent changes`}
923 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 200ms924 />
925 <div className="col-span-2 lg:col-span-1">
926 <Stat
927 label="Need you"
928 dot="bg-warn"
929 value={String(needs.length)}
930 hint={blocking > 0 ? `${blocking} blocking` : needs.length > 0 ? "nothing blocking" : "all clear"}
931 />
932 </div>
933 </section>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look934
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms935 <div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_21rem] xl:grid-cols-[minmax(0,1fr)_23rem]">
936 <section id="work" className="min-w-0 scroll-mt-20 self-start overflow-hidden rounded-xl border border-line bg-surface">
937 <div className="flex items-center gap-2 border-b border-line px-2 sm:px-3">
938 <nav className="-mb-px flex min-w-0 grow gap-1 overflow-x-auto [scrollbar-width:none]" aria-label="Mission control">
939 {(["needs", "waiting", "landed"] as const).map((value) => (
940 <Link
941 key={value}
942 to={link({ tab: value, all: null })}
943 preventScrollReset
944 aria-current={tab === value ? "page" : undefined}
945 className={cn(
946 "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",
947 tab === value ? "border-merged font-medium text-fg" : "border-transparent text-muted hover:text-fg",
948 )}
949 >
950 <span className="sm:hidden">{TAB_SHORT[value]}</span>
951 <span className="hidden sm:inline">{TAB_LABEL[value]}</span>
952 <span
953 className={cn(
954 "rounded-full px-1.5 text-[0.6875rem] tabular-nums",
955 tab === value && value === "needs" && counts.needs > 0 ? "bg-warn/15 text-warn" : "bg-line text-muted",
956 )}
957 >
958 {counts[value]}
959 </span>
960 </Link>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look961 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms962 </nav>
963 <DropdownMenu>
964 <DropdownMenuTrigger
965 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"
966 aria-label="Sort"
967 >
968 <ArrowDownWideNarrow size={14} />
969 <span className="hidden sm:inline">{SORT_LABEL[sort]}</span>
970 </DropdownMenuTrigger>
971 <DropdownMenuContent align="end">
972 {(["impact", "newest"] as const).map((value) => (
973 <DropdownMenuItem key={value} asChild>
974 <Link to={link({ sort: value === "impact" ? null : value })} preventScrollReset>
975 {sort === value ? <Check /> : <span className="size-4" />}
976 {SORT_LABEL[value]}
977 </Link>
978 </DropdownMenuItem>
979 ))}
980 </DropdownMenuContent>
981 </DropdownMenu>
982 </div>
983
984 {!loaded.perRepoLoaded && tab !== "waiting" ? (
985 <div className="p-4">
986 <Unavailable what={tab === "needs" ? "Some of what needs you" : "What landed"} />
987 </div>
988 ) : null}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look989
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms990 {tab === "needs" &&
991 (needs.length === 0 ? (
992 noProjects ? (
993 <Empty
994 action={
995 <Link
996 to={workspace ? `/new?workspace=${workspace}` : "/new"}
997 className="rounded-md bg-fg px-3 py-1.5 text-sm font-medium text-bg hover:bg-white"
998 >
999 Create or import a project
1000 </Link>
1001 }
1002 >
1003 <p className="font-medium text-fg">No projects yet</p>
1004 <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-agent1005 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 200ms1006 </p>
1007 </Empty>
1008 ) : (
1009 <>
1010 <Empty>
1011 <p className="font-medium text-fg">Nothing needs you</p>
1012 <p className="mt-1">
1013 Agents are handling everything.{" "}
1014 {landed.length > 0
1015 ? "Here's what they landed today."
1016 : "Reviews, stopped work and failed deploys show up here first."}
1017 </p>
1018 </Empty>
1019 {landed.length > 0 && (
1020 <div className="border-t border-line">
1021 <List>
1022 {landed.slice(0, ROWS).map((row) => (
1023 <LandedCard key={row.key} row={row} first={false} />
1024 ))}
1025 </List>
1026 </div>
1027 )}
1028 </>
1029 )
1030 ) : (
1031 <List>
1032 {(shown as NeedRow[]).map((row, index) => (
1033 <NeedCard key={row.key} row={row} first={index === 0} />
1034 ))}
1035 </List>
1036 ))}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1037
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1038 {tab === "waiting" &&
1039 (!loaded.runsLoaded && workspace ? (
1040 <div className="p-4">
1041 <Unavailable what="Agent runs" />
1042 </div>
1043 ) : waiting.length === 0 ? (
1044 <Empty>
1045 <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-agent1046 <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 200ms1047 </Empty>
1048 ) : (
1049 <List>
1050 {(shown as WaitingRow[]).map((row, index) => (
1051 <WaitingCard key={row.key} row={row} first={index === 0} />
1052 ))}
1053 </List>
1054 ))}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1055
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1056 {tab === "landed" &&
1057 loaded.perRepoLoaded &&
1058 (landed.length === 0 ? (
1059 <Empty>
1060 <p className="font-medium text-fg">Nothing has landed today yet</p>
1061 <p className="mt-1">
1062 {week.total > 0
1063 ? `${plural(week.total, "change")} landed in the last 7 days.`
1064 : "Merged pull requests show up here the moment they land."}
1065 </p>
1066 </Empty>
1067 ) : (
1068 <List>
1069 {(shown as LandedRow[]).map((row, index) => (
1070 <LandedCard key={row.key} row={row} first={index === 0} />
1071 ))}
1072 </List>
1073 ))}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1074
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1075 {rows.length > ROWS && (
1076 <div className="flex items-center justify-between border-t border-line px-4 py-2.5 text-xs text-muted sm:px-5">
1077 <span>
1078 Showing {shown.length} of {rows.length}
1079 </span>
1080 <Link
1081 to={link({ all: all ? null : "1" })}
1082 preventScrollReset
1083 className="inline-flex items-center gap-1 font-medium text-fg-soft hover:text-fg"
1084 >
1085 {all ? "Show fewer" : "See all"} <ChevronRight size={12} />
1086 </Link>
1087 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1088 )}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1089 </section>
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 <aside className="min-w-0 space-y-6">
1092 <section className="rounded-xl border border-line bg-surface p-5">
1093 <div className="flex items-center justify-between">
1094 <h2 className="text-base font-semibold tracking-tight">This week</h2>
1095 {workspace && (
1096 <Link to={`/${workspace}/-/agents`} className="text-xs text-muted hover:text-fg">
1097 Fleet
1098 </Link>
1099 )}
1100 </div>
1101 <div className="mt-4">{loaded.perRepoLoaded ? <WeekChart week={week} /> : <Unavailable what="The week" />}</div>
1102 {workspace && stats.weekCost > 0 && (
1103 <Link
1104 to={`/${workspace}/-/usage`}
1105 className="mt-4 flex items-center justify-between border-t border-line pt-3 text-xs text-muted hover:text-fg"
1106 >
1107 <span>Agents and sandboxes cost {usd(stats.weekCost)} this week</span>
1108 <ChevronRight size={12} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1109 </Link>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1110 )}
1111 </section>
1112
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix1113 {workspace && (
1114 <TokenUsagePanel
1115 workspace={loaderData.tokens.workspace}
1116 mine={loaderData.tokens.mine}
1117 usageHref={`/${workspace}/-/usage`}
1118 />
1119 )}
1120
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1121 <section id="activity" className="scroll-mt-20 rounded-xl border border-line bg-surface p-5">
1122 <div className="flex items-center justify-between">
1123 <h2 className="text-base font-semibold tracking-tight">Activity</h2>
1124 {groups.length > 8 && (
1125 <Link
1126 to={`${link({ activity: everyActivity ? null : "all" })}#activity`}
1127 preventScrollReset
1128 className="inline-flex items-center gap-0.5 text-xs text-muted hover:text-fg"
1129 >
1130 {everyActivity ? "Less" : "All activity"} <ChevronRight size={12} />
1131 </Link>
1132 )}
1133 </div>
1134 <p className="mt-1 flex items-center gap-3 text-[0.6875rem] text-faint">
1135 <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-agent1136 <Avatar name="g1t" size={12} /> agents
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1137 </span>
1138 <span className="inline-flex items-center gap-1.5">
1139 <Avatar name={viewer.username} size={12} /> people
1140 </span>
1141 </p>
1142 {!loaded.perRepoLoaded ? (
1143 <div className="mt-3">
1144 <Unavailable what="Activity" />
1145 </div>
1146 ) : feed.length === 0 ? (
1147 <p className="mt-3 text-sm leading-6 text-muted">
1148 Nothing has moved yet. Merges, deploys, checks, reviews and what agents learn show up here.
1149 </p>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1150 ) : (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1151 <ol className="mt-2 divide-y divide-line/70">
1152 {feed.map((group) => (
1153 <FeedLine key={group.id} group={group} titles={titles} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1154 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1155 </ol>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1156 )}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1157 </section>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1158
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1159 <section className="rounded-xl border border-line bg-surface p-5">
1160 <h2 className="text-sm font-semibold">Connect your own agent</h2>
1161 <p className="mt-1.5 text-xs leading-5 text-muted">
1162 Add g1t to your coding agent. It signs in through your browser; there is no token to copy.
1163 </p>
1164 <AgentSetup className="mt-3" />
1165 <Link
1166 to="https://docs.g1t.sh/guides/bring-your-own-agent/"
1167 className="mt-3 inline-flex items-center gap-1 text-xs text-muted hover:text-fg"
1168 >
1169 How it works <ArrowRight size={12} />
1170 </Link>
1171 </section>
1172 </aside>
1173 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1174 </main>
1175 );
1176}