Skip to content

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

1,177 lines48,987 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";
web: issues, pull requests, actions and deployments buttons say they are working until the page shows the change; a re-run shows its new log, a refused invitation reloads beside its reason36import { Avatar, SubmitButton, 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
web: issues, pull requests, actions and deployments buttons say they are working until the page shows the change; a re-run shows its new log, a refused invitation reloads beside its reason120/**
121 * A form that acts on a pull request from here: working until the action
122 * has answered and the page has reloaded what it changed, then done, or
123 * the error beside it.
124 */
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms125function QuickForm({ quick, variant = "quiet" }: { quick: QuickAction; variant?: "quiet" | "accent" }) {
126 const fetcher = useFetcher<{ error?: string } | null>();
127 const [sent, setSent] = useState(false);
web: issues, pull requests, actions and deployments buttons say they are working until the page shows the change; a re-run shows its new log, a refused invitation reloads beside its reason128 const done = sent && fetcher.state === "idle" && !fetcher.data?.error;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look129 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms130 <fetcher.Form method="post" action={quick.to} onSubmit={() => setSent(true)} className="contents">
131 {Object.entries(quick.fields).map(([name, value]) => (
132 <input key={name} type="hidden" name={name} value={value} />
133 ))}
web: issues, pull requests, actions and deployments buttons say they are working until the page shows the change; a re-run shows its new log, a refused invitation reloads beside its reason134 <SubmitButton
135 fetcher={fetcher}
136 disabled={done}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms137 className={cn(
138 "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors disabled:cursor-default",
139 variant === "accent"
140 ? "bg-merged text-bg hover:bg-[#c9bfff]"
141 : "border border-line-strong text-fg/90 hover:bg-raised hover:text-fg",
142 done && "border-accent/40 text-accent",
143 )}
144 >
Fast pages, required checks on the branch, self-hosted runners, honest incidents145 {done ? <Check size={14} /> : null}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms146 {done ? quick.done : quick.label}
web: issues, pull requests, actions and deployments buttons say they are working until the page shows the change; a re-run shows its new log, a refused invitation reloads beside its reason147 </SubmitButton>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms148 {fetcher.data?.error && <span className="basis-full text-xs text-danger">{fetcher.data.error}</span>}
149 </fetcher.Form>
150 );
151}
152
153function ProjectMark({ repo }: { repo: { namespace: string; name: string } | null }) {
154 return <Avatar name={repo ? repo.name : "g1t"} size={32} square />;
155}
156
157/** A row that opens to what is known about it; the first one starts open. */
158function Row({
159 open,
160 repo,
161 by,
162 title,
163 sub,
164 chip,
165 at,
166 children,
167}: {
168 open: boolean;
169 repo: { namespace: string; name: string } | null;
170 by: Who | null;
171 title: ReactNode;
172 sub: ReactNode;
173 chip: ReactNode;
174 at: number;
175 children: ReactNode;
176}) {
177 return (
178 <li>
179 <details open={open} className="group">
180 <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">
181 <ProjectMark repo={repo} />
182 <span className="min-w-0 grow">
183 <span className="block truncate text-sm">{title}</span>
184 <span className="mt-0.5 flex min-w-0 items-center gap-1.5 text-xs text-muted">
185 <span className="shrink-0 font-medium text-fg-soft">{repo ? repo.name : "Workspace"}</span>
186 {by && (
187 <>
188 <span className="text-faint">·</span>
189 <Person who={by} size={14} className={cn("shrink-0", by.agent ? "text-merged" : "")} />
190 </>
191 )}
192 {sub && (
193 <>
194 <span className="text-faint">—</span>
195 <span className="truncate">{sub}</span>
196 </>
197 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look198 </span>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms199 </span>
200 <span className="hidden sm:inline-flex">{chip}</span>
201 <span className="hidden w-16 shrink-0 text-right text-xs text-faint sm:block">
202 <TimeAgo at={at} />
203 </span>
204 <ChevronDown size={16} className="shrink-0 text-faint transition-transform group-open:rotate-180" />
205 </summary>
206 <div className="px-4 pb-4 sm:px-5 sm:pb-5">
207 <div className="mb-3 flex items-center gap-2 sm:hidden">
208 {chip}
209 <span className="text-xs text-faint">
210 <TimeAgo at={at} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look211 </span>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms212 </div>
213 {children}
214 </div>
215 </details>
216 </li>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look217 );
218}
219
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms220function NeedCard({ row, first }: { row: NeedRow; first: boolean }) {
221 const isPull = row.to.includes("/pull/");
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look222 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms223 <Row
224 open={first}
225 repo={row.repo}
226 by={row.by}
227 title={
228 <>
229 <span className="font-medium">{row.title}</span>
230 {row.ref && <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>}
231 </>
232 }
233 sub={row.ask}
234 chip={<Chip tone={CHIP_TONE[row.reason]}>{REASON_LABEL[row.reason]}</Chip>}
235 at={row.at}
236 >
237 <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)]">
238 <div className="min-w-0">
239 <Eyebrow>The ask</Eyebrow>
240 <p className="mt-2 text-sm leading-6 text-fg-soft">{row.ask}</p>
241 <p className="mt-2 text-xs leading-5 text-muted">
242 <Link to={row.to} className="font-medium text-fg hover:underline">
243 {row.repo ? `${row.repo.name}${row.ref ?? ""}` : row.title}
244 </Link>
245 {row.for && (
246 <>
247 {" "}
248 · started for <span className="text-fg-soft">{row.for}</span>
249 </>
250 )}
251 </p>
252 </div>
253 <div className="min-w-0">
254 <Eyebrow>What the agent already knows</Eyebrow>
255 <div className="mt-2">
256 <Facts facts={row.facts} />
257 </div>
258 </div>
259 <div className="rounded-lg border border-warn/20 bg-warn/[0.06] p-3.5 md:col-span-2 2xl:col-span-1">
260 <p className="text-xs font-semibold text-warn">Why this needs you</p>
261 <p className="mt-1.5 text-sm leading-6 text-fg-soft">{row.why}</p>
262 <p className="mt-2 text-xs text-faint">
263 Waiting <TimeAgo at={row.at} />
264 </p>
265 </div>
266 </div>
267 <div className="mt-3 flex flex-wrap items-center gap-2">
268 <Link
269 to={row.to}
270 prefetch="intent"
271 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"
272 >
273 Review and respond <ArrowUpRight size={14} />
274 </Link>
275 {row.quick && <QuickForm quick={row.quick} />}
276 {row.link && (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look277 <Link
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms278 to={row.link.to}
279 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 look280 >
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms281 {row.link.label}
282 </Link>
283 )}
284 {row.open !== row.to && (
285 <Link to={row.open} prefetch="intent" className="px-2 py-1.5 text-sm text-muted hover:text-fg">
286 {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 look287 </Link>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms288 )}
289 </div>
290 </Row>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look291 );
292}
293
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms294function WaitingCard({ row, first }: { row: WaitingRow; first: boolean }) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look295 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms296 <Row
297 open={first}
298 repo={row.repo}
299 by={row.by}
300 title={
301 <>
302 <span className="font-medium">{row.title}</span>
303 {row.ref && <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>}
304 </>
305 }
306 sub={row.detail}
307 chip={
308 <Chip tone="border-line-strong bg-raised text-muted">
309 {row.live && <span className="mr-1.5 size-1.5 animate-pulse rounded-full bg-accent" />}
310 {row.chip}
311 </Chip>
312 }
313 at={row.at}
314 >
315 <div className="grid gap-4 rounded-xl border border-line bg-bg/50 p-4 md:grid-cols-2 md:gap-6">
316 <div className="min-w-0">
317 <Eyebrow>{row.live ? "Doing now" : "Where it stands"}</Eyebrow>
318 <p className="mt-2 text-sm leading-6 text-fg-soft">{row.detail}</p>
319 </div>
320 <div className="min-w-0">
321 <Eyebrow>What is known</Eyebrow>
322 <div className="mt-2">
323 <Facts facts={row.facts} />
324 </div>
325 </div>
326 </div>
327 <div className="mt-3 flex flex-wrap gap-2">
328 <Link
329 to={row.to}
330 prefetch="intent"
331 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"
332 >
333 {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 look334 </Link>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms335 {row.run && row.run !== row.to && (
336 <Link to={row.run} prefetch="intent" className="px-2 py-1.5 text-sm text-muted hover:text-fg">
337 Watch the run
338 </Link>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look339 )}
340 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms341 </Row>
342 );
343}
344
345function LandedCard({ row, first }: { row: LandedRow; first: boolean }) {
346 return (
347 <Row
348 open={first}
349 repo={row.repo}
350 by={row.byAgents ? { name: row.agent, agent: isAgent(row.agent) } : row.by}
351 title={
352 <>
353 <span className="font-medium">{row.title}</span>
354 <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>
355 </>
356 }
357 sub={row.byAgents ? "landed without a person" : `merged by ${row.by?.name ?? "a person"}`}
358 chip={
359 row.byAgents ? (
360 <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 look361 ) : (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms362 <Chip tone="border-warn/35 bg-warn/10 text-warn">Needed a person</Chip>
363 )
364 }
365 at={row.at}
366 >
367 <div className="grid gap-4 rounded-xl border border-line bg-bg/50 p-4 md:grid-cols-2 md:gap-6">
368 <div className="min-w-0">
369 <Eyebrow>How it landed</Eyebrow>
370 <p className="mt-2 text-sm leading-6 text-fg-soft">
371 {row.byAgents
372 ? `g1t merged it once everything the repository asks for was met, with no one pressing merge.`
373 : `${row.by?.name ?? "A person"} merged it.`}
374 {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 look375 </p>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms376 </div>
377 <div className="min-w-0">
378 <Eyebrow>What changed</Eyebrow>
379 <div className="mt-2">
380 <Facts facts={row.facts} />
381 </div>
382 </div>
383 </div>
384 <div className="mt-3">
385 <Link
386 to={row.to}
387 prefetch="intent"
388 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"
389 >
390 Open pull request <ArrowUpRight size={14} />
391 </Link>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look392 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms393 </Row>
394 );
395}
396
397function List({ children }: { children: ReactNode }) {
398 return <ul className="divide-y divide-line">{children}</ul>;
399}
400
401function Empty({ children, action }: { children: ReactNode; action?: ReactNode }) {
402 return (
403 <div className="px-5 py-10 text-center">
404 <div className="mx-auto max-w-md text-sm leading-6 text-muted">{children}</div>
405 {action && <div className="mt-4 flex flex-wrap justify-center gap-2">{action}</div>}
406 </div>
407 );
408}
409
410// --- The stat strip -----------------------------------------------------------------
411
412function Stat({ label, value, hint, dot, title }: { label: string; value: string; hint: ReactNode; dot?: string; title?: string }) {
413 return (
414 <div className="min-w-0 bg-surface px-4 py-3.5 sm:px-5 sm:py-4" title={title}>
415 <p className="flex items-center gap-1.5 truncate text-xs text-muted">
416 {dot && <span className={cn("size-1.5 shrink-0 rounded-full", dot)} />}
417 {label}
418 </p>
419 <p className="mt-1 font-display text-2xl font-semibold tracking-tight tabular-nums sm:text-[1.75rem]">{value}</p>
420 <p className="mt-0.5 truncate text-xs text-faint">{hint}</p>
421 </div>
422 );
423}
424
425// --- This week ----------------------------------------------------------------------
426
427/** Seven days of landed changes, each split into what agents landed alone and what a person merged. */
428function WeekChart({ week }: { week: Week }) {
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar429 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 200ms430 const height = 112;
431 const delta = change(week.total, week.previous);
432 return (
433 <div>
434 <div className="flex items-end justify-between gap-3">
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look435 <div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms436 <p className="font-display text-3xl font-semibold tracking-tight tabular-nums">{week.total.toLocaleString("en-US")}</p>
437 <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 look438 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms439 {delta != null ? (
440 <span
441 className={cn(
442 "rounded-full px-2 py-0.5 text-xs font-medium tabular-nums ring-1",
443 delta >= 0 ? "text-accent ring-accent/30" : "text-warn ring-warn/30",
444 )}
445 >
446 {signedPercent(delta)} vs last week
447 </span>
448 ) : week.previous === 0 && week.total > 0 ? (
449 <span className="text-xs text-faint">none the week before</span>
450 ) : null}
451 </div>
452 {week.total === 0 ? (
453 <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 bar454 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 200ms455 </p>
456 ) : (
457 <div className="relative mt-5" style={{ height: height + 20 }}>
458 <div className="absolute inset-x-0 border-t border-line" style={{ top: height }} />
459 <div className="absolute inset-x-0 top-0 flex items-end justify-between gap-1" style={{ height }}>
460 {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 bar461 const total = day.agents + day.assisted + day.people;
462 const bar = (n: number) => Math.max(3, Math.round((n / max) * (height - 4)));
463 // Top to bottom: agents alone, agents with a person, people.
464 const segments = [
465 { n: day.agents, tone: "bg-merged" },
466 { n: day.assisted, tone: "bg-warn" },
467 { n: day.people, tone: "bg-info" },
468 ].filter((segment) => segment.n > 0);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms469 const today = index === week.days.length - 1;
470 return (
471 <div key={day.key} className="group relative flex h-full flex-1 flex-col items-center justify-end">
472 {total > 0 && (
473 <span
474 className={cn(
475 "mb-1 text-[0.625rem] tabular-nums",
476 today ? "text-fg-soft" : "text-faint opacity-0 group-hover:opacity-100",
477 )}
478 >
479 {total}
480 </span>
481 )}
482 <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 bar483 {segments.map((segment, at) => (
484 <span key={segment.tone} className={cn("block", segment.tone, at === 0 && "rounded-t")} style={{ height: bar(segment.n) }} />
485 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms486 {total === 0 && <span className="block h-[2px] rounded-full bg-line-strong" />}
487 </div>
488 <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">
489 <p className="font-medium text-fg">{day.label}</p>
490 <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 bar491 <span className="size-1.5 rounded-full bg-merged" /> {day.agents} by agents on their own
492 </p>
493 <p className="flex items-center gap-1.5 text-muted">
494 <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 200ms495 </p>
496 <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 bar497 <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 200ms498 </p>
499 </div>
500 </div>
501 );
502 })}
503 </div>
504 <div className="absolute inset-x-0 bottom-0 flex justify-between gap-1">
505 {week.days.map((day, index) => (
506 <span
507 key={day.key}
508 className={cn(
509 "flex-1 text-center text-[0.6875rem]",
510 index === week.days.length - 1 ? "font-medium text-fg-soft" : "text-faint",
511 )}
512 >
513 {day.label}
514 </span>
515 ))}
516 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look517 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms518 )}
519 <div className={cn("mt-3 flex-wrap gap-x-4 gap-y-1 text-xs text-muted", week.total === 0 ? "hidden" : "flex")}>
520 <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 bar521 <span className="size-2 rounded-sm bg-merged" /> Agents, on their own
522 </span>
523 <span className="inline-flex items-center gap-1.5">
524 <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 200ms525 </span>
526 <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 bar527 <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 200ms528 </span>
529 </div>
Fixes from the page sweep530 {/* A table does not shrink to sr-only's 1px; its wrapper does. */}
531 <div className="sr-only">
532 <table>
533 <caption>Changes landed each day</caption>
534 <thead>
535 <tr>
536 <th>Day</th>
537 <th>Agents, on their own</th>
538 <th>Agents, merged by a person</th>
539 <th>People</th>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms540 </tr>
Fixes from the page sweep541 </thead>
542 <tbody>
543 {week.days.map((day) => (
544 <tr key={day.key}>
545 <td>{day.key}</td>
546 <td>{day.agents}</td>
547 <td>{day.assisted}</td>
548 <td>{day.people}</td>
549 </tr>
550 ))}
551 </tbody>
552 </table>
553 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms554 </div>
555 );
556}
557
558// --- Activity -----------------------------------------------------------------------
559
560const VERB: Record<Verb, string> = {
561 landed: "landed",
562 opened_issue: "opened",
563 closed_issue: "closed",
564 started: "started on",
565 ready: "marked ready",
566 checks_passed: "checks passed on",
567 checks_failed: "checks failed on",
568 approved: "approved",
569 changes_requested: "asked for changes on",
570 commented: "commented on",
571 asked: "was asked about",
572 deployed: "deployed production",
573 deploy_failed: "production build failed",
574 learned: "learned",
575};
576
577/** One line of the feed: who, what, and the one thing it was about, by name. */
578function FeedLine({ group, titles }: { group: ActivityGroup; titles: Record<string, string> }) {
579 const part = group.parts[0];
580 const base = `/${group.repo.namespace}/${group.repo.name}`;
581 const actor = group.actor ?? "g1t";
582 const agent = group.actor == null || isAgent(group.actor);
583 const number = part.numbers[0];
584 const title = number != null ? titles[`${group.repo.namespace}/${group.repo.name}#${number}`.toLowerCase()] : undefined;
585 const more = group.parts.length - 1 + Math.max(0, part.numbers.length - 1);
586 return (
587 <li className="flex gap-2.5 py-2">
588 <span className="mt-px">
589 <Avatar name={actor} size={22} />
590 </span>
591 <span className="min-w-0 grow text-[0.8125rem] leading-5 text-muted">
592 <span className={cn("font-medium", agent ? "text-merged" : "text-fg")}>{actor}</span> {VERB[part.verb]}{" "}
593 {part.verb === "learned" ? (
594 <Link to={part.to ?? `${base}/memory`} className="text-fg-soft hover:text-fg">
595 “{part.texts[0]}”
596 </Link>
597 ) : number != null ? (
598 <Link to={`${base}/issues/${number}`} prefetch="intent" className="text-fg-soft hover:text-fg">
599 <span className="font-mono text-xs">#{number}</span>
600 {title && <> {title}</>}
601 </Link>
602 ) : part.to ? (
603 <Link to={part.to} className="text-fg-soft hover:text-fg">
604 {group.repo.name}
605 </Link>
606 ) : null}
607 {more > 0 && <span className="text-faint"> and {more} more</span>}
608 <span className="block text-xs text-faint">
609 <span className="font-mono">{group.repo.name}</span> · <TimeAgo at={group.at} />
610 </span>
611 </span>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look612 </li>
613 );
614}
615
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms616// --- Get started --------------------------------------------------------------------
617
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look618type Step = { done: boolean; title: string; about: string; to: string | null; action: string };
619
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms620/** 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 look621function GetStarted({ steps }: { steps: Step[] }) {
622 const left = steps.filter((step) => !step.done).length;
623 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms624 <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 look625 <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 200ms626 <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 look627 <span className="text-xs text-muted">
628 {steps.length - left} of {steps.length} done
629 </span>
630 </div>
631 <ol className="mt-4 space-y-2">
632 {steps.map((step, index) => (
633 <li
634 key={step.title}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms635 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 look636 >
637 <span
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms638 className={cn(
639 "mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full text-[0.6875rem] font-medium",
640 step.done ? "bg-accent text-bg" : "text-muted ring-1 ring-line-strong",
641 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look642 >
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms643 {step.done ? <Check size={12} /> : index + 1}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look644 </span>
645 <span className="min-w-0 grow">
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms646 <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 look647 {!step.done && <span className="mt-0.5 block text-xs leading-5 text-muted">{step.about}</span>}
648 </span>
649 {!step.done && step.to && (
650 <Link
651 to={step.to}
652 className="shrink-0 rounded-md bg-fg px-2.5 py-1 text-xs font-medium text-bg transition-colors hover:bg-white"
653 >
654 {step.action}
655 </Link>
656 )}
657 </li>
658 ))}
659 </ol>
660 </section>
661 );
662}
663
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms664// --- The page -----------------------------------------------------------------------
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look665
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms666const TAB_LABEL: Record<Tab, string> = { needs: "Needs you", waiting: "Waiting on agents", landed: "Landed today" };
667const TAB_SHORT: Record<Tab, string> = { needs: "Needs you", waiting: "Waiting", landed: "Today" };
668const 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 look669
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step670export 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 look671 const shell = useRouteLoaderData("root")?.shell as ShellData | null | undefined;
672 const loaded: Loaded = loaderData;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms673 const [params] = useSearchParams();
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look674 // 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 200ms675 useLiveRefresh(loaded.changing);
676 const [greeting, setGreeting] = useState(loaded.greeting);
677 const [date, setDate] = useState(loaded.date);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look678 useEffect(() => {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms679 // The greeting and date follow the viewer's own clock, and the server
680 // learns their zone for next time.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look681 setGreeting(greetingFor(new Date().getHours()));
682 try {
683 const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms684 setDate(dateLine(Date.now(), zone ?? null));
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look685 if (zone) document.cookie = `${TZ_COOKIE}=${encodeURIComponent(zone)}; Path=/; Max-Age=31536000; SameSite=Lax`;
686 } catch {
687 // Nothing to remember.
688 }
689 }, []);
690
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms691 const { viewer, repos, waiting, landed, liveTotal, week, groups, titles, stats, canRunAgents, trial } = loaded;
692 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 look693
694 // 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 200ms695 const limit = shell?.limit && !shell.limit.comped && shell.limit.state !== "ok" && workspace ? shell.limit : null;
696 const limitRow: NeedRow[] = limit
697 ? [
698 (() => {
699 const detail =
700 limit.state === "stopped"
701 ? "No new agent starts until the limit is raised or a card is added."
702 : `${dollars(limit.exposureMicros)} of ${dollars(limit.ceilingMicros ?? 0)} used. Add a card so work keeps going.`;
703 return {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look704 key: "limit",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms705 reason: "blocking" as const,
706 repo: null,
707 ref: null,
708 title: limit.state === "stopped" ? "Agents are stopped: the usage limit was reached" : "Usage is nearing its limit",
709 ask: detail,
710 by: null,
711 for: null,
712 at: Date.now(),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look713 to: `/${workspace}/-/billing`,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms714 open: `/${workspace}/-/billing`,
715 facts: [
716 { label: "Used", value: `${dollars(limit.exposureMicros)} of ${dollars(limit.ceilingMicros ?? 0)}`, tone: "warn" as const },
717 ],
718 why: whyFor("blocking", { kind: "limit", detail }),
719 quick: null,
720 link: null,
721 };
722 })(),
723 ]
724 : [];
725 const needs = [...limitRow, ...loaded.needs];
726 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 look727
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms728 const counts: Record<Tab, number> = { needs: needs.length, waiting: waiting.length, landed: landed.length };
729 const tab = parseTab(params.get("tab")) ?? "needs";
730 const sort = parseSort(params.get("sort"));
731 const all = params.get("all") === "1";
732 const everyActivity = params.get("activity") === "all";
733 const link = (changes: Record<string, string | null>) => {
734 const next = new URLSearchParams(params);
735 for (const [key, value] of Object.entries(changes)) {
736 if (value == null) next.delete(key);
737 else next.set(key, value);
738 }
739 const text = next.toString();
740 return text ? `?${text}` : "/";
741 };
742
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look743 const steps: Step[] = [
744 {
745 done: Boolean(workspace),
746 title: "Create a workspace",
747 about: "Projects, people and agent credit live in one.",
748 to: "/workspaces/new",
749 action: "Create",
750 },
751 {
752 done: repos.length > 0,
753 title: "Add a project",
754 about: "Create a repository, or import one by its address. Push to it with git as usual.",
755 to: workspace ? `/new?workspace=${workspace}` : "/new",
756 action: "Add",
757 },
758 trial?.open
759 ? {
760 done: true,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent761 title: "Try g1t free",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look762 about: trial.granted
763 ? `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-agent764 : `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 look765 to: workspace ? `/${workspace}/-/integrations` : null,
766 action: "Connect",
767 }
768 : canRunAgents
769 ? {
770 done: Boolean(shell?.free) || shell?.limit == null || shell.limit.comped || shell.limit.state === "ok",
771 title: "Keep work running",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms772 about:
773 "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 look774 to: workspace ? `/${workspace}/-/billing` : null,
775 action: "Billing",
776 }
777 : {
778 done: false,
779 title: "Connect a model",
780 about: `${
781 trialClosed(trial, workspace ?? "This workspace") ? `${trialClosed(trial, workspace ?? "This workspace")} ` : ""
782 }Agents need a model to think with. Connect your Anthropic or OpenAI key, or any compatible endpoint.`,
783 to: workspace ? `/${workspace}/-/integrations` : null,
784 action: "Connect",
785 },
786 {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms787 done: loaded.handedOff,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look788 title: "Hand off an outcome",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms789 about:
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent790 "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 200ms791 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 look792 action: "Write one",
793 },
794 ];
795 const starting = steps.some((step) => !step.done);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms796 const noProjects = repos.length === 0;
797
798 const rows = tab === "needs" ? sortRows(needs, sort) : tab === "waiting" ? sortRows(waiting, sort) : sortRows(landed, sort);
799 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 bar800 // Of the agents' own changes only: people's work is not theirs to land.
801 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 200ms802 const delta = change(week.total, week.previous);
803 const feed = everyActivity ? groups : groups.slice(0, 8);
804
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step805 // "Put an agent on it" first, with "New issue" beside it as before: one
806 // 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 200ms807 const newIssue =
808 repos.length > 0 ? (
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step809 <div className="flex items-stretch">
810 <AgentComposer
811 repos={repos}
812 open={params.get("agent") === "new" || delegated != null}
813 result={delegated}
814 note={
815 canRunAgents ? null : (
816 <>
817 Agents need a model first.{" "}
818 {workspace && (
819 <Link to={`/${workspace}/-/integrations`} className="font-medium text-fg hover:underline">
820 Connect one
821 </Link>
822 )}
823 . The issue still opens.
824 </>
825 )
826 }
827 >
828 <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">
829 <Sparkles size={14} className="text-merged" /> Put an agent on it
830 </span>
831 </AgentComposer>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms832 <DropdownMenu>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step833 <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 200ms834 <Plus size={14} /> New issue
835 </DropdownMenuTrigger>
836 <DropdownMenuContent align="end" className="max-h-80 overflow-y-auto">
837 <DropdownMenuLabel>In which project?</DropdownMenuLabel>
838 {repos.map((repo) => (
839 <DropdownMenuItem key={repo.name} asChild>
840 <Link to={`/${repo.namespace}/${repo.name}/issues/new`}>
841 <Avatar name={repo.name} size={18} square />
842 {repo.name}
843 </Link>
844 </DropdownMenuItem>
845 ))}
846 </DropdownMenuContent>
847 </DropdownMenu>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step848 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms849 ) : (
850 <Link
851 to={workspace ? `/new?workspace=${workspace}` : "/new"}
852 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"
853 >
854 <Plus size={14} /> New project
855 </Link>
856 );
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look857
858 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms859 <main className="mx-auto max-w-7xl space-y-6 px-4 py-8 sm:px-6 sm:py-10">
860 <header className="flex flex-wrap items-end justify-between gap-x-6 gap-y-4">
861 <div className="min-w-0">
862 <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 look863 {greeting}, {loaded.name}
864 </h1>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms865 <p className="mt-1.5 text-sm text-muted" suppressHydrationWarning>
866 <span className="text-fg-soft">{date}</span> · {loaded.summary}
867 </p>
868 </div>
869 <div className="flex flex-wrap items-center gap-2">
870 {newIssue}
871 {needs.length > 0 && (
872 <Link
873 to={`${link({ tab: "needs", all: null })}#work`}
874 preventScrollReset
875 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]"
876 >
877 Review {needs.length} that need{needs.length === 1 ? "s" : ""} you <ArrowRight size={14} />
878 </Link>
879 )}
880 </div>
881 </header>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look882
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms883 {starting && <GetStarted steps={steps} />}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look884
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms885 <section
886 aria-label="At a glance"
887 className="grid grid-cols-2 gap-px overflow-hidden rounded-xl border border-line bg-line lg:grid-cols-5"
888 >
889 <Stat
890 label="Projects"
891 value={stats.projects == null ? "—" : String(stats.projects)}
892 hint={
893 stats.projectsThisMonth == null
894 ? "in this workspace"
895 : stats.projectsThisMonth > 0
896 ? `+${stats.projectsThisMonth} this month`
897 : "none new this month"
898 }
899 />
900 <Stat
901 label="Agents"
902 value={loaded.runsLoaded || !workspace ? String(liveTotal) : "—"}
903 dot={liveTotal > 0 ? "bg-accent animate-pulse" : undefined}
904 hint={
905 liveTotal > 0
906 ? `live now · ${stats.agentHours < 10 ? stats.agentHours.toFixed(1) : Math.round(stats.agentHours)}h this week`
907 : stats.agentHours > 0
908 ? `none live · ${stats.agentHours < 10 ? stats.agentHours.toFixed(1) : Math.round(stats.agentHours)}h this week`
909 : "none live now"
910 }
911 />
912 <Stat
913 label="Changes this week"
914 value={loaded.perRepoLoaded ? String(week.total) : "—"}
915 hint={
916 delta != null ? `${signedPercent(delta)} vs last week` : week.previous === 0 ? "none the week before" : "merged pull requests"
917 }
918 />
919 <Stat
920 label="Landed without you"
921 dot="bg-merged"
922 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 bar923 hint={share == null ? "no agent changes yet" : `${week.byAgents} of ${week.agentChanges} agent changes`}
924 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 200ms925 />
926 <div className="col-span-2 lg:col-span-1">
927 <Stat
928 label="Need you"
929 dot="bg-warn"
930 value={String(needs.length)}
931 hint={blocking > 0 ? `${blocking} blocking` : needs.length > 0 ? "nothing blocking" : "all clear"}
932 />
933 </div>
934 </section>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look935
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms936 <div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_21rem] xl:grid-cols-[minmax(0,1fr)_23rem]">
937 <section id="work" className="min-w-0 scroll-mt-20 self-start overflow-hidden rounded-xl border border-line bg-surface">
938 <div className="flex items-center gap-2 border-b border-line px-2 sm:px-3">
939 <nav className="-mb-px flex min-w-0 grow gap-1 overflow-x-auto [scrollbar-width:none]" aria-label="Mission control">
940 {(["needs", "waiting", "landed"] as const).map((value) => (
941 <Link
942 key={value}
943 to={link({ tab: value, all: null })}
944 preventScrollReset
945 aria-current={tab === value ? "page" : undefined}
946 className={cn(
947 "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",
948 tab === value ? "border-merged font-medium text-fg" : "border-transparent text-muted hover:text-fg",
949 )}
950 >
951 <span className="sm:hidden">{TAB_SHORT[value]}</span>
952 <span className="hidden sm:inline">{TAB_LABEL[value]}</span>
953 <span
954 className={cn(
955 "rounded-full px-1.5 text-[0.6875rem] tabular-nums",
956 tab === value && value === "needs" && counts.needs > 0 ? "bg-warn/15 text-warn" : "bg-line text-muted",
957 )}
958 >
959 {counts[value]}
960 </span>
961 </Link>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look962 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms963 </nav>
964 <DropdownMenu>
965 <DropdownMenuTrigger
966 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"
967 aria-label="Sort"
968 >
969 <ArrowDownWideNarrow size={14} />
970 <span className="hidden sm:inline">{SORT_LABEL[sort]}</span>
971 </DropdownMenuTrigger>
972 <DropdownMenuContent align="end">
973 {(["impact", "newest"] as const).map((value) => (
974 <DropdownMenuItem key={value} asChild>
975 <Link to={link({ sort: value === "impact" ? null : value })} preventScrollReset>
976 {sort === value ? <Check /> : <span className="size-4" />}
977 {SORT_LABEL[value]}
978 </Link>
979 </DropdownMenuItem>
980 ))}
981 </DropdownMenuContent>
982 </DropdownMenu>
983 </div>
984
985 {!loaded.perRepoLoaded && tab !== "waiting" ? (
986 <div className="p-4">
987 <Unavailable what={tab === "needs" ? "Some of what needs you" : "What landed"} />
988 </div>
989 ) : null}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look990
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms991 {tab === "needs" &&
992 (needs.length === 0 ? (
993 noProjects ? (
994 <Empty
995 action={
996 <Link
997 to={workspace ? `/new?workspace=${workspace}` : "/new"}
998 className="rounded-md bg-fg px-3 py-1.5 text-sm font-medium text-bg hover:bg-white"
999 >
1000 Create or import a project
1001 </Link>
1002 }
1003 >
1004 <p className="font-medium text-fg">No projects yet</p>
1005 <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-agent1006 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 200ms1007 </p>
1008 </Empty>
1009 ) : (
1010 <>
1011 <Empty>
1012 <p className="font-medium text-fg">Nothing needs you</p>
1013 <p className="mt-1">
1014 Agents are handling everything.{" "}
1015 {landed.length > 0
1016 ? "Here's what they landed today."
1017 : "Reviews, stopped work and failed deploys show up here first."}
1018 </p>
1019 </Empty>
1020 {landed.length > 0 && (
1021 <div className="border-t border-line">
1022 <List>
1023 {landed.slice(0, ROWS).map((row) => (
1024 <LandedCard key={row.key} row={row} first={false} />
1025 ))}
1026 </List>
1027 </div>
1028 )}
1029 </>
1030 )
1031 ) : (
1032 <List>
1033 {(shown as NeedRow[]).map((row, index) => (
1034 <NeedCard key={row.key} row={row} first={index === 0} />
1035 ))}
1036 </List>
1037 ))}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1038
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1039 {tab === "waiting" &&
1040 (!loaded.runsLoaded && workspace ? (
1041 <div className="p-4">
1042 <Unavailable what="Agent runs" />
1043 </div>
1044 ) : waiting.length === 0 ? (
1045 <Empty>
1046 <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-agent1047 <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 200ms1048 </Empty>
1049 ) : (
1050 <List>
1051 {(shown as WaitingRow[]).map((row, index) => (
1052 <WaitingCard key={row.key} row={row} first={index === 0} />
1053 ))}
1054 </List>
1055 ))}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1056
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1057 {tab === "landed" &&
1058 loaded.perRepoLoaded &&
1059 (landed.length === 0 ? (
1060 <Empty>
1061 <p className="font-medium text-fg">Nothing has landed today yet</p>
1062 <p className="mt-1">
1063 {week.total > 0
1064 ? `${plural(week.total, "change")} landed in the last 7 days.`
1065 : "Merged pull requests show up here the moment they land."}
1066 </p>
1067 </Empty>
1068 ) : (
1069 <List>
1070 {(shown as LandedRow[]).map((row, index) => (
1071 <LandedCard key={row.key} row={row} first={index === 0} />
1072 ))}
1073 </List>
1074 ))}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1075
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1076 {rows.length > ROWS && (
1077 <div className="flex items-center justify-between border-t border-line px-4 py-2.5 text-xs text-muted sm:px-5">
1078 <span>
1079 Showing {shown.length} of {rows.length}
1080 </span>
1081 <Link
1082 to={link({ all: all ? null : "1" })}
1083 preventScrollReset
1084 className="inline-flex items-center gap-1 font-medium text-fg-soft hover:text-fg"
1085 >
1086 {all ? "Show fewer" : "See all"} <ChevronRight size={12} />
1087 </Link>
1088 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1089 )}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1090 </section>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1091
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1092 <aside className="min-w-0 space-y-6">
1093 <section className="rounded-xl border border-line bg-surface p-5">
1094 <div className="flex items-center justify-between">
1095 <h2 className="text-base font-semibold tracking-tight">This week</h2>
1096 {workspace && (
1097 <Link to={`/${workspace}/-/agents`} className="text-xs text-muted hover:text-fg">
1098 Fleet
1099 </Link>
1100 )}
1101 </div>
1102 <div className="mt-4">{loaded.perRepoLoaded ? <WeekChart week={week} /> : <Unavailable what="The week" />}</div>
1103 {workspace && stats.weekCost > 0 && (
1104 <Link
1105 to={`/${workspace}/-/usage`}
1106 className="mt-4 flex items-center justify-between border-t border-line pt-3 text-xs text-muted hover:text-fg"
1107 >
1108 <span>Agents and sandboxes cost {usd(stats.weekCost)} this week</span>
1109 <ChevronRight size={12} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1110 </Link>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1111 )}
1112 </section>
1113
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix1114 {workspace && (
1115 <TokenUsagePanel
1116 workspace={loaderData.tokens.workspace}
1117 mine={loaderData.tokens.mine}
1118 usageHref={`/${workspace}/-/usage`}
1119 />
1120 )}
1121
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1122 <section id="activity" className="scroll-mt-20 rounded-xl border border-line bg-surface p-5">
1123 <div className="flex items-center justify-between">
1124 <h2 className="text-base font-semibold tracking-tight">Activity</h2>
1125 {groups.length > 8 && (
1126 <Link
1127 to={`${link({ activity: everyActivity ? null : "all" })}#activity`}
1128 preventScrollReset
1129 className="inline-flex items-center gap-0.5 text-xs text-muted hover:text-fg"
1130 >
1131 {everyActivity ? "Less" : "All activity"} <ChevronRight size={12} />
1132 </Link>
1133 )}
1134 </div>
1135 <p className="mt-1 flex items-center gap-3 text-[0.6875rem] text-faint">
1136 <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-agent1137 <Avatar name="g1t" size={12} /> agents
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1138 </span>
1139 <span className="inline-flex items-center gap-1.5">
1140 <Avatar name={viewer.username} size={12} /> people
1141 </span>
1142 </p>
1143 {!loaded.perRepoLoaded ? (
1144 <div className="mt-3">
1145 <Unavailable what="Activity" />
1146 </div>
1147 ) : feed.length === 0 ? (
1148 <p className="mt-3 text-sm leading-6 text-muted">
1149 Nothing has moved yet. Merges, deploys, checks, reviews and what agents learn show up here.
1150 </p>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1151 ) : (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1152 <ol className="mt-2 divide-y divide-line/70">
1153 {feed.map((group) => (
1154 <FeedLine key={group.id} group={group} titles={titles} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1155 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1156 </ol>
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 </section>
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 className="rounded-xl border border-line bg-surface p-5">
1161 <h2 className="text-sm font-semibold">Connect your own agent</h2>
1162 <p className="mt-1.5 text-xs leading-5 text-muted">
1163 Add g1t to your coding agent. It signs in through your browser; there is no token to copy.
1164 </p>
1165 <AgentSetup className="mt-3" />
1166 <Link
1167 to="https://docs.g1t.sh/guides/bring-your-own-agent/"
1168 className="mt-3 inline-flex items-center gap-1 text-xs text-muted hover:text-fg"
1169 >
1170 How it works <ArrowRight size={12} />
1171 </Link>
1172 </section>
1173 </aside>
1174 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1175 </main>
1176 );
1177}