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