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

1,200 lines50,132 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Mission control: a place to ask g1t, shown and not on yet, with the actions under it1import { ArrowDownWideNarrow, ArrowRight, ArrowUpRight, Check, ChevronDown, ChevronRight, ListTree, Plus, Sparkles } from "lucide-react";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look2import { type ReactNode, useEffect, useState } from "react";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms3import { Link, useFetcher, useRouteLoaderData, useSearchParams } from "react-router";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look4
5import { trialClosed } from "../lib/trial";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms6import { type ActivityGroup, type Verb, greetingFor, isAgent } from "../lib/mission";
7import {
8 BLOCKING,
9 type Fact,
10 type LandedRow,
11 type NeedRow,
12 type QuickAction,
13 REASON_LABEL,
14 type Reason,
15 type Sort,
16 type Tab,
17 type WaitingRow,
18 type Week,
19 type Who,
20 change,
21 dateLine,
22 parseSort,
23 parseTab,
24 signedPercent,
25 sortRows,
26 usd,
27 whyFor,
28} from "../lib/mission-control";
29import { cn } from "../lib/cn";
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step30import { AgentComposer, type ComposerResult } from "./agent-composer";
Mission control: a place to ask g1t, shown and not on yet, with the actions under it31import { AskComposer } from "./ask-composer";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look32import { AgentSetup } from "./agent-setup";
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all33import { InboxNeedsCard } from "./inbox";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look34import type { ShellData } from "./shell";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms35import { useLiveRefresh } from "./agents";
36import { Unavailable } from "./mission";
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix37import { TokenUsagePanel } from "./token-usage";
Merge branch 'worktree-agent-a985c01f43538af01'38import { Avatar, SubmitButton, TimeAgo } from "./ui";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms39import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger } from "./ui/dropdown-menu";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look40import type { Loaded } from "../routes/home";
41
42/*
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms43 * Mission control: the home page of someone signed in. It answers where
44 * the viewer is needed, what agents are doing, and what landed without
45 * them. It is a chunk of its own, loaded only for them, so the signed-out
46 * home page does not carry it. routes/home.tsx loads its data;
47 * lib/mission-control.ts shapes it.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look48 */
49
50/** The viewer's time zone, set by the page itself, so the greeting fits their day. */
51const TZ_COOKIE = "g1t_tz";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms52/** Rows a tab shows before "See all". */
53const ROWS = 6;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look54
55const 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 200ms56const 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 look57
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms58// --- Small pieces -----------------------------------------------------------------
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look59
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms60const CHIP_TONE: Record<Reason, string> = {
61 blocking: "border-danger/35 bg-danger/10 text-danger",
62 checks_failing: "border-danger/35 bg-danger/10 text-danger",
63 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 step64 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 200ms65 stalled: "border-warn/35 bg-warn/10 text-warn",
66 asked_for_you: "border-merged/35 bg-merged/10 text-merged",
67 needs_review: "border-info/35 bg-info/10 text-info",
68 ready_to_merge: "border-accent/35 bg-accent/10 text-accent",
69};
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look70
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms71function Chip({ children, tone }: { children: ReactNode; tone: string }) {
72 return (
73 <span
74 className={cn(
75 "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",
76 tone,
77 )}
78 >
79 {children}
80 </span>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look81 );
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms82}
83
84/** A person or an agent, by name, wearing the mark that tells them apart. */
85function 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 look86 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms87 <span className={cn("inline-flex min-w-0 items-center gap-1.5", className)}>
88 <Avatar name={who.name} size={size} />
89 <span className="truncate">{who.name}</span>
90 </span>
91 );
92}
93
94function Eyebrow({ children }: { children: ReactNode }) {
95 return <p className="font-mono text-[0.625rem] font-medium tracking-[0.12em] text-faint uppercase">{children}</p>;
96}
97
98const FACT_TONE = { good: "text-accent", warn: "text-warn", bad: "text-danger" } as const;
99
100function Facts({ facts }: { facts: Fact[] }) {
101 if (facts.length === 0) return <p className="text-sm text-muted">Nothing more is known about it yet.</p>;
102 return (
103 <dl className="grid grid-cols-2 gap-x-4 gap-y-2.5">
104 {facts.map((fact) => (
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step105 <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 200ms106 <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 step107 <dd
108 className={cn(
109 "mt-0.5 text-sm font-medium tabular-nums",
110 fact.wide ? "leading-5" : "truncate",
111 fact.tone ? FACT_TONE[fact.tone] : "text-fg-soft",
112 )}
113 >
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms114 {fact.value}
115 </dd>
116 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look117 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms118 </dl>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look119 );
120}
121
Merge branch 'worktree-agent-a985c01f43538af01'122/**
123 * A form that acts on a pull request from here: working until the action
124 * has answered and the page has reloaded what it changed, then done, or
125 * the error beside it.
126 */
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms127function QuickForm({ quick, variant = "quiet" }: { quick: QuickAction; variant?: "quiet" | "accent" }) {
128 const fetcher = useFetcher<{ error?: string } | null>();
129 const [sent, setSent] = useState(false);
Merge branch 'worktree-agent-a985c01f43538af01'130 const done = sent && fetcher.state === "idle" && !fetcher.data?.error;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look131 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms132 <fetcher.Form method="post" action={quick.to} onSubmit={() => setSent(true)} className="contents">
133 {Object.entries(quick.fields).map(([name, value]) => (
134 <input key={name} type="hidden" name={name} value={value} />
135 ))}
Merge branch 'worktree-agent-a985c01f43538af01'136 <SubmitButton
137 fetcher={fetcher}
138 disabled={done}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms139 className={cn(
140 "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors disabled:cursor-default",
141 variant === "accent"
142 ? "bg-merged text-bg hover:bg-[#c9bfff]"
143 : "border border-line-strong text-fg/90 hover:bg-raised hover:text-fg",
144 done && "border-accent/40 text-accent",
145 )}
146 >
Fast pages, required checks on the branch, self-hosted runners, honest incidents147 {done ? <Check size={14} /> : null}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms148 {done ? quick.done : quick.label}
Merge branch 'worktree-agent-a985c01f43538af01'149 </SubmitButton>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms150 {fetcher.data?.error && <span className="basis-full text-xs text-danger">{fetcher.data.error}</span>}
151 </fetcher.Form>
152 );
153}
154
155function ProjectMark({ repo }: { repo: { namespace: string; name: string } | null }) {
156 return <Avatar name={repo ? repo.name : "g1t"} size={32} square />;
157}
158
159/** A row that opens to what is known about it; the first one starts open. */
160function Row({
161 open,
162 repo,
163 by,
164 title,
165 sub,
166 chip,
167 at,
168 children,
169}: {
170 open: boolean;
171 repo: { namespace: string; name: string } | null;
172 by: Who | null;
173 title: ReactNode;
174 sub: ReactNode;
175 chip: ReactNode;
176 at: number;
177 children: ReactNode;
178}) {
179 return (
180 <li>
181 <details open={open} className="group">
182 <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">
183 <ProjectMark repo={repo} />
184 <span className="min-w-0 grow">
185 <span className="block truncate text-sm">{title}</span>
186 <span className="mt-0.5 flex min-w-0 items-center gap-1.5 text-xs text-muted">
187 <span className="shrink-0 font-medium text-fg-soft">{repo ? repo.name : "Workspace"}</span>
188 {by && (
189 <>
190 <span className="text-faint">·</span>
191 <Person who={by} size={14} className={cn("shrink-0", by.agent ? "text-merged" : "")} />
192 </>
193 )}
194 {sub && (
195 <>
196 <span className="text-faint">—</span>
197 <span className="truncate">{sub}</span>
198 </>
199 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look200 </span>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms201 </span>
202 <span className="hidden sm:inline-flex">{chip}</span>
203 <span className="hidden w-16 shrink-0 text-right text-xs text-faint sm:block">
204 <TimeAgo at={at} />
205 </span>
206 <ChevronDown size={16} className="shrink-0 text-faint transition-transform group-open:rotate-180" />
207 </summary>
208 <div className="px-4 pb-4 sm:px-5 sm:pb-5">
209 <div className="mb-3 flex items-center gap-2 sm:hidden">
210 {chip}
211 <span className="text-xs text-faint">
212 <TimeAgo at={at} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look213 </span>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms214 </div>
215 {children}
216 </div>
217 </details>
218 </li>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look219 );
220}
221
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms222function NeedCard({ row, first }: { row: NeedRow; first: boolean }) {
223 const isPull = row.to.includes("/pull/");
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look224 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms225 <Row
226 open={first}
227 repo={row.repo}
228 by={row.by}
229 title={
230 <>
231 <span className="font-medium">{row.title}</span>
232 {row.ref && <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>}
233 </>
234 }
235 sub={row.ask}
236 chip={<Chip tone={CHIP_TONE[row.reason]}>{REASON_LABEL[row.reason]}</Chip>}
237 at={row.at}
238 >
239 <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)]">
240 <div className="min-w-0">
241 <Eyebrow>The ask</Eyebrow>
242 <p className="mt-2 text-sm leading-6 text-fg-soft">{row.ask}</p>
243 <p className="mt-2 text-xs leading-5 text-muted">
244 <Link to={row.to} className="font-medium text-fg hover:underline">
245 {row.repo ? `${row.repo.name}${row.ref ?? ""}` : row.title}
246 </Link>
247 {row.for && (
248 <>
249 {" "}
250 · started for <span className="text-fg-soft">{row.for}</span>
251 </>
252 )}
253 </p>
254 </div>
255 <div className="min-w-0">
256 <Eyebrow>What the agent already knows</Eyebrow>
257 <div className="mt-2">
258 <Facts facts={row.facts} />
259 </div>
260 </div>
261 <div className="rounded-lg border border-warn/20 bg-warn/[0.06] p-3.5 md:col-span-2 2xl:col-span-1">
262 <p className="text-xs font-semibold text-warn">Why this needs you</p>
263 <p className="mt-1.5 text-sm leading-6 text-fg-soft">{row.why}</p>
264 <p className="mt-2 text-xs text-faint">
265 Waiting <TimeAgo at={row.at} />
266 </p>
267 </div>
268 </div>
269 <div className="mt-3 flex flex-wrap items-center gap-2">
270 <Link
271 to={row.to}
272 prefetch="intent"
273 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"
274 >
275 Review and respond <ArrowUpRight size={14} />
276 </Link>
277 {row.quick && <QuickForm quick={row.quick} />}
278 {row.link && (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look279 <Link
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms280 to={row.link.to}
281 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 look282 >
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms283 {row.link.label}
284 </Link>
285 )}
286 {row.open !== row.to && (
287 <Link to={row.open} prefetch="intent" className="px-2 py-1.5 text-sm text-muted hover:text-fg">
288 {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 look289 </Link>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms290 )}
291 </div>
292 </Row>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look293 );
294}
295
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms296function WaitingCard({ row, first }: { row: WaitingRow; first: boolean }) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look297 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms298 <Row
299 open={first}
300 repo={row.repo}
301 by={row.by}
302 title={
303 <>
304 <span className="font-medium">{row.title}</span>
305 {row.ref && <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>}
306 </>
307 }
308 sub={row.detail}
309 chip={
310 <Chip tone="border-line-strong bg-raised text-muted">
311 {row.live && <span className="mr-1.5 size-1.5 animate-pulse rounded-full bg-accent" />}
312 {row.chip}
313 </Chip>
314 }
315 at={row.at}
316 >
317 <div className="grid gap-4 rounded-xl border border-line bg-bg/50 p-4 md:grid-cols-2 md:gap-6">
318 <div className="min-w-0">
319 <Eyebrow>{row.live ? "Doing now" : "Where it stands"}</Eyebrow>
320 <p className="mt-2 text-sm leading-6 text-fg-soft">{row.detail}</p>
321 </div>
322 <div className="min-w-0">
323 <Eyebrow>What is known</Eyebrow>
324 <div className="mt-2">
325 <Facts facts={row.facts} />
326 </div>
327 </div>
328 </div>
329 <div className="mt-3 flex flex-wrap gap-2">
330 <Link
331 to={row.to}
332 prefetch="intent"
333 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"
334 >
335 {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 look336 </Link>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms337 {row.run && row.run !== row.to && (
338 <Link to={row.run} prefetch="intent" className="px-2 py-1.5 text-sm text-muted hover:text-fg">
339 Watch the run
340 </Link>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look341 )}
342 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms343 </Row>
344 );
345}
346
347function LandedCard({ row, first }: { row: LandedRow; first: boolean }) {
348 return (
349 <Row
350 open={first}
351 repo={row.repo}
352 by={row.byAgents ? { name: row.agent, agent: isAgent(row.agent) } : row.by}
353 title={
354 <>
355 <span className="font-medium">{row.title}</span>
356 <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>
357 </>
358 }
359 sub={row.byAgents ? "landed without a person" : `merged by ${row.by?.name ?? "a person"}`}
360 chip={
361 row.byAgents ? (
362 <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 look363 ) : (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms364 <Chip tone="border-warn/35 bg-warn/10 text-warn">Needed a person</Chip>
365 )
366 }
367 at={row.at}
368 >
369 <div className="grid gap-4 rounded-xl border border-line bg-bg/50 p-4 md:grid-cols-2 md:gap-6">
370 <div className="min-w-0">
371 <Eyebrow>How it landed</Eyebrow>
372 <p className="mt-2 text-sm leading-6 text-fg-soft">
373 {row.byAgents
374 ? `g1t merged it once everything the repository asks for was met, with no one pressing merge.`
375 : `${row.by?.name ?? "A person"} merged it.`}
376 {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 look377 </p>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms378 </div>
379 <div className="min-w-0">
380 <Eyebrow>What changed</Eyebrow>
381 <div className="mt-2">
382 <Facts facts={row.facts} />
383 </div>
384 </div>
385 </div>
386 <div className="mt-3">
387 <Link
388 to={row.to}
389 prefetch="intent"
390 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"
391 >
392 Open pull request <ArrowUpRight size={14} />
393 </Link>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look394 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms395 </Row>
396 );
397}
398
399function List({ children }: { children: ReactNode }) {
400 return <ul className="divide-y divide-line">{children}</ul>;
401}
402
403function Empty({ children, action }: { children: ReactNode; action?: ReactNode }) {
404 return (
405 <div className="px-5 py-10 text-center">
406 <div className="mx-auto max-w-md text-sm leading-6 text-muted">{children}</div>
407 {action && <div className="mt-4 flex flex-wrap justify-center gap-2">{action}</div>}
408 </div>
409 );
410}
411
412// --- The stat strip -----------------------------------------------------------------
413
414function Stat({ label, value, hint, dot, title }: { label: string; value: string; hint: ReactNode; dot?: string; title?: string }) {
415 return (
416 <div className="min-w-0 bg-surface px-4 py-3.5 sm:px-5 sm:py-4" title={title}>
417 <p className="flex items-center gap-1.5 truncate text-xs text-muted">
418 {dot && <span className={cn("size-1.5 shrink-0 rounded-full", dot)} />}
419 {label}
420 </p>
421 <p className="mt-1 font-display text-2xl font-semibold tracking-tight tabular-nums sm:text-[1.75rem]">{value}</p>
422 <p className="mt-0.5 truncate text-xs text-faint">{hint}</p>
423 </div>
424 );
425}
426
427// --- This week ----------------------------------------------------------------------
428
429/** Seven days of landed changes, each split into what agents landed alone and what a person merged. */
430function WeekChart({ week }: { week: Week }) {
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar431 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 200ms432 const height = 112;
433 const delta = change(week.total, week.previous);
434 return (
435 <div>
436 <div className="flex items-end justify-between gap-3">
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 <p className="font-display text-3xl font-semibold tracking-tight tabular-nums">{week.total.toLocaleString("en-US")}</p>
439 <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 look440 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms441 {delta != null ? (
442 <span
443 className={cn(
444 "rounded-full px-2 py-0.5 text-xs font-medium tabular-nums ring-1",
445 delta >= 0 ? "text-accent ring-accent/30" : "text-warn ring-warn/30",
446 )}
447 >
448 {signedPercent(delta)} vs last week
449 </span>
450 ) : week.previous === 0 && week.total > 0 ? (
451 <span className="text-xs text-faint">none the week before</span>
452 ) : null}
453 </div>
454 {week.total === 0 ? (
455 <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 bar456 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 200ms457 </p>
458 ) : (
459 <div className="relative mt-5" style={{ height: height + 20 }}>
460 <div className="absolute inset-x-0 border-t border-line" style={{ top: height }} />
461 <div className="absolute inset-x-0 top-0 flex items-end justify-between gap-1" style={{ height }}>
462 {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 bar463 const total = day.agents + day.assisted + day.people;
464 const bar = (n: number) => Math.max(3, Math.round((n / max) * (height - 4)));
465 // Top to bottom: agents alone, agents with a person, people.
466 const segments = [
467 { n: day.agents, tone: "bg-merged" },
468 { n: day.assisted, tone: "bg-warn" },
469 { n: day.people, tone: "bg-info" },
470 ].filter((segment) => segment.n > 0);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms471 const today = index === week.days.length - 1;
472 return (
473 <div key={day.key} className="group relative flex h-full flex-1 flex-col items-center justify-end">
474 {total > 0 && (
475 <span
476 className={cn(
477 "mb-1 text-[0.625rem] tabular-nums",
478 today ? "text-fg-soft" : "text-faint opacity-0 group-hover:opacity-100",
479 )}
480 >
481 {total}
482 </span>
483 )}
484 <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 bar485 {segments.map((segment, at) => (
486 <span key={segment.tone} className={cn("block", segment.tone, at === 0 && "rounded-t")} style={{ height: bar(segment.n) }} />
487 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms488 {total === 0 && <span className="block h-[2px] rounded-full bg-line-strong" />}
489 </div>
490 <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">
491 <p className="font-medium text-fg">{day.label}</p>
492 <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 bar493 <span className="size-1.5 rounded-full bg-merged" /> {day.agents} by agents on their own
494 </p>
495 <p className="flex items-center gap-1.5 text-muted">
496 <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 200ms497 </p>
498 <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 bar499 <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 200ms500 </p>
501 </div>
502 </div>
503 );
504 })}
505 </div>
506 <div className="absolute inset-x-0 bottom-0 flex justify-between gap-1">
507 {week.days.map((day, index) => (
508 <span
509 key={day.key}
510 className={cn(
511 "flex-1 text-center text-[0.6875rem]",
512 index === week.days.length - 1 ? "font-medium text-fg-soft" : "text-faint",
513 )}
514 >
515 {day.label}
516 </span>
517 ))}
518 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look519 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms520 )}
521 <div className={cn("mt-3 flex-wrap gap-x-4 gap-y-1 text-xs text-muted", week.total === 0 ? "hidden" : "flex")}>
522 <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 bar523 <span className="size-2 rounded-sm bg-merged" /> Agents, on their own
524 </span>
525 <span className="inline-flex items-center gap-1.5">
526 <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 200ms527 </span>
528 <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 bar529 <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 200ms530 </span>
531 </div>
Fixes from the page sweep532 {/* A table does not shrink to sr-only's 1px; its wrapper does. */}
533 <div className="sr-only">
534 <table>
535 <caption>Changes landed each day</caption>
536 <thead>
537 <tr>
538 <th>Day</th>
539 <th>Agents, on their own</th>
540 <th>Agents, merged by a person</th>
541 <th>People</th>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms542 </tr>
Fixes from the page sweep543 </thead>
544 <tbody>
545 {week.days.map((day) => (
546 <tr key={day.key}>
547 <td>{day.key}</td>
548 <td>{day.agents}</td>
549 <td>{day.assisted}</td>
550 <td>{day.people}</td>
551 </tr>
552 ))}
553 </tbody>
554 </table>
555 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms556 </div>
557 );
558}
559
560// --- Activity -----------------------------------------------------------------------
561
562const VERB: Record<Verb, string> = {
563 landed: "landed",
564 opened_issue: "opened",
565 closed_issue: "closed",
566 started: "started on",
567 ready: "marked ready",
568 checks_passed: "checks passed on",
569 checks_failed: "checks failed on",
570 approved: "approved",
571 changes_requested: "asked for changes on",
572 commented: "commented on",
573 asked: "was asked about",
574 deployed: "deployed production",
575 deploy_failed: "production build failed",
576 learned: "learned",
577};
578
579/** One line of the feed: who, what, and the one thing it was about, by name. */
580function FeedLine({ group, titles }: { group: ActivityGroup; titles: Record<string, string> }) {
581 const part = group.parts[0];
582 const base = `/${group.repo.namespace}/${group.repo.name}`;
583 const actor = group.actor ?? "g1t";
584 const agent = group.actor == null || isAgent(group.actor);
585 const number = part.numbers[0];
586 const title = number != null ? titles[`${group.repo.namespace}/${group.repo.name}#${number}`.toLowerCase()] : undefined;
587 const more = group.parts.length - 1 + Math.max(0, part.numbers.length - 1);
588 return (
589 <li className="flex gap-2.5 py-2">
590 <span className="mt-px">
591 <Avatar name={actor} size={22} />
592 </span>
593 <span className="min-w-0 grow text-[0.8125rem] leading-5 text-muted">
594 <span className={cn("font-medium", agent ? "text-merged" : "text-fg")}>{actor}</span> {VERB[part.verb]}{" "}
595 {part.verb === "learned" ? (
596 <Link to={part.to ?? `${base}/memory`} className="text-fg-soft hover:text-fg">
597 “{part.texts[0]}”
598 </Link>
599 ) : number != null ? (
600 <Link to={`${base}/issues/${number}`} prefetch="intent" className="text-fg-soft hover:text-fg">
601 <span className="font-mono text-xs">#{number}</span>
602 {title && <> {title}</>}
603 </Link>
604 ) : part.to ? (
605 <Link to={part.to} className="text-fg-soft hover:text-fg">
606 {group.repo.name}
607 </Link>
608 ) : null}
609 {more > 0 && <span className="text-faint"> and {more} more</span>}
610 <span className="block text-xs text-faint">
611 <span className="font-mono">{group.repo.name}</span> · <TimeAgo at={group.at} />
612 </span>
613 </span>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look614 </li>
615 );
616}
617
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms618// --- Get started --------------------------------------------------------------------
619
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look620type Step = { done: boolean; title: string; about: string; to: string | null; action: string };
621
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms622/** 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 look623function GetStarted({ steps }: { steps: Step[] }) {
624 const left = steps.filter((step) => !step.done).length;
625 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms626 <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 look627 <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 200ms628 <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 look629 <span className="text-xs text-muted">
630 {steps.length - left} of {steps.length} done
631 </span>
632 </div>
633 <ol className="mt-4 space-y-2">
634 {steps.map((step, index) => (
635 <li
636 key={step.title}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms637 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 look638 >
639 <span
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms640 className={cn(
641 "mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full text-[0.6875rem] font-medium",
642 step.done ? "bg-accent text-bg" : "text-muted ring-1 ring-line-strong",
643 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look644 >
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms645 {step.done ? <Check size={12} /> : index + 1}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look646 </span>
647 <span className="min-w-0 grow">
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms648 <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 look649 {!step.done && <span className="mt-0.5 block text-xs leading-5 text-muted">{step.about}</span>}
650 </span>
651 {!step.done && step.to && (
652 <Link
653 to={step.to}
654 className="shrink-0 rounded-md bg-fg px-2.5 py-1 text-xs font-medium text-bg transition-colors hover:bg-white"
655 >
656 {step.action}
657 </Link>
658 )}
659 </li>
660 ))}
661 </ol>
662 </section>
663 );
664}
665
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms666// --- The page -----------------------------------------------------------------------
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look667
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms668const TAB_LABEL: Record<Tab, string> = { needs: "Needs you", waiting: "Waiting on agents", landed: "Landed today" };
669const TAB_SHORT: Record<Tab, string> = { needs: "Needs you", waiting: "Waiting", landed: "Today" };
670const 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 look671
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step672export 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 look673 const shell = useRouteLoaderData("root")?.shell as ShellData | null | undefined;
674 const loaded: Loaded = loaderData;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms675 const [params] = useSearchParams();
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look676 // 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 200ms677 useLiveRefresh(loaded.changing);
678 const [greeting, setGreeting] = useState(loaded.greeting);
679 const [date, setDate] = useState(loaded.date);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look680 useEffect(() => {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms681 // The greeting and date follow the viewer's own clock, and the server
682 // learns their zone for next time.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look683 setGreeting(greetingFor(new Date().getHours()));
684 try {
685 const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms686 setDate(dateLine(Date.now(), zone ?? null));
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look687 if (zone) document.cookie = `${TZ_COOKIE}=${encodeURIComponent(zone)}; Path=/; Max-Age=31536000; SameSite=Lax`;
688 } catch {
689 // Nothing to remember.
690 }
691 }, []);
692
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms693 const { viewer, repos, waiting, landed, liveTotal, week, groups, titles, stats, canRunAgents, trial } = loaded;
694 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 look695
696 // 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 200ms697 const limit = shell?.limit && !shell.limit.comped && shell.limit.state !== "ok" && workspace ? shell.limit : null;
698 const limitRow: NeedRow[] = limit
699 ? [
700 (() => {
701 const detail =
702 limit.state === "stopped"
703 ? "No new agent starts until the limit is raised or a card is added."
704 : `${dollars(limit.exposureMicros)} of ${dollars(limit.ceilingMicros ?? 0)} used. Add a card so work keeps going.`;
705 return {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look706 key: "limit",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms707 reason: "blocking" as const,
708 repo: null,
709 ref: null,
710 title: limit.state === "stopped" ? "Agents are stopped: the usage limit was reached" : "Usage is nearing its limit",
711 ask: detail,
712 by: null,
713 for: null,
714 at: Date.now(),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look715 to: `/${workspace}/-/billing`,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms716 open: `/${workspace}/-/billing`,
717 facts: [
718 { label: "Used", value: `${dollars(limit.exposureMicros)} of ${dollars(limit.ceilingMicros ?? 0)}`, tone: "warn" as const },
719 ],
720 why: whyFor("blocking", { kind: "limit", detail }),
721 quick: null,
722 link: null,
723 };
724 })(),
725 ]
726 : [];
727 const needs = [...limitRow, ...loaded.needs];
728 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 look729
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms730 const counts: Record<Tab, number> = { needs: needs.length, waiting: waiting.length, landed: landed.length };
731 const tab = parseTab(params.get("tab")) ?? "needs";
732 const sort = parseSort(params.get("sort"));
733 const all = params.get("all") === "1";
734 const everyActivity = params.get("activity") === "all";
735 const link = (changes: Record<string, string | null>) => {
736 const next = new URLSearchParams(params);
737 for (const [key, value] of Object.entries(changes)) {
738 if (value == null) next.delete(key);
739 else next.set(key, value);
740 }
741 const text = next.toString();
742 return text ? `?${text}` : "/";
743 };
744
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look745 const steps: Step[] = [
746 {
747 done: Boolean(workspace),
748 title: "Create a workspace",
749 about: "Projects, people and agent credit live in one.",
750 to: "/workspaces/new",
751 action: "Create",
752 },
753 {
754 done: repos.length > 0,
755 title: "Add a project",
756 about: "Create a repository, or import one by its address. Push to it with git as usual.",
757 to: workspace ? `/new?workspace=${workspace}` : "/new",
758 action: "Add",
759 },
760 trial?.open
761 ? {
762 done: true,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent763 title: "Try g1t free",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look764 about: trial.granted
765 ? `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-agent766 : `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 look767 to: workspace ? `/${workspace}/-/integrations` : null,
768 action: "Connect",
769 }
770 : canRunAgents
771 ? {
772 done: Boolean(shell?.free) || shell?.limit == null || shell.limit.comped || shell.limit.state === "ok",
773 title: "Keep work running",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms774 about:
775 "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 look776 to: workspace ? `/${workspace}/-/billing` : null,
777 action: "Billing",
778 }
779 : {
780 done: false,
781 title: "Connect a model",
782 about: `${
783 trialClosed(trial, workspace ?? "This workspace") ? `${trialClosed(trial, workspace ?? "This workspace")} ` : ""
784 }Agents need a model to think with. Connect your Anthropic or OpenAI key, or any compatible endpoint.`,
785 to: workspace ? `/${workspace}/-/integrations` : null,
786 action: "Connect",
787 },
788 {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms789 done: loaded.handedOff,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look790 title: "Hand off an outcome",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms791 about:
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent792 "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 200ms793 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 look794 action: "Write one",
795 },
796 ];
797 const starting = steps.some((step) => !step.done);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms798 const noProjects = repos.length === 0;
799
800 const rows = tab === "needs" ? sortRows(needs, sort) : tab === "waiting" ? sortRows(waiting, sort) : sortRows(landed, sort);
801 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 bar802 // Of the agents' own changes only: people's work is not theirs to land.
803 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 200ms804 const delta = change(week.total, week.previous);
805 const feed = everyActivity ? groups : groups.slice(0, 8);
806
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step807 // "Put an agent on it" first, with "New issue" beside it as before: one
808 // 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 200ms809 const newIssue =
810 repos.length > 0 ? (
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step811 <div className="flex items-stretch">
812 <AgentComposer
813 repos={repos}
814 open={params.get("agent") === "new" || delegated != null}
815 result={delegated}
816 note={
817 canRunAgents ? null : (
818 <>
819 Agents need a model first.{" "}
820 {workspace && (
821 <Link to={`/${workspace}/-/integrations`} className="font-medium text-fg hover:underline">
822 Connect one
823 </Link>
824 )}
825 . The issue still opens.
826 </>
827 )
828 }
829 >
830 <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">
831 <Sparkles size={14} className="text-merged" /> Put an agent on it
832 </span>
833 </AgentComposer>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms834 <DropdownMenu>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step835 <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 200ms836 <Plus size={14} /> New issue
837 </DropdownMenuTrigger>
838 <DropdownMenuContent align="end" className="max-h-80 overflow-y-auto">
839 <DropdownMenuLabel>In which project?</DropdownMenuLabel>
840 {repos.map((repo) => (
841 <DropdownMenuItem key={repo.name} asChild>
842 <Link to={`/${repo.namespace}/${repo.name}/issues/new`}>
843 <Avatar name={repo.name} size={18} square />
844 {repo.name}
845 </Link>
846 </DropdownMenuItem>
847 ))}
848 </DropdownMenuContent>
849 </DropdownMenu>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step850 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms851 ) : (
852 <Link
853 to={workspace ? `/new?workspace=${workspace}` : "/new"}
854 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"
855 >
856 <Plus size={14} /> New project
857 </Link>
858 );
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look859
860 return (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms861 <main className="mx-auto max-w-7xl space-y-6 px-4 py-8 sm:px-6 sm:py-10">
862 <header className="flex flex-wrap items-end justify-between gap-x-6 gap-y-4">
863 <div className="min-w-0">
864 <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 look865 {greeting}, {loaded.name}
866 </h1>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms867 <p className="mt-1.5 text-sm text-muted" suppressHydrationWarning>
868 <span className="text-fg-soft">{date}</span> · {loaded.summary}
869 </p>
870 </div>
871 </header>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look872
Mission control: a place to ask g1t, shown and not on yet, with the actions under it873 {/* Asking g1t is not on yet; the actions under it do the work today. */}
874 <AskComposer>
875 {newIssue}
876 {repos.length > 0 && (
877 <DropdownMenu>
878 <DropdownMenuTrigger className="inline-flex items-center gap-1.5 rounded-md border border-line-strong px-3 py-2 text-sm font-medium text-fg/90 transition-colors hover:bg-raised hover:text-fg">
879 <ListTree size={14} /> Plan work
880 </DropdownMenuTrigger>
881 <DropdownMenuContent align="start" className="max-h-80 overflow-y-auto">
882 <DropdownMenuLabel>Plan in which project?</DropdownMenuLabel>
883 {repos.map((repo) => (
884 <DropdownMenuItem key={repo.name} asChild>
885 <Link to={`/${repo.namespace}/${repo.name}/plans`}>
886 <Avatar name={repo.name} size={18} square />
887 {repo.name}
888 </Link>
889 </DropdownMenuItem>
890 ))}
891 </DropdownMenuContent>
892 </DropdownMenu>
893 )}
894 {needs.length > 0 && (
895 <Link
896 to={`${link({ tab: "needs", all: null })}#work`}
897 preventScrollReset
898 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]"
899 >
900 Review {needs.length} that need{needs.length === 1 ? "s" : ""} you <ArrowRight size={14} />
901 </Link>
902 )}
903 </AskComposer>
904
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms905 {starting && <GetStarted steps={steps} />}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look906
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms907 <section
908 aria-label="At a glance"
909 className="grid grid-cols-2 gap-px overflow-hidden rounded-xl border border-line bg-line lg:grid-cols-5"
910 >
911 <Stat
912 label="Projects"
913 value={stats.projects == null ? "—" : String(stats.projects)}
914 hint={
915 stats.projectsThisMonth == null
916 ? "in this workspace"
917 : stats.projectsThisMonth > 0
918 ? `+${stats.projectsThisMonth} this month`
919 : "none new this month"
920 }
921 />
922 <Stat
923 label="Agents"
924 value={loaded.runsLoaded || !workspace ? String(liveTotal) : "—"}
925 dot={liveTotal > 0 ? "bg-accent animate-pulse" : undefined}
926 hint={
927 liveTotal > 0
928 ? `live now · ${stats.agentHours < 10 ? stats.agentHours.toFixed(1) : Math.round(stats.agentHours)}h this week`
929 : stats.agentHours > 0
930 ? `none live · ${stats.agentHours < 10 ? stats.agentHours.toFixed(1) : Math.round(stats.agentHours)}h this week`
931 : "none live now"
932 }
933 />
934 <Stat
935 label="Changes this week"
936 value={loaded.perRepoLoaded ? String(week.total) : "—"}
937 hint={
938 delta != null ? `${signedPercent(delta)} vs last week` : week.previous === 0 ? "none the week before" : "merged pull requests"
939 }
940 />
941 <Stat
942 label="Landed without you"
943 dot="bg-merged"
944 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 bar945 hint={share == null ? "no agent changes yet" : `${week.byAgents} of ${week.agentChanges} agent changes`}
946 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 200ms947 />
948 <div className="col-span-2 lg:col-span-1">
949 <Stat
950 label="Need you"
951 dot="bg-warn"
952 value={String(needs.length)}
953 hint={blocking > 0 ? `${blocking} blocking` : needs.length > 0 ? "nothing blocking" : "all clear"}
954 />
955 </div>
956 </section>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look957
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms958 <div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_21rem] xl:grid-cols-[minmax(0,1fr)_23rem]">
959 <section id="work" className="min-w-0 scroll-mt-20 self-start overflow-hidden rounded-xl border border-line bg-surface">
960 <div className="flex items-center gap-2 border-b border-line px-2 sm:px-3">
961 <nav className="-mb-px flex min-w-0 grow gap-1 overflow-x-auto [scrollbar-width:none]" aria-label="Mission control">
962 {(["needs", "waiting", "landed"] as const).map((value) => (
963 <Link
964 key={value}
965 to={link({ tab: value, all: null })}
966 preventScrollReset
967 aria-current={tab === value ? "page" : undefined}
968 className={cn(
969 "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",
970 tab === value ? "border-merged font-medium text-fg" : "border-transparent text-muted hover:text-fg",
971 )}
972 >
973 <span className="sm:hidden">{TAB_SHORT[value]}</span>
974 <span className="hidden sm:inline">{TAB_LABEL[value]}</span>
975 <span
976 className={cn(
977 "rounded-full px-1.5 text-[0.6875rem] tabular-nums",
978 tab === value && value === "needs" && counts.needs > 0 ? "bg-warn/15 text-warn" : "bg-line text-muted",
979 )}
980 >
981 {counts[value]}
982 </span>
983 </Link>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look984 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms985 </nav>
986 <DropdownMenu>
987 <DropdownMenuTrigger
988 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"
989 aria-label="Sort"
990 >
991 <ArrowDownWideNarrow size={14} />
992 <span className="hidden sm:inline">{SORT_LABEL[sort]}</span>
993 </DropdownMenuTrigger>
994 <DropdownMenuContent align="end">
995 {(["impact", "newest"] as const).map((value) => (
996 <DropdownMenuItem key={value} asChild>
997 <Link to={link({ sort: value === "impact" ? null : value })} preventScrollReset>
998 {sort === value ? <Check /> : <span className="size-4" />}
999 {SORT_LABEL[value]}
1000 </Link>
1001 </DropdownMenuItem>
1002 ))}
1003 </DropdownMenuContent>
1004 </DropdownMenu>
1005 </div>
1006
1007 {!loaded.perRepoLoaded && tab !== "waiting" ? (
1008 <div className="p-4">
1009 <Unavailable what={tab === "needs" ? "Some of what needs you" : "What landed"} />
1010 </div>
1011 ) : null}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1012
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1013 {tab === "needs" &&
1014 (needs.length === 0 ? (
1015 noProjects ? (
1016 <Empty
1017 action={
1018 <Link
1019 to={workspace ? `/new?workspace=${workspace}` : "/new"}
1020 className="rounded-md bg-fg px-3 py-1.5 text-sm font-medium text-bg hover:bg-white"
1021 >
1022 Create or import a project
1023 </Link>
1024 }
1025 >
1026 <p className="font-medium text-fg">No projects yet</p>
1027 <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-agent1028 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 200ms1029 </p>
1030 </Empty>
1031 ) : (
1032 <>
1033 <Empty>
1034 <p className="font-medium text-fg">Nothing needs you</p>
1035 <p className="mt-1">
1036 Agents are handling everything.{" "}
1037 {landed.length > 0
1038 ? "Here's what they landed today."
1039 : "Reviews, stopped work and failed deploys show up here first."}
1040 </p>
1041 </Empty>
1042 {landed.length > 0 && (
1043 <div className="border-t border-line">
1044 <List>
1045 {landed.slice(0, ROWS).map((row) => (
1046 <LandedCard key={row.key} row={row} first={false} />
1047 ))}
1048 </List>
1049 </div>
1050 )}
1051 </>
1052 )
1053 ) : (
1054 <List>
1055 {(shown as NeedRow[]).map((row, index) => (
1056 <NeedCard key={row.key} row={row} first={index === 0} />
1057 ))}
1058 </List>
1059 ))}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1060
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1061 {tab === "waiting" &&
1062 (!loaded.runsLoaded && workspace ? (
1063 <div className="p-4">
1064 <Unavailable what="Agent runs" />
1065 </div>
1066 ) : waiting.length === 0 ? (
1067 <Empty>
1068 <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-agent1069 <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 200ms1070 </Empty>
1071 ) : (
1072 <List>
1073 {(shown as WaitingRow[]).map((row, index) => (
1074 <WaitingCard key={row.key} row={row} first={index === 0} />
1075 ))}
1076 </List>
1077 ))}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1078
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1079 {tab === "landed" &&
1080 loaded.perRepoLoaded &&
1081 (landed.length === 0 ? (
1082 <Empty>
1083 <p className="font-medium text-fg">Nothing has landed today yet</p>
1084 <p className="mt-1">
1085 {week.total > 0
1086 ? `${plural(week.total, "change")} landed in the last 7 days.`
1087 : "Merged pull requests show up here the moment they land."}
1088 </p>
1089 </Empty>
1090 ) : (
1091 <List>
1092 {(shown as LandedRow[]).map((row, index) => (
1093 <LandedCard key={row.key} row={row} first={index === 0} />
1094 ))}
1095 </List>
1096 ))}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1097
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1098 {rows.length > ROWS && (
1099 <div className="flex items-center justify-between border-t border-line px-4 py-2.5 text-xs text-muted sm:px-5">
1100 <span>
1101 Showing {shown.length} of {rows.length}
1102 </span>
1103 <Link
1104 to={link({ all: all ? null : "1" })}
1105 preventScrollReset
1106 className="inline-flex items-center gap-1 font-medium text-fg-soft hover:text-fg"
1107 >
1108 {all ? "Show fewer" : "See all"} <ChevronRight size={12} />
1109 </Link>
1110 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1111 )}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1112 </section>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1113
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1114 <aside className="min-w-0 space-y-6">
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all1115 <InboxNeedsCard items={loaderData.inboxNeeds.items} total={loaderData.inboxNeeds.total} />
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1116 <section className="rounded-xl border border-line bg-surface p-5">
1117 <div className="flex items-center justify-between">
1118 <h2 className="text-base font-semibold tracking-tight">This week</h2>
1119 {workspace && (
1120 <Link to={`/${workspace}/-/agents`} className="text-xs text-muted hover:text-fg">
1121 Fleet
1122 </Link>
1123 )}
1124 </div>
1125 <div className="mt-4">{loaded.perRepoLoaded ? <WeekChart week={week} /> : <Unavailable what="The week" />}</div>
1126 {workspace && stats.weekCost > 0 && (
1127 <Link
1128 to={`/${workspace}/-/usage`}
1129 className="mt-4 flex items-center justify-between border-t border-line pt-3 text-xs text-muted hover:text-fg"
1130 >
1131 <span>Agents and sandboxes cost {usd(stats.weekCost)} this week</span>
1132 <ChevronRight size={12} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1133 </Link>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1134 )}
1135 </section>
1136
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix1137 {workspace && (
1138 <TokenUsagePanel
1139 workspace={loaderData.tokens.workspace}
1140 mine={loaderData.tokens.mine}
1141 usageHref={`/${workspace}/-/usage`}
1142 />
1143 )}
1144
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1145 <section id="activity" className="scroll-mt-20 rounded-xl border border-line bg-surface p-5">
1146 <div className="flex items-center justify-between">
1147 <h2 className="text-base font-semibold tracking-tight">Activity</h2>
1148 {groups.length > 8 && (
1149 <Link
1150 to={`${link({ activity: everyActivity ? null : "all" })}#activity`}
1151 preventScrollReset
1152 className="inline-flex items-center gap-0.5 text-xs text-muted hover:text-fg"
1153 >
1154 {everyActivity ? "Less" : "All activity"} <ChevronRight size={12} />
1155 </Link>
1156 )}
1157 </div>
1158 <p className="mt-1 flex items-center gap-3 text-[0.6875rem] text-faint">
1159 <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-agent1160 <Avatar name="g1t" size={12} /> agents
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1161 </span>
1162 <span className="inline-flex items-center gap-1.5">
1163 <Avatar name={viewer.username} size={12} /> people
1164 </span>
1165 </p>
1166 {!loaded.perRepoLoaded ? (
1167 <div className="mt-3">
1168 <Unavailable what="Activity" />
1169 </div>
1170 ) : feed.length === 0 ? (
1171 <p className="mt-3 text-sm leading-6 text-muted">
1172 Nothing has moved yet. Merges, deploys, checks, reviews and what agents learn show up here.
1173 </p>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1174 ) : (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1175 <ol className="mt-2 divide-y divide-line/70">
1176 {feed.map((group) => (
1177 <FeedLine key={group.id} group={group} titles={titles} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1178 ))}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1179 </ol>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1180 )}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1181 </section>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1182
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1183 <section className="rounded-xl border border-line bg-surface p-5">
1184 <h2 className="text-sm font-semibold">Connect your own agent</h2>
1185 <p className="mt-1.5 text-xs leading-5 text-muted">
1186 Add g1t to your coding agent. It signs in through your browser; there is no token to copy.
1187 </p>
1188 <AgentSetup className="mt-3" />
1189 <Link
1190 to="https://docs.g1t.sh/guides/bring-your-own-agent/"
1191 className="mt-3 inline-flex items-center gap-1 text-xs text-muted hover:text-fg"
1192 >
1193 How it works <ArrowRight size={12} />
1194 </Link>
1195 </section>
1196 </aside>
1197 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1198 </main>
1199 );
1200}

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