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"; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 3 | import { Link, useFetcher, useLocation, 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"; |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 31 | import { AgentSetup } from "./agent-setup"; |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 32 | import { InboxNeedsCard } from "./inbox"; |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 33 | import type { ShellData } from "./shell"; |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 34 | import { useLiveRefresh } from "./agents"; |
| 35 | 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 | 36 | import { TokenUsagePanel } from "./token-usage"; |
| Merge branch 'worktree-agent-a985c01f43538af01' | 37 | import { Avatar, SubmitButton, TimeAgo } from "./ui"; |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 38 | import { Hint } from "./ui/hint"; |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 39 | import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger } from "./ui/dropdown-menu"; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 40 | import type { Loaded } from "../lib/mission-control.server"; |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 41 | |
| 42 | /* | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 43 | * 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 look | 48 | */ |
| 49 | ||
| 50 | /** The viewer's time zone, set by the page itself, so the greeting fits their day. */ | |
| 51 | const TZ_COOKIE = "g1t_tz"; | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 52 | /** Rows a tab shows before "See all". */ |
| 53 | const ROWS = 6; | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 54 | |
| 55 | 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 | 56 | 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 | 57 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 58 | // --- Small pieces ----------------------------------------------------------------- |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 59 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 60 | const 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 step | 64 | 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 | 65 | stalled: "border-warn/35 bg-warn/10 text-warn", |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 66 | 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 | 67 | needs_review: "border-info/35 bg-info/10 text-info", |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 68 | 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 | 69 | }; |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 70 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 71 | function 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 look | 81 | ); |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 82 | } |
| 83 | ||
| 84 | /** A person or an agent, by name, wearing the mark that tells them apart. */ | |
| 85 | 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 | 86 | return ( |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 87 | <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 | ||
| 94 | function 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 | ||
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 98 | 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 | 99 | |
| 100 | function 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 step | 105 | <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 | 106 | <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 | 107 | <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 200ms | 114 | {fact.value} |
| 115 | </dd> | |
| 116 | </div> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 117 | ))} |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 118 | </dl> |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 119 | ); |
| 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 200ms | 127 | function 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 look | 131 | return ( |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 132 | <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 200ms | 139 | 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" | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 142 | ? "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 | 143 | : "border border-line-strong text-fg/90 hover:bg-raised hover:text-fg", |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 144 | done && "border-success/40 text-success", |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 145 | )} |
| 146 | > | |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 147 | {done ? <Check size={14} /> : null} |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 148 | {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 200ms | 150 | {fetcher.data?.error && <span className="basis-full text-xs text-danger">{fetcher.data.error}</span>} |
| 151 | </fetcher.Form> | |
| 152 | ); | |
| 153 | } | |
| 154 | ||
| 155 | function 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. */ | |
| 160 | function 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> | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 191 | <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 | 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 look | 200 | </span> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 201 | </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 look | 213 | </span> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 214 | </div> |
| 215 | {children} | |
| 216 | </div> | |
| 217 | </details> | |
| 218 | </li> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 219 | ); |
| 220 | } | |
| 221 | ||
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 222 | function 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 look | 224 | return ( |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 225 | <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"> | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 256 | <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 | 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"> | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 265 | Started waiting <TimeAgo at={row.at} /> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 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" | |
| You choose how g1t looks: Auto follows your system, Light and Dark stay put, set from the account menu, the phone's You and help sheet, Settings → Account or the command palette; the page is drawn in your choice from the first byte, every colour has a light value that reads at 4.5:1 or better, code and diffs are highlighted for both, diagrams and the document editor follow along, the product tour and the g1t mark's avatar stay dark, and Dark is what you see until you choose; the workspaces guide says how. | 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-fg-hover" |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 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 look | 279 | <Link |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 280 | 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 look | 282 | > |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 283 | {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 look | 289 | </Link> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 290 | )} |
| 291 | </div> | |
| 292 | </Row> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 293 | ); |
| 294 | } | |
| 295 | ||
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 296 | function WaitingCard({ row, first }: { row: WaitingRow; first: boolean }) { |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 297 | return ( |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 298 | <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"> | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 311 | {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 | 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 look | 336 | </Link> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 337 | {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 look | 341 | )} |
| 342 | </div> | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 343 | </Row> |
| 344 | ); | |
| 345 | } | |
| 346 | ||
| 347 | function 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 ? ( | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 362 | <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 | 363 | ) : ( |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 364 | <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 look | 377 | </p> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 378 | </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 look | 394 | </div> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 395 | </Row> |
| 396 | ); | |
| 397 | } | |
| 398 | ||
| 399 | function List({ children }: { children: ReactNode }) { | |
| 400 | return <ul className="divide-y divide-line">{children}</ul>; | |
| 401 | } | |
| 402 | ||
| 403 | function 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 | ||
| 414 | function Stat({ label, value, hint, dot, title }: { label: string; value: string; hint: ReactNode; dot?: string; title?: string }) { | |
| 415 | return ( | |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 416 | <Hint label={title}> |
| 417 | <div className="min-w-0 bg-surface px-4 py-3.5 sm:px-5 sm:py-4"> | |
| 418 | <p className="flex items-center gap-1.5 truncate text-xs text-muted"> | |
| 419 | {dot && <span className={cn("size-1.5 shrink-0 rounded-full", dot)} />} | |
| 420 | {label} | |
| 421 | </p> | |
| 422 | <p className="mt-1 font-display text-2xl font-semibold tracking-tight tabular-nums sm:text-[1.75rem]">{value}</p> | |
| 423 | <p className="mt-0.5 truncate text-xs text-faint">{hint}</p> | |
| 424 | </div> | |
| 425 | </Hint> | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 426 | ); |
| 427 | } | |
| 428 | ||
| 429 | // --- This week ---------------------------------------------------------------------- | |
| 430 | ||
| 431 | /** Seven days of landed changes, each split into what agents landed alone and what a person merged. */ | |
| 432 | 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 | 433 | 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 | 434 | const height = 112; |
| 435 | const delta = change(week.total, week.previous); | |
| 436 | return ( | |
| 437 | <div> | |
| 438 | <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 | 439 | <div> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 440 | <p className="font-display text-3xl font-semibold tracking-tight tabular-nums">{week.total.toLocaleString("en-US")}</p> |
| 441 | <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 | 442 | </div> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 443 | {delta != null ? ( |
| 444 | <span | |
| 445 | className={cn( | |
| 446 | "rounded-full px-2 py-0.5 text-xs font-medium tabular-nums ring-1", | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 447 | 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 | 448 | )} |
| 449 | > | |
| 450 | {signedPercent(delta)} vs last week | |
| 451 | </span> | |
| 452 | ) : week.previous === 0 && week.total > 0 ? ( | |
| 453 | <span className="text-xs text-faint">none the week before</span> | |
| 454 | ) : null} | |
| 455 | </div> | |
| 456 | {week.total === 0 ? ( | |
| 457 | <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 | 458 | 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 | 459 | </p> |
| 460 | ) : ( | |
| 461 | <div className="relative mt-5" style={{ height: height + 20 }}> | |
| 462 | <div className="absolute inset-x-0 border-t border-line" style={{ top: height }} /> | |
| 463 | <div className="absolute inset-x-0 top-0 flex items-end justify-between gap-1" style={{ height }}> | |
| 464 | {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 | 465 | const total = day.agents + day.assisted + day.people; |
| 466 | const bar = (n: number) => Math.max(3, Math.round((n / max) * (height - 4))); | |
| 467 | // Top to bottom: agents alone, agents with a person, people. | |
| 468 | const segments = [ | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 469 | { 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 | 470 | { n: day.assisted, tone: "bg-warn" }, |
| 471 | { n: day.people, tone: "bg-info" }, | |
| 472 | ].filter((segment) => segment.n > 0); | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 473 | const today = index === week.days.length - 1; |
| 474 | return ( | |
| 475 | <div key={day.key} className="group relative flex h-full flex-1 flex-col items-center justify-end"> | |
| 476 | {total > 0 && ( | |
| 477 | <span | |
| 478 | className={cn( | |
| 479 | "mb-1 text-[0.625rem] tabular-nums", | |
| Phones reach every page: each mode's menu from the top bar or by tapping the tab you're on, Docs first in the avatar sheet, a project's pages as a swipeable strip; dialogs as bottom sheets above the keyboard, safe areas, menus kept on screen, 40px targets, touch-visible controls, save bars above the tab bar | 480 | today ? "text-fg-soft" : "text-faint opacity-0 group-hover:opacity-100 max-md:hidden", |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 481 | )} |
| 482 | > | |
| 483 | {total} | |
| 484 | </span> | |
| 485 | )} | |
| 486 | <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 | 487 | {segments.map((segment, at) => ( |
| 488 | <span key={segment.tone} className={cn("block", segment.tone, at === 0 && "rounded-t")} style={{ height: bar(segment.n) }} /> | |
| 489 | ))} | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 490 | {total === 0 && <span className="block h-[2px] rounded-full bg-line-strong" />} |
| 491 | </div> | |
| Phones reach every page: each mode's menu from the top bar or by tapping the tab you're on, Docs first in the avatar sheet, a project's pages as a swipeable strip; dialogs as bottom sheets above the keyboard, safe areas, menus kept on screen, 40px targets, touch-visible controls, save bars above the tab bar | 492 | <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 md:group-hover:block"> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 493 | <p className="font-medium text-fg">{day.label}</p> |
| 494 | <p className="mt-0.5 flex items-center gap-1.5 text-muted"> | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 495 | <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 | 496 | </p> |
| 497 | <p className="flex items-center gap-1.5 text-muted"> | |
| 498 | <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 | 499 | </p> |
| 500 | <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 | 501 | <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 | 502 | </p> |
| 503 | </div> | |
| 504 | </div> | |
| 505 | ); | |
| 506 | })} | |
| 507 | </div> | |
| 508 | <div className="absolute inset-x-0 bottom-0 flex justify-between gap-1"> | |
| 509 | {week.days.map((day, index) => ( | |
| 510 | <span | |
| 511 | key={day.key} | |
| 512 | className={cn( | |
| 513 | "flex-1 text-center text-[0.6875rem]", | |
| 514 | index === week.days.length - 1 ? "font-medium text-fg-soft" : "text-faint", | |
| 515 | )} | |
| 516 | > | |
| 517 | {day.label} | |
| 518 | </span> | |
| 519 | ))} | |
| 520 | </div> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 521 | </div> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 522 | )} |
| 523 | <div className={cn("mt-3 flex-wrap gap-x-4 gap-y-1 text-xs text-muted", week.total === 0 ? "hidden" : "flex")}> | |
| 524 | <span className="inline-flex items-center gap-1.5"> | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 525 | <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 | 526 | </span> |
| 527 | <span className="inline-flex items-center gap-1.5"> | |
| 528 | <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 | 529 | </span> |
| 530 | <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 | 531 | <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 | 532 | </span> |
| 533 | </div> | |
| Fixes from the page sweep | 534 | {/* A table does not shrink to sr-only's 1px; its wrapper does. */} |
| 535 | <div className="sr-only"> | |
| 536 | <table> | |
| 537 | <caption>Changes landed each day</caption> | |
| 538 | <thead> | |
| 539 | <tr> | |
| 540 | <th>Day</th> | |
| 541 | <th>Agents, on their own</th> | |
| 542 | <th>Agents, merged by a person</th> | |
| 543 | <th>People</th> | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 544 | </tr> |
| Fixes from the page sweep | 545 | </thead> |
| 546 | <tbody> | |
| 547 | {week.days.map((day) => ( | |
| 548 | <tr key={day.key}> | |
| 549 | <td>{day.key}</td> | |
| 550 | <td>{day.agents}</td> | |
| 551 | <td>{day.assisted}</td> | |
| 552 | <td>{day.people}</td> | |
| 553 | </tr> | |
| 554 | ))} | |
| 555 | </tbody> | |
| 556 | </table> | |
| 557 | </div> | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 558 | </div> |
| 559 | ); | |
| 560 | } | |
| 561 | ||
| 562 | // --- Activity ----------------------------------------------------------------------- | |
| 563 | ||
| 564 | const VERB: Record<Verb, string> = { | |
| 565 | landed: "landed", | |
| 566 | opened_issue: "opened", | |
| 567 | closed_issue: "closed", | |
| 568 | started: "started on", | |
| 569 | ready: "marked ready", | |
| 570 | checks_passed: "checks passed on", | |
| 571 | checks_failed: "checks failed on", | |
| 572 | approved: "approved", | |
| 573 | changes_requested: "asked for changes on", | |
| 574 | commented: "commented on", | |
| 575 | asked: "was asked about", | |
| 576 | deployed: "deployed production", | |
| 577 | deploy_failed: "production build failed", | |
| Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md | 578 | pushed: "pushed to the default branch", |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 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} | |
| You choose how g1t looks: Auto follows your system, Light and Dark stay put, set from the account menu, the phone's You and help sheet, Settings → Account or the command palette; the page is drawn in your choice from the first byte, every colour has a light value that reads at 4.5:1 or better, code and diffs are highlighted for both, diagrams and the document editor follow along, the product tour and the g1t mark's avatar stay dark, and Dark is what you see until you choose; the workspaces guide says how. | 657 | className="shrink-0 rounded-md bg-fg px-2.5 py-1 text-xs font-medium text-bg transition-colors hover:bg-fg-hover" |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 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 | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 675 | export default function MissionControl({ |
| 676 | loaderData, | |
| 677 | delegated = null, | |
| 678 | variant = "code", | |
| 679 | }: { | |
| 680 | loaderData: Loaded; | |
| 681 | delegated?: ComposerResult; | |
| 682 | /** Code's Overview: its own title in place of the greeting, and the actions on their own (Home has the Agent box). */ | |
| 683 | variant?: "code"; | |
| 684 | }) { | |
| 685 | const { pathname } = useLocation(); | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 686 | const shell = useRouteLoaderData("root")?.shell as ShellData | null | undefined; |
| 687 | const loaded: Loaded = loaderData; | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 688 | const [params] = useSearchParams(); |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 689 | // 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 | 690 | useLiveRefresh(loaded.changing); |
| 691 | const [greeting, setGreeting] = useState(loaded.greeting); | |
| 692 | const [date, setDate] = useState(loaded.date); | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 693 | useEffect(() => { |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 694 | // The greeting and date follow the viewer's own clock, and the server |
| 695 | // learns their zone for next time. | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 696 | setGreeting(greetingFor(new Date().getHours())); |
| 697 | try { | |
| 698 | const zone = Intl.DateTimeFormat().resolvedOptions().timeZone; | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 699 | setDate(dateLine(Date.now(), zone ?? null)); |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 700 | if (zone) document.cookie = `${TZ_COOKIE}=${encodeURIComponent(zone)}; Path=/; Max-Age=31536000; SameSite=Lax`; |
| 701 | } catch { | |
| 702 | // Nothing to remember. | |
| 703 | } | |
| 704 | }, []); | |
| 705 | ||
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 706 | const { viewer, repos, waiting, landed, liveTotal, week, groups, titles, stats, canRunAgents, trial } = loaded; |
| 707 | 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 | 708 | |
| 709 | // 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 | 710 | const limit = shell?.limit && !shell.limit.comped && shell.limit.state !== "ok" && workspace ? shell.limit : null; |
| 711 | const limitRow: NeedRow[] = limit | |
| 712 | ? [ | |
| 713 | (() => { | |
| 714 | const detail = | |
| 715 | limit.state === "stopped" | |
| 716 | ? "No new agent starts until the limit is raised or a card is added." | |
| 717 | : `${dollars(limit.exposureMicros)} of ${dollars(limit.ceilingMicros ?? 0)} used. Add a card so work keeps going.`; | |
| 718 | return { | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 719 | key: "limit", |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 720 | reason: "blocking" as const, |
| 721 | repo: null, | |
| 722 | ref: null, | |
| 723 | title: limit.state === "stopped" ? "Agents are stopped: the usage limit was reached" : "Usage is nearing its limit", | |
| 724 | ask: detail, | |
| 725 | by: null, | |
| 726 | for: null, | |
| 727 | at: Date.now(), | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 728 | to: `/${workspace}/-/billing`, |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 729 | open: `/${workspace}/-/billing`, |
| 730 | facts: [ | |
| 731 | { label: "Used", value: `${dollars(limit.exposureMicros)} of ${dollars(limit.ceilingMicros ?? 0)}`, tone: "warn" as const }, | |
| 732 | ], | |
| 733 | why: whyFor("blocking", { kind: "limit", detail }), | |
| 734 | quick: null, | |
| 735 | link: null, | |
| 736 | }; | |
| 737 | })(), | |
| 738 | ] | |
| 739 | : []; | |
| 740 | const needs = [...limitRow, ...loaded.needs]; | |
| 741 | 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 | 742 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 743 | const counts: Record<Tab, number> = { needs: needs.length, waiting: waiting.length, landed: landed.length }; |
| 744 | const tab = parseTab(params.get("tab")) ?? "needs"; | |
| 745 | const sort = parseSort(params.get("sort")); | |
| 746 | const all = params.get("all") === "1"; | |
| 747 | const everyActivity = params.get("activity") === "all"; | |
| 748 | const link = (changes: Record<string, string | null>) => { | |
| 749 | const next = new URLSearchParams(params); | |
| 750 | for (const [key, value] of Object.entries(changes)) { | |
| 751 | if (value == null) next.delete(key); | |
| 752 | else next.set(key, value); | |
| 753 | } | |
| 754 | const text = next.toString(); | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 755 | return text ? `?${text}` : pathname; |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 756 | }; |
| 757 | ||
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 758 | const steps: Step[] = [ |
| 759 | { | |
| 760 | done: Boolean(workspace), | |
| 761 | title: "Create a workspace", | |
| 762 | about: "Projects, people and agent credit live in one.", | |
| 763 | to: "/workspaces/new", | |
| 764 | action: "Create", | |
| 765 | }, | |
| 766 | { | |
| 767 | done: repos.length > 0, | |
| 768 | title: "Add a project", | |
| 769 | about: "Create a repository, or import one by its address. Push to it with git as usual.", | |
| 770 | to: workspace ? `/new?workspace=${workspace}` : "/new", | |
| 771 | action: "Add", | |
| 772 | }, | |
| 773 | trial?.open | |
| 774 | ? { | |
| 775 | done: true, | |
| g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent | 776 | title: "Try g1t free", |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 777 | about: trial.granted |
| 778 | ? `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 | 779 | : `This workspace gets ${dollars(trial.limitMicros)} of trial credit, for g1t's models and sandboxes, the first time g1t works here.`, |
| Integrations are a directory of connectors, for a workspace and for each person | 780 | to: workspace ? `/${workspace}/-/integrations/models` : null, |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 781 | action: "Connect", |
| 782 | } | |
| 783 | : canRunAgents | |
| 784 | ? { | |
| 785 | done: Boolean(shell?.free) || shell?.limit == null || shell.limit.comped || shell.limit.state === "ok", | |
| 786 | title: "Keep work running", | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 787 | about: |
| 788 | "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 | 789 | to: workspace ? `/${workspace}/-/billing` : null, |
| 790 | action: "Billing", | |
| 791 | } | |
| 792 | : { | |
| 793 | done: false, | |
| 794 | title: "Connect a model", | |
| 795 | about: `${ | |
| 796 | trialClosed(trial, workspace ?? "This workspace") ? `${trialClosed(trial, workspace ?? "This workspace")} ` : "" | |
| 797 | }Agents need a model to think with. Connect your Anthropic or OpenAI key, or any compatible endpoint.`, | |
| Integrations are a directory of connectors, for a workspace and for each person | 798 | to: workspace ? `/${workspace}/-/integrations/models` : null, |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 799 | action: "Connect", |
| 800 | }, | |
| 801 | { | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 802 | done: loaded.handedOff, |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 803 | title: "Hand off an outcome", |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 804 | about: |
| g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent | 805 | "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 | 806 | 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 | 807 | action: "Write one", |
| 808 | }, | |
| 809 | ]; | |
| 810 | 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 | 811 | const noProjects = repos.length === 0; |
| 812 | ||
| 813 | const rows = tab === "needs" ? sortRows(needs, sort) : tab === "waiting" ? sortRows(waiting, sort) : sortRows(landed, sort); | |
| 814 | 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 | 815 | // Of the agents' own changes only: people's work is not theirs to land. |
| 816 | 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 | 817 | const delta = change(week.total, week.previous); |
| 818 | const feed = everyActivity ? groups : groups.slice(0, 8); | |
| 819 | ||
| Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step | 820 | // "Put an agent on it" first, with "New issue" beside it as before: one |
| 821 | // 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 | 822 | const newIssue = |
| 823 | repos.length > 0 ? ( | |
| Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step | 824 | <div className="flex items-stretch"> |
| 825 | <AgentComposer | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 826 | action={pathname} |
| Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step | 827 | repos={repos} |
| 828 | open={params.get("agent") === "new" || delegated != null} | |
| 829 | result={delegated} | |
| 830 | note={ | |
| 831 | canRunAgents ? null : ( | |
| 832 | <> | |
| 833 | Agents need a model first.{" "} | |
| 834 | {workspace && ( | |
| Integrations are a directory of connectors, for a workspace and for each person | 835 | <Link to={`/${workspace}/-/integrations/models`} className="font-medium text-fg hover:underline"> |
| Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step | 836 | Connect one |
| 837 | </Link> | |
| 838 | )} | |
| 839 | . The issue still opens. | |
| 840 | </> | |
| 841 | ) | |
| 842 | } | |
| 843 | > | |
| 844 | <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' | 845 | <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 | 846 | </span> |
| 847 | </AgentComposer> | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 848 | <DropdownMenu> |
| Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step | 849 | <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 | 850 | <Plus size={14} /> New issue |
| 851 | </DropdownMenuTrigger> | |
| 852 | <DropdownMenuContent align="end" className="max-h-80 overflow-y-auto"> | |
| 853 | <DropdownMenuLabel>In which project?</DropdownMenuLabel> | |
| 854 | {repos.map((repo) => ( | |
| 855 | <DropdownMenuItem key={repo.name} asChild> | |
| 856 | <Link to={`/${repo.namespace}/${repo.name}/issues/new`}> | |
| 857 | <Avatar name={repo.name} size={18} square /> | |
| 858 | {repo.name} | |
| 859 | </Link> | |
| 860 | </DropdownMenuItem> | |
| 861 | ))} | |
| 862 | </DropdownMenuContent> | |
| 863 | </DropdownMenu> | |
| Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step | 864 | </div> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 865 | ) : ( |
| 866 | <Link | |
| 867 | to={workspace ? `/new?workspace=${workspace}` : "/new"} | |
| 868 | 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" | |
| 869 | > | |
| 870 | <Plus size={14} /> New project | |
| 871 | </Link> | |
| 872 | ); | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 873 | |
| 874 | return ( | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 875 | <main className="mx-auto max-w-7xl space-y-6 px-4 py-8 sm:px-6 sm:py-10"> |
| 876 | <header className="flex flex-wrap items-end justify-between gap-x-6 gap-y-4"> | |
| 877 | <div className="min-w-0"> | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 878 | <h1 className="text-2xl leading-tight font-semibold tracking-tight" suppressHydrationWarning> |
| 879 | {variant === "code" ? "Overview" : `${greeting}, ${loaded.name}`} | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 880 | </h1> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 881 | <p className="mt-1.5 text-sm text-muted" suppressHydrationWarning> |
| 882 | <span className="text-fg-soft">{date}</span> · {loaded.summary} | |
| 883 | </p> | |
| 884 | </div> | |
| 885 | </header> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 886 | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 887 | {/* The ways to start work. The Agent box itself is on Home. */} |
| 888 | <section aria-label="Start work" className="flex flex-wrap items-center gap-2"> | |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 889 | {newIssue} |
| 890 | {repos.length > 0 && ( | |
| 891 | <DropdownMenu> | |
| 892 | <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"> | |
| 893 | <ListTree size={14} /> Plan work | |
| 894 | </DropdownMenuTrigger> | |
| 895 | <DropdownMenuContent align="start" className="max-h-80 overflow-y-auto"> | |
| 896 | <DropdownMenuLabel>Plan in which project?</DropdownMenuLabel> | |
| 897 | {repos.map((repo) => ( | |
| 898 | <DropdownMenuItem key={repo.name} asChild> | |
| 899 | <Link to={`/${repo.namespace}/${repo.name}/plans`}> | |
| 900 | <Avatar name={repo.name} size={18} square /> | |
| 901 | {repo.name} | |
| 902 | </Link> | |
| 903 | </DropdownMenuItem> | |
| 904 | ))} | |
| 905 | </DropdownMenuContent> | |
| 906 | </DropdownMenu> | |
| 907 | )} | |
| 908 | {needs.length > 0 && ( | |
| 909 | <Link | |
| 910 | to={`${link({ tab: "needs", all: null })}#work`} | |
| 911 | preventScrollReset | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 912 | 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 | 913 | > |
| 914 | Review {needs.length} that need{needs.length === 1 ? "s" : ""} you <ArrowRight size={14} /> | |
| 915 | </Link> | |
| 916 | )} | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 917 | </section> |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 918 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 919 | {starting && <GetStarted steps={steps} />} |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 920 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 921 | <section |
| 922 | aria-label="At a glance" | |
| 923 | className="grid grid-cols-2 gap-px overflow-hidden rounded-xl border border-line bg-line lg:grid-cols-5" | |
| 924 | > | |
| 925 | <Stat | |
| 926 | label="Projects" | |
| 927 | value={stats.projects == null ? "—" : String(stats.projects)} | |
| 928 | hint={ | |
| 929 | stats.projectsThisMonth == null | |
| 930 | ? "in this workspace" | |
| 931 | : stats.projectsThisMonth > 0 | |
| 932 | ? `+${stats.projectsThisMonth} this month` | |
| 933 | : "none new this month" | |
| 934 | } | |
| 935 | /> | |
| 936 | <Stat | |
| 937 | label="Agents" | |
| 938 | value={loaded.runsLoaded || !workspace ? String(liveTotal) : "—"} | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 939 | 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 | 940 | hint={ |
| 941 | liveTotal > 0 | |
| 942 | ? `live now · ${stats.agentHours < 10 ? stats.agentHours.toFixed(1) : Math.round(stats.agentHours)}h this week` | |
| 943 | : stats.agentHours > 0 | |
| 944 | ? `none live · ${stats.agentHours < 10 ? stats.agentHours.toFixed(1) : Math.round(stats.agentHours)}h this week` | |
| 945 | : "none live now" | |
| 946 | } | |
| 947 | /> | |
| 948 | <Stat | |
| 949 | label="Changes this week" | |
| 950 | value={loaded.perRepoLoaded ? String(week.total) : "—"} | |
| 951 | hint={ | |
| 952 | delta != null ? `${signedPercent(delta)} vs last week` : week.previous === 0 ? "none the week before" : "merged pull requests" | |
| 953 | } | |
| 954 | /> | |
| 955 | <Stat | |
| 956 | label="Landed without you" | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 957 | dot="bg-accent" |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 958 | 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 | 959 | hint={share == null ? "no agent changes yet" : `${week.byAgents} of ${week.agentChanges} agent changes`} |
| 960 | 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 | 961 | /> |
| 962 | <div className="col-span-2 lg:col-span-1"> | |
| 963 | <Stat | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 964 | label="Needs you" |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 965 | dot="bg-warn" |
| 966 | value={String(needs.length)} | |
| 967 | hint={blocking > 0 ? `${blocking} blocking` : needs.length > 0 ? "nothing blocking" : "all clear"} | |
| 968 | /> | |
| 969 | </div> | |
| 970 | </section> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 971 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 972 | <div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_21rem] xl:grid-cols-[minmax(0,1fr)_23rem]"> |
| 973 | <section id="work" className="min-w-0 scroll-mt-20 self-start overflow-hidden rounded-xl border border-line bg-surface"> | |
| 974 | <div className="flex items-center gap-2 border-b border-line px-2 sm:px-3"> | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 975 | <nav className="-mb-px flex min-w-0 grow gap-1 overflow-x-auto [scrollbar-width:none]" aria-label="Overview"> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 976 | {(["needs", "waiting", "landed"] as const).map((value) => ( |
| 977 | <Link | |
| 978 | key={value} | |
| 979 | to={link({ tab: value, all: null })} | |
| 980 | preventScrollReset | |
| 981 | aria-current={tab === value ? "page" : undefined} | |
| 982 | className={cn( | |
| 983 | "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' | 984 | 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 | 985 | )} |
| 986 | > | |
| 987 | <span className="sm:hidden">{TAB_SHORT[value]}</span> | |
| 988 | <span className="hidden sm:inline">{TAB_LABEL[value]}</span> | |
| 989 | <span | |
| 990 | className={cn( | |
| 991 | "rounded-full px-1.5 text-[0.6875rem] tabular-nums", | |
| 992 | tab === value && value === "needs" && counts.needs > 0 ? "bg-warn/15 text-warn" : "bg-line text-muted", | |
| 993 | )} | |
| 994 | > | |
| 995 | {counts[value]} | |
| 996 | </span> | |
| 997 | </Link> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 998 | ))} |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 999 | </nav> |
| 1000 | <DropdownMenu> | |
| 1001 | <DropdownMenuTrigger | |
| 1002 | 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" | |
| 1003 | aria-label="Sort" | |
| 1004 | > | |
| 1005 | <ArrowDownWideNarrow size={14} /> | |
| 1006 | <span className="hidden sm:inline">{SORT_LABEL[sort]}</span> | |
| 1007 | </DropdownMenuTrigger> | |
| 1008 | <DropdownMenuContent align="end"> | |
| 1009 | {(["impact", "newest"] as const).map((value) => ( | |
| 1010 | <DropdownMenuItem key={value} asChild> | |
| 1011 | <Link to={link({ sort: value === "impact" ? null : value })} preventScrollReset> | |
| 1012 | {sort === value ? <Check /> : <span className="size-4" />} | |
| 1013 | {SORT_LABEL[value]} | |
| 1014 | </Link> | |
| 1015 | </DropdownMenuItem> | |
| 1016 | ))} | |
| 1017 | </DropdownMenuContent> | |
| 1018 | </DropdownMenu> | |
| 1019 | </div> | |
| 1020 | ||
| 1021 | {!loaded.perRepoLoaded && tab !== "waiting" ? ( | |
| 1022 | <div className="p-4"> | |
| 1023 | <Unavailable what={tab === "needs" ? "Some of what needs you" : "What landed"} /> | |
| 1024 | </div> | |
| 1025 | ) : null} | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1026 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1027 | {tab === "needs" && |
| 1028 | (needs.length === 0 ? ( | |
| 1029 | noProjects ? ( | |
| 1030 | <Empty | |
| 1031 | action={ | |
| 1032 | <Link | |
| 1033 | to={workspace ? `/new?workspace=${workspace}` : "/new"} | |
| You choose how g1t looks: Auto follows your system, Light and Dark stay put, set from the account menu, the phone's You and help sheet, Settings → Account or the command palette; the page is drawn in your choice from the first byte, every colour has a light value that reads at 4.5:1 or better, code and diffs are highlighted for both, diagrams and the document editor follow along, the product tour and the g1t mark's avatar stay dark, and Dark is what you see until you choose; the workspaces guide says how. | 1034 | className="rounded-md bg-fg px-3 py-1.5 text-sm font-medium text-bg hover:bg-fg-hover" |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1035 | > |
| 1036 | Create or import a project | |
| 1037 | </Link> | |
| 1038 | } | |
| 1039 | > | |
| 1040 | <p className="font-medium text-fg">No projects yet</p> | |
| 1041 | <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 | 1042 | 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 | 1043 | </p> |
| 1044 | </Empty> | |
| 1045 | ) : ( | |
| 1046 | <> | |
| 1047 | <Empty> | |
| 1048 | <p className="font-medium text-fg">Nothing needs you</p> | |
| 1049 | <p className="mt-1"> | |
| 1050 | Agents are handling everything.{" "} | |
| 1051 | {landed.length > 0 | |
| 1052 | ? "Here's what they landed today." | |
| 1053 | : "Reviews, stopped work and failed deploys show up here first."} | |
| 1054 | </p> | |
| 1055 | </Empty> | |
| 1056 | {landed.length > 0 && ( | |
| 1057 | <div className="border-t border-line"> | |
| 1058 | <List> | |
| 1059 | {landed.slice(0, ROWS).map((row) => ( | |
| 1060 | <LandedCard key={row.key} row={row} first={false} /> | |
| 1061 | ))} | |
| 1062 | </List> | |
| 1063 | </div> | |
| 1064 | )} | |
| 1065 | </> | |
| 1066 | ) | |
| 1067 | ) : ( | |
| 1068 | <List> | |
| 1069 | {(shown as NeedRow[]).map((row, index) => ( | |
| 1070 | <NeedCard key={row.key} row={row} first={index === 0} /> | |
| 1071 | ))} | |
| 1072 | </List> | |
| 1073 | ))} | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1074 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1075 | {tab === "waiting" && |
| 1076 | (!loaded.runsLoaded && workspace ? ( | |
| 1077 | <div className="p-4"> | |
| 1078 | <Unavailable what="Agent runs" /> | |
| 1079 | </div> | |
| 1080 | ) : waiting.length === 0 ? ( | |
| 1081 | <Empty> | |
| 1082 | <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 | 1083 | <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 | 1084 | </Empty> |
| 1085 | ) : ( | |
| 1086 | <List> | |
| 1087 | {(shown as WaitingRow[]).map((row, index) => ( | |
| 1088 | <WaitingCard key={row.key} row={row} first={index === 0} /> | |
| 1089 | ))} | |
| 1090 | </List> | |
| 1091 | ))} | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1092 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1093 | {tab === "landed" && |
| 1094 | loaded.perRepoLoaded && | |
| 1095 | (landed.length === 0 ? ( | |
| 1096 | <Empty> | |
| 1097 | <p className="font-medium text-fg">Nothing has landed today yet</p> | |
| 1098 | <p className="mt-1"> | |
| 1099 | {week.total > 0 | |
| 1100 | ? `${plural(week.total, "change")} landed in the last 7 days.` | |
| 1101 | : "Merged pull requests show up here the moment they land."} | |
| 1102 | </p> | |
| 1103 | </Empty> | |
| 1104 | ) : ( | |
| 1105 | <List> | |
| 1106 | {(shown as LandedRow[]).map((row, index) => ( | |
| 1107 | <LandedCard key={row.key} row={row} first={index === 0} /> | |
| 1108 | ))} | |
| 1109 | </List> | |
| 1110 | ))} | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1111 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1112 | {rows.length > ROWS && ( |
| 1113 | <div className="flex items-center justify-between border-t border-line px-4 py-2.5 text-xs text-muted sm:px-5"> | |
| 1114 | <span> | |
| 1115 | Showing {shown.length} of {rows.length} | |
| 1116 | </span> | |
| 1117 | <Link | |
| 1118 | to={link({ all: all ? null : "1" })} | |
| 1119 | preventScrollReset | |
| 1120 | className="inline-flex items-center gap-1 font-medium text-fg-soft hover:text-fg" | |
| 1121 | > | |
| 1122 | {all ? "Show fewer" : "See all"} <ChevronRight size={12} /> | |
| 1123 | </Link> | |
| 1124 | </div> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1125 | )} |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1126 | </section> |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1127 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1128 | <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 | 1129 | <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 | 1130 | <section className="rounded-xl border border-line bg-surface p-5"> |
| 1131 | <div className="flex items-center justify-between"> | |
| 1132 | <h2 className="text-base font-semibold tracking-tight">This week</h2> | |
| 1133 | {workspace && ( | |
| 1134 | <Link to={`/${workspace}/-/agents`} className="text-xs text-muted hover:text-fg"> | |
| 1135 | Fleet | |
| 1136 | </Link> | |
| 1137 | )} | |
| 1138 | </div> | |
| 1139 | <div className="mt-4">{loaded.perRepoLoaded ? <WeekChart week={week} /> : <Unavailable what="The week" />}</div> | |
| 1140 | {workspace && stats.weekCost > 0 && ( | |
| 1141 | <Link | |
| 1142 | to={`/${workspace}/-/usage`} | |
| 1143 | className="mt-4 flex items-center justify-between border-t border-line pt-3 text-xs text-muted hover:text-fg" | |
| 1144 | > | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 1145 | <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 | 1146 | <ChevronRight size={12} /> |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1147 | </Link> |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1148 | )} |
| 1149 | </section> | |
| 1150 | ||
| Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix | 1151 | {workspace && ( |
| 1152 | <TokenUsagePanel | |
| 1153 | workspace={loaderData.tokens.workspace} | |
| 1154 | mine={loaderData.tokens.mine} | |
| 1155 | usageHref={`/${workspace}/-/usage`} | |
| 1156 | /> | |
| 1157 | )} | |
| 1158 | ||
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1159 | <section id="activity" className="scroll-mt-20 rounded-xl border border-line bg-surface p-5"> |
| 1160 | <div className="flex items-center justify-between"> | |
| 1161 | <h2 className="text-base font-semibold tracking-tight">Activity</h2> | |
| 1162 | {groups.length > 8 && ( | |
| 1163 | <Link | |
| 1164 | to={`${link({ activity: everyActivity ? null : "all" })}#activity`} | |
| 1165 | preventScrollReset | |
| 1166 | className="inline-flex items-center gap-0.5 text-xs text-muted hover:text-fg" | |
| 1167 | > | |
| 1168 | {everyActivity ? "Less" : "All activity"} <ChevronRight size={12} /> | |
| 1169 | </Link> | |
| 1170 | )} | |
| 1171 | </div> | |
| 1172 | <p className="mt-1 flex items-center gap-3 text-[0.6875rem] text-faint"> | |
| 1173 | <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 | 1174 | <Avatar name="g1t" size={12} /> agents |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1175 | </span> |
| 1176 | <span className="inline-flex items-center gap-1.5"> | |
| 1177 | <Avatar name={viewer.username} size={12} /> people | |
| 1178 | </span> | |
| 1179 | </p> | |
| 1180 | {!loaded.perRepoLoaded ? ( | |
| 1181 | <div className="mt-3"> | |
| 1182 | <Unavailable what="Activity" /> | |
| 1183 | </div> | |
| 1184 | ) : feed.length === 0 ? ( | |
| 1185 | <p className="mt-3 text-sm leading-6 text-muted"> | |
| 1186 | Nothing has moved yet. Merges, deploys, checks, reviews and what agents learn show up here. | |
| 1187 | </p> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1188 | ) : ( |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1189 | <ol className="mt-2 divide-y divide-line/70"> |
| 1190 | {feed.map((group) => ( | |
| 1191 | <FeedLine key={group.id} group={group} titles={titles} /> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1192 | ))} |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1193 | </ol> |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1194 | )} |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1195 | </section> |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1196 | |
| Mission control shows where you are needed and what agents landed without you; git answers in about 200ms | 1197 | <section className="rounded-xl border border-line bg-surface p-5"> |
| 1198 | <h2 className="text-sm font-semibold">Connect your own agent</h2> | |
| 1199 | <p className="mt-1.5 text-xs leading-5 text-muted"> | |
| 1200 | Add g1t to your coding agent. It signs in through your browser; there is no token to copy. | |
| 1201 | </p> | |
| 1202 | <AgentSetup className="mt-3" /> | |
| 1203 | <Link | |
| 1204 | to="https://docs.g1t.sh/guides/bring-your-own-agent/" | |
| 1205 | className="mt-3 inline-flex items-center gap-1 text-xs text-muted hover:text-fg" | |
| 1206 | > | |
| 1207 | How it works <ArrowRight size={12} /> | |
| 1208 | </Link> | |
| 1209 | </section> | |
| 1210 | </aside> | |
| 1211 | </div> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1212 | </main> |
| 1213 | ); | |
| 1214 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.