g1t/apps/web/app/components/mission-control.tsx
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 it | 1 | import { 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 look | 2 | import { type ReactNode, useEffect, useState } from "react"; |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 3 | import { Link, useFetcher, useRouteLoaderData, useSearchParams } from "react-router"; |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 4 | |
| 5 | import { trialClosed } from "../lib/trial"; | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 6 | import { type ActivityGroup, type Verb, greetingFor, isAgent } from "../lib/mission"; |
| 7 | import { | |
| 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"; | |
| 29 | import { cn } from "../lib/cn"; | |
| Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step | 30 | import { AgentComposer, type ComposerResult } from "./agent-composer"; |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 31 | import { AskComposer } from "./ask-composer"; |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 32 | import { AgentSetup } from "./agent-setup"; |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 33 | import { InboxNeedsCard } from "./inbox"; |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 34 | import type { ShellData } from "./shell"; |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 35 | import { useLiveRefresh } from "./agents"; |
| 36 | import { Unavailable } from "./mission"; | |
| Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix | 37 | import { TokenUsagePanel } from "./token-usage"; |
| Merge branch 'worktree-agent-a985c01f43538af01' | 38 | import { Avatar, SubmitButton, TimeAgo } from "./ui"; |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 39 | import { Hint } from "./ui/hint"; |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 40 | import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger } from "./ui/dropdown-menu"; |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 41 | import type { Loaded } from "../routes/home"; |
| 42 | ||
| 43 | /* | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 44 | * Mission control: the home page of someone signed in. It answers where |
| 45 | * the viewer is needed, what agents are doing, and what landed without | |
| 46 | * them. It is a chunk of its own, loaded only for them, so the signed-out | |
| 47 | * home page does not carry it. routes/home.tsx loads its data; | |
| 48 | * lib/mission-control.ts shapes it. | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 49 | */ |
| 50 | ||
| 51 | /** The viewer's time zone, set by the page itself, so the greeting fits their day. */ | |
| 52 | const TZ_COOKIE = "g1t_tz"; | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 53 | /** Rows a tab shows before "See all". */ |
| 54 | const ROWS = 6; | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 55 | |
| 56 | const 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 200ms | 57 | const 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 look | 58 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 59 | // --- Small pieces ----------------------------------------------------------------- |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 60 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 61 | const CHIP_TONE: Record<Reason, string> = { |
| 62 | blocking: "border-danger/35 bg-danger/10 text-danger", | |
| 63 | checks_failing: "border-danger/35 bg-danger/10 text-danger", | |
| 64 | outside_guardrails: "border-warn/35 bg-warn/10 text-warn", | |
| Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step | 65 | 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 200ms | 66 | stalled: "border-warn/35 bg-warn/10 text-warn", |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 67 | asked_for_you: "border-accent/35 bg-accent/10 text-accent", |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 68 | needs_review: "border-info/35 bg-info/10 text-info", |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 69 | ready_to_merge: "border-success/35 bg-success/10 text-success", |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 70 | }; |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 71 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 72 | function Chip({ children, tone }: { children: ReactNode; tone: string }) { |
| 73 | return ( | |
| 74 | <span | |
| 75 | className={cn( | |
| 76 | "inline-flex shrink-0 items-center rounded border px-1.5 py-px font-mono text-[0.625rem] font-medium tracking-wider whitespace-nowrap uppercase", | |
| 77 | tone, | |
| 78 | )} | |
| 79 | > | |
| 80 | {children} | |
| 81 | </span> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 82 | ); |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 83 | } |
| 84 | ||
| 85 | /** A person or an agent, by name, wearing the mark that tells them apart. */ | |
| 86 | function 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 look | 87 | return ( |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 88 | <span className={cn("inline-flex min-w-0 items-center gap-1.5", className)}> |
| 89 | <Avatar name={who.name} size={size} /> | |
| 90 | <span className="truncate">{who.name}</span> | |
| 91 | </span> | |
| 92 | ); | |
| 93 | } | |
| 94 | ||
| 95 | function Eyebrow({ children }: { children: ReactNode }) { | |
| 96 | return <p className="font-mono text-[0.625rem] font-medium tracking-[0.12em] text-faint uppercase">{children}</p>; | |
| 97 | } | |
| 98 | ||
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 99 | const FACT_TONE = { good: "text-success", warn: "text-warn", bad: "text-danger" } as const; |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 100 | |
| 101 | function Facts({ facts }: { facts: Fact[] }) { | |
| 102 | if (facts.length === 0) return <p className="text-sm text-muted">Nothing more is known about it yet.</p>; | |
| 103 | return ( | |
| 104 | <dl className="grid grid-cols-2 gap-x-4 gap-y-2.5"> | |
| 105 | {facts.map((fact) => ( | |
| Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step | 106 | <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 200ms | 107 | <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 step | 108 | <dd |
| 109 | className={cn( | |
| 110 | "mt-0.5 text-sm font-medium tabular-nums", | |
| 111 | fact.wide ? "leading-5" : "truncate", | |
| 112 | fact.tone ? FACT_TONE[fact.tone] : "text-fg-soft", | |
| 113 | )} | |
| 114 | > | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 115 | {fact.value} |
| 116 | </dd> | |
| 117 | </div> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 118 | ))} |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 119 | </dl> |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 120 | ); |
| 121 | } | |
| 122 | ||
| Merge branch 'worktree-agent-a985c01f43538af01' | 123 | /** |
| 124 | * A form that acts on a pull request from here: working until the action | |
| 125 | * has answered and the page has reloaded what it changed, then done, or | |
| 126 | * the error beside it. | |
| 127 | */ | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 128 | function QuickForm({ quick, variant = "quiet" }: { quick: QuickAction; variant?: "quiet" | "accent" }) { |
| 129 | const fetcher = useFetcher<{ error?: string } | null>(); | |
| 130 | const [sent, setSent] = useState(false); | |
| Merge branch 'worktree-agent-a985c01f43538af01' | 131 | const done = sent && fetcher.state === "idle" && !fetcher.data?.error; |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 132 | return ( |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 133 | <fetcher.Form method="post" action={quick.to} onSubmit={() => setSent(true)} className="contents"> |
| 134 | {Object.entries(quick.fields).map(([name, value]) => ( | |
| 135 | <input key={name} type="hidden" name={name} value={value} /> | |
| 136 | ))} | |
| Merge branch 'worktree-agent-a985c01f43538af01' | 137 | <SubmitButton |
| 138 | fetcher={fetcher} | |
| 139 | disabled={done} | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 140 | className={cn( |
| 141 | "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors disabled:cursor-default", | |
| 142 | variant === "accent" | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 143 | ? "bg-accent text-bg hover:bg-accent-hover" |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 144 | : "border border-line-strong text-fg/90 hover:bg-raised hover:text-fg", |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 145 | done && "border-success/40 text-success", |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 146 | )} |
| 147 | > | |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 148 | {done ? <Check size={14} /> : null} |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 149 | {done ? quick.done : quick.label} |
| Merge branch 'worktree-agent-a985c01f43538af01' | 150 | </SubmitButton> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 151 | {fetcher.data?.error && <span className="basis-full text-xs text-danger">{fetcher.data.error}</span>} |
| 152 | </fetcher.Form> | |
| 153 | ); | |
| 154 | } | |
| 155 | ||
| 156 | function ProjectMark({ repo }: { repo: { namespace: string; name: string } | null }) { | |
| 157 | return <Avatar name={repo ? repo.name : "g1t"} size={32} square />; | |
| 158 | } | |
| 159 | ||
| 160 | /** A row that opens to what is known about it; the first one starts open. */ | |
| 161 | function Row({ | |
| 162 | open, | |
| 163 | repo, | |
| 164 | by, | |
| 165 | title, | |
| 166 | sub, | |
| 167 | chip, | |
| 168 | at, | |
| 169 | children, | |
| 170 | }: { | |
| 171 | open: boolean; | |
| 172 | repo: { namespace: string; name: string } | null; | |
| 173 | by: Who | null; | |
| 174 | title: ReactNode; | |
| 175 | sub: ReactNode; | |
| 176 | chip: ReactNode; | |
| 177 | at: number; | |
| 178 | children: ReactNode; | |
| 179 | }) { | |
| 180 | return ( | |
| 181 | <li> | |
| 182 | <details open={open} className="group"> | |
| 183 | <summary className="flex cursor-pointer list-none items-center gap-3 px-4 py-3 transition-colors select-none hover:bg-raised/60 sm:px-5 [&::-webkit-details-marker]:hidden"> | |
| 184 | <ProjectMark repo={repo} /> | |
| 185 | <span className="min-w-0 grow"> | |
| 186 | <span className="block truncate text-sm">{title}</span> | |
| 187 | <span className="mt-0.5 flex min-w-0 items-center gap-1.5 text-xs text-muted"> | |
| 188 | <span className="shrink-0 font-medium text-fg-soft">{repo ? repo.name : "Workspace"}</span> | |
| 189 | {by && ( | |
| 190 | <> | |
| 191 | <span className="text-faint">·</span> | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 192 | <Person who={by} size={14} className={cn("shrink-0", by.agent ? "text-accent" : "")} /> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 193 | </> |
| 194 | )} | |
| 195 | {sub && ( | |
| 196 | <> | |
| 197 | <span className="text-faint">—</span> | |
| 198 | <span className="truncate">{sub}</span> | |
| 199 | </> | |
| 200 | )} | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 201 | </span> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 202 | </span> |
| 203 | <span className="hidden sm:inline-flex">{chip}</span> | |
| 204 | <span className="hidden w-16 shrink-0 text-right text-xs text-faint sm:block"> | |
| 205 | <TimeAgo at={at} /> | |
| 206 | </span> | |
| 207 | <ChevronDown size={16} className="shrink-0 text-faint transition-transform group-open:rotate-180" /> | |
| 208 | </summary> | |
| 209 | <div className="px-4 pb-4 sm:px-5 sm:pb-5"> | |
| 210 | <div className="mb-3 flex items-center gap-2 sm:hidden"> | |
| 211 | {chip} | |
| 212 | <span className="text-xs text-faint"> | |
| 213 | <TimeAgo at={at} /> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 214 | </span> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 215 | </div> |
| 216 | {children} | |
| 217 | </div> | |
| 218 | </details> | |
| 219 | </li> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 220 | ); |
| 221 | } | |
| 222 | ||
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 223 | function NeedCard({ row, first }: { row: NeedRow; first: boolean }) { |
| 224 | const isPull = row.to.includes("/pull/"); | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 225 | return ( |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 226 | <Row |
| 227 | open={first} | |
| 228 | repo={row.repo} | |
| 229 | by={row.by} | |
| 230 | title={ | |
| 231 | <> | |
| 232 | <span className="font-medium">{row.title}</span> | |
| 233 | {row.ref && <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>} | |
| 234 | </> | |
| 235 | } | |
| 236 | sub={row.ask} | |
| 237 | chip={<Chip tone={CHIP_TONE[row.reason]}>{REASON_LABEL[row.reason]}</Chip>} | |
| 238 | at={row.at} | |
| 239 | > | |
| 240 | <div className="grid gap-4 rounded-xl border border-line bg-bg/50 p-4 md:grid-cols-2 md:gap-x-6 2xl:grid-cols-[minmax(0,1.1fr)_minmax(0,1fr)_minmax(0,0.95fr)]"> | |
| 241 | <div className="min-w-0"> | |
| 242 | <Eyebrow>The ask</Eyebrow> | |
| 243 | <p className="mt-2 text-sm leading-6 text-fg-soft">{row.ask}</p> | |
| 244 | <p className="mt-2 text-xs leading-5 text-muted"> | |
| 245 | <Link to={row.to} className="font-medium text-fg hover:underline"> | |
| 246 | {row.repo ? `${row.repo.name}${row.ref ?? ""}` : row.title} | |
| 247 | </Link> | |
| 248 | {row.for && ( | |
| 249 | <> | |
| 250 | {" "} | |
| 251 | · started for <span className="text-fg-soft">{row.for}</span> | |
| 252 | </> | |
| 253 | )} | |
| 254 | </p> | |
| 255 | </div> | |
| 256 | <div className="min-w-0"> | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 257 | <Eyebrow>{row.by?.agent ? "What the agent already knows" : "What is known"}</Eyebrow> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 258 | <div className="mt-2"> |
| 259 | <Facts facts={row.facts} /> | |
| 260 | </div> | |
| 261 | </div> | |
| 262 | <div className="rounded-lg border border-warn/20 bg-warn/[0.06] p-3.5 md:col-span-2 2xl:col-span-1"> | |
| 263 | <p className="text-xs font-semibold text-warn">Why this needs you</p> | |
| 264 | <p className="mt-1.5 text-sm leading-6 text-fg-soft">{row.why}</p> | |
| 265 | <p className="mt-2 text-xs text-faint"> | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 266 | Started waiting <TimeAgo at={row.at} /> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 267 | </p> |
| 268 | </div> | |
| 269 | </div> | |
| 270 | <div className="mt-3 flex flex-wrap items-center gap-2"> | |
| 271 | <Link | |
| 272 | to={row.to} | |
| 273 | prefetch="intent" | |
| 274 | className="inline-flex items-center gap-1.5 rounded-md bg-fg px-3 py-1.5 text-sm font-medium text-bg transition-colors hover:bg-white" | |
| 275 | > | |
| 276 | Review and respond <ArrowUpRight size={14} /> | |
| 277 | </Link> | |
| 278 | {row.quick && <QuickForm quick={row.quick} />} | |
| 279 | {row.link && ( | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 280 | <Link |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 281 | to={row.link.to} |
| 282 | className="inline-flex items-center rounded-md border border-line-strong px-3 py-1.5 text-sm font-medium text-fg/90 hover:bg-raised hover:text-fg" | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 283 | > |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 284 | {row.link.label} |
| 285 | </Link> | |
| 286 | )} | |
| 287 | {row.open !== row.to && ( | |
| 288 | <Link to={row.open} prefetch="intent" className="px-2 py-1.5 text-sm text-muted hover:text-fg"> | |
| 289 | {row.open.includes("/issues/") ? "Open issue" : isPull ? "See the changes" : "Open pull request"} | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 290 | </Link> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 291 | )} |
| 292 | </div> | |
| 293 | </Row> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 294 | ); |
| 295 | } | |
| 296 | ||
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 297 | function WaitingCard({ row, first }: { row: WaitingRow; first: boolean }) { |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 298 | return ( |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 299 | <Row |
| 300 | open={first} | |
| 301 | repo={row.repo} | |
| 302 | by={row.by} | |
| 303 | title={ | |
| 304 | <> | |
| 305 | <span className="font-medium">{row.title}</span> | |
| 306 | {row.ref && <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span>} | |
| 307 | </> | |
| 308 | } | |
| 309 | sub={row.detail} | |
| 310 | chip={ | |
| 311 | <Chip tone="border-line-strong bg-raised text-muted"> | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 312 | {row.live && <span className="mr-1.5 size-1.5 animate-pulse rounded-full bg-success" />} |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 313 | {row.chip} |
| 314 | </Chip> | |
| 315 | } | |
| 316 | at={row.at} | |
| 317 | > | |
| 318 | <div className="grid gap-4 rounded-xl border border-line bg-bg/50 p-4 md:grid-cols-2 md:gap-6"> | |
| 319 | <div className="min-w-0"> | |
| 320 | <Eyebrow>{row.live ? "Doing now" : "Where it stands"}</Eyebrow> | |
| 321 | <p className="mt-2 text-sm leading-6 text-fg-soft">{row.detail}</p> | |
| 322 | </div> | |
| 323 | <div className="min-w-0"> | |
| 324 | <Eyebrow>What is known</Eyebrow> | |
| 325 | <div className="mt-2"> | |
| 326 | <Facts facts={row.facts} /> | |
| 327 | </div> | |
| 328 | </div> | |
| 329 | </div> | |
| 330 | <div className="mt-3 flex flex-wrap gap-2"> | |
| 331 | <Link | |
| 332 | to={row.to} | |
| 333 | prefetch="intent" | |
| 334 | className="inline-flex items-center gap-1.5 rounded-md border border-line-strong px-3 py-1.5 text-sm font-medium text-fg/90 hover:bg-raised hover:text-fg" | |
| 335 | > | |
| 336 | {row.to.includes("/runs/") ? "Watch the run" : "Open pull request"} <ArrowUpRight size={14} /> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 337 | </Link> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 338 | {row.run && row.run !== row.to && ( |
| 339 | <Link to={row.run} prefetch="intent" className="px-2 py-1.5 text-sm text-muted hover:text-fg"> | |
| 340 | Watch the run | |
| 341 | </Link> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 342 | )} |
| 343 | </div> | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 344 | </Row> |
| 345 | ); | |
| 346 | } | |
| 347 | ||
| 348 | function LandedCard({ row, first }: { row: LandedRow; first: boolean }) { | |
| 349 | return ( | |
| 350 | <Row | |
| 351 | open={first} | |
| 352 | repo={row.repo} | |
| 353 | by={row.byAgents ? { name: row.agent, agent: isAgent(row.agent) } : row.by} | |
| 354 | title={ | |
| 355 | <> | |
| 356 | <span className="font-medium">{row.title}</span> | |
| 357 | <span className="ml-1 font-mono text-xs text-faint">{row.ref}</span> | |
| 358 | </> | |
| 359 | } | |
| 360 | sub={row.byAgents ? "landed without a person" : `merged by ${row.by?.name ?? "a person"}`} | |
| 361 | chip={ | |
| 362 | row.byAgents ? ( | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 363 | <Chip tone="border-accent/35 bg-accent/10 text-accent">By agents</Chip> |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 364 | ) : ( |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 365 | <Chip tone="border-warn/35 bg-warn/10 text-warn">Needed a person</Chip> |
| 366 | ) | |
| 367 | } | |
| 368 | at={row.at} | |
| 369 | > | |
| 370 | <div className="grid gap-4 rounded-xl border border-line bg-bg/50 p-4 md:grid-cols-2 md:gap-6"> | |
| 371 | <div className="min-w-0"> | |
| 372 | <Eyebrow>How it landed</Eyebrow> | |
| 373 | <p className="mt-2 text-sm leading-6 text-fg-soft"> | |
| 374 | {row.byAgents | |
| 375 | ? `g1t merged it once everything the repository asks for was met, with no one pressing merge.` | |
| 376 | : `${row.by?.name ?? "A person"} merged it.`} | |
| 377 | {row.agent && isAgent(row.agent) && ` ${row.agent} made the change.`} | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 378 | </p> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 379 | </div> |
| 380 | <div className="min-w-0"> | |
| 381 | <Eyebrow>What changed</Eyebrow> | |
| 382 | <div className="mt-2"> | |
| 383 | <Facts facts={row.facts} /> | |
| 384 | </div> | |
| 385 | </div> | |
| 386 | </div> | |
| 387 | <div className="mt-3"> | |
| 388 | <Link | |
| 389 | to={row.to} | |
| 390 | prefetch="intent" | |
| 391 | className="inline-flex items-center gap-1.5 rounded-md border border-line-strong px-3 py-1.5 text-sm font-medium text-fg/90 hover:bg-raised hover:text-fg" | |
| 392 | > | |
| 393 | Open pull request <ArrowUpRight size={14} /> | |
| 394 | </Link> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 395 | </div> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 396 | </Row> |
| 397 | ); | |
| 398 | } | |
| 399 | ||
| 400 | function List({ children }: { children: ReactNode }) { | |
| 401 | return <ul className="divide-y divide-line">{children}</ul>; | |
| 402 | } | |
| 403 | ||
| 404 | function Empty({ children, action }: { children: ReactNode; action?: ReactNode }) { | |
| 405 | return ( | |
| 406 | <div className="px-5 py-10 text-center"> | |
| 407 | <div className="mx-auto max-w-md text-sm leading-6 text-muted">{children}</div> | |
| 408 | {action && <div className="mt-4 flex flex-wrap justify-center gap-2">{action}</div>} | |
| 409 | </div> | |
| 410 | ); | |
| 411 | } | |
| 412 | ||
| 413 | // --- The stat strip ----------------------------------------------------------------- | |
| 414 | ||
| 415 | function Stat({ label, value, hint, dot, title }: { label: string; value: string; hint: ReactNode; dot?: string; title?: string }) { | |
| 416 | return ( | |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 417 | <Hint label={title}> |
| 418 | <div className="min-w-0 bg-surface px-4 py-3.5 sm:px-5 sm:py-4"> | |
| 419 | <p className="flex items-center gap-1.5 truncate text-xs text-muted"> | |
| 420 | {dot && <span className={cn("size-1.5 shrink-0 rounded-full", dot)} />} | |
| 421 | {label} | |
| 422 | </p> | |
| 423 | <p className="mt-1 font-display text-2xl font-semibold tracking-tight tabular-nums sm:text-[1.75rem]">{value}</p> | |
| 424 | <p className="mt-0.5 truncate text-xs text-faint">{hint}</p> | |
| 425 | </div> | |
| 426 | </Hint> | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 427 | ); |
| 428 | } | |
| 429 | ||
| 430 | // --- This week ---------------------------------------------------------------------- | |
| 431 | ||
| 432 | /** Seven days of landed changes, each split into what agents landed alone and what a person merged. */ | |
| 433 | function WeekChart({ week }: { week: Week }) { | |
| Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar | 434 | 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 200ms | 435 | const height = 112; |
| 436 | const delta = change(week.total, week.previous); | |
| 437 | return ( | |
| 438 | <div> | |
| 439 | <div className="flex items-end justify-between gap-3"> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 440 | <div> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 441 | <p className="font-display text-3xl font-semibold tracking-tight tabular-nums">{week.total.toLocaleString("en-US")}</p> |
| 442 | <p className="text-xs text-muted">changes landed in 7 days</p> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 443 | </div> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 444 | {delta != null ? ( |
| 445 | <span | |
| 446 | className={cn( | |
| 447 | "rounded-full px-2 py-0.5 text-xs font-medium tabular-nums ring-1", | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 448 | delta >= 0 ? "text-success ring-success/30" : "text-warn ring-warn/30", |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 449 | )} |
| 450 | > | |
| 451 | {signedPercent(delta)} vs last week | |
| 452 | </span> | |
| 453 | ) : week.previous === 0 && week.total > 0 ? ( | |
| 454 | <span className="text-xs text-faint">none the week before</span> | |
| 455 | ) : null} | |
| 456 | </div> | |
| 457 | {week.total === 0 ? ( | |
| 458 | <p className="mt-4 rounded-lg border border-dashed border-line px-4 py-6 text-center text-sm leading-6 text-muted"> | |
| Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar | 459 | 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 200ms | 460 | </p> |
| 461 | ) : ( | |
| 462 | <div className="relative mt-5" style={{ height: height + 20 }}> | |
| 463 | <div className="absolute inset-x-0 border-t border-line" style={{ top: height }} /> | |
| 464 | <div className="absolute inset-x-0 top-0 flex items-end justify-between gap-1" style={{ height }}> | |
| 465 | {week.days.map((day, index) => { | |
| Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar | 466 | const total = day.agents + day.assisted + day.people; |
| 467 | const bar = (n: number) => Math.max(3, Math.round((n / max) * (height - 4))); | |
| 468 | // Top to bottom: agents alone, agents with a person, people. | |
| 469 | const segments = [ | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 470 | { n: day.agents, tone: "bg-accent" }, |
| Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar | 471 | { n: day.assisted, tone: "bg-warn" }, |
| 472 | { n: day.people, tone: "bg-info" }, | |
| 473 | ].filter((segment) => segment.n > 0); | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 474 | const today = index === week.days.length - 1; |
| 475 | return ( | |
| 476 | <div key={day.key} className="group relative flex h-full flex-1 flex-col items-center justify-end"> | |
| 477 | {total > 0 && ( | |
| 478 | <span | |
| 479 | className={cn( | |
| 480 | "mb-1 text-[0.625rem] tabular-nums", | |
| 481 | today ? "text-fg-soft" : "text-faint opacity-0 group-hover:opacity-100", | |
| 482 | )} | |
| 483 | > | |
| 484 | {total} | |
| 485 | </span> | |
| 486 | )} | |
| 487 | <div className="flex w-full max-w-6 flex-col items-stretch gap-[2px]"> | |
| Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar | 488 | {segments.map((segment, at) => ( |
| 489 | <span key={segment.tone} className={cn("block", segment.tone, at === 0 && "rounded-t")} style={{ height: bar(segment.n) }} /> | |
| 490 | ))} | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 491 | {total === 0 && <span className="block h-[2px] rounded-full bg-line-strong" />} |
| 492 | </div> | |
| 493 | <div className="pointer-events-none absolute bottom-full left-1/2 z-10 mb-1 hidden -translate-x-1/2 rounded-md border border-line-strong bg-raised px-2.5 py-1.5 text-xs whitespace-nowrap shadow-lg shadow-black/40 group-hover:block"> | |
| 494 | <p className="font-medium text-fg">{day.label}</p> | |
| 495 | <p className="mt-0.5 flex items-center gap-1.5 text-muted"> | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 496 | <span className="size-1.5 rounded-full bg-accent" /> {day.agents} by agents on their own |
| Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar | 497 | </p> |
| 498 | <p className="flex items-center gap-1.5 text-muted"> | |
| 499 | <span className="size-1.5 rounded-full bg-warn" /> {day.assisted} by agents, merged by a person | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 500 | </p> |
| 501 | <p className="flex items-center gap-1.5 text-muted"> | |
| Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar | 502 | <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 200ms | 503 | </p> |
| 504 | </div> | |
| 505 | </div> | |
| 506 | ); | |
| 507 | })} | |
| 508 | </div> | |
| 509 | <div className="absolute inset-x-0 bottom-0 flex justify-between gap-1"> | |
| 510 | {week.days.map((day, index) => ( | |
| 511 | <span | |
| 512 | key={day.key} | |
| 513 | className={cn( | |
| 514 | "flex-1 text-center text-[0.6875rem]", | |
| 515 | index === week.days.length - 1 ? "font-medium text-fg-soft" : "text-faint", | |
| 516 | )} | |
| 517 | > | |
| 518 | {day.label} | |
| 519 | </span> | |
| 520 | ))} | |
| 521 | </div> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 522 | </div> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 523 | )} |
| 524 | <div className={cn("mt-3 flex-wrap gap-x-4 gap-y-1 text-xs text-muted", week.total === 0 ? "hidden" : "flex")}> | |
| 525 | <span className="inline-flex items-center gap-1.5"> | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 526 | <span className="size-2 rounded-sm bg-accent" /> Agents, on their own |
| Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar | 527 | </span> |
| 528 | <span className="inline-flex items-center gap-1.5"> | |
| 529 | <span className="size-2 rounded-sm bg-warn" /> Agents, merged by a person | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 530 | </span> |
| 531 | <span className="inline-flex items-center gap-1.5"> | |
| Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar | 532 | <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 200ms | 533 | </span> |
| 534 | </div> | |
| Fixes from the page sweep | 535 | {/* A table does not shrink to sr-only's 1px; its wrapper does. */} |
| 536 | <div className="sr-only"> | |
| 537 | <table> | |
| 538 | <caption>Changes landed each day</caption> | |
| 539 | <thead> | |
| 540 | <tr> | |
| 541 | <th>Day</th> | |
| 542 | <th>Agents, on their own</th> | |
| 543 | <th>Agents, merged by a person</th> | |
| 544 | <th>People</th> | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 545 | </tr> |
| Fixes from the page sweep | 546 | </thead> |
| 547 | <tbody> | |
| 548 | {week.days.map((day) => ( | |
| 549 | <tr key={day.key}> | |
| 550 | <td>{day.key}</td> | |
| 551 | <td>{day.agents}</td> | |
| 552 | <td>{day.assisted}</td> | |
| 553 | <td>{day.people}</td> | |
| 554 | </tr> | |
| 555 | ))} | |
| 556 | </tbody> | |
| 557 | </table> | |
| 558 | </div> | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 559 | </div> |
| 560 | ); | |
| 561 | } | |
| 562 | ||
| 563 | // --- Activity ----------------------------------------------------------------------- | |
| 564 | ||
| 565 | const VERB: Record<Verb, string> = { | |
| 566 | landed: "landed", | |
| 567 | opened_issue: "opened", | |
| 568 | closed_issue: "closed", | |
| 569 | started: "started on", | |
| 570 | ready: "marked ready", | |
| 571 | checks_passed: "checks passed on", | |
| 572 | checks_failed: "checks failed on", | |
| 573 | approved: "approved", | |
| 574 | changes_requested: "asked for changes on", | |
| 575 | commented: "commented on", | |
| 576 | asked: "was asked about", | |
| 577 | deployed: "deployed production", | |
| 578 | deploy_failed: "production build failed", | |
| 579 | learned: "learned", | |
| 580 | }; | |
| 581 | ||
| 582 | /** One line of the feed: who, what, and the one thing it was about, by name. */ | |
| 583 | function FeedLine({ group, titles }: { group: ActivityGroup; titles: Record<string, string> }) { | |
| 584 | const part = group.parts[0]; | |
| 585 | const base = `/${group.repo.namespace}/${group.repo.name}`; | |
| 586 | const actor = group.actor ?? "g1t"; | |
| 587 | const agent = group.actor == null || isAgent(group.actor); | |
| 588 | const number = part.numbers[0]; | |
| 589 | const title = number != null ? titles[`${group.repo.namespace}/${group.repo.name}#${number}`.toLowerCase()] : undefined; | |
| 590 | const more = group.parts.length - 1 + Math.max(0, part.numbers.length - 1); | |
| 591 | return ( | |
| 592 | <li className="flex gap-2.5 py-2"> | |
| 593 | <span className="mt-px"> | |
| 594 | <Avatar name={actor} size={22} /> | |
| 595 | </span> | |
| 596 | <span className="min-w-0 grow text-[0.8125rem] leading-5 text-muted"> | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 597 | <span className={cn("font-medium", agent ? "text-accent" : "text-fg")}>{actor}</span> {VERB[part.verb]}{" "} |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 598 | {part.verb === "learned" ? ( |
| 599 | <Link to={part.to ?? `${base}/memory`} className="text-fg-soft hover:text-fg"> | |
| 600 | “{part.texts[0]}” | |
| 601 | </Link> | |
| 602 | ) : number != null ? ( | |
| 603 | <Link to={`${base}/issues/${number}`} prefetch="intent" className="text-fg-soft hover:text-fg"> | |
| 604 | <span className="font-mono text-xs">#{number}</span> | |
| 605 | {title && <> {title}</>} | |
| 606 | </Link> | |
| 607 | ) : part.to ? ( | |
| 608 | <Link to={part.to} className="text-fg-soft hover:text-fg"> | |
| 609 | {group.repo.name} | |
| 610 | </Link> | |
| 611 | ) : null} | |
| 612 | {more > 0 && <span className="text-faint"> and {more} more</span>} | |
| 613 | <span className="block text-xs text-faint"> | |
| 614 | <span className="font-mono">{group.repo.name}</span> · <TimeAgo at={group.at} /> | |
| 615 | </span> | |
| 616 | </span> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 617 | </li> |
| 618 | ); | |
| 619 | } | |
| 620 | ||
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 621 | // --- Get started -------------------------------------------------------------------- |
| 622 | ||
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 623 | type Step = { done: boolean; title: string; about: string; to: string | null; action: string }; |
| 624 | ||
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 625 | /** 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 look | 626 | function GetStarted({ steps }: { steps: Step[] }) { |
| 627 | const left = steps.filter((step) => !step.done).length; | |
| 628 | return ( | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 629 | <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 look | 630 | <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 200ms | 631 | <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 look | 632 | <span className="text-xs text-muted"> |
| 633 | {steps.length - left} of {steps.length} done | |
| 634 | </span> | |
| 635 | </div> | |
| 636 | <ol className="mt-4 space-y-2"> | |
| 637 | {steps.map((step, index) => ( | |
| 638 | <li | |
| 639 | key={step.title} | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 640 | 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 look | 641 | > |
| 642 | <span | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 643 | className={cn( |
| 644 | "mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full text-[0.6875rem] font-medium", | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 645 | step.done ? "bg-success text-bg" : "text-muted ring-1 ring-line-strong", |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 646 | )} |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 647 | > |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 648 | {step.done ? <Check size={12} /> : index + 1} |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 649 | </span> |
| 650 | <span className="min-w-0 grow"> | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 651 | <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 look | 652 | {!step.done && <span className="mt-0.5 block text-xs leading-5 text-muted">{step.about}</span>} |
| 653 | </span> | |
| 654 | {!step.done && step.to && ( | |
| 655 | <Link | |
| 656 | to={step.to} | |
| 657 | className="shrink-0 rounded-md bg-fg px-2.5 py-1 text-xs font-medium text-bg transition-colors hover:bg-white" | |
| 658 | > | |
| 659 | {step.action} | |
| 660 | </Link> | |
| 661 | )} | |
| 662 | </li> | |
| 663 | ))} | |
| 664 | </ol> | |
| 665 | </section> | |
| 666 | ); | |
| 667 | } | |
| 668 | ||
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 669 | // --- The page ----------------------------------------------------------------------- |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 670 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 671 | const TAB_LABEL: Record<Tab, string> = { needs: "Needs you", waiting: "Waiting on agents", landed: "Landed today" }; |
| 672 | const TAB_SHORT: Record<Tab, string> = { needs: "Needs you", waiting: "Waiting", landed: "Today" }; | |
| 673 | const 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 look | 674 | |
| Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step | 675 | export 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 look | 676 | const shell = useRouteLoaderData("root")?.shell as ShellData | null | undefined; |
| 677 | const loaded: Loaded = loaderData; | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 678 | const [params] = useSearchParams(); |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 679 | // 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 200ms | 680 | useLiveRefresh(loaded.changing); |
| 681 | const [greeting, setGreeting] = useState(loaded.greeting); | |
| 682 | const [date, setDate] = useState(loaded.date); | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 683 | useEffect(() => { |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 684 | // The greeting and date follow the viewer's own clock, and the server |
| 685 | // learns their zone for next time. | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 686 | setGreeting(greetingFor(new Date().getHours())); |
| 687 | try { | |
| 688 | const zone = Intl.DateTimeFormat().resolvedOptions().timeZone; | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 689 | setDate(dateLine(Date.now(), zone ?? null)); |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 690 | if (zone) document.cookie = `${TZ_COOKIE}=${encodeURIComponent(zone)}; Path=/; Max-Age=31536000; SameSite=Lax`; |
| 691 | } catch { | |
| 692 | // Nothing to remember. | |
| 693 | } | |
| 694 | }, []); | |
| 695 | ||
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 696 | const { viewer, repos, waiting, landed, liveTotal, week, groups, titles, stats, canRunAgents, trial } = loaded; |
| 697 | const workspace = shell?.workspace?.slug ?? loaded.workspace ?? viewer.workspaces?.[0]?.slug ?? null; | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 698 | |
| 699 | // The usage limit, as the sidebar knows it, heads the list when it bites. | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 700 | const limit = shell?.limit && !shell.limit.comped && shell.limit.state !== "ok" && workspace ? shell.limit : null; |
| 701 | const limitRow: NeedRow[] = limit | |
| 702 | ? [ | |
| 703 | (() => { | |
| 704 | const detail = | |
| 705 | limit.state === "stopped" | |
| 706 | ? "No new agent starts until the limit is raised or a card is added." | |
| 707 | : `${dollars(limit.exposureMicros)} of ${dollars(limit.ceilingMicros ?? 0)} used. Add a card so work keeps going.`; | |
| 708 | return { | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 709 | key: "limit", |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 710 | reason: "blocking" as const, |
| 711 | repo: null, | |
| 712 | ref: null, | |
| 713 | title: limit.state === "stopped" ? "Agents are stopped: the usage limit was reached" : "Usage is nearing its limit", | |
| 714 | ask: detail, | |
| 715 | by: null, | |
| 716 | for: null, | |
| 717 | at: Date.now(), | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 718 | to: `/${workspace}/-/billing`, |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 719 | open: `/${workspace}/-/billing`, |
| 720 | facts: [ | |
| 721 | { label: "Used", value: `${dollars(limit.exposureMicros)} of ${dollars(limit.ceilingMicros ?? 0)}`, tone: "warn" as const }, | |
| 722 | ], | |
| 723 | why: whyFor("blocking", { kind: "limit", detail }), | |
| 724 | quick: null, | |
| 725 | link: null, | |
| 726 | }; | |
| 727 | })(), | |
| 728 | ] | |
| 729 | : []; | |
| 730 | const needs = [...limitRow, ...loaded.needs]; | |
| 731 | const blocking = needs.filter((row) => BLOCKING.has(row.reason)).length; | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 732 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 733 | const counts: Record<Tab, number> = { needs: needs.length, waiting: waiting.length, landed: landed.length }; |
| 734 | const tab = parseTab(params.get("tab")) ?? "needs"; | |
| 735 | const sort = parseSort(params.get("sort")); | |
| 736 | const all = params.get("all") === "1"; | |
| 737 | const everyActivity = params.get("activity") === "all"; | |
| 738 | const link = (changes: Record<string, string | null>) => { | |
| 739 | const next = new URLSearchParams(params); | |
| 740 | for (const [key, value] of Object.entries(changes)) { | |
| 741 | if (value == null) next.delete(key); | |
| 742 | else next.set(key, value); | |
| 743 | } | |
| 744 | const text = next.toString(); | |
| 745 | return text ? `?${text}` : "/"; | |
| 746 | }; | |
| 747 | ||
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 748 | const steps: Step[] = [ |
| 749 | { | |
| 750 | done: Boolean(workspace), | |
| 751 | title: "Create a workspace", | |
| 752 | about: "Projects, people and agent credit live in one.", | |
| 753 | to: "/workspaces/new", | |
| 754 | action: "Create", | |
| 755 | }, | |
| 756 | { | |
| 757 | done: repos.length > 0, | |
| 758 | title: "Add a project", | |
| 759 | about: "Create a repository, or import one by its address. Push to it with git as usual.", | |
| 760 | to: workspace ? `/new?workspace=${workspace}` : "/new", | |
| 761 | action: "Add", | |
| 762 | }, | |
| 763 | trial?.open | |
| 764 | ? { | |
| 765 | done: true, | |
| g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent | 766 | title: "Try g1t free", |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 767 | about: trial.granted |
| 768 | ? `This workspace has ${dollars(trial.limitMicros - trial.usedMicros)} of its ${dollars(trial.limitMicros)} trial credit left, for g1t's models and sandboxes.` | |
| g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent | 769 | : `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 look | 770 | to: workspace ? `/${workspace}/-/integrations` : null, |
| 771 | action: "Connect", | |
| 772 | } | |
| 773 | : canRunAgents | |
| 774 | ? { | |
| 775 | done: Boolean(shell?.free) || shell?.limit == null || shell.limit.comped || shell.limit.state === "ok", | |
| 776 | title: "Keep work running", | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 777 | about: |
| 778 | "Usage is charged after it runs. Add a card under Billing, so g1t charges it as you near your limit instead of stopping work.", | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 779 | to: workspace ? `/${workspace}/-/billing` : null, |
| 780 | action: "Billing", | |
| 781 | } | |
| 782 | : { | |
| 783 | done: false, | |
| 784 | title: "Connect a model", | |
| 785 | about: `${ | |
| 786 | trialClosed(trial, workspace ?? "This workspace") ? `${trialClosed(trial, workspace ?? "This workspace")} ` : "" | |
| 787 | }Agents need a model to think with. Connect your Anthropic or OpenAI key, or any compatible endpoint.`, | |
| 788 | to: workspace ? `/${workspace}/-/integrations` : null, | |
| 789 | action: "Connect", | |
| 790 | }, | |
| 791 | { | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 792 | done: loaded.handedOff, |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 793 | title: "Hand off an outcome", |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 794 | about: |
| g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent | 795 | "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 200ms | 796 | 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 look | 797 | action: "Write one", |
| 798 | }, | |
| 799 | ]; | |
| 800 | const starting = steps.some((step) => !step.done); | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 801 | const noProjects = repos.length === 0; |
| 802 | ||
| 803 | const rows = tab === "needs" ? sortRows(needs, sort) : tab === "waiting" ? sortRows(waiting, sort) : sortRows(landed, sort); | |
| 804 | const shown = all ? rows : rows.slice(0, ROWS); | |
| Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar | 805 | // Of the agents' own changes only: people's work is not theirs to land. |
| 806 | const share = week.agentChanges > 0 ? week.byAgents / week.agentChanges : null; | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 807 | const delta = change(week.total, week.previous); |
| 808 | const feed = everyActivity ? groups : groups.slice(0, 8); | |
| 809 | ||
| Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step | 810 | // "Put an agent on it" first, with "New issue" beside it as before: one |
| 811 | // split control, the agent the main way in. | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 812 | const newIssue = |
| 813 | repos.length > 0 ? ( | |
| Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step | 814 | <div className="flex items-stretch"> |
| 815 | <AgentComposer | |
| 816 | repos={repos} | |
| 817 | open={params.get("agent") === "new" || delegated != null} | |
| 818 | result={delegated} | |
| 819 | note={ | |
| 820 | canRunAgents ? null : ( | |
| 821 | <> | |
| 822 | Agents need a model first.{" "} | |
| 823 | {workspace && ( | |
| 824 | <Link to={`/${workspace}/-/integrations`} className="font-medium text-fg hover:underline"> | |
| 825 | Connect one | |
| 826 | </Link> | |
| 827 | )} | |
| 828 | . The issue still opens. | |
| 829 | </> | |
| 830 | ) | |
| 831 | } | |
| 832 | > | |
| 833 | <span className="inline-flex cursor-pointer items-center gap-1.5 rounded-l-md border border-line-strong bg-raised px-3 py-2 text-sm font-medium text-fg transition-colors hover:bg-line/60 group-open/composer:bg-line/60"> | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 834 | <Sparkles size={14} className="text-accent" /> Put an agent on it |
| Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step | 835 | </span> |
| 836 | </AgentComposer> | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 837 | <DropdownMenu> |
| Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step | 838 | <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 200ms | 839 | <Plus size={14} /> New issue |
| 840 | </DropdownMenuTrigger> | |
| 841 | <DropdownMenuContent align="end" className="max-h-80 overflow-y-auto"> | |
| 842 | <DropdownMenuLabel>In which project?</DropdownMenuLabel> | |
| 843 | {repos.map((repo) => ( | |
| 844 | <DropdownMenuItem key={repo.name} asChild> | |
| 845 | <Link to={`/${repo.namespace}/${repo.name}/issues/new`}> | |
| 846 | <Avatar name={repo.name} size={18} square /> | |
| 847 | {repo.name} | |
| 848 | </Link> | |
| 849 | </DropdownMenuItem> | |
| 850 | ))} | |
| 851 | </DropdownMenuContent> | |
| 852 | </DropdownMenu> | |
| Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step | 853 | </div> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 854 | ) : ( |
| 855 | <Link | |
| 856 | to={workspace ? `/new?workspace=${workspace}` : "/new"} | |
| 857 | className="inline-flex items-center gap-1.5 rounded-md border border-line-strong px-3 py-2 text-sm font-medium text-fg/90 hover:bg-raised hover:text-fg" | |
| 858 | > | |
| 859 | <Plus size={14} /> New project | |
| 860 | </Link> | |
| 861 | ); | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 862 | |
| 863 | return ( | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 864 | <main className="mx-auto max-w-7xl space-y-6 px-4 py-8 sm:px-6 sm:py-10"> |
| 865 | <header className="flex flex-wrap items-end justify-between gap-x-6 gap-y-4"> | |
| 866 | <div className="min-w-0"> | |
| 867 | <h1 className="text-[1.75rem] leading-tight font-semibold tracking-tight sm:text-[2rem]" suppressHydrationWarning> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 868 | {greeting}, {loaded.name} |
| 869 | </h1> | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 870 | <p className="mt-1.5 text-sm text-muted" suppressHydrationWarning> |
| 871 | <span className="text-fg-soft">{date}</span> · {loaded.summary} | |
| 872 | </p> | |
| 873 | </div> | |
| 874 | </header> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 875 | |
| Previews build from any branch; Agent in place of Ask AI; pin from the sidebar | 876 | {/* The agent is not on yet; the actions under it do the work today. */} |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 877 | <AskComposer> |
| 878 | {newIssue} | |
| 879 | {repos.length > 0 && ( | |
| 880 | <DropdownMenu> | |
| 881 | <DropdownMenuTrigger className="inline-flex items-center gap-1.5 rounded-md border border-line-strong px-3 py-2 text-sm font-medium text-fg/90 transition-colors hover:bg-raised hover:text-fg"> | |
| 882 | <ListTree size={14} /> Plan work | |
| 883 | </DropdownMenuTrigger> | |
| 884 | <DropdownMenuContent align="start" className="max-h-80 overflow-y-auto"> | |
| 885 | <DropdownMenuLabel>Plan in which project?</DropdownMenuLabel> | |
| 886 | {repos.map((repo) => ( | |
| 887 | <DropdownMenuItem key={repo.name} asChild> | |
| 888 | <Link to={`/${repo.namespace}/${repo.name}/plans`}> | |
| 889 | <Avatar name={repo.name} size={18} square /> | |
| 890 | {repo.name} | |
| 891 | </Link> | |
| 892 | </DropdownMenuItem> | |
| 893 | ))} | |
| 894 | </DropdownMenuContent> | |
| 895 | </DropdownMenu> | |
| 896 | )} | |
| 897 | {needs.length > 0 && ( | |
| 898 | <Link | |
| 899 | to={`${link({ tab: "needs", all: null })}#work`} | |
| 900 | preventScrollReset | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 901 | className="inline-flex items-center gap-1.5 rounded-md bg-accent px-3.5 py-2 text-sm font-semibold text-bg transition-colors hover:bg-accent-hover" |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 902 | > |
| 903 | Review {needs.length} that need{needs.length === 1 ? "s" : ""} you <ArrowRight size={14} /> | |
| 904 | </Link> | |
| 905 | )} | |
| 906 | </AskComposer> | |
| 907 | ||
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 908 | {starting && <GetStarted steps={steps} />} |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 909 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 910 | <section |
| 911 | aria-label="At a glance" | |
| 912 | className="grid grid-cols-2 gap-px overflow-hidden rounded-xl border border-line bg-line lg:grid-cols-5" | |
| 913 | > | |
| 914 | <Stat | |
| 915 | label="Projects" | |
| 916 | value={stats.projects == null ? "—" : String(stats.projects)} | |
| 917 | hint={ | |
| 918 | stats.projectsThisMonth == null | |
| 919 | ? "in this workspace" | |
| 920 | : stats.projectsThisMonth > 0 | |
| 921 | ? `+${stats.projectsThisMonth} this month` | |
| 922 | : "none new this month" | |
| 923 | } | |
| 924 | /> | |
| 925 | <Stat | |
| 926 | label="Agents" | |
| 927 | value={loaded.runsLoaded || !workspace ? String(liveTotal) : "—"} | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 928 | dot={liveTotal > 0 ? "bg-success animate-pulse" : undefined} |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 929 | hint={ |
| 930 | liveTotal > 0 | |
| 931 | ? `live now · ${stats.agentHours < 10 ? stats.agentHours.toFixed(1) : Math.round(stats.agentHours)}h this week` | |
| 932 | : stats.agentHours > 0 | |
| 933 | ? `none live · ${stats.agentHours < 10 ? stats.agentHours.toFixed(1) : Math.round(stats.agentHours)}h this week` | |
| 934 | : "none live now" | |
| 935 | } | |
| 936 | /> | |
| 937 | <Stat | |
| 938 | label="Changes this week" | |
| 939 | value={loaded.perRepoLoaded ? String(week.total) : "—"} | |
| 940 | hint={ | |
| 941 | delta != null ? `${signedPercent(delta)} vs last week` : week.previous === 0 ? "none the week before" : "merged pull requests" | |
| 942 | } | |
| 943 | /> | |
| 944 | <Stat | |
| 945 | label="Landed without you" | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 946 | dot="bg-accent" |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 947 | 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 bar | 948 | hint={share == null ? "no agent changes yet" : `${week.byAgents} of ${week.agentChanges} agent changes`} |
| 949 | title="Of the changes agents wrote, those g1t merged by auto-merge or the merge queue, with no person pressing merge. People's own changes are not counted." | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 950 | /> |
| 951 | <div className="col-span-2 lg:col-span-1"> | |
| 952 | <Stat | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 953 | label="Needs you" |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 954 | dot="bg-warn" |
| 955 | value={String(needs.length)} | |
| 956 | hint={blocking > 0 ? `${blocking} blocking` : needs.length > 0 ? "nothing blocking" : "all clear"} | |
| 957 | /> | |
| 958 | </div> | |
| 959 | </section> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 960 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 961 | <div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_21rem] xl:grid-cols-[minmax(0,1fr)_23rem]"> |
| 962 | <section id="work" className="min-w-0 scroll-mt-20 self-start overflow-hidden rounded-xl border border-line bg-surface"> | |
| 963 | <div className="flex items-center gap-2 border-b border-line px-2 sm:px-3"> | |
| 964 | <nav className="-mb-px flex min-w-0 grow gap-1 overflow-x-auto [scrollbar-width:none]" aria-label="Mission control"> | |
| 965 | {(["needs", "waiting", "landed"] as const).map((value) => ( | |
| 966 | <Link | |
| 967 | key={value} | |
| 968 | to={link({ tab: value, all: null })} | |
| 969 | preventScrollReset | |
| 970 | aria-current={tab === value ? "page" : undefined} | |
| 971 | className={cn( | |
| 972 | "flex shrink-0 items-center gap-1.5 border-b-2 px-2 py-3 text-sm whitespace-nowrap transition-colors sm:px-2.5", | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 973 | tab === value ? "border-accent font-medium text-fg" : "border-transparent text-muted hover:text-fg", |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 974 | )} |
| 975 | > | |
| 976 | <span className="sm:hidden">{TAB_SHORT[value]}</span> | |
| 977 | <span className="hidden sm:inline">{TAB_LABEL[value]}</span> | |
| 978 | <span | |
| 979 | className={cn( | |
| 980 | "rounded-full px-1.5 text-[0.6875rem] tabular-nums", | |
| 981 | tab === value && value === "needs" && counts.needs > 0 ? "bg-warn/15 text-warn" : "bg-line text-muted", | |
| 982 | )} | |
| 983 | > | |
| 984 | {counts[value]} | |
| 985 | </span> | |
| 986 | </Link> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 987 | ))} |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 988 | </nav> |
| 989 | <DropdownMenu> | |
| 990 | <DropdownMenuTrigger | |
| 991 | className="inline-flex shrink-0 items-center gap-1.5 rounded-md px-2 py-1.5 text-xs text-muted hover:bg-raised hover:text-fg" | |
| 992 | aria-label="Sort" | |
| 993 | > | |
| 994 | <ArrowDownWideNarrow size={14} /> | |
| 995 | <span className="hidden sm:inline">{SORT_LABEL[sort]}</span> | |
| 996 | </DropdownMenuTrigger> | |
| 997 | <DropdownMenuContent align="end"> | |
| 998 | {(["impact", "newest"] as const).map((value) => ( | |
| 999 | <DropdownMenuItem key={value} asChild> | |
| 1000 | <Link to={link({ sort: value === "impact" ? null : value })} preventScrollReset> | |
| 1001 | {sort === value ? <Check /> : <span className="size-4" />} | |
| 1002 | {SORT_LABEL[value]} | |
| 1003 | </Link> | |
| 1004 | </DropdownMenuItem> | |
| 1005 | ))} | |
| 1006 | </DropdownMenuContent> | |
| 1007 | </DropdownMenu> | |
| 1008 | </div> | |
| 1009 | ||
| 1010 | {!loaded.perRepoLoaded && tab !== "waiting" ? ( | |
| 1011 | <div className="p-4"> | |
| 1012 | <Unavailable what={tab === "needs" ? "Some of what needs you" : "What landed"} /> | |
| 1013 | </div> | |
| 1014 | ) : null} | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1015 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1016 | {tab === "needs" && |
| 1017 | (needs.length === 0 ? ( | |
| 1018 | noProjects ? ( | |
| 1019 | <Empty | |
| 1020 | action={ | |
| 1021 | <Link | |
| 1022 | to={workspace ? `/new?workspace=${workspace}` : "/new"} | |
| 1023 | className="rounded-md bg-fg px-3 py-1.5 text-sm font-medium text-bg hover:bg-white" | |
| 1024 | > | |
| 1025 | Create or import a project | |
| 1026 | </Link> | |
| 1027 | } | |
| 1028 | > | |
| 1029 | <p className="font-medium text-fg">No projects yet</p> | |
| 1030 | <p className="mt-1"> | |
| g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent | 1031 | 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 200ms | 1032 | </p> |
| 1033 | </Empty> | |
| 1034 | ) : ( | |
| 1035 | <> | |
| 1036 | <Empty> | |
| 1037 | <p className="font-medium text-fg">Nothing needs you</p> | |
| 1038 | <p className="mt-1"> | |
| 1039 | Agents are handling everything.{" "} | |
| 1040 | {landed.length > 0 | |
| 1041 | ? "Here's what they landed today." | |
| 1042 | : "Reviews, stopped work and failed deploys show up here first."} | |
| 1043 | </p> | |
| 1044 | </Empty> | |
| 1045 | {landed.length > 0 && ( | |
| 1046 | <div className="border-t border-line"> | |
| 1047 | <List> | |
| 1048 | {landed.slice(0, ROWS).map((row) => ( | |
| 1049 | <LandedCard key={row.key} row={row} first={false} /> | |
| 1050 | ))} | |
| 1051 | </List> | |
| 1052 | </div> | |
| 1053 | )} | |
| 1054 | </> | |
| 1055 | ) | |
| 1056 | ) : ( | |
| 1057 | <List> | |
| 1058 | {(shown as NeedRow[]).map((row, index) => ( | |
| 1059 | <NeedCard key={row.key} row={row} first={index === 0} /> | |
| 1060 | ))} | |
| 1061 | </List> | |
| 1062 | ))} | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1063 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1064 | {tab === "waiting" && |
| 1065 | (!loaded.runsLoaded && workspace ? ( | |
| 1066 | <div className="p-4"> | |
| 1067 | <Unavailable what="Agent runs" /> | |
| 1068 | </div> | |
| 1069 | ) : waiting.length === 0 ? ( | |
| 1070 | <Empty> | |
| 1071 | <p className="font-medium text-fg">No agent is at work right now</p> | |
| g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent | 1072 | <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 200ms | 1073 | </Empty> |
| 1074 | ) : ( | |
| 1075 | <List> | |
| 1076 | {(shown as WaitingRow[]).map((row, index) => ( | |
| 1077 | <WaitingCard key={row.key} row={row} first={index === 0} /> | |
| 1078 | ))} | |
| 1079 | </List> | |
| 1080 | ))} | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1081 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1082 | {tab === "landed" && |
| 1083 | loaded.perRepoLoaded && | |
| 1084 | (landed.length === 0 ? ( | |
| 1085 | <Empty> | |
| 1086 | <p className="font-medium text-fg">Nothing has landed today yet</p> | |
| 1087 | <p className="mt-1"> | |
| 1088 | {week.total > 0 | |
| 1089 | ? `${plural(week.total, "change")} landed in the last 7 days.` | |
| 1090 | : "Merged pull requests show up here the moment they land."} | |
| 1091 | </p> | |
| 1092 | </Empty> | |
| 1093 | ) : ( | |
| 1094 | <List> | |
| 1095 | {(shown as LandedRow[]).map((row, index) => ( | |
| 1096 | <LandedCard key={row.key} row={row} first={index === 0} /> | |
| 1097 | ))} | |
| 1098 | </List> | |
| 1099 | ))} | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1100 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1101 | {rows.length > ROWS && ( |
| 1102 | <div className="flex items-center justify-between border-t border-line px-4 py-2.5 text-xs text-muted sm:px-5"> | |
| 1103 | <span> | |
| 1104 | Showing {shown.length} of {rows.length} | |
| 1105 | </span> | |
| 1106 | <Link | |
| 1107 | to={link({ all: all ? null : "1" })} | |
| 1108 | preventScrollReset | |
| 1109 | className="inline-flex items-center gap-1 font-medium text-fg-soft hover:text-fg" | |
| 1110 | > | |
| 1111 | {all ? "Show fewer" : "See all"} <ChevronRight size={12} /> | |
| 1112 | </Link> | |
| 1113 | </div> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1114 | )} |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1115 | </section> |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1116 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1117 | <aside className="min-w-0 space-y-6"> |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 1118 | <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 200ms | 1119 | <section className="rounded-xl border border-line bg-surface p-5"> |
| 1120 | <div className="flex items-center justify-between"> | |
| 1121 | <h2 className="text-base font-semibold tracking-tight">This week</h2> | |
| 1122 | {workspace && ( | |
| 1123 | <Link to={`/${workspace}/-/agents`} className="text-xs text-muted hover:text-fg"> | |
| 1124 | Fleet | |
| 1125 | </Link> | |
| 1126 | )} | |
| 1127 | </div> | |
| 1128 | <div className="mt-4">{loaded.perRepoLoaded ? <WeekChart week={week} /> : <Unavailable what="The week" />}</div> | |
| 1129 | {workspace && stats.weekCost > 0 && ( | |
| 1130 | <Link | |
| 1131 | to={`/${workspace}/-/usage`} | |
| 1132 | className="mt-4 flex items-center justify-between border-t border-line pt-3 text-xs text-muted hover:text-fg" | |
| 1133 | > | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 1134 | <span>Usage at price this week: {usd(stats.weekCost)}</span> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1135 | <ChevronRight size={12} /> |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1136 | </Link> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1137 | )} |
| 1138 | </section> | |
| 1139 | ||
| Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix | 1140 | {workspace && ( |
| 1141 | <TokenUsagePanel | |
| 1142 | workspace={loaderData.tokens.workspace} | |
| 1143 | mine={loaderData.tokens.mine} | |
| 1144 | usageHref={`/${workspace}/-/usage`} | |
| 1145 | /> | |
| 1146 | )} | |
| 1147 | ||
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1148 | <section id="activity" className="scroll-mt-20 rounded-xl border border-line bg-surface p-5"> |
| 1149 | <div className="flex items-center justify-between"> | |
| 1150 | <h2 className="text-base font-semibold tracking-tight">Activity</h2> | |
| 1151 | {groups.length > 8 && ( | |
| 1152 | <Link | |
| 1153 | to={`${link({ activity: everyActivity ? null : "all" })}#activity`} | |
| 1154 | preventScrollReset | |
| 1155 | className="inline-flex items-center gap-0.5 text-xs text-muted hover:text-fg" | |
| 1156 | > | |
| 1157 | {everyActivity ? "Less" : "All activity"} <ChevronRight size={12} /> | |
| 1158 | </Link> | |
| 1159 | )} | |
| 1160 | </div> | |
| 1161 | <p className="mt-1 flex items-center gap-3 text-[0.6875rem] text-faint"> | |
| 1162 | <span className="inline-flex items-center gap-1.5"> | |
| g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent | 1163 | <Avatar name="g1t" size={12} /> agents |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1164 | </span> |
| 1165 | <span className="inline-flex items-center gap-1.5"> | |
| 1166 | <Avatar name={viewer.username} size={12} /> people | |
| 1167 | </span> | |
| 1168 | </p> | |
| 1169 | {!loaded.perRepoLoaded ? ( | |
| 1170 | <div className="mt-3"> | |
| 1171 | <Unavailable what="Activity" /> | |
| 1172 | </div> | |
| 1173 | ) : feed.length === 0 ? ( | |
| 1174 | <p className="mt-3 text-sm leading-6 text-muted"> | |
| 1175 | Nothing has moved yet. Merges, deploys, checks, reviews and what agents learn show up here. | |
| 1176 | </p> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1177 | ) : ( |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1178 | <ol className="mt-2 divide-y divide-line/70"> |
| 1179 | {feed.map((group) => ( | |
| 1180 | <FeedLine key={group.id} group={group} titles={titles} /> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1181 | ))} |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1182 | </ol> |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1183 | )} |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1184 | </section> |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1185 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1186 | <section className="rounded-xl border border-line bg-surface p-5"> |
| 1187 | <h2 className="text-sm font-semibold">Connect your own agent</h2> | |
| 1188 | <p className="mt-1.5 text-xs leading-5 text-muted"> | |
| 1189 | Add g1t to your coding agent. It signs in through your browser; there is no token to copy. | |
| 1190 | </p> | |
| 1191 | <AgentSetup className="mt-3" /> | |
| 1192 | <Link | |
| 1193 | to="https://docs.g1t.sh/guides/bring-your-own-agent/" | |
| 1194 | className="mt-3 inline-flex items-center gap-1 text-xs text-muted hover:text-fg" | |
| 1195 | > | |
| 1196 | How it works <ArrowRight size={12} /> | |
| 1197 | </Link> | |
| 1198 | </section> | |
| 1199 | </aside> | |
| 1200 | </div> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1201 | </main> |
| 1202 | ); | |
| 1203 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.