Skip to content
2,010 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.

Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules1import { Activity, 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
Chat and workspace agents: channels, DMs and named agents you talk to5import { type Abilities, type InboxCounts, type Membership, type Spike, type User, hasCodeAccess, mayCreateTeams } from "@g1t/contracts";
Agents as a team: lifecycle, merge queue, billing and a new shell6
Not found and error pages have one main landmark: their own when signed out, the shell's when signed in7import { InMain } from "./landmark";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit8import { CommandPalette, type PaletteCommand, PaletteKey, usePaletteShortcut } from "./command-palette";
Previews build from any branch; Agent in place of Ask AI; pin from the sidebar9import { AgentButton, InboxBell } from "./inbox";
10import { PinButton } from "./pin-button";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar11import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
Merge branch 'worktree-agent-ae1299e92e4462012'12import { Hint } from "./ui/hint";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit13import { 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 quotas14import { StatusDot, useSiteStatus } from "./footer";
The 1 beside the workspace switcher again, in a square hover the switcher's height15import { Logo, Mark } from "./logo";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit16import { Avatar, SoonPill } from "./ui";
Fast pages, required checks on the branch, self-hosted runners, honest incidents17import { Skeleton } from "./ui/skeleton";
Agents as a team: lifecycle, merge queue, billing and a new shell18import {
19 DropdownMenu,
20 DropdownMenuContent,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas21 DropdownMenuGroup,
Agents as a team: lifecycle, merge queue, billing and a new shell22 DropdownMenuItem,
23 DropdownMenuLabel,
24 DropdownMenuSeparator,
25 DropdownMenuTrigger,
26} from "./ui/dropdown-menu";
Merge branch 'worktree-agent-ad7c6d88d93adc817'27import { type RoadmapItem, roadmapIn, roadmapItem } from "../lib/roadmap";
Chat and workspace agents: channels, DMs and named agents you talk to28import { type ModeKey, SETTINGS_PAGES, modeOf, sidebarCurrent } from "../lib/workspace-nav";
29import { AgentsSidebar } from "./agents-mode";
30import { ChatSidebar } from "./chat/sidebar";
31import { Rail } from "./rail";
32import { useChatSidebar } from "./chat/actions";
33import { unreadTotals } from "../lib/chat";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look34import { 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 put35import { VISITOR_LINKS, projectPages } from "../lib/chrome";
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas36import { ACCOUNT_SETTINGS, type AccountSettingsPage, FIRST_SETTINGS_PAGE, accountSettingsPage } from "../lib/account-settings";
37import { GithubMark } from "./github";
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put38import { withNext } from "../lib/next";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look39import { useSignUpCopy } from "../lib/registration";
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas40import { STATUS_URL, statusTitle } from "../lib/status";
Navigation: the workspace's own page with tabs, pins and recent in the sidebar41import { 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 quotas42import type { AccountMenuData } from "../routes/settings-menu-json";
Agents as a team: lifecycle, merge queue, billing and a new shell43
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put44/**
45 * What the sidebar needs, worked out by the root loader. For a visitor who
46 * is not signed in there is no workspace, no projects and no usage: only
47 * the project being looked at, if they can see it.
48 */
Agents as a team: lifecycle, merge queue, billing and a new shell49export type ShellData = {
50 /** The workspace the sidebar is about: the one being looked at, or their first. */
Workspace names and icons, and a component kit for every control51 workspace: Membership | null;
Navigation: the workspace's own page with tabs, pins and recent in the sidebar52 /** All its projects, by name, for the palette: `name` is the slug in their address. */
53 repos: ShortcutProject[];
54 /** The person's pinned projects in it, in their order (lib/pins.ts). */
55 pinned?: ShortcutProject[];
56 /** What they opened there last, latest first, leaving out the pinned. */
57 recent?: ShortcutProject[];
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look58 /** Repositories shared with them in workspaces they do not belong to. */
59 shared?: { namespace: string; name: string; isPrivate: boolean }[];
Projects: what a workspace builds and runs, first on every page60 /** The project being looked at, if any, whoever owns it. */
Agents as a team: lifecycle, merge queue, billing and a new shell61 repo: {
62 namespace: string;
63 name: string;
64 member: boolean;
65 issues: number;
66 pulls: number;
67 } | null;
Billing accounts, terms and enterprises; g1t is no longer free68 /** Where the workspace stands against its usage limit, if billing is on. */
69 limit: {
70 exposureMicros: number;
71 ceilingMicros: number | null;
72 state: "ok" | "warning" | "stopped";
73 comped: boolean;
74 } | null;
Free while g1t is being built out; agents can check out their own forks75 /** Whether g1t charges nothing for now, while it is being built out. */
76 free?: boolean;
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request77 /** What its agents have cost since the start of the month. */
Usage while free is shown at cost; agents get rustfmt and clippy78 /** This month's usage: charged, or at cost while g1t is free. */
79 monthUsageMicros: number | null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look80 /** Whether new compute is paused (a spend spike or a hold), and whether the viewer can answer it. */
81 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 all82 /** What is unread in their inbox, for the bell. Absent for a visitor. */
83 inbox?: InboxCounts | null;
Chat and workspace agents: channels, DMs and named agents you talk to84 /** What is unread in chat, for the rail's badge; null when chat did not answer. */
85 chat?: { unread: number; mentions: number } | null;
Agents as a team: lifecycle, merge queue, billing and a new shell86};
87
88function SidebarLink({
89 to,
90 icon,
91 end,
92 count,
93 also,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look94 drill,
Merge branch 'worktree-agent-ad7c6d88d93adc817'95 current: lit,
Agents as a team: lifecycle, merge queue, billing and a new shell96 children,
97}: {
98 to: string;
99 icon: ReactNode;
100 end?: boolean;
101 count?: number;
Projects: what a workspace builds and runs, first on every page102 /** Other path prefixes under which this link is the current one. */
103 also?: string | string[];
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look104 /** It opens a list of its own: a chevron says so, always or on hover. */
105 drill?: boolean | "hover";
Merge branch 'worktree-agent-ad7c6d88d93adc817'106 /** Whether it is the current row, when the list works that out itself. */
107 current?: boolean;
Agents as a team: lifecycle, merge queue, billing and a new shell108 children: ReactNode;
109}) {
110 const { pathname } = useLocation();
111 return (
112 <NavLink
113 to={to}
114 end={end}
115 prefetch="intent"
116 className={({ isActive, isPending }) => {
Projects: what a workspace builds and runs, first on every page117 const current =
Merge branch 'worktree-agent-ad7c6d88d93adc817'118 lit ??
119 (isActive || [also ?? []].flat().some((prefix) => pathname === prefix || pathname.startsWith(prefix + "/")));
A Vercel-like sidebar and a new mark120 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 shell121 current
122 ? "bg-raised font-medium text-fg"
123 : isPending
124 ? "bg-raised/60 text-fg"
125 : "text-muted hover:bg-raised/60 hover:text-fg"
126 }`;
127 }}
128 >
129 <span className="shrink-0 text-faint group-hover:text-muted">{icon}</span>
130 <span className="min-w-0 grow truncate">{children}</span>
131 {count != null && count > 0 && (
132 <span className="rounded bg-line px-1.5 text-[0.6875rem] tabular-nums text-muted">
133 {count}
134 </span>
135 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look136 {drill && (
137 <ChevronRight
138 size={14}
139 aria-hidden="true"
140 className={`-mr-0.5 shrink-0 text-faint transition-opacity ${drill === "hover" ? "opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100" : ""}`}
141 />
142 )}
Agents as a team: lifecycle, merge queue, billing and a new shell143 </NavLink>
144 );
145}
146
Pull requests say where they are live; the project menu shows what is coming147/**
148 * Something a project will have, shown where it will live so people can
149 * see where g1t is going. Not a link: nothing is behind it yet.
150 */
151function SidebarSoon({ icon, children, about }: { icon: ReactNode; children: ReactNode; about: string }) {
152 return (
Merge branch 'worktree-agent-ae1299e92e4462012'153 <Hint label={about} side="right">
154 <div aria-disabled="true" className="flex h-8 cursor-default items-center gap-2.5 rounded-md px-2 text-[0.8125rem] text-faint">
155 <span className="shrink-0 opacity-70">{icon}</span>
156 <span className="grow truncate">{children}</span>
157 <span className="sr-only">{about}</span>
158 <SoonPill />
159 </div>
160 </Hint>
Pull requests say where they are live; the project menu shows what is coming161 );
162}
163
The project menu opens by section, and the workspace has its own panel164/**
A flat project menu with Code first, views as tabs, and Stripe for every owner165 * A page that is coming, as a link to its roadmap page: faint, with Soon,
166 * and marked current on any of its section's Soon pages.
The project menu opens by section, and the workspace has its own panel167 */
A flat project menu with Code first, views as tabs, and Stripe for every owner168function SidebarSoonLink({
The project menu opens by section, and the workspace has its own panel169 to,
170 also,
A flat project menu with Code first, views as tabs, and Stripe for every owner171 icon,
172 about,
Merge branch 'worktree-agent-ad7c6d88d93adc817'173 current: lit,
The project menu opens by section, and the workspace has its own panel174 children,
175}: {
176 to: string;
A flat project menu with Code first, views as tabs, and Stripe for every owner177 also?: string[];
178 icon: ReactNode;
179 about: string;
Merge branch 'worktree-agent-ad7c6d88d93adc817'180 /** Whether it is the current row, when the list works that out itself. */
181 current?: boolean;
The project menu opens by section, and the workspace has its own panel182 children: ReactNode;
183}) {
184 const { pathname } = useLocation();
185 return (
Sidebar: soon rows and the project row keep their styles inside a hint; tiny amounts read as zero186 // The hint wraps a plain box: Radix's asChild merges className as a
187 // string, which would break NavLink's className function.
Merge branch 'worktree-agent-ae1299e92e4462012'188 <Hint label={about} side="right">
Sidebar: soon rows and the project row keep their styles inside a hint; tiny amounts read as zero189 <div>
The project menu opens by section, and the workspace has its own panel190 <NavLink
191 to={to}
192 prefetch="intent"
A flat project menu with Code first, views as tabs, and Stripe for every owner193 className={({ isActive }) => {
Merge branch 'worktree-agent-ad7c6d88d93adc817'194 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 owner195 return `group flex h-8 items-center gap-2.5 rounded-md px-2 text-[0.8125rem] transition-colors ${
196 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 panel197 }`;
198 }}
199 >
A flat project menu with Code first, views as tabs, and Stripe for every owner200 <span className="shrink-0 opacity-80">{icon}</span>
The project menu opens by section, and the workspace has its own panel201 <span className="min-w-0 grow truncate">{children}</span>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit202 <SoonPill />
A menu shaped by how g1t works, Soon pages for every promise, and sudo for sales203 </NavLink>
Sidebar: soon rows and the project row keep their styles inside a hint; tiny amounts read as zero204 </div>
Merge branch 'worktree-agent-ae1299e92e4462012'205 </Hint>
The project menu opens by section, and the workspace has its own panel206 );
207}
208
A flat project menu with Code first, views as tabs, and Stripe for every owner209/** Icons for what spans projects. */
210const WORKSPACE_ICONS: Record<string, ReactNode> = {
211 board: <KanbanSquare size={15} />,
212 roadmap: <GanttChart size={15} />,
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member213 teams: <UsersRound size={15} />,
A flat project menu with Code first, views as tabs, and Stripe for every owner214 packages: <Package size={15} />,
215 fleet: <Bot size={15} />,
216};
A menu shaped by how g1t works, Soon pages for every promise, and sudo for sales217
A flat project menu with Code first, views as tabs, and Stripe for every owner218/** The Soon pages of a section, as paths, so its link is current on them. */
219function soonPaths(base: string, section: RoadmapItem["section"]): string[] {
220 return roadmapIn(section).map((item) => `${base}/soon/${item.key}`);
A menu shaped by how g1t works, Soon pages for every promise, and sudo for sales221}
222
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look223function SidebarGroup({
224 title,
225 action,
226 className = "",
227 children,
228}: {
229 title: string;
230 action?: ReactNode;
231 className?: string;
232 children: ReactNode;
233}) {
Agents as a team: lifecycle, merge queue, billing and a new shell234 return (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look235 <section className={className}>
A Vercel-like sidebar and a new mark236 <div className="mb-1 flex h-6 items-center justify-between px-2">
237 <h2 className="text-xs font-medium text-faint">{title}</h2>
Agents as a team: lifecycle, merge queue, billing and a new shell238 {action}
239 </div>
240 <div className="space-y-px">{children}</div>
241 </section>
242 );
243}
244
Workspace names and icons, and a component kit for every control245/** What a workspace is called where people read it: its display name, else its slug. */
246function displayName(membership: Membership): string {
247 return membership.name?.trim() || membership.slug;
248}
249
Agents as a team: lifecycle, merge queue, billing and a new shell250function WorkspaceSwitcher({ user, shell }: { user: User; shell: ShellData }) {
Workspace names and icons, and a component kit for every control251 const workspace = shell.workspace;
252 const current = workspace?.slug;
253 const label = workspace ? displayName(workspace) : "Choose a workspace";
Agents as a team: lifecycle, merge queue, billing and a new shell254 return (
255 <DropdownMenu>
Workspace names and icons, and a component kit for every control256 {/* The name goes to the workspace; only the arrows switch it. The
257 name gets all the room there is, and the whole of it on hover. */}
Merge branch 'worktree-agent-ae1299e92e4462012'258 <Hint label={workspace ? `${label} · g1t.sh/${workspace.slug}` : undefined}>
A Vercel-like sidebar and a new mark259 <Link
260 to={current ? `/${current}` : "/workspaces/new"}
Fast pages, required checks on the branch, self-hosted runners, honest incidents261 prefetch="intent"
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look262 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 mark263 >
Workspace names and icons, and a component kit for every control264 {workspace ? (
265 <Avatar name={workspace.slug} image={workspace.avatar} size={20} square />
Agents as a team: lifecycle, merge queue, billing and a new shell266 ) : (
Workspace names and icons, and a component kit for every control267 <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 shell268 )}
Workspace names and icons, and a component kit for every control269 <span className="min-w-0 truncate text-[0.8125rem] font-medium">{label}</span>
A Vercel-like sidebar and a new mark270 </Link>
Merge branch 'worktree-agent-ae1299e92e4462012'271 </Hint>
A Vercel-like sidebar and a new mark272 <DropdownMenuTrigger
273 aria-label="Switch workspace"
Usage, Billing settings and prepaid AI credit; fixes from the UX audit274 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 mark275 >
276 <ChevronsUpDown size={14} />
Agents as a team: lifecycle, merge queue, billing and a new shell277 </DropdownMenuTrigger>
Workspace names and icons, and a component kit for every control278 <DropdownMenuContent align="start" className="w-64">
Navigation: the workspace's own page with tabs, pins and recent in the sidebar279 {workspace && (
280 <>
281 {/* The workspace's own page: its projects, packages and people. */}
282 <DropdownMenuItem asChild>
283 <Link to={`/${workspace.slug}`}>
284 <LayoutGrid />
285 Workspace overview
286 </Link>
287 </DropdownMenuItem>
288 <DropdownMenuItem asChild>
289 <Link to={`/${workspace.slug}/-/projects`}>
290 <Box />
291 All projects
292 </Link>
293 </DropdownMenuItem>
294 <DropdownMenuSeparator />
295 </>
296 )}
Agents as a team: lifecycle, merge queue, billing and a new shell297 <DropdownMenuLabel>Workspaces</DropdownMenuLabel>
298 {(user.workspaces ?? []).map((membership) => (
Merge branch 'worktree-agent-ae1299e92e4462012'299 <Hint key={membership.slug} label={displayName(membership)} side="right">
300 <DropdownMenuItem asChild>
301 <Link to={`/${membership.slug}`}>
Workspace names and icons, and a component kit for every control302 <Avatar name={membership.slug} image={membership.avatar} size={24} square />
303 <span className="flex min-w-0 grow flex-col leading-tight">
304 <span className="truncate">{displayName(membership)}</span>
305 <span className="truncate font-mono text-[0.6875rem] text-faint">{membership.slug}</span>
306 </span>
307 <span className="shrink-0 text-[0.6875rem] text-faint capitalize">{membership.role}</span>
308 {membership.slug === current ? (
309 <Check className="shrink-0 text-accent" />
310 ) : (
311 <span className="size-4 shrink-0" aria-hidden="true" />
312 )}
Agents as a team: lifecycle, merge queue, billing and a new shell313 </Link>
314 </DropdownMenuItem>
Merge branch 'worktree-agent-ae1299e92e4462012'315 </Hint>
Agents as a team: lifecycle, merge queue, billing and a new shell316 ))}
317 <DropdownMenuSeparator />
318 <DropdownMenuItem asChild>
319 <Link to="/workspaces/new">
320 <Plus />
321 New workspace
322 </Link>
323 </DropdownMenuItem>
324 </DropdownMenuContent>
325 </DropdownMenu>
A Vercel-like sidebar and a new mark326 );
327}
328
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas329/**
330 * The fine print, as one quiet row at the foot of the account menu and the
331 * visitor's panel: each link a full-height target, left-aligned. In the
332 * menu each is a menu item, so the arrow keys reach it.
333 */
334const LEGAL_LINKS: [string, string][] = [
335 ["Policies", "/policies"],
336 ["Privacy", "/policies/privacy"],
337 ["Security", "/security"],
338];
339const LEGAL_LINK = "flex h-7 items-center rounded px-1.5 text-xs text-faint outline-none transition-colors";
340
341function MenuLegalRow() {
342 return (
343 <div role="group" aria-label="Legal" className="flex items-center gap-0.5 px-0.5 pt-0.5">
344 {LEGAL_LINKS.map(([label, to], index) => (
345 <span key={to} className="flex items-center gap-0.5">
346 {index > 0 && (
347 <span aria-hidden="true" className="text-[0.625rem] text-line-strong">
348 ·
349 </span>
350 )}
351 <DropdownMenuItem asChild className={`${LEGAL_LINK} py-0 data-highlighted:bg-line data-highlighted:text-fg`}>
352 <Link to={to}>{label}</Link>
353 </DropdownMenuItem>
354 </span>
355 ))}
356 </div>
357 );
358}
359
360/** Status's row: the live dot and two or three words, fetched when the menu opens. */
361function StatusSummary({ open }: { open: boolean }) {
362 const status = useSiteStatus(open);
363 const words = statusTitle(status);
364 return (
365 <span className="ml-auto flex min-w-0 items-center gap-1.5 pl-2 text-xs text-faint">
366 {/* The words say it; the dot's own words for screen readers would repeat them. */}
367 <span aria-hidden="true" className="flex">
368 <StatusDot state={status?.overall.state ?? null} />
369 </span>
Fast pages, required checks on the branch, self-hosted runners, honest incidents370 {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 quotas371 </span>
372 );
373}
374
375/** A menu row: an icon, words, and whatever sits at its end. */
376const MENU_ROW = "h-9 gap-2.5 px-2.5 text-[0.8125rem]";
377
Chat and workspace agents: channels, DMs and named agents you talk to378function AccountMenu({ user, rail = false }: { user: User; rail?: boolean }) {
A Vercel-like sidebar and a new mark379 const submit = useSubmit();
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas380 const [open, setOpen] = useState(false);
Fast pages, required checks on the branch, self-hosted runners, honest incidents381 // Name, primary address and invites left: asked for once, as soon as the
382 // 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 quotas383 const details = useFetcher<AccountMenuData | null>({ key: "account-menu" });
Fast pages, required checks on the branch, self-hosted runners, honest incidents384 const prefetch = () => {
385 if (details.state === "idle" && details.data === undefined) details.load("/settings/menu.json");
386 };
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas387 useEffect(() => {
Fast pages, required checks on the branch, self-hosted runners, honest incidents388 if (open) prefetch();
389 // eslint-disable-next-line react-hooks/exhaustive-deps
390 }, [open]);
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas391 const me = details.data ?? null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents392 // Still on its way: shapes where the words will be, the same size.
393 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 quotas394 const profile = `/u/${user.username}`;
A Vercel-like sidebar and a new mark395 return (
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas396 <DropdownMenu open={open} onOpenChange={setOpen}>
Chat and workspace agents: channels, DMs and named agents you talk to397 {rail ? (
398 // On the rail: the avatar alone, with a dot that says you are here.
399 <DropdownMenuTrigger
400 aria-label={`Account menu for ${user.username}`}
401 onPointerEnter={prefetch}
402 onFocus={prefetch}
403 className="relative rounded-full outline-none transition-transform hover:scale-[1.04] focus-visible:ring-2 focus-visible:ring-accent data-[state=open]:ring-2 data-[state=open]:ring-line-strong"
404 >
405 <Avatar name={user.username} image={user.avatar} size={34} />
406 <span aria-hidden="true" className="absolute -right-0.5 -bottom-0.5 size-3 rounded-full bg-success ring-[2.5px] ring-[#0b0b0d]" />
407 </DropdownMenuTrigger>
408 ) : (
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas409 <DropdownMenuTrigger
410 aria-label={`Account menu for ${user.username}`}
Fast pages, required checks on the branch, self-hosted runners, honest incidents411 onPointerEnter={prefetch}
412 onFocus={prefetch}
Merge branch 'worktree-agent-ad7c6d88d93adc817'413 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 quotas414 >
Workspace names and icons, and a component kit for every control415 <Avatar name={user.username} image={user.avatar} size={22} />
A Vercel-like sidebar and a new mark416 <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 quotas417 <ChevronsUpDown size={14} className="shrink-0 text-faint" />
A Vercel-like sidebar and a new mark418 </DropdownMenuTrigger>
Chat and workspace agents: channels, DMs and named agents you talk to419 )}
420 <DropdownMenuContent align={rail ? "end" : "start"} side={rail ? "right" : "top"} collisionPadding={8} className="w-[17.5rem] p-1.5">
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas421 {/* Who is signed in, and a way to their profile. */}
422 <DropdownMenuItem asChild className="gap-3 px-2 py-2">
423 <Link to={profile} aria-label={`${me?.name ?? user.username} (@${user.username}), your profile`}>
424 <Avatar name={user.username} image={user.avatar} size={36} />
Fast pages, required checks on the branch, self-hosted runners, honest incidents425 <span className="flex min-w-0 grow flex-col leading-tight" aria-busy={loading}>
426 {loading ? (
427 <Skeleton className="my-[0.1875rem] h-3.5 w-28" />
428 ) : (
429 <span className="truncate text-sm font-medium text-fg">{me?.name ?? user.username}</span>
430 )}
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas431 <span className="truncate font-mono text-xs text-muted">@{user.username}</span>
Fast pages, required checks on the branch, self-hosted runners, honest incidents432 {loading ? (
433 <Skeleton className="mt-1 h-3 w-40" />
434 ) : (
435 me?.email && <span className="mt-0.5 truncate text-xs text-faint">{me.email}</span>
436 )}
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas437 </span>
A Vercel-like sidebar and a new mark438 </Link>
439 </DropdownMenuItem>
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas440 <DropdownMenuSeparator className="my-1.5" />
441 <DropdownMenuGroup>
442 <DropdownMenuItem asChild className={MENU_ROW}>
443 <Link to={profile}>
444 <CircleUserRound />
445 Your profile
446 </Link>
447 </DropdownMenuItem>
448 <DropdownMenuItem asChild className={MENU_ROW}>
449 <Link to={FIRST_SETTINGS_PAGE}>
450 <Settings />
451 Your settings
452 </Link>
453 </DropdownMenuItem>
454 <DropdownMenuItem asChild className={MENU_ROW}>
455 <Link to="/settings/invites">
456 <Ticket />
457 Invites
Fast pages, required checks on the branch, self-hosted runners, honest incidents458 {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 quotas459 {me?.invites_left != null && (
460 <span className="ml-auto rounded-full bg-line px-1.5 text-[0.6875rem] tabular-nums text-muted">
461 {me.invites_left} left
462 </span>
463 )}
464 </Link>
465 </DropdownMenuItem>
466 </DropdownMenuGroup>
467 <DropdownMenuSeparator className="my-1.5" />
468 <DropdownMenuGroup>
469 <DropdownMenuItem asChild className={MENU_ROW}>
470 <a href="https://docs.g1t.sh/">
471 <BookOpen />
472 Documentation
473 </a>
474 </DropdownMenuItem>
475 <DropdownMenuItem asChild className={MENU_ROW}>
476 <Link to="/support">
477 <LifeBuoy />
478 Support
479 </Link>
480 </DropdownMenuItem>
481 <DropdownMenuItem asChild className={MENU_ROW}>
482 <a href={STATUS_URL}>
483 <Activity />
484 Status
485 <StatusSummary open={open} />
486 </a>
487 </DropdownMenuItem>
488 </DropdownMenuGroup>
489 <DropdownMenuSeparator className="my-1.5" />
A Vercel-like sidebar and a new mark490 {/* Submitted from here: the menu closes on select, and a button
491 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 quotas492 <DropdownMenuItem className={MENU_ROW} onSelect={() => submit(null, { method: "post", action: "/logout" })}>
A Vercel-like sidebar and a new mark493 <LogOut />
494 Sign out
495 </DropdownMenuItem>
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas496 <DropdownMenuSeparator className="my-1.5" />
497 <MenuLegalRow />
A Vercel-like sidebar and a new mark498 </DropdownMenuContent>
499 </DropdownMenu>
500 );
501}
502
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look503/**
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar504 * A workspace's settings pages, which the sidebar drills into: how it is
505 * set up and connected (guardrails, secrets, runners, integrations,
506 * webhooks), what it pays, its repositories, tokens and record. The main
507 * list keeps the places work happens and who belongs; every member can
508 * still open these.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look509 */
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar510const SETTINGS_PAGE =
Merge branch 'worktree-agent-ad7c6d88d93adc817'511 new RegExp(`^/([^/]+)/-/(${SETTINGS_PAGES.join("|")})(/|$)`);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look512/** A project's settings pages, which the project's menu drills into. */
Project settings move into the sidebar, grouped, with a heading per page513const REPO_SETTINGS_PAGE = /^\/([^/]+)\/([^/-][^/]*)\/settings(\/|$)/;
A catalogue of model providers, and settings that feel like settings514
Polish: phones, copy boxes, the plan page, the landing page, a real glide515/**
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look516 * The sidebar's lists are a stack, the way a phone pushes a screen: one
517 * drilled into slides in from the right over the full width, while the one
518 * it covers drifts a quarter of the way left and fades, both on one long
Sidebar: the panels really slide519 * ease-out, the fade quicker than the move so the two never blur together.
520 * Going back reverses it.
Polish: phones, copy boxes, the plan page, the landing page, a real glide521 */
Sidebar: no settings flash on the way into a repository, and a better slide522const LAYER =
Sidebar: the panels really slide523 "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 look524/** One list, filling its layer. */
Sidebar: no settings flash on the way into a repository, and a better slide525const PANEL = "h-full overflow-y-auto px-2 pb-4";
A catalogue of model providers, and settings that feel like settings526
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look527/** One list in the stack: what it is, and its rows. */
528type Level = { key: string; node: ReactNode };
529
530/** True once the page has hydrated, so what the server drew never animates in. */
531function useHydrated(): boolean {
532 const [hydrated, setHydrated] = useState(false);
533 useEffect(() => setHydrated(true), []);
534 return hydrated;
535}
536
537/**
538 * Drilling in and out of the sidebar's lists. `trail` is the way from the
539 * main list to the one shown, worked out from the address, so a link
540 * straight to a settings page opens the sidebar already drilled in, and
541 * leaving it comes back out. Lists deeper than the one shown stay in
542 * place, off to the right, so going back slides them away rather than
543 * dropping them. Moving to a list at the same depth (a workspace's settings
544 * to a project) fades the new one in where it is.
545 */
546function Drill({ trail }: { trail: Level[] }) {
547 const depth = trail.length - 1;
548 const hydrated = useHydrated();
549 const stack = useRef<Level[]>(trail);
550 const before = stack.current;
551 const within = trail.every((level, index) => before[index]?.key === level.key);
552 stack.current = within ? [...trail, ...before.slice(trail.length)] : trail;
553 const levels = stack.current;
554 // What was showing last time, to tell drilling in from moving across.
555 const shown = useRef({ depth, keys: levels.map((level) => level.key) });
556 useEffect(() => {
557 shown.current = { depth, keys: levels.map((level) => level.key) };
558 });
A catalogue of model providers, and settings that feel like settings559 return (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look560 <div className="relative min-h-0 grow overflow-hidden">
561 {levels.map((level, index) => {
562 const current = index === depth;
563 const place =
564 index < depth
565 ? "pointer-events-none -translate-x-1/4 opacity-0"
566 : current
567 ? "translate-x-0 opacity-100"
568 : "pointer-events-none translate-x-full opacity-0";
569 // A list that was not there before arrives: from the right when
570 // drilling in, in place when moving across.
571 const arriving = hydrated && current && !shown.current.keys.includes(level.key);
572 const enter = arriving
573 ? depth > shown.current.depth
574 ? "starting:translate-x-full starting:opacity-0"
575 : "starting:opacity-0"
576 : "";
577 return (
578 <div key={level.key} className={`${LAYER} ${place} ${enter}`} inert={!current} aria-hidden={!current || undefined}>
579 {level.node}
580 </div>
581 );
582 })}
583 </div>
584 );
585}
586
587/**
588 * The row at the top of a list drilled into: the list's name, which goes
589 * back out, and what it belongs to, faint at the end.
590 */
591function BackRow({ to, label, context }: { to: string; label: ReactNode; context?: ReactNode }) {
592 return (
593 <Link
594 to={to}
595 prefetch="intent"
596 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 settings597 >
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look598 <ChevronLeft size={16} className="shrink-0 text-faint transition-transform group-hover:-translate-x-0.5 group-hover:text-muted" />
599 <span className="min-w-0 shrink-0 truncate">{label}</span>
600 {context && (
601 <span className="ml-auto min-w-0 truncate pl-2 font-mono text-[0.6875rem] font-normal text-faint">{context}</span>
602 )}
603 </Link>
604 );
605}
606
607/** A thin rule between groups of a list. */
608function Rule() {
609 return <div role="separator" className="mx-2 my-2.5 h-px bg-line" />;
610}
611
Navigation: the workspace's own page with tabs, pins and recent in the sidebar612/** A quiet heading inside a group: Pinned, Recent. */
613function SidebarSubhead({ children }: { children: ReactNode }) {
614 return <h3 className="px-2 pt-1.5 pb-0.5 text-[0.6875rem] font-medium tracking-wide text-faint">{children}</h3>;
615}
616
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look617/**
Navigation: the workspace's own page with tabs, pins and recent in the sidebar618 * The workspace's projects as the sidebar keeps them, however many there
619 * are: the person's pins in their order, what they opened last, and the
620 * way to all of them. Pins move by dragging, or with Alt and the arrow
621 * keys; the projects service keeps the order.
622 */
Merge branch 'worktree-agent-ad7c6d88d93adc817'623function SidebarProjects({ slug, shell, current }: { slug: string; shell: ShellData; current: boolean }) {
Navigation: the workspace's own page with tabs, pins and recent in the sidebar624 const reorder = useFetcher({ key: `pins:${slug}` });
625 const saved = shell.pinned ?? [];
626 // While a new order is on its way, it shows as made.
627 const asked = reorder.formData?.get("intent") === "reorder" ? reorder.formData.getAll("slug").map(String) : null;
628 const pinned = asked
629 ? asked.map((name) => saved.find((project) => project.name === name)).filter((project): project is ShortcutProject => project != null)
630 : saved;
631 const active = shell.repo ? { namespace: shell.repo.namespace, name: shell.repo.name, isPrivate: false } : null;
632 const recent = recentWith(shell.recent ?? [], pinned, active, slug);
633 const [dragging, setDragging] = useState<number | null>(null);
634 const move = (from: number, by: number) => {
635 const next = movedPin(pinned, from, by);
636 if (!next) return;
637 const form = new FormData();
638 form.set("intent", "reorder");
639 for (const project of next) form.append("slug", project.name);
640 reorder.submit(form, { method: "post", action: `/${slug}/-/pins` });
641 };
Previews build from any branch; Agent in place of Ask AI; pin from the sidebar642 // Each row pins or unpins in place: the pin shows on hover or focus, and
643 // stays shown on a pinned row's hover so it reads as "unpin".
644 const row = (project: ShortcutProject, isPinned: boolean) => (
645 <div className="group/row relative">
646 <SidebarLink to={`/${project.namespace}/${project.name}`} icon={project.isPrivate ? <Lock size={15} /> : <Box size={15} />} drill="hover">
647 <span className="block truncate pr-6">{project.title ?? project.name}</span>
648 </SidebarLink>
649 <PinButton
650 workspace={slug}
651 slug={project.name}
652 name={project.title ?? project.name}
653 pinned={isPinned}
654 small
655 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"
656 />
657 </div>
Navigation: the workspace's own page with tabs, pins and recent in the sidebar658 );
659 return (
660 <SidebarGroup
661 title="Projects"
662 action={
663 <Link to={`/new?workspace=${slug}`} aria-label="New project" className="rounded p-0.5 text-faint hover:bg-raised hover:text-fg">
664 <Plus size={13} />
665 </Link>
666 }
667 >
Previews build from any branch; Agent in place of Ask AI; pin from the sidebar668 {/* Only there once something is pinned: each row below has its own pin. */}
669 {pinned.length > 0 && (
670 <>
671 <SidebarSubhead>Pinned</SidebarSubhead>
Navigation: the workspace's own page with tabs, pins and recent in the sidebar672 <ul aria-label="Pinned projects" className="space-y-px">
673 {pinned.map((project, index) => (
674 <li
675 key={project.name}
676 draggable={pinned.length > 1}
677 onDragStart={(event) => {
678 setDragging(index);
679 event.dataTransfer.effectAllowed = "move";
680 }}
681 onDragOver={(event) => {
682 if (dragging != null) event.preventDefault();
683 }}
684 onDrop={(event) => {
685 event.preventDefault();
686 if (dragging != null && dragging !== index) move(dragging, index - dragging);
687 setDragging(null);
688 }}
689 onDragEnd={() => setDragging(null)}
690 onKeyDown={(event) => {
691 if (!event.altKey || (event.key !== "ArrowUp" && event.key !== "ArrowDown")) return;
692 event.preventDefault();
693 move(index, event.key === "ArrowUp" ? -1 : 1);
694 }}
695 className={`group/pin relative ${dragging === index ? "opacity-50" : ""}`}
696 >
Previews build from any branch; Agent in place of Ask AI; pin from the sidebar697 {row(project, true)}
Navigation: the workspace's own page with tabs, pins and recent in the sidebar698 {pinned.length > 1 && (
Merge branch 'worktree-agent-ae1299e92e4462012'699 <Hint label="Drag, or Alt and an arrow key, to reorder" side="right">
700 <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">
701 <GripVertical size={12} aria-hidden="true" />
702 </span>
703 </Hint>
Navigation: the workspace's own page with tabs, pins and recent in the sidebar704 )}
705 </li>
706 ))}
707 </ul>
Previews build from any branch; Agent in place of Ask AI; pin from the sidebar708 </>
Navigation: the workspace's own page with tabs, pins and recent in the sidebar709 )}
710 {recent.length > 0 && (
711 <>
712 <SidebarSubhead>Recent</SidebarSubhead>
713 {recent.map((project) => (
Previews build from any branch; Agent in place of Ask AI; pin from the sidebar714 <div key={`${project.namespace}/${project.name}`}>{row(project, false)}</div>
Navigation: the workspace's own page with tabs, pins and recent in the sidebar715 ))}
716 </>
717 )}
718 <div className="pt-1">
Merge branch 'worktree-agent-ad7c6d88d93adc817'719 <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 sidebar720 All projects
721 </SidebarLink>
722 </div>
723 </SidebarGroup>
724 );
725}
726
727/**
Merge branch 'worktree-agent-ad7c6d88d93adc817'728 * The main list, in two parts a rule apart. Above it, what is yours
729 * whichever workspace you are in: Mission control, your inbox, and what
730 * others have shared with you. Below it, under the workspace's name, the
731 * workspace: its overview, its projects, what it builds and runs with
732 * across them, then its people, usage, support and settings. One row is
733 * lit wherever you are (lib/workspace-nav.ts).
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look734 */
735function MainMenu({ user, shell }: { user: User | null; shell: ShellData }) {
736 const ws = shell.workspace;
Merge branch 'worktree-agent-ad7c6d88d93adc817'737 const { pathname } = useLocation();
738 const going = useNavigation().location?.pathname;
739 // The row lights as the link is followed, not once the page arrives.
740 const here = sidebarCurrent(going ?? pathname, ws?.slug ?? null);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look741 // A visitor browses: no workspace, no projects of their own.
742 if (!user) {
743 return (
744 <nav aria-label="g1t" className={PANEL}>
745 <div className="mt-3 space-y-px">
746 {VISITOR_LINKS.map((link) => (
747 <SidebarLink key={link.to} to={link.to} icon={link.to === "/search" ? <Search size={15} /> : <Compass size={15} />}>
748 {link.label}
749 </SidebarLink>
750 ))}
751 </div>
752 </nav>
753 );
754 }
Merge branch 'worktree-agent-ad7c6d88d93adc817'755 const shared = shell.shared ?? [];
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look756 return (
757 <nav aria-label="g1t" className={PANEL}>
Merge branch 'worktree-agent-ad7c6d88d93adc817'758 {/* 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 look759 <div className="mt-3 space-y-px">
Merge branch 'worktree-agent-ad7c6d88d93adc817'760 <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 look761 Mission control
762 </SidebarLink>
Merge branch 'worktree-agent-ad7c6d88d93adc817'763 <SidebarLink to="/inbox" icon={<Inbox size={15} />} count={shell.inbox?.unread ?? undefined} current={here === "inbox"}>
764 Inbox
765 </SidebarLink>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look766 </div>
Merge branch 'worktree-agent-ad7c6d88d93adc817'767 {shared.length > 0 && (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look768 <SidebarGroup title="Shared with you" className="mt-3">
Merge branch 'worktree-agent-ad7c6d88d93adc817'769 {shared.map((repo) => (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look770 <SidebarLink
771 key={`${repo.namespace}/${repo.name}`}
772 to={`/${repo.namespace}/${repo.name}`}
773 icon={repo.isPrivate ? <Lock size={15} /> : <Box size={15} />}
774 drill="hover"
775 >
776 <span className="font-mono text-faint">{repo.namespace}/</span>
777 {repo.name}
778 </SidebarLink>
779 ))}
780 </SidebarGroup>
781 )}
782
Merge branch 'worktree-agent-ad7c6d88d93adc817'783 <Rule />
784 {ws ? (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look785 <>
Merge branch 'worktree-agent-ad7c6d88d93adc817'786 {/* The workspace the switcher names: its own page first. */}
787 <SidebarGroup title={displayName(ws)}>
788 <SidebarLink to={`/${ws.slug}`} end icon={<LayoutDashboard size={15} />} current={here === "overview"}>
789 Overview
790 </SidebarLink>
791 </SidebarGroup>
792 <div className="mt-3">
793 <SidebarProjects slug={ws.slug} shell={shell} current={here === "projects"} />
794 </div>
795
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member796 <Rule />
Merge branch 'worktree-agent-ad7c6d88d93adc817'797 <div className="space-y-px">
798 <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 look799 Agent fleet
800 </SidebarLink>
Merge branch 'worktree-agent-ad7c6d88d93adc817'801 <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 look802 Context
803 </SidebarLink>
Merge branch 'worktree-agent-ad7c6d88d93adc817'804 <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 look805 Memory
806 </SidebarLink>
Merge branch 'worktree-agent-ad7c6d88d93adc817'807 <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 look808 Security
Fast pages, required checks on the branch, self-hosted runners, honest incidents809 </SidebarLink>
Merge branch 'worktree-agent-ad7c6d88d93adc817'810 <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 member811 Packages
812 </SidebarLink>
Merge branch 'worktree-agent-ad7c6d88d93adc817'813 {INSIGHTS && (
814 <SidebarSoonLink
815 to={`/${ws.slug}/-/insights`}
816 icon={<TrendingUp size={15} />}
817 about={INSIGHTS.summary}
818 current={here === "insights"}
819 >
820 {INSIGHTS.title}
821 </SidebarSoonLink>
822 )}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar823 {roadmapIn("Workspace").filter((item) => item.key !== "insights").map((item) => (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look824 <SidebarSoonLink
825 key={item.key}
826 to={`/${ws.slug}/-/soon/${item.key}`}
827 icon={WORKSPACE_ICONS[item.key] ?? <Sparkles size={15} />}
828 about={item.summary}
829 >
830 {item.title === "Board" ? "Boards" : item.title}
831 </SidebarSoonLink>
832 ))}
Merge branch 'worktree-agent-ad7c6d88d93adc817'833 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look834
835 <Rule />
836 <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 bar837 {/* Who belongs, for every member to see; owners invite and manage there. */}
Merge branch 'worktree-agent-ad7c6d88d93adc817'838 <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 sidebar839 People
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar840 </SidebarLink>
Merge branch 'worktree-agent-ad7c6d88d93adc817'841 <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 bar842 Teams
843 </SidebarLink>
Merge branch 'worktree-agent-ad7c6d88d93adc817'844 <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 look845 Usage
846 </SidebarLink>
Merge branch 'worktree-agent-ad7c6d88d93adc817'847 <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 look848 Support
849 </SidebarLink>
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar850 {/* 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 look851 <SidebarLink
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member852 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 look853 icon={<Settings size={15} />}
854 drill
Merge branch 'worktree-agent-ad7c6d88d93adc817'855 current={here === "settings"}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look856 >
857 Settings
858 </SidebarLink>
859 </div>
860 </>
Merge branch 'worktree-agent-ad7c6d88d93adc817'861 ) : (
862 <SidebarGroup title="Projects">
863 <p className="px-2 py-1 text-xs text-faint">None yet.</p>
864 </SidebarGroup>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look865 )}
866 </nav>
867 );
868}
869
Merge branch 'worktree-agent-ad7c6d88d93adc817'870/** Insights, coming: the workspace's own page says what it will be. */
871const INSIGHTS = roadmapItem("insights");
872
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look873/** A workspace's settings, drilled into from Settings in the main list. */
874function SettingsMenu({ slug, owner }: { slug: string; owner: boolean }) {
875 return (
876 <nav aria-label="Workspace settings" className={PANEL}>
877 <BackRow to={`/${slug}`} label="Settings" context={slug} />
878 <div className="mt-2 space-y-px">
A catalogue of model providers, and settings that feel like settings879 {owner && (
880 <SidebarLink to={`/${slug}/-/settings`} icon={<Settings size={15} />}>
881 General
882 </SidebarLink>
883 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look884 <SidebarLink to={`/${slug}/-/repositories`} icon={<BookMarked size={15} />}>
885 Repositories
886 </SidebarLink>
Secrets and variables: one list, rows per environment, for workflows and deployments887 <SidebarLink to={`/${slug}/-/tokens`} icon={<KeyRound size={15} />}>
888 Access tokens
889 </SidebarLink>
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules890 {owner && (
891 <SidebarLink to={`/${slug}/-/personal-access-tokens`} icon={<UserRoundKey size={15} />}>
892 Personal access tokens
893 </SidebarLink>
894 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge895 <SidebarLink to={`/${slug}/-/rules`} icon={<Scale size={15} />}>
896 Rules
897 </SidebarLink>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look898 </div>
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar899 <SidebarGroup title="Agents and runs" className="mt-3">
900 <SidebarLink to={`/${slug}/-/guardrails`} icon={<Gauge size={15} />}>
901 Guardrails
902 </SidebarLink>
903 <SidebarLink to={`/${slug}/-/secrets`} icon={<Lock size={15} />}>
904 Secrets and variables
905 </SidebarLink>
Merge branch 'worktree-agent-a3abfcce648e87dca'906 <SidebarLink to={`/${slug}/-/actions`} icon={<PlayCircle size={15} />}>
907 Actions
908 </SidebarLink>
Runners are owner-only: the page, its actions, the Settings entry and the links to it909 {owner && (
910 <SidebarLink to={`/${slug}/-/runners`} icon={<ServerCog size={15} />}>
911 Runners
912 </SidebarLink>
913 )}
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar914 </SidebarGroup>
915 <SidebarGroup title="Connections" className="mt-3">
916 <SidebarLink to={`/${slug}/-/integrations`} icon={<Plug size={15} />}>
917 Integrations
918 </SidebarLink>
919 <SidebarLink to={`/${slug}/-/webhooks`} icon={<Webhook size={15} />}>
920 Webhooks
921 </SidebarLink>
922 </SidebarGroup>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look923 <Rule />
924 <div className="space-y-px">
A catalogue of model providers, and settings that feel like settings925 <SidebarLink to={`/${slug}/-/billing`} icon={<CreditCard size={15} />}>
Secrets and variables: one list, rows per environment, for workflows and deployments926 Billing and plans
A catalogue of model providers, and settings that feel like settings927 </SidebarLink>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API928 <SidebarLink to={`/${slug}/-/audit`} icon={<History size={15} />}>
929 Audit log
930 </SidebarLink>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look931 </div>
A catalogue of model providers, and settings that feel like settings932 </nav>
933 );
934}
935
Sidebar: no settings flash on the way into a repository, and a better slide936/**
937 * What the repository menu needs. Known from the address before the page's
938 * data arrives, so the menu is right from the first frame; the counts fill
939 * in once it does.
940 */
941type MenuRepo = {
942 namespace: string;
943 name: string;
944 member: boolean;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look945 /** Whether they see its settings; from the repository's page once it loads. */
946 settings?: boolean;
947 /** What they may do there, once the repository's page loads. */
948 can?: Abilities;
Sidebar: no settings flash on the way into a repository, and a better slide949 issues?: number;
950 pulls?: number;
951};
952
953function sameRepo(a: { namespace: string; name: string } | null, b: { namespace: string; name: string } | null) {
954 return (
955 a != null &&
956 b != null &&
957 a.namespace.toLowerCase() === b.namespace.toLowerCase() &&
958 a.name.toLowerCase() === b.name.toLowerCase()
959 );
960}
A repository has its own sidebar, as settings do961
962/**
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look963 * A project's own list, drilled into while you are in it: everything about
964 * the project, running and its code, and nothing else, with the way back
965 * out to all of them.
A repository has its own sidebar, as settings do966 */
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put967function RepoMenu({
968 repo,
969 isPrivate,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look970 back,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put971}: {
972 repo: MenuRepo;
973 isPrivate: boolean;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look974 /** Where the way back leads: the workspace's projects, mission control, or Explore. */
975 back: { to: string; label: string };
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put976}) {
A repository has its own sidebar, as settings do977 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 put978 // 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 look979 const shows = new Set(projectPages(repo.member, repo.can));
A repository has its own sidebar, as settings do980 return (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look981 <nav aria-label={`${repo.namespace}/${repo.name}`} className={PANEL}>
982 <BackRow to={back.to} label={back.label} />
Merge branch 'worktree-agent-ae1299e92e4462012'983 <Hint label="Overview" side="right">
Sidebar: soon rows and the project row keep their styles inside a hint; tiny amounts read as zero984 <div>
A flat project menu with Code first, views as tabs, and Stripe for every owner985 <NavLink
A repository has its own sidebar, as settings do986 to={base}
A flat project menu with Code first, views as tabs, and Stripe for every owner987 end
Fast pages, required checks on the branch, self-hosted runners, honest incidents988 prefetch="intent"
A flat project menu with Code first, views as tabs, and Stripe for every owner989 className={({ isActive }) =>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look990 `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 owner991 }
A repository has its own sidebar, as settings do992 >
993 <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 page994 {isPrivate ? <Lock size={13} /> : <Box size={13} />}
A repository has its own sidebar, as settings do995 </span>
996 <span className="min-w-0 truncate font-mono text-[0.8125rem]">
997 <span className="text-faint">{repo.namespace}/</span>
998 <span className="font-semibold text-fg">{repo.name}</span>
999 </span>
A flat project menu with Code first, views as tabs, and Stripe for every owner1000 </NavLink>
Sidebar: soon rows and the project row keep their styles inside a hint; tiny amounts read as zero1001 </div>
Merge branch 'worktree-agent-ae1299e92e4462012'1002 </Hint>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1003 {/* A project's pages, in the order people use them. A page with more
1004 than one view shows them as tabs across its top. */}
1005 <Rule />
1006 <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 request1007 <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 owner1008 Code
1009 </SidebarLink>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1010 <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 owner1011 Issues
1012 </SidebarLink>
1013 <SidebarLink to={`${base}/pulls`} also={[`${base}/pull`, `${base}/queue`]} icon={<GitPullRequest size={15} />} count={repo.pulls}>
1014 Pull requests
1015 </SidebarLink>
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1016 <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 owner1017 Agents
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1018 </SidebarLink>
A flat project menu with Code first, views as tabs, and Stripe for every owner1019 <SidebarLink to={`${base}/actions`} icon={<PlayCircle size={15} />}>
1020 Workflows
Projects: what a workspace builds and runs, first on every page1021 </SidebarLink>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1022 </div>
1023 <Rule />
1024 <div className="space-y-px">
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1025 {shows.has("deployments") ? (
A flat project menu with Code first, views as tabs, and Stripe for every owner1026 <SidebarLink to={`${base}/deployments`} also={soonPaths(base, "Deployments")} icon={<Rocket size={15} />}>
1027 Deployments
1028 </SidebarLink>
1029 ) : null}
Observability gets its own page; tabs never overflow1030 <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.">
1031 Observability
A flat project menu with Code first, views as tabs, and Stripe for every owner1032 </SidebarSoonLink>
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1033 {shows.has("security") ? (
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1034 <SidebarLink to={`${base}/security`} also={soonPaths(base, "Security")} icon={<ShieldCheck size={15} />}>
1035 Security
1036 </SidebarLink>
1037 ) : null}
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971038 <SidebarLink
1039 to={`${base}/contributors`}
1040 also={[`${base}/activity`, `${base}/stargazers`, ...soonPaths(base, "Insights")]}
1041 icon={<BarChart3 size={15} />}
1042 >
A flat project menu with Code first, views as tabs, and Stripe for every owner1043 Insights
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971044 </SidebarLink>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1045 </div>
1046 {(repo.settings ?? shows.has("settings")) && (
1047 <>
1048 <Rule />
1049 <SidebarLink to={`${base}/settings`} icon={<Settings size={15} />} drill>
A flat project menu with Code first, views as tabs, and Stripe for every owner1050 Settings
1051 </SidebarLink>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1052 </>
1053 )}
A repository has its own sidebar, as settings do1054 </nav>
1055 );
1056}
1057
Project settings move into the sidebar, grouped, with a heading per page1058/**
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1059 * A project's settings, drilled into from its list: what is about running
1060 * it, then its agents, then its code and who can reach it. The way back
1061 * leads to the project.
Project settings move into the sidebar, grouped, with a heading per page1062 */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1063function RepoSettingsMenu({ repo }: { repo: MenuRepo }) {
Project settings move into the sidebar, grouped, with a heading per page1064 const base = `/${repo.namespace}/${repo.name}`;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1065 // Each page shows to the roles that can use it, once the role is known.
1066 const shows = (page: string) => !repo.can || repo.can[SETTINGS_CAPABILITY[page] ?? "administer"];
1067 const running = shows("deployments");
1068 const agents = shows("agents") || shows("guardrails");
Project settings move into the sidebar, grouped, with a heading per page1069 return (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1070 <nav aria-label={`${repo.namespace}/${repo.name} settings`} className={PANEL}>
1071 <BackRow to={base} label="Settings" context={`${repo.namespace}/${repo.name}`} />
1072 {shows("") && (
1073 <div className="mt-2 space-y-px">
1074 <SidebarLink to={`${base}/settings`} end icon={<Settings size={15} />}>
1075 General
1076 </SidebarLink>
1077 </div>
1078 )}
1079 {running && (
1080 <>
1081 {shows("") && <Rule />}
1082 <div className={shows("") ? "space-y-px" : "mt-2 space-y-px"}>
1083 <SidebarLink to={`${base}/settings/deployments`} icon={<Rocket size={15} />}>
1084 Deployments
1085 </SidebarLink>
1086 <SidebarLink to={`${base}/settings/domains`} icon={<Globe size={15} />}>
1087 Domains
1088 </SidebarLink>
1089 <SidebarLink to={`${base}/settings/dependencies`} icon={<Network size={15} />}>
1090 Dependencies
1091 </SidebarLink>
1092 </div>
1093 </>
1094 )}
1095 {agents && (
1096 <>
1097 <Rule />
1098 <div className="space-y-px">
1099 {shows("agents") && (
1100 <SidebarLink to={`${base}/settings/agents`} icon={<Bot size={15} />}>
1101 Agents
1102 </SidebarLink>
1103 )}
1104 {shows("guardrails") && (
1105 <SidebarLink to={`${base}/settings/guardrails`} icon={<ShieldCheck size={15} />}>
1106 Guardrails
1107 </SidebarLink>
1108 )}
1109 </div>
1110 </>
1111 )}
1112 <Rule />
1113 <div className="space-y-px">
1114 {shows("repository") && (
1115 <SidebarLink to={`${base}/settings/repository`} icon={<BookMarked size={15} />}>
1116 Repository
1117 </SidebarLink>
1118 )}
1119 {shows("access") && (
1120 <SidebarLink to={`${base}/settings/access`} icon={<Users size={15} />}>
1121 Access
1122 </SidebarLink>
1123 )}
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca1124 {shows("keys") && (
1125 <SidebarLink to={`${base}/settings/keys`} icon={<KeyRound size={15} />}>
1126 Deploy keys
1127 </SidebarLink>
1128 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1129 {shows("branches") && (
1130 <SidebarLink to={`${base}/settings/branches`} icon={<GitBranch size={15} />}>
1131 Branches and merging
1132 </SidebarLink>
1133 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1134 {shows("rules") && (
1135 <SidebarLink to={`${base}/settings/rules`} icon={<Scale size={15} />}>
1136 Rules
1137 </SidebarLink>
1138 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1139 {shows("secrets") && (
1140 <SidebarLink to={`${base}/settings/secrets`} icon={<Lock size={15} />}>
1141 Secrets and variables
1142 </SidebarLink>
1143 )}
Merge branch 'worktree-agent-a3abfcce648e87dca'1144 {shows("actions") && (
1145 <SidebarLink to={`${base}/settings/actions`} icon={<PlayCircle size={15} />}>
1146 Actions
1147 </SidebarLink>
1148 )}
1149 {shows("environments") && (
1150 <SidebarLink to={`${base}/settings/environments`} icon={<Layers size={15} />}>
1151 Environments
1152 </SidebarLink>
1153 )}
Fast pages, required checks on the branch, self-hosted runners, honest incidents1154 {shows("runners") && (
1155 <SidebarLink to={`${base}/settings/runners`} icon={<ServerCog size={15} />}>
1156 Runners
1157 </SidebarLink>
1158 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1159 {shows("webhooks") && (
1160 <SidebarLink to={`${base}/settings/webhooks`} icon={<Webhook size={15} />}>
1161 Webhooks
1162 </SidebarLink>
1163 )}
1164 </div>
Project settings move into the sidebar, grouped, with a heading per page1165 </nav>
1166 );
1167}
1168
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1169/** Your own settings, drilled into from Your settings: one page each. */
1170function AccountSettingsMenu({ username }: { username: string }) {
1171 const link = (page: AccountSettingsPage, icon: ReactNode) => (
1172 <SidebarLink to={`/settings/${page}`} icon={icon}>
1173 {ACCOUNT_SETTINGS[page].title}
1174 </SidebarLink>
Account settings in the same shape as a workspace's1175 );
1176 return (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1177 <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 quotas1178 <BackRow to="/" label="Your settings" context={username} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1179 <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 quotas1180 {link("profile", <CircleUserRound size={15} />)}
1181 {link("emails", <Mail size={15} />)}
1182 {link("invites", <Ticket size={15} />)}
1183 </div>
1184 <Rule />
1185 <div className="space-y-px">
1186 {link("keys", <Fingerprint size={15} />)}
1187 {link("tokens", <KeyRound size={15} />)}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1188 </div>
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1189 <Rule />
1190 <div className="space-y-px">
1191 {link("github", <GithubMark className="size-[15px]" />)}
1192 {link("applications", <Plug size={15} />)}
1193 </div>
1194 <Rule />
1195 <div className="space-y-px">
Merge main (membership, two-factor, GitHub repo roles) into tokens1196 {link("two-factor", <ShieldCheck size={15} />)}
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1197 {link("security-log", <History size={15} />)}
1198 </div>
Account settings in the same shape as a workspace's1199 </nav>
1200 );
1201}
1202
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1203/**
1204 * Signing in and signing up, in place of the account for a visitor. Signing
1205 * in brings them back to the page they are on.
1206 */
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1207/** Help, status and the fine print under a visitor's sign-in buttons. */
1208function VisitorLinks() {
1209 const status = useSiteStatus();
Merge branch 'worktree-agent-ad7c6d88d93adc817'1210 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 quotas1211 const dot = (
1212 <span aria-hidden="true" className="text-[0.625rem] text-line-strong">
1213 ·
1214 </span>
1215 );
1216 return (
1217 // Two short rows, so a narrow sidebar never starts a line with a dot.
1218 <nav aria-label="About g1t" className="pt-0.5">
1219 <div className="flex items-center gap-0.5">
1220 <a href={STATUS_URL} className={`${quiet} gap-1.5`}>
1221 <StatusDot state={status?.overall.state ?? null} />
1222 Status
1223 </a>
1224 {dot}
1225 <Link to="/support" className={quiet}>
1226 Support
1227 </Link>
1228 </div>
1229 <div className="flex items-center gap-0.5">
1230 {LEGAL_LINKS.map(([label, to], index) => (
1231 <span key={to} className="flex items-center gap-0.5">
1232 {index > 0 && dot}
1233 <Link to={to} className={quiet}>
1234 {label}
1235 </Link>
1236 </span>
1237 ))}
1238 </div>
1239 </nav>
1240 );
1241}
1242
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1243function VisitorPanel() {
1244 const { pathname, search } = useLocation();
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1245 const signUp = useSignUpCopy();
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1246 return (
1247 <div className="space-y-2">
1248 <p className="px-1 text-xs text-muted">Sign in to open issues, review pull requests and run agents.</p>
1249 <div className="grid grid-cols-2 gap-2">
1250 <Link
1251 to={withNext("/login", pathname + search)}
1252 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"
1253 >
1254 <LogIn size={14} />
1255 Sign in
1256 </Link>
1257 <Link
1258 to={withNext("/register", pathname + search)}
1259 className="flex h-9 items-center justify-center rounded-md bg-fg text-[0.8125rem] font-medium text-bg transition-colors hover:bg-white"
1260 >
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1261 {signUp.primary}
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1262 </Link>
1263 </div>
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1264 <VisitorLinks />
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1265 </div>
1266 );
1267}
1268
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1269function Sidebar({
1270 user,
1271 shell,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1272 missing = false,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1273 onFind,
1274 onClose,
1275}: {
1276 user: User | null;
1277 shell: ShellData;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1278 /** The page is a 404: the address names nothing the viewer can see. */
1279 missing?: boolean;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1280 onFind: () => void;
1281 /** In the sheet on a small screen: closing it, at the end of the top row. */
1282 onClose?: () => void;
1283}) {
Agents as a team: lifecycle, merge queue, billing and a new shell1284 const ws = shell.workspace;
A catalogue of model providers, and settings that feel like settings1285 const { pathname } = useLocation();
1286 const going = useNavigation().location?.pathname;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1287 // Where the sidebar is drilled to follows the page being gone to, so it
1288 // moves as the link is followed, not once the page arrives.
1289 const target = going ?? pathname;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1290 // On a 404, the address is not a place: no list is drilled into from it,
1291 // except the project's own when the project is real and visible and only
1292 // something inside it is missing (a file, a commit, an issue).
1293 const lost = missing && going == null;
1294 const inSettings = !lost && ws != null && SETTINGS_PAGE.exec(target)?.[1]?.toLowerCase() === ws.slug;
1295 const inAccount = !lost && target === "/settings" || target.startsWith("/settings/");
A repository has its own sidebar, as settings do1296 const active = shell.repo;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1297 // In a repository, or on the way into one, its own list.
A repository has its own sidebar, as settings do1298 const repoPath = /^\/([^/]+)\/([^/-][^/]*)(\/|$)/.exec(target);
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all1299 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 look1300 // An invitation to a repository is answered before its menu means anything.
1301 const inRepo =
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1302 repoPath != null &&
1303 !reserved.has(repoPath[1]) &&
1304 repoPath[2] !== "-" &&
1305 !/^\/[^/]+\/[^/]+\/invitations\/?$/.test(target) &&
1306 (!lost || sameRepo(active, { namespace: repoPath[1]!, name: repoPath[2]! }));
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1307 // A project's settings, one level further in.
Project settings move into the sidebar, grouped, with a heading per page1308 const inRepoSettings = inRepo && REPO_SETTINGS_PAGE.test(target);
Sidebar: no settings flash on the way into a repository, and a better slide1309 // The repository the menu is for: the one loaded if it is the one being
1310 // gone to, else what the address says, at once.
1311 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 look1312 const guessed: MenuRepo | null = targetRepo
Sidebar: no settings flash on the way into a repository, and a better slide1313 ? sameRepo(active, targetRepo)
1314 ? active
1315 : {
1316 ...targetRepo,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1317 member: (user?.workspaces ?? []).some((m) => m.slug === targetRepo.namespace.toLowerCase()),
Sidebar: no settings flash on the way into a repository, and a better slide1318 }
1319 : null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1320 // The viewer's role there, from the repository's page once it has loaded.
1321 const page = useRouteLoaderData("routes/repo/layout") as
1322 | { repo?: { namespace: string; name: string }; access?: ViewerAccess }
1323 | undefined;
1324 const menuRepo: MenuRepo | null =
1325 guessed && page?.access && page.repo && sameRepo(page.repo, guessed)
1326 ? { ...guessed, member: page.access.insider, settings: seesSettings(page.access), can: page.access.can }
1327 : guessed;
1328
1329 // The way from the main list to the one shown.
1330 const trail: Level[] = [{ key: "main", node: <MainMenu user={user} shell={shell} /> }];
1331 if (user && inAccount) {
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1332 trail.push({ key: "account", node: <AccountSettingsMenu username={user.username} /> });
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1333 } else if (ws && inSettings) {
1334 trail.push({ key: `settings:${ws.slug}`, node: <SettingsMenu slug={ws.slug} owner={ws.role === "owner"} /> });
1335 } else if (menuRepo) {
1336 const key = `repo:${menuRepo.namespace}/${menuRepo.name}`.toLowerCase();
1337 // Out of a project: to its workspace's projects when they are yours.
1338 const home = (user?.workspaces ?? []).find((m) => m.slug === menuRepo.namespace.toLowerCase());
1339 const back = !user
1340 ? { to: "/explore", label: "Explore" }
1341 : home
Navigation: the workspace's own page with tabs, pins and recent in the sidebar1342 ? { to: `/${home.slug}/-/projects`, label: "All projects" }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1343 : { to: "/", label: "Mission control" };
1344 trail.push({
1345 key,
1346 node: (
1347 <RepoMenu
1348 repo={menuRepo}
1349 isPrivate={[...shell.repos, ...(shell.shared ?? [])].some((repo) => sameRepo(repo, menuRepo) && repo.isPrivate)}
1350 back={back}
1351 />
1352 ),
1353 });
1354 if (inRepoSettings) trail.push({ key: `${key}:settings`, node: <RepoSettingsMenu repo={menuRepo} /> });
1355 }
1356
Agents as a team: lifecycle, merge queue, billing and a new shell1357 return (
1358 <div className="flex h-full flex-col">
A Vercel-like sidebar and a new mark1359 {/* 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 bar1360 <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 put1361 {user ? (
1362 <>
The 1 beside the workspace switcher again, in a square hover the switcher's height1363 {/* The 1 alone beside the workspace: a square hover the height of the
1364 switcher, the mark as tall as the workspace avatar. */}
1365 <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 bar1366 <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 put1367 </Link>
1368 <span className="shrink-0 text-line-strong" aria-hidden="true">
1369 /
1370 </span>
1371 <WorkspaceSwitcher user={user} shell={shell} />
1372 </>
1373 ) : (
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1374 <Link to="/" aria-label="g1t home" className="mr-auto flex h-9 items-center rounded-md px-2 transition-colors hover:bg-raised">
1375 <Logo className="text-[1.25rem]" />
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1376 </Link>
1377 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1378 {onClose && (
1379 <button
1380 type="button"
1381 aria-label="Close menu"
1382 onClick={onClose}
1383 className="flex size-9 shrink-0 items-center justify-center rounded-md text-faint hover:bg-raised hover:text-fg"
1384 >
1385 <X size={16} />
1386 </button>
1387 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1388 </div>
A Vercel-like sidebar and a new mark1389 <div className="px-2 pt-3">
1390 <button
1391 type="button"
1392 onClick={onFind}
Two limits, real invoices, trust that grows by itself, sales signals1393 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 mark1394 >
1395 <Search size={14} />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1396 <span className="grow text-left">Search or jump to…</span>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1397 <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 mark1398 </button>
Agents as a team: lifecycle, merge queue, billing and a new shell1399 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1400 <Drill trail={trail} />
1401 {/* The account, or signing in, as one row at the very bottom. */}
1402 <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 put1403 {user ? <AccountMenu user={user} /> : <VisitorPanel />}
A Vercel-like sidebar and a new mark1404 </div>
1405 </div>
1406 );
1407}
1408
1409/** Words for the sections a path can end in. */
1410const SECTIONS: Record<string, string> = {
1411 issues: "Issues",
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1412 milestones: "Milestones",
1413 labels: "Labels",
A Vercel-like sidebar and a new mark1414 pulls: "Pull requests",
1415 queue: "Merge queue",
1416 commits: "Commits",
A menu shaped by how g1t works, Soon pages for every promise, and sudo for sales1417 plans: "Outcomes",
1418 actions: "Workflows",
1419 soon: "Soon",
Deployments: a preview for every pull request, production on g1t.page1420 deployments: "Deployments",
Projects: what a workspace builds and runs, first on every page1421 repository: "Repository",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1422 access: "Access",
1423 invitations: "Invitation",
1424 repositories: "Repositories",
Branches and Tags pages, each file's last commit, and the branch menu on files1425 branches: "Branches",
1426 tags: "Tags",
Compare: any two branches, the commits one has that the other does not, and the diff, with Open a pull request1427 compare: "Compare",
Project dependencies: addresses, preview stacks, Affects, and agents who know1428 dependencies: "Dependencies",
A menu shaped by how g1t works, Soon pages for every promise, and sudo for sales1429 code: "Files",
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs1430 secrets: "Secrets and variables",
A Vercel-like sidebar and a new mark1431 settings: "Settings",
Navigation: the workspace's own page with tabs, pins and recent in the sidebar1432 people: "People",
1433 projects: "Projects",
1434 teams: "Teams",
A Vercel-like sidebar and a new mark1435 tokens: "Access tokens",
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules1436 "personal-access-tokens": "Personal access tokens",
A Vercel-like sidebar and a new mark1437 usage: "Usage",
Site: the AI Gateway page's breadcrumb reads "AI Gateway"1438 gateway: "AI Gateway",
Secrets and variables: one list, rows per environment, for workflows and deployments1439 billing: "Billing and plans",
A catalogue of model providers, and settings that feel like settings1440 integrations: "Integrations",
Webhooks: every event, to your own addresses, signed and retried1441 webhooks: "Webhooks",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1442 domains: "Domains",
1443 guardrails: "Guardrails",
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1444 rules: "Rules",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1445 audit: "Audit log",
Files pages say Files in the trail and keep the description in About only1446 tree: "Files",
1447 blob: "Files",
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail1448 agents: "Agents",
1449 context: "Context",
1450 memory: "Memory",
1451 security: "Security",
1452 packages: "Packages",
1453 runners: "Runners",
Merge branch 'worktree-agent-a3abfcce648e87dca'1454 environments: "Environments",
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail1455 workflows: "Workflows",
1456 observability: "Observability",
1457 insights: "Insights",
1458 sessions: "Sessions",
Chat and workspace agents: channels, DMs and named agents you talk to1459 chat: "Chat",
1460 docs: "Docs",
1461 home: "Home",
1462 "code-access": "Code access",
A Vercel-like sidebar and a new mark1463};
1464
Merge branch 'worktree-agent-a3abfcce648e87dca'1465/** Settings pages whose name differs from the section's of the same word. */
1466const SETTINGS_SECTIONS: Record<string, string> = { branches: "Branches and merging", actions: "Actions" };
1467
A Vercel-like sidebar and a new mark1468/** 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 quotas1469function Breadcrumbs({ pathname, missing, repo }: { pathname: string; missing?: boolean; repo?: ShellData["repo"] }) {
1470 // A 404 names nothing from the address, unless the project is real and
1471 // visible and only something inside it is missing.
1472 const visibleRepo = /^\/([^/]+)\/([^/-][^/]*)(\/|$)/.exec(pathname);
1473 if (missing && !(visibleRepo && sameRepo(repo ?? null, { namespace: visibleRepo[1]!, name: visibleRepo[2]! }))) {
1474 return <span className="text-sm font-medium">Not found</span>;
1475 }
A Vercel-like sidebar and a new mark1476 const parts = pathname.split("/").filter(Boolean);
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all1477 const reserved = ["settings", "explore", "new", "search", "workspaces", "policies", "security", "support", "status", "invite", "inbox"];
A Vercel-like sidebar and a new mark1478 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 quotas1479 // Your settings: Settings / Emails.
1480 if (parts[0] === "settings") {
1481 const page = accountSettingsPage(pathname);
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1482 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 quotas1483 }
A Vercel-like sidebar and a new mark1484 if (reserved.includes(parts[0]!)) {
1485 const words: Record<string, string> = {
Account settings in the same shape as a workspace's1486 settings: "Account",
A Vercel-like sidebar and a new mark1487 explore: "Explore",
Projects live: fixes from walking them end to end1488 new: "New project",
A Vercel-like sidebar and a new mark1489 search: "Search",
1490 workspaces: "New workspace",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1491 policies: "Policies",
1492 security: "Security",
1493 support: "Support",
1494 status: "Status",
1495 invite: "Invite",
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all1496 inbox: "Inbox",
A Vercel-like sidebar and a new mark1497 };
1498 return <span className="text-sm font-medium">{words[parts[0]!]}</span>;
1499 }
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1500 // A person's profile, by their handle.
1501 if (parts[0] === "u" && parts[1]) {
1502 return <span className="truncate font-mono text-[0.8125rem] font-medium">@{parts[1]}</span>;
1503 }
A Vercel-like sidebar and a new mark1504 const [owner, second, third, fourth] = parts;
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1505 const trail: Crumb[] = [{ label: owner!, to: `/${owner}`, mono: true }];
A Vercel-like sidebar and a new mark1506 if (second === "-") {
A catalogue of model providers, and settings that feel like settings1507 const page = `/${owner}/-/${third}`;
1508 if (third && SETTINGS_PAGE.test(page)) {
1509 trail.push({ label: "Settings", to: `/${owner}/-/settings` });
1510 trail.push({ label: third === "settings" ? "General" : (SECTIONS[third] ?? third), to: page });
1511 } else if (third) trail.push({ label: SECTIONS[third] ?? third, to: page });
A Vercel-like sidebar and a new mark1512 } else if (second) {
1513 const repo = `/${owner}/${second}`;
1514 trail.push({ label: second, to: repo, mono: true });
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1515 if (third === "pull" && fourth) trail.push({ label: `Pull request #${fourth}`, short: `#${fourth}`, to: `${repo}/pull/${fourth}` });
1516 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 mark1517 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 deployments1518 else if (third && SECTIONS[third]) {
1519 trail.push({ label: SECTIONS[third]!, to: `${repo}/${third}` });
1520 // A settings page names which one: Settings / Secrets and variables.
1521 if (third === "settings" && fourth && SECTIONS[fourth]) {
Merge branch 'worktree-agent-a3abfcce648e87dca'1522 trail.push({ label: SETTINGS_SECTIONS[fourth] ?? SECTIONS[fourth]!, to: `${repo}/settings/${fourth}` });
Secrets and variables: one list, rows per environment, for workflows and deployments1523 }
1524 }
A Vercel-like sidebar and a new mark1525 }
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1526 return <Trail trail={trail} />;
1527}
1528
1529/** One link of the trail; `short` is what a phone shows when it is the page itself. */
1530type Crumb = { label: string; to: string; mono?: boolean; short?: string };
1531
1532/**
1533 * The trail of links. On a phone there is room for one: the page itself,
1534 * with a way back to where it sits; the whole trail from a wider screen up.
1535 */
1536function Trail({ trail }: { trail: Crumb[] }) {
1537 const up = trail.length > 1 ? trail[trail.length - 2]! : null;
A Vercel-like sidebar and a new mark1538 return (
1539 <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 audit1540 {up && (
1541 <Link
1542 to={up.to}
1543 prefetch="intent"
1544 aria-label={`Back to ${up.label}`}
1545 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"
1546 >
1547 <ChevronLeft size={16} />
1548 </Link>
1549 )}
1550 {trail.map((crumb, index) => {
1551 const last = index === trail.length - 1;
1552 return (
1553 <span key={index} className={`min-w-0 items-center gap-1.5 ${last ? "flex" : "hidden sm:flex"}`}>
1554 {index > 0 && <span className="hidden text-line-strong sm:inline">/</span>}
1555 <Link
1556 to={crumb.to}
1557 prefetch="intent"
1558 aria-current={last ? "page" : undefined}
1559 className={`truncate rounded px-1 py-0.5 transition-colors hover:bg-raised ${
1560 last ? "font-medium text-fg" : "text-muted hover:text-fg"
1561 } ${crumb.mono ? "font-mono text-[0.8125rem]" : ""}`}
1562 >
1563 {last && crumb.short ? (
1564 <>
1565 <span className="sm:hidden">{crumb.short}</span>
1566 <span className="hidden sm:inline">{crumb.label}</span>
1567 </>
1568 ) : (
1569 crumb.label
1570 )}
1571 </Link>
1572 </span>
1573 );
1574 })}
A Vercel-like sidebar and a new mark1575 </nav>
Agents as a team: lifecycle, merge queue, billing and a new shell1576 );
1577}
1578
Search across all of g1t, Explore, and a command palette1579type Command = PaletteCommand;
Agents as a team: lifecycle, merge queue, billing and a new shell1580
1581/** 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 look1582function commandsFor(user: User | null, shell: ShellData, here: string, signUpLabel = "Sign up"): Command[] {
1583 if (!user) return visitorCommands(shell, here, signUpLabel);
Agents as a team: lifecycle, merge queue, billing and a new shell1584 const commands: Command[] = [
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1585 { 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 step1586 ...(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-agent1587 ? [{ 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 step1588 : []),
Agents as a team: lifecycle, merge queue, billing and a new shell1589 { label: "Explore repositories", to: "/explore", icon: <Compass size={15} /> },
Search across all of g1t, Explore, and a command palette1590 { 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 all1591 { label: "Inbox", hint: "What needs you", to: "/inbox", icon: <Bell size={15} /> },
Projects: what a workspace builds and runs, first on every page1592 { label: "New project", to: "/new", icon: <Plus size={15} /> },
Agents as a team: lifecycle, merge queue, billing and a new shell1593 { 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 quotas1594 { label: "Your settings", to: FIRST_SETTINGS_PAGE, icon: <Settings size={15} /> },
1595 ...(Object.keys(ACCOUNT_SETTINGS) as AccountSettingsPage[])
1596 .filter((page) => page !== "profile")
1597 .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 shell1598 ];
1599 const repo = shell.repo;
1600 if (repo) {
1601 const base = `/${repo.namespace}/${repo.name}`;
1602 const name = `${repo.namespace}/${repo.name}`;
1603 commands.unshift(
Projects: what a workspace builds and runs, first on every page1604 { label: "Overview", hint: name, to: base, icon: <LayoutGrid size={15} /> },
1605 { label: "Code", hint: name, to: `${base}/code`, icon: <Code2 size={15} /> },
Agents as a team: lifecycle, merge queue, billing and a new shell1606 { label: "Issues", hint: name, to: `${base}/issues`, icon: <CircleDot size={15} /> },
1607 { 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 step1608 ...(repo.member
1609 ? [{ label: `Put an agent on ${repo.name}`, hint: name, to: `${base}/issues/new?agent=1`, icon: <Sparkles size={15} /> }]
1610 : []),
Agents as a team: lifecycle, merge queue, billing and a new shell1611 { label: "Pull requests", hint: name, to: `${base}/pulls`, icon: <GitPullRequest size={15} /> },
1612 { label: "Commits", hint: name, to: `${base}/commits`, icon: <History size={15} /> },
1613 ...(repo.member
1614 ? [
1615 { label: "Plan work", hint: name, to: `${base}/plans`, icon: <ListTree size={15} /> },
Projects: what a workspace builds and runs, first on every page1616 { label: "Deployments", hint: name, to: `${base}/deployments`, icon: <Rocket size={15} /> },
1617 { label: "Secrets and variables", hint: name, to: `${base}/settings/secrets`, icon: <Lock size={15} /> },
1618 { label: "Project settings", hint: name, to: `${base}/settings`, icon: <Settings size={15} /> },
1619 { label: "Repository settings", hint: name, to: `${base}/settings/repository`, icon: <Settings size={15} /> },
Agents as a team: lifecycle, merge queue, billing and a new shell1620 ]
1621 : []),
1622 );
1623 }
1624 for (const membership of user.workspaces ?? []) {
1625 commands.push(
Workspace names and icons, and a component kit for every control1626 { 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 sidebar1627 { label: "All projects", hint: membership.slug, to: `/${membership.slug}/-/projects`, icon: <LayoutGrid size={15} /> },
1628 { 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 request1629 { 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 tokens1630 { 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 page1631 { 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 settings1632 { 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 look1633 { label: "Integrations", hint: membership.slug, to: `/${membership.slug}/-/integrations`, icon: <Plug size={15} /> },
Agents as a team: lifecycle, merge queue, billing and a new shell1634 );
1635 }
1636 for (const listed of shell.repos) {
1637 commands.push({
Projects: what a workspace builds and runs, first on every page1638 label: listed.title ?? listed.name,
1639 hint: `${listed.namespace} · Project`,
Agents as a team: lifecycle, merge queue, billing and a new shell1640 to: `/${listed.namespace}/${listed.name}`,
Projects: what a workspace builds and runs, first on every page1641 icon: <Box size={15} />,
Agents as a team: lifecycle, merge queue, billing and a new shell1642 });
1643 }
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step1644 // "Put an agent on …": one per project, after the projects themselves.
1645 for (const listed of shell.repos) {
1646 if (repo && listed.namespace === repo.namespace && listed.name === repo.name) continue;
1647 commands.push({
1648 label: `Put an agent on ${listed.title ?? listed.name}`,
1649 hint: `${listed.namespace}/${listed.name}`,
1650 to: `/${listed.namespace}/${listed.name}/issues/new?agent=1`,
1651 icon: <Sparkles size={15} />,
1652 });
1653 }
Agents as a team: lifecycle, merge queue, billing and a new shell1654 return commands;
1655}
1656
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1657/** 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 look1658function 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 put1659 const commands: Command[] = [];
1660 const repo = shell.repo;
1661 if (repo) {
1662 const base = `/${repo.namespace}/${repo.name}`;
1663 const name = `${repo.namespace}/${repo.name}`;
1664 commands.push(
1665 { label: "Overview", hint: name, to: base, icon: <LayoutGrid size={15} /> },
1666 { label: "Code", hint: name, to: `${base}/code`, icon: <Code2 size={15} /> },
1667 { label: "Issues", hint: name, to: `${base}/issues`, icon: <CircleDot size={15} /> },
1668 { label: "Pull requests", hint: name, to: `${base}/pulls`, icon: <GitPullRequest size={15} /> },
1669 { label: "Commits", hint: name, to: `${base}/commits`, icon: <History size={15} /> },
1670 );
1671 }
1672 commands.push(
1673 { label: "Explore", hint: "Public projects", to: "/explore", icon: <Compass size={15} /> },
1674 { label: "Search g1t", hint: "Repositories, code, issues, people", to: "/search", icon: <Search size={15} /> },
1675 { label: "Pricing", to: "/pricing", icon: <CreditCard size={15} /> },
1676 { label: "Documentation", to: "https://docs.g1t.sh/", icon: <BookOpen size={15} /> },
1677 { 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 look1678 { 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 put1679 );
1680 return commands;
1681}
1682
Agents as a team: lifecycle, merge queue, billing and a new shell1683/**
Merge branch 'worktree-agent-aca4790ea223aa235'1684 * The page while the next one loads: after a moment it dims, under the
1685 * bar below, so it is plainly on its way out rather than frozen, and the
1686 * next page fades up from there. Sending a form does not dim it: the
1687 * form's button says it is working. Spread on the element holding the page.
1688 */
1689export function useLeaving() {
1690 const navigation = useNavigation();
1691 const method = navigation.formMethod?.toUpperCase();
1692 const leaving = navigation.state === "loading" && (method == null || method === "GET");
1693 return {
1694 "aria-busy": leaving || undefined,
1695 className: `transition-opacity motion-reduce:transition-none ${leaving ? "opacity-60 delay-200 duration-300" : "duration-150"}`,
1696 };
1697}
1698
1699/**
Agents as a team: lifecycle, merge queue, billing and a new shell1700 * A bar across the top of the page while the next one loads, as GitHub
1701 * has: it appears at once, creeps towards the end while waiting, then fills
1702 * and fades when the page arrives.
1703 */
1704export function Progress() {
1705 const navigation = useNavigation();
1706 const busy = navigation.state !== "idle";
1707 const [width, setWidth] = useState(0);
1708 const [visible, setVisible] = useState(false);
1709 useEffect(() => {
1710 if (busy) {
1711 setVisible(true);
1712 setWidth(12);
1713 // Slows as it goes, never quite arriving until the page does.
1714 const timer = setInterval(() => setWidth((now) => now + (90 - now) * 0.12), 200);
1715 return () => clearInterval(timer);
1716 }
1717 setWidth((now) => (now > 0 ? 100 : 0));
1718 const fade = setTimeout(() => setVisible(false), 250);
1719 const reset = setTimeout(() => setWidth(0), 550);
1720 return () => {
1721 clearTimeout(fade);
1722 clearTimeout(reset);
1723 };
1724 }, [busy]);
1725 return (
1726 <div
1727 aria-hidden="true"
1728 className={`pointer-events-none fixed inset-x-0 top-0 z-[60] h-[3px] transition-opacity duration-300 ${
1729 visible ? "opacity-100" : "opacity-0"
1730 }`}
1731 >
1732 <div
1733 className="h-full bg-accent shadow-[0_0_10px_var(--color-accent)] transition-[width] duration-200 ease-out"
1734 style={{ width: `${width}%` }}
1735 />
1736 </div>
1737 );
1738}
1739
Chat and workspace agents: channels, DMs and named agents you talk to1740/** The mode's sidebar beside the rail: a shade lighter than the page. */
1741const SIDEBAR_BOX = "h-full border-r border-line bg-[color-mix(in_srgb,var(--color-surface)_70%,var(--color-bg))]";
1742
Agents as a team: lifecycle, merge queue, billing and a new shell1743/**
Chat and workspace agents: channels, DMs and named agents you talk to1744 * Which sidebar sits beside the rail: Chat's and Agents' own, Code's (the
1745 * one with projects, also on Mission control and the inbox for someone with
1746 * Code), or none (Docs, and Home or the inbox without Code).
1747 */
1748function sidebarFor(mode: ModeKey | null, code: boolean, pathname: string): "code" | "chat" | "agents" | null {
1749 if (mode == null) return "code";
1750 if (mode === "chat") return "chat";
1751 if (mode === "agents") return "agents";
1752 if (mode === "docs") return null;
1753 if (mode === "code") return "code";
1754 if (!code) return null;
1755 return mode === "inbox" || pathname === "/" ? "code" : null;
1756}
1757
1758/**
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1759 * The app: a sidebar with the workspace, its repositories and the sections
1760 * of the one being looked at; a slim bar with search and the account; and
1761 * the page. A visitor who is not signed in gets the same frame, with
1762 * Explore and Search in place of the workspace, and signing in in place of
1763 * the account.
Agents as a team: lifecycle, merge queue, billing and a new shell1764 */
1765export function AppShell({
1766 user,
1767 shell,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1768 missing = false,
Agents as a team: lifecycle, merge queue, billing and a new shell1769 banner,
1770 children,
1771}: {
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1772 user: User | null;
Agents as a team: lifecycle, merge queue, billing and a new shell1773 shell: ShellData;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1774 /** The page is a 404 (see Sidebar). */
1775 missing?: boolean;
Agents as a team: lifecycle, merge queue, billing and a new shell1776 banner?: ReactNode;
1777 children: ReactNode;
1778}) {
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1779 const { pathname, search } = useLocation();
Agents as a team: lifecycle, merge queue, billing and a new shell1780 const [drawer, setDrawer] = useState(false);
1781 const [palette, setPalette] = useState(false);
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1782 const here = pathname + search;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1783 const signUpLabel = useSignUpCopy().primary;
1784 const commands = useMemo(() => commandsFor(user, shell, here, signUpLabel), [user, shell, here, signUpLabel]);
Agents as a team: lifecycle, merge queue, billing and a new shell1785
1786 // A new page closes the drawer on small screens.
1787 useEffect(() => setDrawer(false), [pathname]);
Fast pages, required checks on the branch, self-hosted runners, honest incidents1788 // Mission control's code, fetched while the browser is idle, so going
1789 // home never waits on it (routes/home.tsx).
1790 useEffect(() => {
1791 if (!user) return;
1792 const load = () => void import("./mission-control").catch(() => undefined);
1793 const idle = (window as { requestIdleCallback?: (callback: () => void) => number }).requestIdleCallback;
1794 if (idle) idle(load);
1795 else setTimeout(load, 1500);
1796 }, [user]);
Search across all of g1t, Explore, and a command palette1797 usePaletteShortcut(() => setPalette((open) => !open));
Merge branch 'worktree-agent-aca4790ea223aa235'1798 const leaving = useLeaving();
Agents as a team: lifecycle, merge queue, billing and a new shell1799
Chat and workspace agents: channels, DMs and named agents you talk to1800 // The rail and the mode's sidebar (docs/WORKSPACE.md, "Shell"), for
1801 // someone in a workspace. A visitor, or someone with none, keeps the one
1802 // sidebar.
1803 const going = useNavigation().location?.pathname;
1804 const ws = user ? shell.workspace : null;
1805 const mode: ModeKey | null = ws ? modeOf(going ?? pathname, ws.slug) : null;
1806 const panel = sidebarFor(mode, ws ? hasCodeAccess(ws) : true, going ?? pathname);
1807 const { sidebar: chatSidebar } = useChatSidebar();
1808 const chatUnread = chatSidebar ? unreadTotals(chatSidebar.entries) : (shell.chat ?? { unread: 0, mentions: 0 });
1809 const sidebarNode = (inSheet: boolean) =>
1810 panel === "chat" && ws ? (
1811 <ChatSidebar slug={ws.slug} />
1812 ) : panel === "agents" && ws ? (
1813 <AgentsSidebar slug={ws.slug} />
1814 ) : panel === "code" ? (
1815 <Sidebar
1816 user={user}
1817 shell={shell}
1818 missing={missing}
1819 onFind={() => {
1820 if (inSheet) setDrawer(false);
1821 setPalette(true);
1822 }}
1823 onClose={inSheet ? () => setDrawer(false) : undefined}
1824 />
1825 ) : null;
1826 const rail =
1827 user && ws ? (
1828 <Rail
1829 user={user}
1830 workspace={ws}
1831 unread={{ inbox: shell.inbox?.unread ?? 0, chat: chatUnread.unread, mentions: chatUnread.mentions }}
1832 account={<AccountMenu user={user} rail />}
1833 />
1834 ) : null;
1835 const pad = rail ? (panel ? "lg:pl-[21rem]" : "lg:pl-20") : "lg:pl-64";
1836
Agents as a team: lifecycle, merge queue, billing and a new shell1837 return (
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1838 // The phone's menu is a sheet: a dialog that holds focus, closes on
1839 // Escape or a tap outside, and gives focus back to the menu button.
1840 <Sheet open={drawer} onOpenChange={setDrawer}>
Agents as a team: lifecycle, merge queue, billing and a new shell1841 <div className="min-h-screen">
1842 <Progress />
Chat and workspace agents: channels, DMs and named agents you talk to1843 <aside className="fixed inset-y-0 left-0 z-40 hidden lg:flex">
1844 {rail}
1845 {panel && <div className={`${SIDEBAR_BOX} w-64`}>{sidebarNode(false)}</div>}
Agents as a team: lifecycle, merge queue, billing and a new shell1846 </aside>
Chat and workspace agents: channels, DMs and named agents you talk to1847 <SheetContent
1848 side="left"
1849 showClose={false}
1850 aria-describedby={undefined}
1851 className={`lg:hidden ${rail ? `flex flex-row ${panel ? "w-[21rem] max-w-[92vw] sm:max-w-[21rem]" : "w-20 sm:max-w-20"}` : "w-72 max-w-[85vw] sm:max-w-72"}`}
1852 >
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1853 <SheetTitle className="sr-only">Menu</SheetTitle>
Chat and workspace agents: channels, DMs and named agents you talk to1854 {rail}
1855 {panel && <div className={`min-w-0 grow ${rail ? SIDEBAR_BOX : ""}`}>{sidebarNode(true)}</div>}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1856 </SheetContent>
Agents as a team: lifecycle, merge queue, billing and a new shell1857
Chat and workspace agents: channels, DMs and named agents you talk to1858 <div className={`flex min-h-screen min-w-0 flex-col ${pad}`}>
1859 <header
1860 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 ${mode === "chat" ? "lg:hidden" : ""}`}
1861 >
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1862 <SheetTrigger
Agents as a team: lifecycle, merge queue, billing and a new shell1863 aria-label="Open menu"
1864 className="rounded-md p-1.5 text-muted hover:bg-raised hover:text-fg lg:hidden"
1865 >
1866 <Menu size={18} />
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1867 </SheetTrigger>
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1868 <Breadcrumbs pathname={pathname} missing={missing} repo={shell.repo} />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1869 <div className="ml-auto flex items-center gap-1.5">
1870 {/* Search lives in the sidebar ("Search or jump to"); with the sidebar folded away, this opens the same palette. */}
1871 <button
1872 type="button"
1873 aria-label="Search or jump to"
1874 onClick={() => setPalette(true)}
1875 className="flex size-9 items-center justify-center rounded-md text-muted transition-colors hover:bg-raised hover:text-fg lg:hidden"
1876 >
1877 <Search size={16} />
1878 </button>
Navigation: the workspace's own page with tabs, pins and recent in the sidebar1879 {/* All of g1t's public projects: not any one workspace's, so here, not in the sidebar. */}
1880 <NavLink
1881 to="/explore"
1882 prefetch="intent"
1883 aria-label="Explore"
1884 className={({ isActive }) =>
1885 `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"}`
1886 }
1887 >
1888 <Compass size={16} className="sm:hidden" />
1889 <span className="hidden sm:inline">Explore</span>
1890 </NavLink>
A Vercel-like sidebar and a new mark1891 <a
1892 href="https://docs.g1t.sh/"
Two limits, real invoices, trust that grows by itself, sales signals1893 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 mark1894 >
1895 Docs
1896 </a>
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all1897 {user && (
1898 <>
Previews build from any branch; Agent in place of Ask AI; pin from the sidebar1899 <AgentButton />
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all1900 <InboxBell counts={shell.inbox ?? null} />
1901 </>
1902 )}
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1903 {!user ? (
1904 // The sidebar has these too, but on a phone it is folded away.
1905 <Link
1906 to={withNext("/login", here)}
1907 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"
1908 >
1909 Sign in
1910 </Link>
1911 ) : (
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1912 <>
1913 {/* Room between what comes in (the inbox) and what you make. */}
1914 <span aria-hidden="true" className="mx-1 h-5 w-px bg-line" />
Agents as a team: lifecycle, merge queue, billing and a new shell1915 <DropdownMenu>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1916 <Tooltip>
1917 <TooltipTrigger asChild>
1918 <DropdownMenuTrigger
1919 aria-label="Create new"
1920 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"
1921 >
1922 <Plus size={16} />
1923 <ChevronDown size={13} className="text-muted" />
1924 </DropdownMenuTrigger>
1925 </TooltipTrigger>
1926 <TooltipContent>Create new…</TooltipContent>
1927 </Tooltip>
1928 <DropdownMenuContent align="end" className="min-w-52">
Agents as a team: lifecycle, merge queue, billing and a new shell1929 {shell.repo && (
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1930 <>
1931 <DropdownMenuLabel className="truncate">In {shell.repo.name}</DropdownMenuLabel>
1932 <DropdownMenuItem asChild>
1933 <Link to={`/${shell.repo.namespace}/${shell.repo.name}/issues/new`}>
1934 <CircleDot />
1935 New issue
1936 </Link>
1937 </DropdownMenuItem>
1938 {shell.repo.member && (
1939 <DropdownMenuItem asChild>
1940 <Link to={`/${shell.repo.namespace}/${shell.repo.name}/plans`}>
1941 <ListTree />
1942 Plan work
1943 </Link>
1944 </DropdownMenuItem>
1945 )}
1946 <DropdownMenuSeparator />
1947 </>
Agents as a team: lifecycle, merge queue, billing and a new shell1948 )}
1949 <DropdownMenuItem asChild>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1950 <Link to="/?agent=new">
1951 <Sparkles />
1952 Put an agent on it
1953 </Link>
1954 </DropdownMenuItem>
1955 <DropdownMenuItem asChild>
Agents as a team: lifecycle, merge queue, billing and a new shell1956 <Link to="/new">
Projects: what a workspace builds and runs, first on every page1957 <Box />
1958 New project
Agents as a team: lifecycle, merge queue, billing and a new shell1959 </Link>
1960 </DropdownMenuItem>
1961 <DropdownMenuItem asChild>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1962 <Link to="/new/github">
1963 <GitBranch />
1964 Import from GitHub
1965 </Link>
1966 </DropdownMenuItem>
1967 <DropdownMenuSeparator />
1968 <DropdownMenuItem asChild>
Agents as a team: lifecycle, merge queue, billing and a new shell1969 <Link to="/workspaces/new">
1970 <Users />
1971 New workspace
1972 </Link>
1973 </DropdownMenuItem>
Merge branch 'worktree-agent-ad7c6d88d93adc817'1974 {/* A team in the workspace the sidebar is about, for whoever it lets create one. */}
1975 {shell.workspace &&
1976 (mayCreateTeams(shell.workspace.team_creation, shell.workspace.role) ? (
1977 <DropdownMenuItem asChild>
1978 <Link to={`/${shell.workspace.slug}/-/teams/new`}>
1979 <UsersRound />
1980 New team
1981 </Link>
1982 </DropdownMenuItem>
1983 ) : (
1984 <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 bar1985 <UsersRound />
1986 New team
Merge branch 'worktree-agent-ad7c6d88d93adc817'1987 <span className="ml-auto pl-3 text-xs text-faint">Owners only</span>
1988 </DropdownMenuItem>
1989 ))}
Agents as a team: lifecycle, merge queue, billing and a new shell1990 </DropdownMenuContent>
1991 </DropdownMenu>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1992 </>
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1993 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1994 </div>
1995 </header>
1996 {banner}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1997 <main id="content" tabIndex={-1} {...leaving} className={`min-w-0 grow outline-none ${leaving.className}`}>
Not found and error pages have one main landmark: their own when signed out, the shell's when signed in1998 <InMain.Provider value={true}>{children}</InMain.Provider>
Merge branch 'worktree-agent-aca4790ea223aa235'1999 </main>
Agents as a team: lifecycle, merge queue, billing and a new shell2000 </div>
Search across all of g1t, Explore, and a command palette2001 <CommandPalette
2002 open={palette}
2003 onOpenChange={setPalette}
2004 commands={commands}
2005 repo={shell.repo ? `${shell.repo.namespace}/${shell.repo.name}` : null}
2006 />
Agents as a team: lifecycle, merge queue, billing and a new shell2007 </div>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit2008 </Sheet>
Agents as a team: lifecycle, merge queue, billing and a new shell2009 );
2010}

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