Skip to content
1,935 linesCodeBlameRaw

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.

Mirroring on the site and in the docs1import { Activity, ArrowLeftRight, BarChart3, Bell, BookMarked, BookOpen, Bot, Box, Brain, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronsUpDown, CircleDot, GripVertical, CircleUserRound, Code2, Compass, CreditCard, Fingerprint, GanttChart, Gauge, GitBranch, GitPullRequest, Globe, History, House, Inbox, KanbanSquare, KeyRound, Layers, LayoutDashboard, LayoutGrid, LifeBuoy, ListTree, Lock, LogIn, LogOut, Mail, Menu, Network, Package, PlayCircle, Plug, Plus, Rocket, Search, ServerCog, Settings, ShieldCheck, Scale, Sparkles, Ticket, TrendingUp, UserRoundKey, Users, UsersRound, Webhook, X } from "lucide-react";
Agents as a team: lifecycle, merge queue, billing and a new shell2import { type ReactNode, useEffect, useMemo, useRef, useState } from "react";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar3import { Link, NavLink, useFetcher, useLocation, useNavigation, useRouteLoaderData, useSubmit } from "react-router";
Agents as a team: lifecycle, merge queue, billing and a new shell4
Merge branch 'worktree-agent-ad7c6d88d93adc817'5import { type Abilities, type InboxCounts, type Membership, type Spike, type User, mayCreateTeams } from "@g1t/contracts";
Agents as a team: lifecycle, merge queue, billing and a new shell6
Usage, Billing settings and prepaid AI credit; fixes from the UX audit7import { CommandPalette, type PaletteCommand, PaletteKey, usePaletteShortcut } from "./command-palette";
Previews build from any branch; Agent in place of Ask AI; pin from the sidebar8import { AgentButton, InboxBell } from "./inbox";
9import { PinButton } from "./pin-button";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar10import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
Merge branch 'worktree-agent-ae1299e92e4462012'11import { Hint } from "./ui/hint";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit12import { Sheet, SheetContent, SheetTitle, SheetTrigger } from "./ui/sheet";
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas13import { StatusDot, useSiteStatus } from "./footer";
The 1 beside the workspace switcher again, in a square hover the switcher's height14import { Logo, Mark } from "./logo";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit15import { Avatar, SoonPill } from "./ui";
Fast pages, required checks on the branch, self-hosted runners, honest incidents16import { Skeleton } from "./ui/skeleton";
Agents as a team: lifecycle, merge queue, billing and a new shell17import {
18 DropdownMenu,
19 DropdownMenuContent,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas20 DropdownMenuGroup,
Agents as a team: lifecycle, merge queue, billing and a new shell21 DropdownMenuItem,
22 DropdownMenuLabel,
23 DropdownMenuSeparator,
24 DropdownMenuTrigger,
25} from "./ui/dropdown-menu";
Merge branch 'worktree-agent-ad7c6d88d93adc817'26import { type RoadmapItem, roadmapIn, roadmapItem } from "../lib/roadmap";
27import { SETTINGS_PAGES, sidebarCurrent } from "../lib/workspace-nav";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look28import { SETTINGS_CAPABILITY, type ViewerAccess, seesSettings } from "../lib/access";
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put29import { VISITOR_LINKS, projectPages } from "../lib/chrome";
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas30import { ACCOUNT_SETTINGS, type AccountSettingsPage, FIRST_SETTINGS_PAGE, accountSettingsPage } from "../lib/account-settings";
31import { GithubMark } from "./github";
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put32import { withNext } from "../lib/next";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look33import { useSignUpCopy } from "../lib/registration";
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas34import { STATUS_URL, statusTitle } from "../lib/status";
Navigation: the workspace's own page with tabs, pins and recent in the sidebar35import { type ShortcutProject, movedPin, recentWith } from "../lib/pins";
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas36import type { AccountMenuData } from "../routes/settings-menu-json";
Agents as a team: lifecycle, merge queue, billing and a new shell37
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put38/**
39 * What the sidebar needs, worked out by the root loader. For a visitor who
40 * is not signed in there is no workspace, no projects and no usage: only
41 * the project being looked at, if they can see it.
42 */
Agents as a team: lifecycle, merge queue, billing and a new shell43export type ShellData = {
44 /** The workspace the sidebar is about: the one being looked at, or their first. */
Workspace names and icons, and a component kit for every control45 workspace: Membership | null;
Navigation: the workspace's own page with tabs, pins and recent in the sidebar46 /** All its projects, by name, for the palette: `name` is the slug in their address. */
47 repos: ShortcutProject[];
48 /** The person's pinned projects in it, in their order (lib/pins.ts). */
49 pinned?: ShortcutProject[];
50 /** What they opened there last, latest first, leaving out the pinned. */
51 recent?: ShortcutProject[];
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look52 /** Repositories shared with them in workspaces they do not belong to. */
53 shared?: { namespace: string; name: string; isPrivate: boolean }[];
Projects: what a workspace builds and runs, first on every page54 /** The project being looked at, if any, whoever owns it. */
Agents as a team: lifecycle, merge queue, billing and a new shell55 repo: {
56 namespace: string;
57 name: string;
58 member: boolean;
59 issues: number;
60 pulls: number;
61 } | null;
Billing accounts, terms and enterprises; g1t is no longer free62 /** Where the workspace stands against its usage limit, if billing is on. */
63 limit: {
64 exposureMicros: number;
65 ceilingMicros: number | null;
66 state: "ok" | "warning" | "stopped";
67 comped: boolean;
68 } | null;
Free while g1t is being built out; agents can check out their own forks69 /** Whether g1t charges nothing for now, while it is being built out. */
70 free?: boolean;
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request71 /** What its agents have cost since the start of the month. */
Usage while free is shown at cost; agents get rustfmt and clippy72 /** This month's usage: charged, or at cost while g1t is free. */
73 monthUsageMicros: number | null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look74 /** Whether new compute is paused (a spend spike or a hold), and whether the viewer can answer it. */
75 compute?: { paused: string | null; spike: Spike | null; owner: boolean } | null;
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all76 /** What is unread in their inbox, for the bell. Absent for a visitor. */
77 inbox?: InboxCounts | null;
Agents as a team: lifecycle, merge queue, billing and a new shell78};
79
80function SidebarLink({
81 to,
82 icon,
83 end,
84 count,
85 also,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look86 drill,
Merge branch 'worktree-agent-ad7c6d88d93adc817'87 current: lit,
Agents as a team: lifecycle, merge queue, billing and a new shell88 children,
89}: {
90 to: string;
91 icon: ReactNode;
92 end?: boolean;
93 count?: number;
Projects: what a workspace builds and runs, first on every page94 /** Other path prefixes under which this link is the current one. */
95 also?: string | string[];
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look96 /** It opens a list of its own: a chevron says so, always or on hover. */
97 drill?: boolean | "hover";
Merge branch 'worktree-agent-ad7c6d88d93adc817'98 /** Whether it is the current row, when the list works that out itself. */
99 current?: boolean;
Agents as a team: lifecycle, merge queue, billing and a new shell100 children: ReactNode;
101}) {
102 const { pathname } = useLocation();
103 return (
104 <NavLink
105 to={to}
106 end={end}
107 prefetch="intent"
108 className={({ isActive, isPending }) => {
Projects: what a workspace builds and runs, first on every page109 const current =
Merge branch 'worktree-agent-ad7c6d88d93adc817'110 lit ??
111 (isActive || [also ?? []].flat().some((prefix) => pathname === prefix || pathname.startsWith(prefix + "/")));
A Vercel-like sidebar and a new mark112 return `group flex h-8 items-center gap-2.5 rounded-md px-2 text-[0.8125rem] transition-colors ${
Agents as a team: lifecycle, merge queue, billing and a new shell113 current
114 ? "bg-raised font-medium text-fg"
115 : isPending
116 ? "bg-raised/60 text-fg"
117 : "text-muted hover:bg-raised/60 hover:text-fg"
118 }`;
119 }}
120 >
121 <span className="shrink-0 text-faint group-hover:text-muted">{icon}</span>
122 <span className="min-w-0 grow truncate">{children}</span>
123 {count != null && count > 0 && (
124 <span className="rounded bg-line px-1.5 text-[0.6875rem] tabular-nums text-muted">
125 {count}
126 </span>
127 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look128 {drill && (
129 <ChevronRight
130 size={14}
131 aria-hidden="true"
132 className={`-mr-0.5 shrink-0 text-faint transition-opacity ${drill === "hover" ? "opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100" : ""}`}
133 />
134 )}
Agents as a team: lifecycle, merge queue, billing and a new shell135 </NavLink>
136 );
137}
138
Pull requests say where they are live; the project menu shows what is coming139/**
140 * Something a project will have, shown where it will live so people can
141 * see where g1t is going. Not a link: nothing is behind it yet.
142 */
143function SidebarSoon({ icon, children, about }: { icon: ReactNode; children: ReactNode; about: string }) {
144 return (
Merge branch 'worktree-agent-ae1299e92e4462012'145 <Hint label={about} side="right">
146 <div aria-disabled="true" className="flex h-8 cursor-default items-center gap-2.5 rounded-md px-2 text-[0.8125rem] text-faint">
147 <span className="shrink-0 opacity-70">{icon}</span>
148 <span className="grow truncate">{children}</span>
149 <span className="sr-only">{about}</span>
150 <SoonPill />
151 </div>
152 </Hint>
Pull requests say where they are live; the project menu shows what is coming153 );
154}
155
The project menu opens by section, and the workspace has its own panel156/**
A flat project menu with Code first, views as tabs, and Stripe for every owner157 * A page that is coming, as a link to its roadmap page: faint, with Soon,
158 * and marked current on any of its section's Soon pages.
The project menu opens by section, and the workspace has its own panel159 */
A flat project menu with Code first, views as tabs, and Stripe for every owner160function SidebarSoonLink({
The project menu opens by section, and the workspace has its own panel161 to,
162 also,
A flat project menu with Code first, views as tabs, and Stripe for every owner163 icon,
164 about,
Merge branch 'worktree-agent-ad7c6d88d93adc817'165 current: lit,
The project menu opens by section, and the workspace has its own panel166 children,
167}: {
168 to: string;
A flat project menu with Code first, views as tabs, and Stripe for every owner169 also?: string[];
170 icon: ReactNode;
171 about: string;
Merge branch 'worktree-agent-ad7c6d88d93adc817'172 /** Whether it is the current row, when the list works that out itself. */
173 current?: boolean;
The project menu opens by section, and the workspace has its own panel174 children: ReactNode;
175}) {
176 const { pathname } = useLocation();
177 return (
Sidebar: soon rows and the project row keep their styles inside a hint; tiny amounts read as zero178 // The hint wraps a plain box: Radix's asChild merges className as a
179 // string, which would break NavLink's className function.
Merge branch 'worktree-agent-ae1299e92e4462012'180 <Hint label={about} side="right">
Sidebar: soon rows and the project row keep their styles inside a hint; tiny amounts read as zero181 <div>
The project menu opens by section, and the workspace has its own panel182 <NavLink
183 to={to}
184 prefetch="intent"
A flat project menu with Code first, views as tabs, and Stripe for every owner185 className={({ isActive }) => {
Merge branch 'worktree-agent-ad7c6d88d93adc817'186 const current = lit ?? (isActive || (also ?? []).some((prefix) => pathname === prefix || pathname.startsWith(prefix + "/")));
A flat project menu with Code first, views as tabs, and Stripe for every owner187 return `group flex h-8 items-center gap-2.5 rounded-md px-2 text-[0.8125rem] transition-colors ${
188 current ? "bg-raised font-medium text-fg" : "text-faint hover:bg-raised/60 hover:text-muted"
The project menu opens by section, and the workspace has its own panel189 }`;
190 }}
191 >
A flat project menu with Code first, views as tabs, and Stripe for every owner192 <span className="shrink-0 opacity-80">{icon}</span>
The project menu opens by section, and the workspace has its own panel193 <span className="min-w-0 grow truncate">{children}</span>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit194 <SoonPill />
A menu shaped by how g1t works, Soon pages for every promise, and sudo for sales195 </NavLink>
Sidebar: soon rows and the project row keep their styles inside a hint; tiny amounts read as zero196 </div>
Merge branch 'worktree-agent-ae1299e92e4462012'197 </Hint>
The project menu opens by section, and the workspace has its own panel198 );
199}
200
A flat project menu with Code first, views as tabs, and Stripe for every owner201/** Icons for what spans projects. */
202const WORKSPACE_ICONS: Record<string, ReactNode> = {
203 board: <KanbanSquare size={15} />,
204 roadmap: <GanttChart size={15} />,
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member205 teams: <UsersRound size={15} />,
A flat project menu with Code first, views as tabs, and Stripe for every owner206 packages: <Package size={15} />,
207 fleet: <Bot size={15} />,
208};
A menu shaped by how g1t works, Soon pages for every promise, and sudo for sales209
A flat project menu with Code first, views as tabs, and Stripe for every owner210/** The Soon pages of a section, as paths, so its link is current on them. */
211function soonPaths(base: string, section: RoadmapItem["section"]): string[] {
212 return roadmapIn(section).map((item) => `${base}/soon/${item.key}`);
A menu shaped by how g1t works, Soon pages for every promise, and sudo for sales213}
214
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look215function SidebarGroup({
216 title,
217 action,
218 className = "",
219 children,
220}: {
221 title: string;
222 action?: ReactNode;
223 className?: string;
224 children: ReactNode;
225}) {
Agents as a team: lifecycle, merge queue, billing and a new shell226 return (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look227 <section className={className}>
A Vercel-like sidebar and a new mark228 <div className="mb-1 flex h-6 items-center justify-between px-2">
229 <h2 className="text-xs font-medium text-faint">{title}</h2>
Agents as a team: lifecycle, merge queue, billing and a new shell230 {action}
231 </div>
232 <div className="space-y-px">{children}</div>
233 </section>
234 );
235}
236
Workspace names and icons, and a component kit for every control237/** What a workspace is called where people read it: its display name, else its slug. */
238function displayName(membership: Membership): string {
239 return membership.name?.trim() || membership.slug;
240}
241
Agents as a team: lifecycle, merge queue, billing and a new shell242function WorkspaceSwitcher({ user, shell }: { user: User; shell: ShellData }) {
Workspace names and icons, and a component kit for every control243 const workspace = shell.workspace;
244 const current = workspace?.slug;
245 const label = workspace ? displayName(workspace) : "Choose a workspace";
Agents as a team: lifecycle, merge queue, billing and a new shell246 return (
247 <DropdownMenu>
Workspace names and icons, and a component kit for every control248 {/* The name goes to the workspace; only the arrows switch it. The
249 name gets all the room there is, and the whole of it on hover. */}
Merge branch 'worktree-agent-ae1299e92e4462012'250 <Hint label={workspace ? `${label} · g1t.sh/${workspace.slug}` : undefined}>
A Vercel-like sidebar and a new mark251 <Link
252 to={current ? `/${current}` : "/workspaces/new"}
Fast pages, required checks on the branch, self-hosted runners, honest incidents253 prefetch="intent"
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look254 className="flex h-9 min-w-0 grow items-center gap-2 rounded-md px-2 transition-colors hover:bg-raised"
A Vercel-like sidebar and a new mark255 >
Workspace names and icons, and a component kit for every control256 {workspace ? (
257 <Avatar name={workspace.slug} image={workspace.avatar} size={20} square />
Agents as a team: lifecycle, merge queue, billing and a new shell258 ) : (
Workspace names and icons, and a component kit for every control259 <span className="size-5 shrink-0 rounded-md border border-dashed border-line-strong" />
Agents as a team: lifecycle, merge queue, billing and a new shell260 )}
Workspace names and icons, and a component kit for every control261 <span className="min-w-0 truncate text-[0.8125rem] font-medium">{label}</span>
A Vercel-like sidebar and a new mark262 </Link>
Merge branch 'worktree-agent-ae1299e92e4462012'263 </Hint>
A Vercel-like sidebar and a new mark264 <DropdownMenuTrigger
265 aria-label="Switch workspace"
Usage, Billing settings and prepaid AI credit; fixes from the UX audit266 className="flex h-9 w-7 shrink-0 items-center justify-center rounded-md text-faint outline-none transition-colors hover:bg-raised hover:text-fg focus-visible:ring-2 focus-visible:ring-accent data-[state=open]:bg-raised data-[state=open]:text-fg"
A Vercel-like sidebar and a new mark267 >
268 <ChevronsUpDown size={14} />
Agents as a team: lifecycle, merge queue, billing and a new shell269 </DropdownMenuTrigger>
Workspace names and icons, and a component kit for every control270 <DropdownMenuContent align="start" className="w-64">
Navigation: the workspace's own page with tabs, pins and recent in the sidebar271 {workspace && (
272 <>
273 {/* The workspace's own page: its projects, packages and people. */}
274 <DropdownMenuItem asChild>
275 <Link to={`/${workspace.slug}`}>
276 <LayoutGrid />
277 Workspace overview
278 </Link>
279 </DropdownMenuItem>
280 <DropdownMenuItem asChild>
281 <Link to={`/${workspace.slug}/-/projects`}>
282 <Box />
283 All projects
284 </Link>
285 </DropdownMenuItem>
286 <DropdownMenuSeparator />
287 </>
288 )}
Agents as a team: lifecycle, merge queue, billing and a new shell289 <DropdownMenuLabel>Workspaces</DropdownMenuLabel>
290 {(user.workspaces ?? []).map((membership) => (
Merge branch 'worktree-agent-ae1299e92e4462012'291 <Hint key={membership.slug} label={displayName(membership)} side="right">
292 <DropdownMenuItem asChild>
293 <Link to={`/${membership.slug}`}>
Workspace names and icons, and a component kit for every control294 <Avatar name={membership.slug} image={membership.avatar} size={24} square />
295 <span className="flex min-w-0 grow flex-col leading-tight">
296 <span className="truncate">{displayName(membership)}</span>
297 <span className="truncate font-mono text-[0.6875rem] text-faint">{membership.slug}</span>
298 </span>
299 <span className="shrink-0 text-[0.6875rem] text-faint capitalize">{membership.role}</span>
300 {membership.slug === current ? (
301 <Check className="shrink-0 text-accent" />
302 ) : (
303 <span className="size-4 shrink-0" aria-hidden="true" />
304 )}
Agents as a team: lifecycle, merge queue, billing and a new shell305 </Link>
306 </DropdownMenuItem>
Merge branch 'worktree-agent-ae1299e92e4462012'307 </Hint>
Agents as a team: lifecycle, merge queue, billing and a new shell308 ))}
309 <DropdownMenuSeparator />
310 <DropdownMenuItem asChild>
311 <Link to="/workspaces/new">
312 <Plus />
313 New workspace
314 </Link>
315 </DropdownMenuItem>
316 </DropdownMenuContent>
317 </DropdownMenu>
A Vercel-like sidebar and a new mark318 );
319}
320
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas321/**
322 * The fine print, as one quiet row at the foot of the account menu and the
323 * visitor's panel: each link a full-height target, left-aligned. In the
324 * menu each is a menu item, so the arrow keys reach it.
325 */
326const LEGAL_LINKS: [string, string][] = [
327 ["Policies", "/policies"],
328 ["Privacy", "/policies/privacy"],
329 ["Security", "/security"],
330];
331const LEGAL_LINK = "flex h-7 items-center rounded px-1.5 text-xs text-faint outline-none transition-colors";
332
333function MenuLegalRow() {
334 return (
335 <div role="group" aria-label="Legal" className="flex items-center gap-0.5 px-0.5 pt-0.5">
336 {LEGAL_LINKS.map(([label, to], index) => (
337 <span key={to} className="flex items-center gap-0.5">
338 {index > 0 && (
339 <span aria-hidden="true" className="text-[0.625rem] text-line-strong">
340 ·
341 </span>
342 )}
343 <DropdownMenuItem asChild className={`${LEGAL_LINK} py-0 data-highlighted:bg-line data-highlighted:text-fg`}>
344 <Link to={to}>{label}</Link>
345 </DropdownMenuItem>
346 </span>
347 ))}
348 </div>
349 );
350}
351
352/** Status's row: the live dot and two or three words, fetched when the menu opens. */
353function StatusSummary({ open }: { open: boolean }) {
354 const status = useSiteStatus(open);
355 const words = statusTitle(status);
356 return (
357 <span className="ml-auto flex min-w-0 items-center gap-1.5 pl-2 text-xs text-faint">
358 {/* The words say it; the dot's own words for screen readers would repeat them. */}
359 <span aria-hidden="true" className="flex">
360 <StatusDot state={status?.overall.state ?? null} />
361 </span>
Fast pages, required checks on the branch, self-hosted runners, honest incidents362 {words ? <span className="truncate">{words}</span> : <Skeleton className="h-3 w-20" />}
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas363 </span>
364 );
365}
366
367/** A menu row: an icon, words, and whatever sits at its end. */
368const MENU_ROW = "h-9 gap-2.5 px-2.5 text-[0.8125rem]";
369
A Vercel-like sidebar and a new mark370function AccountMenu({ user }: { user: User }) {
371 const submit = useSubmit();
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas372 const [open, setOpen] = useState(false);
Fast pages, required checks on the branch, self-hosted runners, honest incidents373 // Name, primary address and invites left: asked for once, as soon as the
374 // pointer or focus reaches the button, so they are there when it opens.
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas375 const details = useFetcher<AccountMenuData | null>({ key: "account-menu" });
Fast pages, required checks on the branch, self-hosted runners, honest incidents376 const prefetch = () => {
377 if (details.state === "idle" && details.data === undefined) details.load("/settings/menu.json");
378 };
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas379 useEffect(() => {
Fast pages, required checks on the branch, self-hosted runners, honest incidents380 if (open) prefetch();
381 // eslint-disable-next-line react-hooks/exhaustive-deps
382 }, [open]);
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas383 const me = details.data ?? null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents384 // Still on its way: shapes where the words will be, the same size.
385 const loading = details.data === undefined && details.state !== "idle";
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas386 const profile = `/u/${user.username}`;
A Vercel-like sidebar and a new mark387 return (
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas388 <DropdownMenu open={open} onOpenChange={setOpen}>
389 <DropdownMenuTrigger
390 aria-label={`Account menu for ${user.username}`}
Fast pages, required checks on the branch, self-hosted runners, honest incidents391 onPointerEnter={prefetch}
392 onFocus={prefetch}
Merge branch 'worktree-agent-ad7c6d88d93adc817'393 className="flex h-10 w-full items-center gap-2.5 rounded-md px-2 text-left outline-none transition-colors hover:bg-raised focus-visible:ring-2 focus-visible:ring-accent/60 data-[state=open]:bg-raised"
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas394 >
Workspace names and icons, and a component kit for every control395 <Avatar name={user.username} image={user.avatar} size={22} />
A Vercel-like sidebar and a new mark396 <span className="min-w-0 grow truncate text-[0.8125rem] font-medium">{user.username}</span>
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas397 <ChevronsUpDown size={14} className="shrink-0 text-faint" />
A Vercel-like sidebar and a new mark398 </DropdownMenuTrigger>
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas399 <DropdownMenuContent align="start" side="top" collisionPadding={8} className="w-[17.5rem] p-1.5">
400 {/* Who is signed in, and a way to their profile. */}
401 <DropdownMenuItem asChild className="gap-3 px-2 py-2">
402 <Link to={profile} aria-label={`${me?.name ?? user.username} (@${user.username}), your profile`}>
403 <Avatar name={user.username} image={user.avatar} size={36} />
Fast pages, required checks on the branch, self-hosted runners, honest incidents404 <span className="flex min-w-0 grow flex-col leading-tight" aria-busy={loading}>
405 {loading ? (
406 <Skeleton className="my-[0.1875rem] h-3.5 w-28" />
407 ) : (
408 <span className="truncate text-sm font-medium text-fg">{me?.name ?? user.username}</span>
409 )}
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas410 <span className="truncate font-mono text-xs text-muted">@{user.username}</span>
Fast pages, required checks on the branch, self-hosted runners, honest incidents411 {loading ? (
412 <Skeleton className="mt-1 h-3 w-40" />
413 ) : (
414 me?.email && <span className="mt-0.5 truncate text-xs text-faint">{me.email}</span>
415 )}
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas416 </span>
A Vercel-like sidebar and a new mark417 </Link>
418 </DropdownMenuItem>
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas419 <DropdownMenuSeparator className="my-1.5" />
420 <DropdownMenuGroup>
421 <DropdownMenuItem asChild className={MENU_ROW}>
422 <Link to={profile}>
423 <CircleUserRound />
424 Your profile
425 </Link>
426 </DropdownMenuItem>
427 <DropdownMenuItem asChild className={MENU_ROW}>
428 <Link to={FIRST_SETTINGS_PAGE}>
429 <Settings />
430 Your settings
431 </Link>
432 </DropdownMenuItem>
433 <DropdownMenuItem asChild className={MENU_ROW}>
434 <Link to="/settings/invites">
435 <Ticket />
436 Invites
Fast pages, required checks on the branch, self-hosted runners, honest incidents437 {loading && <Skeleton className="ml-auto h-4 w-12 rounded-full" />}
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas438 {me?.invites_left != null && (
439 <span className="ml-auto rounded-full bg-line px-1.5 text-[0.6875rem] tabular-nums text-muted">
440 {me.invites_left} left
441 </span>
442 )}
443 </Link>
444 </DropdownMenuItem>
445 </DropdownMenuGroup>
446 <DropdownMenuSeparator className="my-1.5" />
447 <DropdownMenuGroup>
448 <DropdownMenuItem asChild className={MENU_ROW}>
449 <a href="https://docs.g1t.sh/">
450 <BookOpen />
451 Documentation
452 </a>
453 </DropdownMenuItem>
454 <DropdownMenuItem asChild className={MENU_ROW}>
455 <Link to="/support">
456 <LifeBuoy />
457 Support
458 </Link>
459 </DropdownMenuItem>
460 <DropdownMenuItem asChild className={MENU_ROW}>
461 <a href={STATUS_URL}>
462 <Activity />
463 Status
464 <StatusSummary open={open} />
465 </a>
466 </DropdownMenuItem>
467 </DropdownMenuGroup>
468 <DropdownMenuSeparator className="my-1.5" />
A Vercel-like sidebar and a new mark469 {/* Submitted from here: the menu closes on select, and a button
470 that has left the page cannot submit a form. */}
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas471 <DropdownMenuItem className={MENU_ROW} onSelect={() => submit(null, { method: "post", action: "/logout" })}>
A Vercel-like sidebar and a new mark472 <LogOut />
473 Sign out
474 </DropdownMenuItem>
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas475 <DropdownMenuSeparator className="my-1.5" />
476 <MenuLegalRow />
A Vercel-like sidebar and a new mark477 </DropdownMenuContent>
478 </DropdownMenu>
479 );
480}
481
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look482/**
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar483 * A workspace's settings pages, which the sidebar drills into: how it is
484 * set up and connected (guardrails, secrets, runners, integrations,
485 * webhooks), what it pays, its repositories, tokens and record. The main
486 * list keeps the places work happens and who belongs; every member can
487 * still open these.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look488 */
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar489const SETTINGS_PAGE =
Merge branch 'worktree-agent-ad7c6d88d93adc817'490 new RegExp(`^/([^/]+)/-/(${SETTINGS_PAGES.join("|")})(/|$)`);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look491/** A project's settings pages, which the project's menu drills into. */
Project settings move into the sidebar, grouped, with a heading per page492const REPO_SETTINGS_PAGE = /^\/([^/]+)\/([^/-][^/]*)\/settings(\/|$)/;
A catalogue of model providers, and settings that feel like settings493
Polish: phones, copy boxes, the plan page, the landing page, a real glide494/**
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look495 * The sidebar's lists are a stack, the way a phone pushes a screen: one
496 * drilled into slides in from the right over the full width, while the one
497 * it covers drifts a quarter of the way left and fades, both on one long
Sidebar: the panels really slide498 * ease-out, the fade quicker than the move so the two never blur together.
499 * Going back reverses it.
Polish: phones, copy boxes, the plan page, the landing page, a real glide500 */
Sidebar: no settings flash on the way into a repository, and a better slide501const LAYER =
Sidebar: the panels really slide502 "absolute inset-0 [transition:translate_380ms_cubic-bezier(0.32,0.72,0,1),opacity_220ms_ease-out] will-change-[translate,opacity] motion-reduce:transition-none";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look503/** One list, filling its layer. */
Sidebar: no settings flash on the way into a repository, and a better slide504const PANEL = "h-full overflow-y-auto px-2 pb-4";
A catalogue of model providers, and settings that feel like settings505
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look506/** One list in the stack: what it is, and its rows. */
507type Level = { key: string; node: ReactNode };
508
509/** True once the page has hydrated, so what the server drew never animates in. */
510function useHydrated(): boolean {
511 const [hydrated, setHydrated] = useState(false);
512 useEffect(() => setHydrated(true), []);
513 return hydrated;
514}
515
516/**
517 * Drilling in and out of the sidebar's lists. `trail` is the way from the
518 * main list to the one shown, worked out from the address, so a link
519 * straight to a settings page opens the sidebar already drilled in, and
520 * leaving it comes back out. Lists deeper than the one shown stay in
521 * place, off to the right, so going back slides them away rather than
522 * dropping them. Moving to a list at the same depth (a workspace's settings
523 * to a project) fades the new one in where it is.
524 */
525function Drill({ trail }: { trail: Level[] }) {
526 const depth = trail.length - 1;
527 const hydrated = useHydrated();
528 const stack = useRef<Level[]>(trail);
529 const before = stack.current;
530 const within = trail.every((level, index) => before[index]?.key === level.key);
531 stack.current = within ? [...trail, ...before.slice(trail.length)] : trail;
532 const levels = stack.current;
533 // What was showing last time, to tell drilling in from moving across.
534 const shown = useRef({ depth, keys: levels.map((level) => level.key) });
535 useEffect(() => {
536 shown.current = { depth, keys: levels.map((level) => level.key) };
537 });
A catalogue of model providers, and settings that feel like settings538 return (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look539 <div className="relative min-h-0 grow overflow-hidden">
540 {levels.map((level, index) => {
541 const current = index === depth;
542 const place =
543 index < depth
544 ? "pointer-events-none -translate-x-1/4 opacity-0"
545 : current
546 ? "translate-x-0 opacity-100"
547 : "pointer-events-none translate-x-full opacity-0";
548 // A list that was not there before arrives: from the right when
549 // drilling in, in place when moving across.
550 const arriving = hydrated && current && !shown.current.keys.includes(level.key);
551 const enter = arriving
552 ? depth > shown.current.depth
553 ? "starting:translate-x-full starting:opacity-0"
554 : "starting:opacity-0"
555 : "";
556 return (
557 <div key={level.key} className={`${LAYER} ${place} ${enter}`} inert={!current} aria-hidden={!current || undefined}>
558 {level.node}
559 </div>
560 );
561 })}
562 </div>
563 );
564}
565
566/**
567 * The row at the top of a list drilled into: the list's name, which goes
568 * back out, and what it belongs to, faint at the end.
569 */
570function BackRow({ to, label, context }: { to: string; label: ReactNode; context?: ReactNode }) {
571 return (
572 <Link
573 to={to}
574 prefetch="intent"
575 className="group mt-3 flex h-8 items-center gap-1.5 rounded-md pr-2 pl-1 text-[0.8125rem] font-medium text-fg transition-colors hover:bg-raised/60"
A catalogue of model providers, and settings that feel like settings576 >
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look577 <ChevronLeft size={16} className="shrink-0 text-faint transition-transform group-hover:-translate-x-0.5 group-hover:text-muted" />
578 <span className="min-w-0 shrink-0 truncate">{label}</span>
579 {context && (
580 <span className="ml-auto min-w-0 truncate pl-2 font-mono text-[0.6875rem] font-normal text-faint">{context}</span>
581 )}
582 </Link>
583 );
584}
585
586/** A thin rule between groups of a list. */
587function Rule() {
588 return <div role="separator" className="mx-2 my-2.5 h-px bg-line" />;
589}
590
Navigation: the workspace's own page with tabs, pins and recent in the sidebar591/** A quiet heading inside a group: Pinned, Recent. */
592function SidebarSubhead({ children }: { children: ReactNode }) {
593 return <h3 className="px-2 pt-1.5 pb-0.5 text-[0.6875rem] font-medium tracking-wide text-faint">{children}</h3>;
594}
595
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look596/**
Navigation: the workspace's own page with tabs, pins and recent in the sidebar597 * The workspace's projects as the sidebar keeps them, however many there
598 * are: the person's pins in their order, what they opened last, and the
599 * way to all of them. Pins move by dragging, or with Alt and the arrow
600 * keys; the projects service keeps the order.
601 */
Merge branch 'worktree-agent-ad7c6d88d93adc817'602function SidebarProjects({ slug, shell, current }: { slug: string; shell: ShellData; current: boolean }) {
Navigation: the workspace's own page with tabs, pins and recent in the sidebar603 const reorder = useFetcher({ key: `pins:${slug}` });
604 const saved = shell.pinned ?? [];
605 // While a new order is on its way, it shows as made.
606 const asked = reorder.formData?.get("intent") === "reorder" ? reorder.formData.getAll("slug").map(String) : null;
607 const pinned = asked
608 ? asked.map((name) => saved.find((project) => project.name === name)).filter((project): project is ShortcutProject => project != null)
609 : saved;
610 const active = shell.repo ? { namespace: shell.repo.namespace, name: shell.repo.name, isPrivate: false } : null;
611 const recent = recentWith(shell.recent ?? [], pinned, active, slug);
612 const [dragging, setDragging] = useState<number | null>(null);
613 const move = (from: number, by: number) => {
614 const next = movedPin(pinned, from, by);
615 if (!next) return;
616 const form = new FormData();
617 form.set("intent", "reorder");
618 for (const project of next) form.append("slug", project.name);
619 reorder.submit(form, { method: "post", action: `/${slug}/-/pins` });
620 };
Previews build from any branch; Agent in place of Ask AI; pin from the sidebar621 // Each row pins or unpins in place: the pin shows on hover or focus, and
622 // stays shown on a pinned row's hover so it reads as "unpin".
623 const row = (project: ShortcutProject, isPinned: boolean) => (
624 <div className="group/row relative">
625 <SidebarLink to={`/${project.namespace}/${project.name}`} icon={project.isPrivate ? <Lock size={15} /> : <Box size={15} />} drill="hover">
626 <span className="block truncate pr-6">{project.title ?? project.name}</span>
627 </SidebarLink>
628 <PinButton
629 workspace={slug}
630 slug={project.name}
631 name={project.title ?? project.name}
632 pinned={isPinned}
633 small
634 className="absolute top-1/2 right-6 -translate-y-1/2 opacity-0 transition-opacity group-hover/row:opacity-100 focus-within:opacity-100 [@media(hover:none)]:opacity-100"
635 />
636 </div>
Navigation: the workspace's own page with tabs, pins and recent in the sidebar637 );
638 return (
639 <SidebarGroup
640 title="Projects"
641 action={
642 <Link to={`/new?workspace=${slug}`} aria-label="New project" className="rounded p-0.5 text-faint hover:bg-raised hover:text-fg">
643 <Plus size={13} />
644 </Link>
645 }
646 >
Previews build from any branch; Agent in place of Ask AI; pin from the sidebar647 {/* Only there once something is pinned: each row below has its own pin. */}
648 {pinned.length > 0 && (
649 <>
650 <SidebarSubhead>Pinned</SidebarSubhead>
Navigation: the workspace's own page with tabs, pins and recent in the sidebar651 <ul aria-label="Pinned projects" className="space-y-px">
652 {pinned.map((project, index) => (
653 <li
654 key={project.name}
655 draggable={pinned.length > 1}
656 onDragStart={(event) => {
657 setDragging(index);
658 event.dataTransfer.effectAllowed = "move";
659 }}
660 onDragOver={(event) => {
661 if (dragging != null) event.preventDefault();
662 }}
663 onDrop={(event) => {
664 event.preventDefault();
665 if (dragging != null && dragging !== index) move(dragging, index - dragging);
666 setDragging(null);
667 }}
668 onDragEnd={() => setDragging(null)}
669 onKeyDown={(event) => {
670 if (!event.altKey || (event.key !== "ArrowUp" && event.key !== "ArrowDown")) return;
671 event.preventDefault();
672 move(index, event.key === "ArrowUp" ? -1 : 1);
673 }}
674 className={`group/pin relative ${dragging === index ? "opacity-50" : ""}`}
675 >
Previews build from any branch; Agent in place of Ask AI; pin from the sidebar676 {row(project, true)}
Navigation: the workspace's own page with tabs, pins and recent in the sidebar677 {pinned.length > 1 && (
Merge branch 'worktree-agent-ae1299e92e4462012'678 <Hint label="Drag, or Alt and an arrow key, to reorder" side="right">
679 <span className="absolute top-1/2 -left-1.5 -translate-y-1/2 cursor-grab text-faint opacity-0 transition-opacity group-hover/pin:opacity-100">
680 <GripVertical size={12} aria-hidden="true" />
681 </span>
682 </Hint>
Navigation: the workspace's own page with tabs, pins and recent in the sidebar683 )}
684 </li>
685 ))}
686 </ul>
Previews build from any branch; Agent in place of Ask AI; pin from the sidebar687 </>
Navigation: the workspace's own page with tabs, pins and recent in the sidebar688 )}
689 {recent.length > 0 && (
690 <>
691 <SidebarSubhead>Recent</SidebarSubhead>
692 {recent.map((project) => (
Previews build from any branch; Agent in place of Ask AI; pin from the sidebar693 <div key={`${project.namespace}/${project.name}`}>{row(project, false)}</div>
Navigation: the workspace's own page with tabs, pins and recent in the sidebar694 ))}
695 </>
696 )}
697 <div className="pt-1">
Merge branch 'worktree-agent-ad7c6d88d93adc817'698 <SidebarLink to={`/${slug}/-/projects`} icon={<LayoutGrid size={15} />} count={shell.repos.length} current={current}>
Navigation: the workspace's own page with tabs, pins and recent in the sidebar699 All projects
700 </SidebarLink>
701 </div>
702 </SidebarGroup>
703 );
704}
705
706/**
Merge branch 'worktree-agent-ad7c6d88d93adc817'707 * The main list, in two parts a rule apart. Above it, what is yours
708 * whichever workspace you are in: Mission control, your inbox, and what
709 * others have shared with you. Below it, under the workspace's name, the
710 * workspace: its overview, its projects, what it builds and runs with
711 * across them, then its people, usage, support and settings. One row is
712 * lit wherever you are (lib/workspace-nav.ts).
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look713 */
714function MainMenu({ user, shell }: { user: User | null; shell: ShellData }) {
715 const ws = shell.workspace;
Merge branch 'worktree-agent-ad7c6d88d93adc817'716 const { pathname } = useLocation();
717 const going = useNavigation().location?.pathname;
718 // The row lights as the link is followed, not once the page arrives.
719 const here = sidebarCurrent(going ?? pathname, ws?.slug ?? null);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look720 // A visitor browses: no workspace, no projects of their own.
721 if (!user) {
722 return (
723 <nav aria-label="g1t" className={PANEL}>
724 <div className="mt-3 space-y-px">
725 {VISITOR_LINKS.map((link) => (
726 <SidebarLink key={link.to} to={link.to} icon={link.to === "/search" ? <Search size={15} /> : <Compass size={15} />}>
727 {link.label}
728 </SidebarLink>
729 ))}
730 </div>
731 </nav>
732 );
733 }
Merge branch 'worktree-agent-ad7c6d88d93adc817'734 const shared = shell.shared ?? [];
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look735 return (
736 <nav aria-label="g1t" className={PANEL}>
Merge branch 'worktree-agent-ad7c6d88d93adc817'737 {/* Yours, in every workspace. Explore, all of g1t, is in the top bar. */}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look738 <div className="mt-3 space-y-px">
Merge branch 'worktree-agent-ad7c6d88d93adc817'739 <SidebarLink to="/" end icon={<House size={15} />} current={here === "mission"}>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look740 Mission control
741 </SidebarLink>
Merge branch 'worktree-agent-ad7c6d88d93adc817'742 <SidebarLink to="/inbox" icon={<Inbox size={15} />} count={shell.inbox?.unread ?? undefined} current={here === "inbox"}>
743 Inbox
744 </SidebarLink>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look745 </div>
Merge branch 'worktree-agent-ad7c6d88d93adc817'746 {shared.length > 0 && (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look747 <SidebarGroup title="Shared with you" className="mt-3">
Merge branch 'worktree-agent-ad7c6d88d93adc817'748 {shared.map((repo) => (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look749 <SidebarLink
750 key={`${repo.namespace}/${repo.name}`}
751 to={`/${repo.namespace}/${repo.name}`}
752 icon={repo.isPrivate ? <Lock size={15} /> : <Box size={15} />}
753 drill="hover"
754 >
755 <span className="font-mono text-faint">{repo.namespace}/</span>
756 {repo.name}
757 </SidebarLink>
758 ))}
759 </SidebarGroup>
760 )}
761
Merge branch 'worktree-agent-ad7c6d88d93adc817'762 <Rule />
763 {ws ? (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look764 <>
Merge branch 'worktree-agent-ad7c6d88d93adc817'765 {/* The workspace the switcher names: its own page first. */}
766 <SidebarGroup title={displayName(ws)}>
767 <SidebarLink to={`/${ws.slug}`} end icon={<LayoutDashboard size={15} />} current={here === "overview"}>
768 Overview
769 </SidebarLink>
770 </SidebarGroup>
771 <div className="mt-3">
772 <SidebarProjects slug={ws.slug} shell={shell} current={here === "projects"} />
773 </div>
774
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member775 <Rule />
Merge branch 'worktree-agent-ad7c6d88d93adc817'776 <div className="space-y-px">
777 <SidebarLink to={`/${ws.slug}/-/agents`} icon={<Bot size={15} />} current={here === "agents"}>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look778 Agent fleet
779 </SidebarLink>
Merge branch 'worktree-agent-ad7c6d88d93adc817'780 <SidebarLink to={`/${ws.slug}/-/context`} icon={<Network size={15} />} current={here === "context"}>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look781 Context
782 </SidebarLink>
Merge branch 'worktree-agent-ad7c6d88d93adc817'783 <SidebarLink to={`/${ws.slug}/-/memory`} icon={<Brain size={15} />} current={here === "memory"}>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look784 Memory
785 </SidebarLink>
Merge branch 'worktree-agent-ad7c6d88d93adc817'786 <SidebarLink to={`/${ws.slug}/-/security`} icon={<ShieldCheck size={15} />} current={here === "security"}>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look787 Security
Fast pages, required checks on the branch, self-hosted runners, honest incidents788 </SidebarLink>
Merge branch 'worktree-agent-ad7c6d88d93adc817'789 <SidebarLink to={`/${ws.slug}/-/packages`} icon={<Package size={15} />} current={here === "packages"}>
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member790 Packages
791 </SidebarLink>
Merge branch 'worktree-agent-ad7c6d88d93adc817'792 {INSIGHTS && (
793 <SidebarSoonLink
794 to={`/${ws.slug}/-/insights`}
795 icon={<TrendingUp size={15} />}
796 about={INSIGHTS.summary}
797 current={here === "insights"}
798 >
799 {INSIGHTS.title}
800 </SidebarSoonLink>
801 )}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar802 {roadmapIn("Workspace").filter((item) => item.key !== "insights").map((item) => (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look803 <SidebarSoonLink
804 key={item.key}
805 to={`/${ws.slug}/-/soon/${item.key}`}
806 icon={WORKSPACE_ICONS[item.key] ?? <Sparkles size={15} />}
807 about={item.summary}
808 >
809 {item.title === "Board" ? "Boards" : item.title}
810 </SidebarSoonLink>
811 ))}
Merge branch 'worktree-agent-ad7c6d88d93adc817'812 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look813
814 <Rule />
815 <div className="space-y-px">
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar816 {/* Who belongs, for every member to see; owners invite and manage there. */}
Merge branch 'worktree-agent-ad7c6d88d93adc817'817 <SidebarLink to={`/${ws.slug}/-/people`} icon={<Users size={15} />} current={here === "people"}>
Navigation: the workspace's own page with tabs, pins and recent in the sidebar818 People
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar819 </SidebarLink>
Merge branch 'worktree-agent-ad7c6d88d93adc817'820 <SidebarLink to={`/${ws.slug}/-/teams`} icon={WORKSPACE_ICONS.teams} current={here === "teams"}>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar821 Teams
822 </SidebarLink>
Merge branch 'worktree-agent-ad7c6d88d93adc817'823 <SidebarLink to={`/${ws.slug}/-/usage`} icon={<BarChart3 size={15} />} current={here === "usage"}>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look824 Usage
825 </SidebarLink>
Merge branch 'worktree-agent-ad7c6d88d93adc817'826 <SidebarLink to="/support" icon={<LifeBuoy size={15} />} current={here === "support"}>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look827 Support
828 </SidebarLink>
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar829 {/* How it is set up and connected, what it pays and its record: a list of their own. */}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look830 <SidebarLink
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member831 to={ws.role === "owner" ? `/${ws.slug}/-/settings` : `/${ws.slug}/-/repositories`}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look832 icon={<Settings size={15} />}
833 drill
Merge branch 'worktree-agent-ad7c6d88d93adc817'834 current={here === "settings"}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look835 >
836 Settings
837 </SidebarLink>
838 </div>
839 </>
Merge branch 'worktree-agent-ad7c6d88d93adc817'840 ) : (
841 <SidebarGroup title="Projects">
842 <p className="px-2 py-1 text-xs text-faint">None yet.</p>
843 </SidebarGroup>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look844 )}
845 </nav>
846 );
847}
848
Merge branch 'worktree-agent-ad7c6d88d93adc817'849/** Insights, coming: the workspace's own page says what it will be. */
850const INSIGHTS = roadmapItem("insights");
851
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look852/** A workspace's settings, drilled into from Settings in the main list. */
853function SettingsMenu({ slug, owner }: { slug: string; owner: boolean }) {
854 return (
855 <nav aria-label="Workspace settings" className={PANEL}>
856 <BackRow to={`/${slug}`} label="Settings" context={slug} />
857 <div className="mt-2 space-y-px">
A catalogue of model providers, and settings that feel like settings858 {owner && (
859 <SidebarLink to={`/${slug}/-/settings`} icon={<Settings size={15} />}>
860 General
861 </SidebarLink>
862 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look863 <SidebarLink to={`/${slug}/-/repositories`} icon={<BookMarked size={15} />}>
864 Repositories
865 </SidebarLink>
Secrets and variables: one list, rows per environment, for workflows and deployments866 <SidebarLink to={`/${slug}/-/tokens`} icon={<KeyRound size={15} />}>
867 Access tokens
868 </SidebarLink>
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules869 {owner && (
870 <SidebarLink to={`/${slug}/-/personal-access-tokens`} icon={<UserRoundKey size={15} />}>
871 Personal access tokens
872 </SidebarLink>
873 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge874 <SidebarLink to={`/${slug}/-/rules`} icon={<Scale size={15} />}>
875 Rules
876 </SidebarLink>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look877 </div>
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar878 <SidebarGroup title="Agents and runs" className="mt-3">
879 <SidebarLink to={`/${slug}/-/guardrails`} icon={<Gauge size={15} />}>
880 Guardrails
881 </SidebarLink>
882 <SidebarLink to={`/${slug}/-/secrets`} icon={<Lock size={15} />}>
883 Secrets and variables
884 </SidebarLink>
Merge branch 'worktree-agent-a3abfcce648e87dca'885 <SidebarLink to={`/${slug}/-/actions`} icon={<PlayCircle size={15} />}>
886 Actions
887 </SidebarLink>
Runners are owner-only: the page, its actions, the Settings entry and the links to it888 {owner && (
889 <SidebarLink to={`/${slug}/-/runners`} icon={<ServerCog size={15} />}>
890 Runners
891 </SidebarLink>
892 )}
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar893 </SidebarGroup>
894 <SidebarGroup title="Connections" className="mt-3">
895 <SidebarLink to={`/${slug}/-/integrations`} icon={<Plug size={15} />}>
896 Integrations
897 </SidebarLink>
898 <SidebarLink to={`/${slug}/-/webhooks`} icon={<Webhook size={15} />}>
899 Webhooks
900 </SidebarLink>
901 </SidebarGroup>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look902 <Rule />
903 <div className="space-y-px">
A catalogue of model providers, and settings that feel like settings904 <SidebarLink to={`/${slug}/-/billing`} icon={<CreditCard size={15} />}>
Secrets and variables: one list, rows per environment, for workflows and deployments905 Billing and plans
A catalogue of model providers, and settings that feel like settings906 </SidebarLink>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API907 <SidebarLink to={`/${slug}/-/audit`} icon={<History size={15} />}>
908 Audit log
909 </SidebarLink>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look910 </div>
A catalogue of model providers, and settings that feel like settings911 </nav>
912 );
913}
914
Sidebar: no settings flash on the way into a repository, and a better slide915/**
916 * What the repository menu needs. Known from the address before the page's
917 * data arrives, so the menu is right from the first frame; the counts fill
918 * in once it does.
919 */
920type MenuRepo = {
921 namespace: string;
922 name: string;
923 member: boolean;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look924 /** Whether they see its settings; from the repository's page once it loads. */
925 settings?: boolean;
926 /** What they may do there, once the repository's page loads. */
927 can?: Abilities;
Sidebar: no settings flash on the way into a repository, and a better slide928 issues?: number;
929 pulls?: number;
930};
931
932function sameRepo(a: { namespace: string; name: string } | null, b: { namespace: string; name: string } | null) {
933 return (
934 a != null &&
935 b != null &&
936 a.namespace.toLowerCase() === b.namespace.toLowerCase() &&
937 a.name.toLowerCase() === b.name.toLowerCase()
938 );
939}
A repository has its own sidebar, as settings do940
941/**
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look942 * A project's own list, drilled into while you are in it: everything about
943 * the project, running and its code, and nothing else, with the way back
944 * out to all of them.
A repository has its own sidebar, as settings do945 */
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put946function RepoMenu({
947 repo,
948 isPrivate,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look949 back,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put950}: {
951 repo: MenuRepo;
952 isPrivate: boolean;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look953 /** Where the way back leads: the workspace's projects, mission control, or Explore. */
954 back: { to: string; label: string };
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put955}) {
A repository has its own sidebar, as settings do956 const base = `/${repo.namespace}/${repo.name}`;
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put957 // What a member sees, and what everyone who can see the project does.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look958 const shows = new Set(projectPages(repo.member, repo.can));
A repository has its own sidebar, as settings do959 return (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look960 <nav aria-label={`${repo.namespace}/${repo.name}`} className={PANEL}>
961 <BackRow to={back.to} label={back.label} />
Merge branch 'worktree-agent-ae1299e92e4462012'962 <Hint label="Overview" side="right">
Sidebar: soon rows and the project row keep their styles inside a hint; tiny amounts read as zero963 <div>
A flat project menu with Code first, views as tabs, and Stripe for every owner964 <NavLink
A repository has its own sidebar, as settings do965 to={base}
A flat project menu with Code first, views as tabs, and Stripe for every owner966 end
Fast pages, required checks on the branch, self-hosted runners, honest incidents967 prefetch="intent"
A flat project menu with Code first, views as tabs, and Stripe for every owner968 className={({ isActive }) =>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look969 `mt-2 flex items-center gap-2 rounded-md px-2 py-1.5 transition-colors ${isActive ? "bg-raised" : "hover:bg-raised/60"}`
A flat project menu with Code first, views as tabs, and Stripe for every owner970 }
A repository has its own sidebar, as settings do971 >
972 <span className="flex size-6 shrink-0 items-center justify-center rounded-md bg-raised text-muted ring-1 ring-line">
Projects: what a workspace builds and runs, first on every page973 {isPrivate ? <Lock size={13} /> : <Box size={13} />}
A repository has its own sidebar, as settings do974 </span>
975 <span className="min-w-0 truncate font-mono text-[0.8125rem]">
976 <span className="text-faint">{repo.namespace}/</span>
977 <span className="font-semibold text-fg">{repo.name}</span>
978 </span>
A flat project menu with Code first, views as tabs, and Stripe for every owner979 </NavLink>
Sidebar: soon rows and the project row keep their styles inside a hint; tiny amounts read as zero980 </div>
Merge branch 'worktree-agent-ae1299e92e4462012'981 </Hint>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look982 {/* A project's pages, in the order people use them. A page with more
983 than one view shows them as tabs across its top. */}
984 <Rule />
985 <div className="space-y-px">
Compare: any two branches, the commits one has that the other does not, and the diff, with Open a pull request986 <SidebarLink to={`${base}/code`} also={[`${base}/tree`, `${base}/blob`, `${base}/commits`, `${base}/commit`, `${base}/branches`, `${base}/tags`, `${base}/compare`, ...soonPaths(base, "Code")]} icon={<Code2 size={15} />}>
A flat project menu with Code first, views as tabs, and Stripe for every owner987 Code
988 </SidebarLink>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit989 <SidebarLink to={`${base}/issues`} also={[`${base}/plans`, `${base}/milestones`, `${base}/labels`, ...soonPaths(base, "Issues")]} icon={<CircleDot size={15} />} count={repo.issues}>
A flat project menu with Code first, views as tabs, and Stripe for every owner990 Issues
991 </SidebarLink>
992 <SidebarLink to={`${base}/pulls`} also={[`${base}/pull`, `${base}/queue`]} icon={<GitPullRequest size={15} />} count={repo.pulls}>
993 Pull requests
994 </SidebarLink>
Agents and memory, checks and conflicts, profiles, slug renames, custom domains995 <SidebarLink to={`${base}/agents`} also={[`${base}/sessions`, `${base}/memory`, ...soonPaths(base, "Agents")]} icon={<Bot size={15} />}>
A flat project menu with Code first, views as tabs, and Stripe for every owner996 Agents
Agents and memory, checks and conflicts, profiles, slug renames, custom domains997 </SidebarLink>
A flat project menu with Code first, views as tabs, and Stripe for every owner998 <SidebarLink to={`${base}/actions`} icon={<PlayCircle size={15} />}>
999 Workflows
Projects: what a workspace builds and runs, first on every page1000 </SidebarLink>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1001 </div>
1002 <Rule />
1003 <div className="space-y-px">
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1004 {shows.has("deployments") ? (
A flat project menu with Code first, views as tabs, and Stripe for every owner1005 <SidebarLink to={`${base}/deployments`} also={soonPaths(base, "Deployments")} icon={<Rocket size={15} />}>
1006 Deployments
1007 </SidebarLink>
1008 ) : null}
Observability gets its own page; tabs never overflow1009 <SidebarSoonLink to={`${base}/soon/logs`} also={soonPaths(base, "Observability")} icon={<Activity size={15} />} about="Logs, errors, uptime and analytics of the project's deployed apps.">
1010 Observability
A flat project menu with Code first, views as tabs, and Stripe for every owner1011 </SidebarSoonLink>
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1012 {shows.has("security") ? (
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1013 <SidebarLink to={`${base}/security`} also={soonPaths(base, "Security")} icon={<ShieldCheck size={15} />}>
1014 Security
1015 </SidebarLink>
1016 ) : null}
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971017 <SidebarLink
1018 to={`${base}/contributors`}
1019 also={[`${base}/activity`, `${base}/stargazers`, ...soonPaths(base, "Insights")]}
1020 icon={<BarChart3 size={15} />}
1021 >
A flat project menu with Code first, views as tabs, and Stripe for every owner1022 Insights
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971023 </SidebarLink>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1024 </div>
1025 {(repo.settings ?? shows.has("settings")) && (
1026 <>
1027 <Rule />
1028 <SidebarLink to={`${base}/settings`} icon={<Settings size={15} />} drill>
A flat project menu with Code first, views as tabs, and Stripe for every owner1029 Settings
1030 </SidebarLink>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1031 </>
1032 )}
A repository has its own sidebar, as settings do1033 </nav>
1034 );
1035}
1036
Project settings move into the sidebar, grouped, with a heading per page1037/**
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1038 * A project's settings, drilled into from its list: what is about running
1039 * it, then its agents, then its code and who can reach it. The way back
1040 * leads to the project.
Project settings move into the sidebar, grouped, with a heading per page1041 */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1042function RepoSettingsMenu({ repo }: { repo: MenuRepo }) {
Project settings move into the sidebar, grouped, with a heading per page1043 const base = `/${repo.namespace}/${repo.name}`;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1044 // Each page shows to the roles that can use it, once the role is known.
1045 const shows = (page: string) => !repo.can || repo.can[SETTINGS_CAPABILITY[page] ?? "administer"];
1046 const running = shows("deployments");
1047 const agents = shows("agents") || shows("guardrails");
Project settings move into the sidebar, grouped, with a heading per page1048 return (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1049 <nav aria-label={`${repo.namespace}/${repo.name} settings`} className={PANEL}>
1050 <BackRow to={base} label="Settings" context={`${repo.namespace}/${repo.name}`} />
1051 {shows("") && (
1052 <div className="mt-2 space-y-px">
1053 <SidebarLink to={`${base}/settings`} end icon={<Settings size={15} />}>
1054 General
1055 </SidebarLink>
1056 </div>
1057 )}
1058 {running && (
1059 <>
1060 {shows("") && <Rule />}
1061 <div className={shows("") ? "space-y-px" : "mt-2 space-y-px"}>
1062 <SidebarLink to={`${base}/settings/deployments`} icon={<Rocket size={15} />}>
1063 Deployments
1064 </SidebarLink>
1065 <SidebarLink to={`${base}/settings/domains`} icon={<Globe size={15} />}>
1066 Domains
1067 </SidebarLink>
1068 <SidebarLink to={`${base}/settings/dependencies`} icon={<Network size={15} />}>
1069 Dependencies
1070 </SidebarLink>
1071 </div>
1072 </>
1073 )}
1074 {agents && (
1075 <>
1076 <Rule />
1077 <div className="space-y-px">
1078 {shows("agents") && (
1079 <SidebarLink to={`${base}/settings/agents`} icon={<Bot size={15} />}>
1080 Agents
1081 </SidebarLink>
1082 )}
1083 {shows("guardrails") && (
1084 <SidebarLink to={`${base}/settings/guardrails`} icon={<ShieldCheck size={15} />}>
1085 Guardrails
1086 </SidebarLink>
1087 )}
1088 </div>
1089 </>
1090 )}
1091 <Rule />
1092 <div className="space-y-px">
1093 {shows("repository") && (
1094 <SidebarLink to={`${base}/settings/repository`} icon={<BookMarked size={15} />}>
1095 Repository
1096 </SidebarLink>
1097 )}
Mirroring on the site and in the docs1098 {shows("mirroring") && (
1099 <SidebarLink to={`${base}/settings/mirroring`} icon={<ArrowLeftRight size={15} />}>
1100 Mirroring
1101 </SidebarLink>
1102 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1103 {shows("access") && (
1104 <SidebarLink to={`${base}/settings/access`} icon={<Users size={15} />}>
1105 Access
1106 </SidebarLink>
1107 )}
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca1108 {shows("keys") && (
1109 <SidebarLink to={`${base}/settings/keys`} icon={<KeyRound size={15} />}>
1110 Deploy keys
1111 </SidebarLink>
1112 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1113 {shows("branches") && (
1114 <SidebarLink to={`${base}/settings/branches`} icon={<GitBranch size={15} />}>
1115 Branches and merging
1116 </SidebarLink>
1117 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1118 {shows("rules") && (
1119 <SidebarLink to={`${base}/settings/rules`} icon={<Scale size={15} />}>
1120 Rules
1121 </SidebarLink>
1122 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1123 {shows("secrets") && (
1124 <SidebarLink to={`${base}/settings/secrets`} icon={<Lock size={15} />}>
1125 Secrets and variables
1126 </SidebarLink>
1127 )}
Merge branch 'worktree-agent-a3abfcce648e87dca'1128 {shows("actions") && (
1129 <SidebarLink to={`${base}/settings/actions`} icon={<PlayCircle size={15} />}>
1130 Actions
1131 </SidebarLink>
1132 )}
1133 {shows("environments") && (
1134 <SidebarLink to={`${base}/settings/environments`} icon={<Layers size={15} />}>
1135 Environments
1136 </SidebarLink>
1137 )}
Fast pages, required checks on the branch, self-hosted runners, honest incidents1138 {shows("runners") && (
1139 <SidebarLink to={`${base}/settings/runners`} icon={<ServerCog size={15} />}>
1140 Runners
1141 </SidebarLink>
1142 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1143 {shows("webhooks") && (
1144 <SidebarLink to={`${base}/settings/webhooks`} icon={<Webhook size={15} />}>
1145 Webhooks
1146 </SidebarLink>
1147 )}
1148 </div>
Project settings move into the sidebar, grouped, with a heading per page1149 </nav>
1150 );
1151}
1152
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1153/** Your own settings, drilled into from Your settings: one page each. */
1154function AccountSettingsMenu({ username }: { username: string }) {
1155 const link = (page: AccountSettingsPage, icon: ReactNode) => (
1156 <SidebarLink to={`/settings/${page}`} icon={icon}>
1157 {ACCOUNT_SETTINGS[page].title}
1158 </SidebarLink>
Account settings in the same shape as a workspace's1159 );
1160 return (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1161 <nav aria-label="Your settings" className={PANEL}>
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1162 <BackRow to="/" label="Your settings" context={username} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1163 <div className="mt-2 space-y-px">
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1164 {link("profile", <CircleUserRound size={15} />)}
1165 {link("emails", <Mail size={15} />)}
1166 {link("invites", <Ticket size={15} />)}
1167 </div>
1168 <Rule />
1169 <div className="space-y-px">
1170 {link("keys", <Fingerprint size={15} />)}
1171 {link("tokens", <KeyRound size={15} />)}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1172 </div>
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1173 <Rule />
1174 <div className="space-y-px">
1175 {link("github", <GithubMark className="size-[15px]" />)}
1176 {link("applications", <Plug size={15} />)}
1177 </div>
1178 <Rule />
1179 <div className="space-y-px">
Merge main (membership, two-factor, GitHub repo roles) into tokens1180 {link("two-factor", <ShieldCheck size={15} />)}
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1181 {link("security-log", <History size={15} />)}
1182 </div>
Account settings in the same shape as a workspace's1183 </nav>
1184 );
1185}
1186
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1187/**
1188 * Signing in and signing up, in place of the account for a visitor. Signing
1189 * in brings them back to the page they are on.
1190 */
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1191/** Help, status and the fine print under a visitor's sign-in buttons. */
1192function VisitorLinks() {
1193 const status = useSiteStatus();
Merge branch 'worktree-agent-ad7c6d88d93adc817'1194 const quiet = `${LEGAL_LINK} hover:bg-raised hover:text-fg focus-visible:ring-2 focus-visible:ring-accent/60`;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1195 const dot = (
1196 <span aria-hidden="true" className="text-[0.625rem] text-line-strong">
1197 ·
1198 </span>
1199 );
1200 return (
1201 // Two short rows, so a narrow sidebar never starts a line with a dot.
1202 <nav aria-label="About g1t" className="pt-0.5">
1203 <div className="flex items-center gap-0.5">
1204 <a href={STATUS_URL} className={`${quiet} gap-1.5`}>
1205 <StatusDot state={status?.overall.state ?? null} />
1206 Status
1207 </a>
1208 {dot}
1209 <Link to="/support" className={quiet}>
1210 Support
1211 </Link>
1212 </div>
1213 <div className="flex items-center gap-0.5">
1214 {LEGAL_LINKS.map(([label, to], index) => (
1215 <span key={to} className="flex items-center gap-0.5">
1216 {index > 0 && dot}
1217 <Link to={to} className={quiet}>
1218 {label}
1219 </Link>
1220 </span>
1221 ))}
1222 </div>
1223 </nav>
1224 );
1225}
1226
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1227function VisitorPanel() {
1228 const { pathname, search } = useLocation();
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1229 const signUp = useSignUpCopy();
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1230 return (
1231 <div className="space-y-2">
1232 <p className="px-1 text-xs text-muted">Sign in to open issues, review pull requests and run agents.</p>
1233 <div className="grid grid-cols-2 gap-2">
1234 <Link
1235 to={withNext("/login", pathname + search)}
1236 className="flex h-9 items-center justify-center gap-1.5 rounded-md border border-line text-[0.8125rem] font-medium text-fg/90 transition-colors hover:border-line-strong hover:bg-raised hover:text-fg"
1237 >
1238 <LogIn size={14} />
1239 Sign in
1240 </Link>
1241 <Link
1242 to={withNext("/register", pathname + search)}
1243 className="flex h-9 items-center justify-center rounded-md bg-fg text-[0.8125rem] font-medium text-bg transition-colors hover:bg-white"
1244 >
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1245 {signUp.primary}
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1246 </Link>
1247 </div>
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1248 <VisitorLinks />
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1249 </div>
1250 );
1251}
1252
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1253function Sidebar({
1254 user,
1255 shell,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1256 missing = false,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1257 onFind,
1258 onClose,
1259}: {
1260 user: User | null;
1261 shell: ShellData;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1262 /** The page is a 404: the address names nothing the viewer can see. */
1263 missing?: boolean;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1264 onFind: () => void;
1265 /** In the sheet on a small screen: closing it, at the end of the top row. */
1266 onClose?: () => void;
1267}) {
Agents as a team: lifecycle, merge queue, billing and a new shell1268 const ws = shell.workspace;
A catalogue of model providers, and settings that feel like settings1269 const { pathname } = useLocation();
1270 const going = useNavigation().location?.pathname;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1271 // Where the sidebar is drilled to follows the page being gone to, so it
1272 // moves as the link is followed, not once the page arrives.
1273 const target = going ?? pathname;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1274 // On a 404, the address is not a place: no list is drilled into from it,
1275 // except the project's own when the project is real and visible and only
1276 // something inside it is missing (a file, a commit, an issue).
1277 const lost = missing && going == null;
1278 const inSettings = !lost && ws != null && SETTINGS_PAGE.exec(target)?.[1]?.toLowerCase() === ws.slug;
1279 const inAccount = !lost && target === "/settings" || target.startsWith("/settings/");
A repository has its own sidebar, as settings do1280 const active = shell.repo;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1281 // In a repository, or on the way into one, its own list.
A repository has its own sidebar, as settings do1282 const repoPath = /^\/([^/]+)\/([^/-][^/]*)(\/|$)/.exec(target);
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all1283 const reserved = new Set(["settings", "explore", "search", "new", "u", "pricing", "avatars", "workspaces", "login", "logout", "register", "verify", "forgot", "reset", "device", "oauth", "policies", "security", "support", "status", "invite", "inbox", ".well-known"]);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1284 // An invitation to a repository is answered before its menu means anything.
1285 const inRepo =
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1286 repoPath != null &&
1287 !reserved.has(repoPath[1]) &&
1288 repoPath[2] !== "-" &&
1289 !/^\/[^/]+\/[^/]+\/invitations\/?$/.test(target) &&
1290 (!lost || sameRepo(active, { namespace: repoPath[1]!, name: repoPath[2]! }));
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1291 // A project's settings, one level further in.
Project settings move into the sidebar, grouped, with a heading per page1292 const inRepoSettings = inRepo && REPO_SETTINGS_PAGE.test(target);
Sidebar: no settings flash on the way into a repository, and a better slide1293 // The repository the menu is for: the one loaded if it is the one being
1294 // gone to, else what the address says, at once.
1295 const targetRepo = inRepo && repoPath ? { namespace: repoPath[1], name: repoPath[2] } : null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1296 const guessed: MenuRepo | null = targetRepo
Sidebar: no settings flash on the way into a repository, and a better slide1297 ? sameRepo(active, targetRepo)
1298 ? active
1299 : {
1300 ...targetRepo,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1301 member: (user?.workspaces ?? []).some((m) => m.slug === targetRepo.namespace.toLowerCase()),
Sidebar: no settings flash on the way into a repository, and a better slide1302 }
1303 : null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1304 // The viewer's role there, from the repository's page once it has loaded.
1305 const page = useRouteLoaderData("routes/repo/layout") as
1306 | { repo?: { namespace: string; name: string }; access?: ViewerAccess }
1307 | undefined;
1308 const menuRepo: MenuRepo | null =
1309 guessed && page?.access && page.repo && sameRepo(page.repo, guessed)
1310 ? { ...guessed, member: page.access.insider, settings: seesSettings(page.access), can: page.access.can }
1311 : guessed;
1312
1313 // The way from the main list to the one shown.
1314 const trail: Level[] = [{ key: "main", node: <MainMenu user={user} shell={shell} /> }];
1315 if (user && inAccount) {
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1316 trail.push({ key: "account", node: <AccountSettingsMenu username={user.username} /> });
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1317 } else if (ws && inSettings) {
1318 trail.push({ key: `settings:${ws.slug}`, node: <SettingsMenu slug={ws.slug} owner={ws.role === "owner"} /> });
1319 } else if (menuRepo) {
1320 const key = `repo:${menuRepo.namespace}/${menuRepo.name}`.toLowerCase();
1321 // Out of a project: to its workspace's projects when they are yours.
1322 const home = (user?.workspaces ?? []).find((m) => m.slug === menuRepo.namespace.toLowerCase());
1323 const back = !user
1324 ? { to: "/explore", label: "Explore" }
1325 : home
Navigation: the workspace's own page with tabs, pins and recent in the sidebar1326 ? { to: `/${home.slug}/-/projects`, label: "All projects" }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1327 : { to: "/", label: "Mission control" };
1328 trail.push({
1329 key,
1330 node: (
1331 <RepoMenu
1332 repo={menuRepo}
1333 isPrivate={[...shell.repos, ...(shell.shared ?? [])].some((repo) => sameRepo(repo, menuRepo) && repo.isPrivate)}
1334 back={back}
1335 />
1336 ),
1337 });
1338 if (inRepoSettings) trail.push({ key: `${key}:settings`, node: <RepoSettingsMenu repo={menuRepo} /> });
1339 }
1340
Agents as a team: lifecycle, merge queue, billing and a new shell1341 return (
1342 <div className="flex h-full flex-col">
A Vercel-like sidebar and a new mark1343 {/* The same height and rule as the top bar, so the two read as one line. */}
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar1344 <div className="flex h-14 shrink-0 items-center gap-1 border-b border-line pr-2 pl-2.5">
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1345 {user ? (
1346 <>
The 1 beside the workspace switcher again, in a square hover the switcher's height1347 {/* The 1 alone beside the workspace: a square hover the height of the
1348 switcher, the mark as tall as the workspace avatar. */}
1349 <Link to="/" aria-label="g1t home" className="flex size-9 shrink-0 items-center justify-center rounded-md transition-colors hover:bg-raised">
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar1350 <Mark tight className="h-4 w-auto" />
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1351 </Link>
1352 <span className="shrink-0 text-line-strong" aria-hidden="true">
1353 /
1354 </span>
1355 <WorkspaceSwitcher user={user} shell={shell} />
1356 </>
1357 ) : (
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1358 <Link to="/" aria-label="g1t home" className="mr-auto flex h-9 items-center rounded-md px-2 transition-colors hover:bg-raised">
1359 <Logo className="text-[1.25rem]" />
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1360 </Link>
1361 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1362 {onClose && (
1363 <button
1364 type="button"
1365 aria-label="Close menu"
1366 onClick={onClose}
1367 className="flex size-9 shrink-0 items-center justify-center rounded-md text-faint hover:bg-raised hover:text-fg"
1368 >
1369 <X size={16} />
1370 </button>
1371 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1372 </div>
A Vercel-like sidebar and a new mark1373 <div className="px-2 pt-3">
1374 <button
1375 type="button"
1376 onClick={onFind}
Two limits, real invoices, trust that grows by itself, sales signals1377 className="flex h-9 w-full items-center gap-2 rounded-md bg-surface px-2.5 text-[0.8125rem] text-faint ring-1 ring-line transition-colors hover:text-muted hover:ring-line-strong"
A Vercel-like sidebar and a new mark1378 >
1379 <Search size={14} />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1380 <span className="grow text-left">Search or jump to…</span>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1381 <PaletteKey className="rounded bg-raised px-1.5 font-mono text-[0.625rem] text-muted ring-1 ring-line" />
A Vercel-like sidebar and a new mark1382 </button>
Agents as a team: lifecycle, merge queue, billing and a new shell1383 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1384 <Drill trail={trail} />
1385 {/* The account, or signing in, as one row at the very bottom. */}
1386 <div className="shrink-0 border-t border-line p-2">
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1387 {user ? <AccountMenu user={user} /> : <VisitorPanel />}
A Vercel-like sidebar and a new mark1388 </div>
1389 </div>
1390 );
1391}
1392
1393/** Words for the sections a path can end in. */
1394const SECTIONS: Record<string, string> = {
1395 issues: "Issues",
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1396 milestones: "Milestones",
1397 labels: "Labels",
A Vercel-like sidebar and a new mark1398 pulls: "Pull requests",
1399 queue: "Merge queue",
1400 commits: "Commits",
A menu shaped by how g1t works, Soon pages for every promise, and sudo for sales1401 plans: "Outcomes",
1402 actions: "Workflows",
1403 soon: "Soon",
Deployments: a preview for every pull request, production on g1t.page1404 deployments: "Deployments",
Projects: what a workspace builds and runs, first on every page1405 repository: "Repository",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1406 access: "Access",
1407 invitations: "Invitation",
1408 repositories: "Repositories",
Branches and Tags pages, each file's last commit, and the branch menu on files1409 branches: "Branches",
1410 tags: "Tags",
Compare: any two branches, the commits one has that the other does not, and the diff, with Open a pull request1411 compare: "Compare",
Project dependencies: addresses, preview stacks, Affects, and agents who know1412 dependencies: "Dependencies",
A menu shaped by how g1t works, Soon pages for every promise, and sudo for sales1413 code: "Files",
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs1414 secrets: "Secrets and variables",
A Vercel-like sidebar and a new mark1415 settings: "Settings",
Navigation: the workspace's own page with tabs, pins and recent in the sidebar1416 people: "People",
1417 projects: "Projects",
1418 teams: "Teams",
A Vercel-like sidebar and a new mark1419 tokens: "Access tokens",
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules1420 "personal-access-tokens": "Personal access tokens",
A Vercel-like sidebar and a new mark1421 usage: "Usage",
Site: the AI Gateway page's breadcrumb reads "AI Gateway"1422 gateway: "AI Gateway",
Secrets and variables: one list, rows per environment, for workflows and deployments1423 billing: "Billing and plans",
A catalogue of model providers, and settings that feel like settings1424 integrations: "Integrations",
Webhooks: every event, to your own addresses, signed and retried1425 webhooks: "Webhooks",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1426 domains: "Domains",
1427 guardrails: "Guardrails",
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1428 rules: "Rules",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1429 audit: "Audit log",
Files pages say Files in the trail and keep the description in About only1430 tree: "Files",
1431 blob: "Files",
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail1432 agents: "Agents",
1433 context: "Context",
1434 memory: "Memory",
1435 security: "Security",
1436 packages: "Packages",
1437 runners: "Runners",
Merge branch 'worktree-agent-a3abfcce648e87dca'1438 environments: "Environments",
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail1439 workflows: "Workflows",
1440 observability: "Observability",
1441 insights: "Insights",
1442 sessions: "Sessions",
A Vercel-like sidebar and a new mark1443};
1444
Merge branch 'worktree-agent-a3abfcce648e87dca'1445/** Settings pages whose name differs from the section's of the same word. */
1446const SETTINGS_SECTIONS: Record<string, string> = { branches: "Branches and merging", actions: "Actions" };
1447
A Vercel-like sidebar and a new mark1448/** Where the page is, as a trail of links: workspace / repository / section. */
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1449function Breadcrumbs({ pathname, missing, repo }: { pathname: string; missing?: boolean; repo?: ShellData["repo"] }) {
1450 // A 404 names nothing from the address, unless the project is real and
1451 // visible and only something inside it is missing.
1452 const visibleRepo = /^\/([^/]+)\/([^/-][^/]*)(\/|$)/.exec(pathname);
1453 if (missing && !(visibleRepo && sameRepo(repo ?? null, { namespace: visibleRepo[1]!, name: visibleRepo[2]! }))) {
1454 return <span className="text-sm font-medium">Not found</span>;
1455 }
A Vercel-like sidebar and a new mark1456 const parts = pathname.split("/").filter(Boolean);
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all1457 const reserved = ["settings", "explore", "new", "search", "workspaces", "policies", "security", "support", "status", "invite", "inbox"];
A Vercel-like sidebar and a new mark1458 if (parts.length === 0) return <span className="text-sm font-medium">Mission control</span>;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1459 // Your settings: Settings / Emails.
1460 if (parts[0] === "settings") {
1461 const page = accountSettingsPage(pathname);
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1462 return <Trail trail={[{ label: "Settings", to: FIRST_SETTINGS_PAGE }, ...(page ? [{ label: ACCOUNT_SETTINGS[page].title, to: `/settings/${page}` }] : [])]} />;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1463 }
A Vercel-like sidebar and a new mark1464 if (reserved.includes(parts[0]!)) {
1465 const words: Record<string, string> = {
Account settings in the same shape as a workspace's1466 settings: "Account",
A Vercel-like sidebar and a new mark1467 explore: "Explore",
Projects live: fixes from walking them end to end1468 new: "New project",
A Vercel-like sidebar and a new mark1469 search: "Search",
1470 workspaces: "New workspace",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1471 policies: "Policies",
1472 security: "Security",
1473 support: "Support",
1474 status: "Status",
1475 invite: "Invite",
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all1476 inbox: "Inbox",
A Vercel-like sidebar and a new mark1477 };
1478 return <span className="text-sm font-medium">{words[parts[0]!]}</span>;
1479 }
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1480 // A person's profile, by their handle.
1481 if (parts[0] === "u" && parts[1]) {
1482 return <span className="truncate font-mono text-[0.8125rem] font-medium">@{parts[1]}</span>;
1483 }
A Vercel-like sidebar and a new mark1484 const [owner, second, third, fourth] = parts;
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1485 const trail: Crumb[] = [{ label: owner!, to: `/${owner}`, mono: true }];
A Vercel-like sidebar and a new mark1486 if (second === "-") {
A catalogue of model providers, and settings that feel like settings1487 const page = `/${owner}/-/${third}`;
1488 if (third && SETTINGS_PAGE.test(page)) {
1489 trail.push({ label: "Settings", to: `/${owner}/-/settings` });
1490 trail.push({ label: third === "settings" ? "General" : (SECTIONS[third] ?? third), to: page });
1491 } else if (third) trail.push({ label: SECTIONS[third] ?? third, to: page });
A Vercel-like sidebar and a new mark1492 } else if (second) {
1493 const repo = `/${owner}/${second}`;
1494 trail.push({ label: second, to: repo, mono: true });
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1495 if (third === "pull" && fourth) trail.push({ label: `Pull request #${fourth}`, short: `#${fourth}`, to: `${repo}/pull/${fourth}` });
1496 else if (third === "issues" && fourth && fourth !== "new") trail.push({ label: `Issue #${fourth}`, short: `#${fourth}`, to: `${repo}/issues/${fourth}` });
A Vercel-like sidebar and a new mark1497 else if (third === "commit" && fourth) trail.push({ label: fourth.slice(0, 7), to: `${repo}/commit/${fourth}`, mono: true });
Secrets and variables: one list, rows per environment, for workflows and deployments1498 else if (third && SECTIONS[third]) {
1499 trail.push({ label: SECTIONS[third]!, to: `${repo}/${third}` });
1500 // A settings page names which one: Settings / Secrets and variables.
1501 if (third === "settings" && fourth && SECTIONS[fourth]) {
Merge branch 'worktree-agent-a3abfcce648e87dca'1502 trail.push({ label: SETTINGS_SECTIONS[fourth] ?? SECTIONS[fourth]!, to: `${repo}/settings/${fourth}` });
Secrets and variables: one list, rows per environment, for workflows and deployments1503 }
1504 }
A Vercel-like sidebar and a new mark1505 }
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1506 return <Trail trail={trail} />;
1507}
1508
1509/** One link of the trail; `short` is what a phone shows when it is the page itself. */
1510type Crumb = { label: string; to: string; mono?: boolean; short?: string };
1511
1512/**
1513 * The trail of links. On a phone there is room for one: the page itself,
1514 * with a way back to where it sits; the whole trail from a wider screen up.
1515 */
1516function Trail({ trail }: { trail: Crumb[] }) {
1517 const up = trail.length > 1 ? trail[trail.length - 2]! : null;
A Vercel-like sidebar and a new mark1518 return (
1519 <nav aria-label="Where you are" className="flex min-w-0 items-center gap-1.5 text-sm">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1520 {up && (
1521 <Link
1522 to={up.to}
1523 prefetch="intent"
1524 aria-label={`Back to ${up.label}`}
1525 className="-ml-1 flex size-8 shrink-0 items-center justify-center rounded-md text-muted transition-colors hover:bg-raised hover:text-fg sm:hidden"
1526 >
1527 <ChevronLeft size={16} />
1528 </Link>
1529 )}
1530 {trail.map((crumb, index) => {
1531 const last = index === trail.length - 1;
1532 return (
1533 <span key={index} className={`min-w-0 items-center gap-1.5 ${last ? "flex" : "hidden sm:flex"}`}>
1534 {index > 0 && <span className="hidden text-line-strong sm:inline">/</span>}
1535 <Link
1536 to={crumb.to}
1537 prefetch="intent"
1538 aria-current={last ? "page" : undefined}
1539 className={`truncate rounded px-1 py-0.5 transition-colors hover:bg-raised ${
1540 last ? "font-medium text-fg" : "text-muted hover:text-fg"
1541 } ${crumb.mono ? "font-mono text-[0.8125rem]" : ""}`}
1542 >
1543 {last && crumb.short ? (
1544 <>
1545 <span className="sm:hidden">{crumb.short}</span>
1546 <span className="hidden sm:inline">{crumb.label}</span>
1547 </>
1548 ) : (
1549 crumb.label
1550 )}
1551 </Link>
1552 </span>
1553 );
1554 })}
A Vercel-like sidebar and a new mark1555 </nav>
Agents as a team: lifecycle, merge queue, billing and a new shell1556 );
1557}
1558
Search across all of g1t, Explore, and a command palette1559type Command = PaletteCommand;
Agents as a team: lifecycle, merge queue, billing and a new shell1560
1561/** Everything the palette can jump to, from what the sidebar already knows. */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1562function commandsFor(user: User | null, shell: ShellData, here: string, signUpLabel = "Sign up"): Command[] {
1563 if (!user) return visitorCommands(shell, here, signUpLabel);
Agents as a team: lifecycle, merge queue, billing and a new shell1564 const commands: Command[] = [
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1565 { label: "Mission control", to: "/", icon: <House size={15} /> },
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step1566 ...(shell.repos.length > 0
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1567 ? [{ label: "Put an agent on it", hint: "Open an issue and assign g1t", to: "/?agent=new", icon: <Sparkles size={15} /> }]
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step1568 : []),
Agents as a team: lifecycle, merge queue, billing and a new shell1569 { label: "Explore repositories", to: "/explore", icon: <Compass size={15} /> },
Search across all of g1t, Explore, and a command palette1570 { label: "Search g1t", hint: "Repositories, code, issues, people", to: "/search", icon: <Search size={15} /> },
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all1571 { label: "Inbox", hint: "What needs you", to: "/inbox", icon: <Bell size={15} /> },
Projects: what a workspace builds and runs, first on every page1572 { label: "New project", to: "/new", icon: <Plus size={15} /> },
Agents as a team: lifecycle, merge queue, billing and a new shell1573 { label: "New workspace", to: "/workspaces/new", icon: <Plus size={15} /> },
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1574 { label: "Your settings", to: FIRST_SETTINGS_PAGE, icon: <Settings size={15} /> },
1575 ...(Object.keys(ACCOUNT_SETTINGS) as AccountSettingsPage[])
1576 .filter((page) => page !== "profile")
1577 .map((page) => ({ label: ACCOUNT_SETTINGS[page].title, hint: "Your settings", to: `/settings/${page}`, icon: <Settings size={15} /> })),
Agents as a team: lifecycle, merge queue, billing and a new shell1578 ];
1579 const repo = shell.repo;
1580 if (repo) {
1581 const base = `/${repo.namespace}/${repo.name}`;
1582 const name = `${repo.namespace}/${repo.name}`;
1583 commands.unshift(
Projects: what a workspace builds and runs, first on every page1584 { label: "Overview", hint: name, to: base, icon: <LayoutGrid size={15} /> },
1585 { label: "Code", hint: name, to: `${base}/code`, icon: <Code2 size={15} /> },
Agents as a team: lifecycle, merge queue, billing and a new shell1586 { label: "Issues", hint: name, to: `${base}/issues`, icon: <CircleDot size={15} /> },
1587 { label: "New issue", hint: name, to: `${base}/issues/new`, icon: <Plus size={15} /> },
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step1588 ...(repo.member
1589 ? [{ label: `Put an agent on ${repo.name}`, hint: name, to: `${base}/issues/new?agent=1`, icon: <Sparkles size={15} /> }]
1590 : []),
Agents as a team: lifecycle, merge queue, billing and a new shell1591 { label: "Pull requests", hint: name, to: `${base}/pulls`, icon: <GitPullRequest size={15} /> },
1592 { label: "Commits", hint: name, to: `${base}/commits`, icon: <History size={15} /> },
1593 ...(repo.member
1594 ? [
1595 { label: "Plan work", hint: name, to: `${base}/plans`, icon: <ListTree size={15} /> },
Projects: what a workspace builds and runs, first on every page1596 { label: "Deployments", hint: name, to: `${base}/deployments`, icon: <Rocket size={15} /> },
1597 { label: "Secrets and variables", hint: name, to: `${base}/settings/secrets`, icon: <Lock size={15} /> },
1598 { label: "Project settings", hint: name, to: `${base}/settings`, icon: <Settings size={15} /> },
1599 { label: "Repository settings", hint: name, to: `${base}/settings/repository`, icon: <Settings size={15} /> },
Agents as a team: lifecycle, merge queue, billing and a new shell1600 ]
1601 : []),
1602 );
1603 }
1604 for (const membership of user.workspaces ?? []) {
1605 commands.push(
Workspace names and icons, and a component kit for every control1606 { label: displayName(membership), hint: `Workspace · ${membership.slug}`, to: `/${membership.slug}`, icon: <Avatar name={membership.slug} image={membership.avatar} size={15} square /> },
Navigation: the workspace's own page with tabs, pins and recent in the sidebar1607 { label: "All projects", hint: membership.slug, to: `/${membership.slug}/-/projects`, icon: <LayoutGrid size={15} /> },
1608 { label: "People", hint: membership.slug, to: `/${membership.slug}/-/people`, icon: <Users size={15} /> },
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1609 { label: "Usage", hint: membership.slug, to: `/${membership.slug}/-/usage`, icon: <BarChart3 size={15} /> },
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens1610 { label: "AI Gateway", hint: `${membership.slug} · Usage`, to: `/${membership.slug}/-/gateway`, icon: <Network size={15} /> },
Projects: what a workspace builds and runs, first on every page1611 { label: "Billing and plans", hint: `${membership.slug} · Settings`, to: `/${membership.slug}/-/billing`, icon: <CreditCard size={15} /> },
A catalogue of model providers, and settings that feel like settings1612 { label: "Access tokens", hint: `${membership.slug} · Settings`, to: `/${membership.slug}/-/tokens`, icon: <KeyRound size={15} /> },
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1613 { label: "Integrations", hint: membership.slug, to: `/${membership.slug}/-/integrations`, icon: <Plug size={15} /> },
Agents as a team: lifecycle, merge queue, billing and a new shell1614 );
1615 }
1616 for (const listed of shell.repos) {
1617 commands.push({
Projects: what a workspace builds and runs, first on every page1618 label: listed.title ?? listed.name,
1619 hint: `${listed.namespace} · Project`,
Agents as a team: lifecycle, merge queue, billing and a new shell1620 to: `/${listed.namespace}/${listed.name}`,
Projects: what a workspace builds and runs, first on every page1621 icon: <Box size={15} />,
Agents as a team: lifecycle, merge queue, billing and a new shell1622 });
1623 }
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step1624 // "Put an agent on …": one per project, after the projects themselves.
1625 for (const listed of shell.repos) {
1626 if (repo && listed.namespace === repo.namespace && listed.name === repo.name) continue;
1627 commands.push({
1628 label: `Put an agent on ${listed.title ?? listed.name}`,
1629 hint: `${listed.namespace}/${listed.name}`,
1630 to: `/${listed.namespace}/${listed.name}/issues/new?agent=1`,
1631 icon: <Sparkles size={15} />,
1632 });
1633 }
Agents as a team: lifecycle, merge queue, billing and a new shell1634 return commands;
1635}
1636
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1637/** What the palette offers a visitor: browsing, the project they are in, and signing in. */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1638function visitorCommands(shell: ShellData, here: string, signUpLabel: string): Command[] {
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1639 const commands: Command[] = [];
1640 const repo = shell.repo;
1641 if (repo) {
1642 const base = `/${repo.namespace}/${repo.name}`;
1643 const name = `${repo.namespace}/${repo.name}`;
1644 commands.push(
1645 { label: "Overview", hint: name, to: base, icon: <LayoutGrid size={15} /> },
1646 { label: "Code", hint: name, to: `${base}/code`, icon: <Code2 size={15} /> },
1647 { label: "Issues", hint: name, to: `${base}/issues`, icon: <CircleDot size={15} /> },
1648 { label: "Pull requests", hint: name, to: `${base}/pulls`, icon: <GitPullRequest size={15} /> },
1649 { label: "Commits", hint: name, to: `${base}/commits`, icon: <History size={15} /> },
1650 );
1651 }
1652 commands.push(
1653 { label: "Explore", hint: "Public projects", to: "/explore", icon: <Compass size={15} /> },
1654 { label: "Search g1t", hint: "Repositories, code, issues, people", to: "/search", icon: <Search size={15} /> },
1655 { label: "Pricing", to: "/pricing", icon: <CreditCard size={15} /> },
1656 { label: "Documentation", to: "https://docs.g1t.sh/", icon: <BookOpen size={15} /> },
1657 { label: "Sign in", to: withNext("/login", here), icon: <LogIn size={15} /> },
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1658 { label: signUpLabel, to: withNext("/register", here), icon: <Plus size={15} /> },
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1659 );
1660 return commands;
1661}
1662
Agents as a team: lifecycle, merge queue, billing and a new shell1663/**
Merge branch 'worktree-agent-aca4790ea223aa235'1664 * The page while the next one loads: after a moment it dims, under the
1665 * bar below, so it is plainly on its way out rather than frozen, and the
1666 * next page fades up from there. Sending a form does not dim it: the
1667 * form's button says it is working. Spread on the element holding the page.
1668 */
1669export function useLeaving() {
1670 const navigation = useNavigation();
1671 const method = navigation.formMethod?.toUpperCase();
1672 const leaving = navigation.state === "loading" && (method == null || method === "GET");
1673 return {
1674 "aria-busy": leaving || undefined,
1675 className: `transition-opacity motion-reduce:transition-none ${leaving ? "opacity-60 delay-200 duration-300" : "duration-150"}`,
1676 };
1677}
1678
1679/**
Agents as a team: lifecycle, merge queue, billing and a new shell1680 * A bar across the top of the page while the next one loads, as GitHub
1681 * has: it appears at once, creeps towards the end while waiting, then fills
1682 * and fades when the page arrives.
1683 */
1684export function Progress() {
1685 const navigation = useNavigation();
1686 const busy = navigation.state !== "idle";
1687 const [width, setWidth] = useState(0);
1688 const [visible, setVisible] = useState(false);
1689 useEffect(() => {
1690 if (busy) {
1691 setVisible(true);
1692 setWidth(12);
1693 // Slows as it goes, never quite arriving until the page does.
1694 const timer = setInterval(() => setWidth((now) => now + (90 - now) * 0.12), 200);
1695 return () => clearInterval(timer);
1696 }
1697 setWidth((now) => (now > 0 ? 100 : 0));
1698 const fade = setTimeout(() => setVisible(false), 250);
1699 const reset = setTimeout(() => setWidth(0), 550);
1700 return () => {
1701 clearTimeout(fade);
1702 clearTimeout(reset);
1703 };
1704 }, [busy]);
1705 return (
1706 <div
1707 aria-hidden="true"
1708 className={`pointer-events-none fixed inset-x-0 top-0 z-[60] h-[3px] transition-opacity duration-300 ${
1709 visible ? "opacity-100" : "opacity-0"
1710 }`}
1711 >
1712 <div
1713 className="h-full bg-accent shadow-[0_0_10px_var(--color-accent)] transition-[width] duration-200 ease-out"
1714 style={{ width: `${width}%` }}
1715 />
1716 </div>
1717 );
1718}
1719
1720/**
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1721 * The app: a sidebar with the workspace, its repositories and the sections
1722 * of the one being looked at; a slim bar with search and the account; and
1723 * the page. A visitor who is not signed in gets the same frame, with
1724 * Explore and Search in place of the workspace, and signing in in place of
1725 * the account.
Agents as a team: lifecycle, merge queue, billing and a new shell1726 */
1727export function AppShell({
1728 user,
1729 shell,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1730 missing = false,
Agents as a team: lifecycle, merge queue, billing and a new shell1731 banner,
1732 children,
1733}: {
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1734 user: User | null;
Agents as a team: lifecycle, merge queue, billing and a new shell1735 shell: ShellData;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1736 /** The page is a 404 (see Sidebar). */
1737 missing?: boolean;
Agents as a team: lifecycle, merge queue, billing and a new shell1738 banner?: ReactNode;
1739 children: ReactNode;
1740}) {
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1741 const { pathname, search } = useLocation();
Agents as a team: lifecycle, merge queue, billing and a new shell1742 const [drawer, setDrawer] = useState(false);
1743 const [palette, setPalette] = useState(false);
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1744 const here = pathname + search;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1745 const signUpLabel = useSignUpCopy().primary;
1746 const commands = useMemo(() => commandsFor(user, shell, here, signUpLabel), [user, shell, here, signUpLabel]);
Agents as a team: lifecycle, merge queue, billing and a new shell1747
1748 // A new page closes the drawer on small screens.
1749 useEffect(() => setDrawer(false), [pathname]);
Fast pages, required checks on the branch, self-hosted runners, honest incidents1750 // Mission control's code, fetched while the browser is idle, so going
1751 // home never waits on it (routes/home.tsx).
1752 useEffect(() => {
1753 if (!user) return;
1754 const load = () => void import("./mission-control").catch(() => undefined);
1755 const idle = (window as { requestIdleCallback?: (callback: () => void) => number }).requestIdleCallback;
1756 if (idle) idle(load);
1757 else setTimeout(load, 1500);
1758 }, [user]);
Search across all of g1t, Explore, and a command palette1759 usePaletteShortcut(() => setPalette((open) => !open));
Merge branch 'worktree-agent-aca4790ea223aa235'1760 const leaving = useLeaving();
Agents as a team: lifecycle, merge queue, billing and a new shell1761
1762 return (
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1763 // The phone's menu is a sheet: a dialog that holds focus, closes on
1764 // Escape or a tap outside, and gives focus back to the menu button.
1765 <Sheet open={drawer} onOpenChange={setDrawer}>
Agents as a team: lifecycle, merge queue, billing and a new shell1766 <div className="min-h-screen">
1767 <Progress />
1768 <aside className="fixed inset-y-0 left-0 z-40 hidden w-64 border-r border-line bg-[color-mix(in_srgb,var(--color-surface)_70%,var(--color-bg))] lg:block">
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1769 <Sidebar user={user} shell={shell} missing={missing} onFind={() => setPalette(true)} />
Agents as a team: lifecycle, merge queue, billing and a new shell1770 </aside>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1771 <SheetContent side="left" showClose={false} aria-describedby={undefined} className="w-72 max-w-[85vw] sm:max-w-72 lg:hidden">
1772 <SheetTitle className="sr-only">Menu</SheetTitle>
1773 <Sidebar
1774 user={user}
1775 shell={shell}
1776 missing={missing}
1777 onFind={() => {
1778 setDrawer(false);
1779 setPalette(true);
1780 }}
1781 onClose={() => setDrawer(false)}
1782 />
1783 </SheetContent>
Agents as a team: lifecycle, merge queue, billing and a new shell1784
Polish: phones, copy boxes, the plan page, the landing page, a real glide1785 <div className="flex min-h-screen min-w-0 flex-col lg:pl-64">
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar1786 <header className="sticky top-0 z-30 flex h-14 items-center gap-3 border-b border-line bg-bg/85 px-4 backdrop-blur sm:px-6">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1787 <SheetTrigger
Agents as a team: lifecycle, merge queue, billing and a new shell1788 aria-label="Open menu"
1789 className="rounded-md p-1.5 text-muted hover:bg-raised hover:text-fg lg:hidden"
1790 >
1791 <Menu size={18} />
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1792 </SheetTrigger>
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1793 <Breadcrumbs pathname={pathname} missing={missing} repo={shell.repo} />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1794 <div className="ml-auto flex items-center gap-1.5">
1795 {/* Search lives in the sidebar ("Search or jump to"); with the sidebar folded away, this opens the same palette. */}
1796 <button
1797 type="button"
1798 aria-label="Search or jump to"
1799 onClick={() => setPalette(true)}
1800 className="flex size-9 items-center justify-center rounded-md text-muted transition-colors hover:bg-raised hover:text-fg lg:hidden"
1801 >
1802 <Search size={16} />
1803 </button>
Navigation: the workspace's own page with tabs, pins and recent in the sidebar1804 {/* All of g1t's public projects: not any one workspace's, so here, not in the sidebar. */}
1805 <NavLink
1806 to="/explore"
1807 prefetch="intent"
1808 aria-label="Explore"
1809 className={({ isActive }) =>
1810 `flex h-9 items-center gap-1.5 rounded-md px-2 text-sm transition-colors hover:bg-raised hover:text-fg sm:px-2.5 ${isActive ? "text-fg" : "text-muted"}`
1811 }
1812 >
1813 <Compass size={16} className="sm:hidden" />
1814 <span className="hidden sm:inline">Explore</span>
1815 </NavLink>
A Vercel-like sidebar and a new mark1816 <a
1817 href="https://docs.g1t.sh/"
Two limits, real invoices, trust that grows by itself, sales signals1818 className="hidden rounded-md px-2.5 py-1.5 text-sm text-muted transition-colors hover:bg-raised hover:text-fg sm:block"
A Vercel-like sidebar and a new mark1819 >
1820 Docs
1821 </a>
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all1822 {user && (
1823 <>
Previews build from any branch; Agent in place of Ask AI; pin from the sidebar1824 <AgentButton />
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all1825 <InboxBell counts={shell.inbox ?? null} />
1826 </>
1827 )}
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1828 {!user ? (
1829 // The sidebar has these too, but on a phone it is folded away.
1830 <Link
1831 to={withNext("/login", here)}
1832 className="flex h-9 items-center rounded-md bg-fg px-3 text-sm font-medium text-bg transition-colors hover:bg-white lg:hidden"
1833 >
1834 Sign in
1835 </Link>
1836 ) : (
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1837 <>
1838 {/* Room between what comes in (the inbox) and what you make. */}
1839 <span aria-hidden="true" className="mx-1 h-5 w-px bg-line" />
Agents as a team: lifecycle, merge queue, billing and a new shell1840 <DropdownMenu>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1841 <Tooltip>
1842 <TooltipTrigger asChild>
1843 <DropdownMenuTrigger
1844 aria-label="Create new"
1845 className="flex h-9 items-center gap-1 rounded-md border border-line px-2 text-fg/90 outline-none transition-colors hover:border-line-strong hover:bg-raised hover:text-fg focus-visible:ring-2 focus-visible:ring-accent data-[state=open]:bg-raised"
1846 >
1847 <Plus size={16} />
1848 <ChevronDown size={13} className="text-muted" />
1849 </DropdownMenuTrigger>
1850 </TooltipTrigger>
1851 <TooltipContent>Create new…</TooltipContent>
1852 </Tooltip>
1853 <DropdownMenuContent align="end" className="min-w-52">
Agents as a team: lifecycle, merge queue, billing and a new shell1854 {shell.repo && (
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1855 <>
1856 <DropdownMenuLabel className="truncate">In {shell.repo.name}</DropdownMenuLabel>
1857 <DropdownMenuItem asChild>
1858 <Link to={`/${shell.repo.namespace}/${shell.repo.name}/issues/new`}>
1859 <CircleDot />
1860 New issue
1861 </Link>
1862 </DropdownMenuItem>
1863 {shell.repo.member && (
1864 <DropdownMenuItem asChild>
1865 <Link to={`/${shell.repo.namespace}/${shell.repo.name}/plans`}>
1866 <ListTree />
1867 Plan work
1868 </Link>
1869 </DropdownMenuItem>
1870 )}
1871 <DropdownMenuSeparator />
1872 </>
Agents as a team: lifecycle, merge queue, billing and a new shell1873 )}
1874 <DropdownMenuItem asChild>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1875 <Link to="/?agent=new">
1876 <Sparkles />
1877 Put an agent on it
1878 </Link>
1879 </DropdownMenuItem>
1880 <DropdownMenuItem asChild>
Agents as a team: lifecycle, merge queue, billing and a new shell1881 <Link to="/new">
Projects: what a workspace builds and runs, first on every page1882 <Box />
1883 New project
Agents as a team: lifecycle, merge queue, billing and a new shell1884 </Link>
1885 </DropdownMenuItem>
1886 <DropdownMenuItem asChild>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1887 <Link to="/new/github">
1888 <GitBranch />
1889 Import from GitHub
1890 </Link>
1891 </DropdownMenuItem>
1892 <DropdownMenuSeparator />
1893 <DropdownMenuItem asChild>
Agents as a team: lifecycle, merge queue, billing and a new shell1894 <Link to="/workspaces/new">
1895 <Users />
1896 New workspace
1897 </Link>
1898 </DropdownMenuItem>
Merge branch 'worktree-agent-ad7c6d88d93adc817'1899 {/* A team in the workspace the sidebar is about, for whoever it lets create one. */}
1900 {shell.workspace &&
1901 (mayCreateTeams(shell.workspace.team_creation, shell.workspace.role) ? (
1902 <DropdownMenuItem asChild>
1903 <Link to={`/${shell.workspace.slug}/-/teams/new`}>
1904 <UsersRound />
1905 New team
1906 </Link>
1907 </DropdownMenuItem>
1908 ) : (
1909 <DropdownMenuItem disabled title="Only owners can create teams in this workspace.">
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1910 <UsersRound />
1911 New team
Merge branch 'worktree-agent-ad7c6d88d93adc817'1912 <span className="ml-auto pl-3 text-xs text-faint">Owners only</span>
1913 </DropdownMenuItem>
1914 ))}
Agents as a team: lifecycle, merge queue, billing and a new shell1915 </DropdownMenuContent>
1916 </DropdownMenu>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1917 </>
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1918 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1919 </div>
1920 </header>
1921 {banner}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1922 <main id="content" tabIndex={-1} {...leaving} className={`min-w-0 grow outline-none ${leaving.className}`}>
Merge branch 'worktree-agent-aca4790ea223aa235'1923 {children}
1924 </main>
Agents as a team: lifecycle, merge queue, billing and a new shell1925 </div>
Search across all of g1t, Explore, and a command palette1926 <CommandPalette
1927 open={palette}
1928 onOpenChange={setPalette}
1929 commands={commands}
1930 repo={shell.repo ? `${shell.repo.namespace}/${shell.repo.name}` : null}
1931 />
Agents as a team: lifecycle, merge queue, billing and a new shell1932 </div>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1933 </Sheet>
Agents as a team: lifecycle, merge queue, billing and a new shell1934 );
1935}

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