Skip to content
546 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.

Merge branch 'worktree-agent-a64e51c8f386f46ac'1import { useEffect, useMemo, useRef, useState } from "react";
New workspaces are told to connect a model, before anything fails2import {
3 isRouteErrorResponse,
4 Link,
5 Links,
6 Meta,
7 Outlet,
8 Scripts,
9 ScrollRestoration,
10 type ShouldRevalidateFunctionArgs,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put11 useLocation,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit12 useMatches,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas13 useRouteError,
New workspaces are told to connect a model, before anything fails14 useRouteLoaderData,
15} from "react-router";
16
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)17import { type User, awaitsConfirmation, hasAccessIn, sharedWorkspaces } from "@g1t/contracts";
New workspaces are told to connect a model, before anything fails18
19import type { Route } from "./+types/root";
Load the stylesheet first; plain legal contact details20import appCss from "./app.css?url";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look21import displayFont from "@g1t/theme/fonts/bricolage-grotesque-latin.woff2?url";
22import sansFont from "@g1t/theme/fonts/hanken-grotesk-latin.woff2?url";
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.23import { ButtonLink } from "./components/ui";
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.24import { Card } from "./components/ui/card";
Merge branch 'worktree-agent-aca4790ea223aa235'25import { AppShell, Progress, type ShellData, useLeaving } from "./components/shell";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look26import { SiteFooter } from "./components/footer";
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.27import { PublicHeader } from "./components/public-header";
Fast pages, required checks on the branch, self-hosted runners, honest incidents28import { SpikeBanner } from "./components/spike-banner";
Merge main (membership, two-factor, GitHub repo roles) into tokens29import { PolicyNotice } from "./components/policy-notice";
Merge site-audit: SEO routes, analytics with consent first in Europe, security page, founder and X account, docs header; the tour opens on the pull request and leads with Code, on the real shell, with Agents and Docs working today30import { identify } from "./lib/analytics.client";
31import { visitorAsksFirst } from "./lib/analytics-consent";
32import { AnalyticsConsent } from "./components/analytics-consent";
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put33import { readCookie } from "./lib/mission";
34import { WORKSPACE_COOKIE, workspaceFor } from "./lib/workspace-choice";
Not found and error pages have one main landmark: their own when signed out, the shell's when signed in35import { PageMain } from "./components/landmark";
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put36import { NotFound } from "./components/not-found";
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.37import { frameOf } from "./lib/chrome";
The apps you pin to your dock are kept with your account, per workspace and in your order, so the dock is the same on every device: identity keeps them in dock_pins and answers dock_pins and set_dock_pins, the dock reads them with the rest of the page, pins kept only on this device carry over with your first change, this device's copy still draws the dock when identity can't be reached, a pin that can't be saved says so, and they go when you or the workspace do; the workspaces guide says how.38import { DOCK_COOKIE, SIDEBAR_COOKIE, pinsToShow, sidebarClosed } from "./lib/apps";
39import { savedPins } from "./lib/dock.server";
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.40import { StandaloneFrame } from "./components/standalone";
An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how.41import { billing, chat, identity, inbox, projects, workspaceAgents } from "./lib/services.server";
42import { teamsByAgent } from "./lib/people";
Chat and workspace agents: channels, DMs and named agents you talk to43import { unreadTotals } from "./lib/chat";
Fast pages, required checks on the branch, self-hosted runners, honest incidents44import { countsFor, readableRepos } from "./lib/access.server";
45import { shortCache } from "./lib/cache.server";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look46import { getViewer, viewerMiddleware } from "./lib/session.server";
Navigation: the workspace's own page with tabs, pins and recent in the sidebar47import { shortcutOf, workspaceProjects } from "./lib/workspace-projects.server";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look48import { registrationMode } from "./lib/registration.server";
Merge branch 'worktree-agent-aaf03bdceac799c89'49import { addresses } from "./lib/addresses.server";
Merge branch 'worktree-agent-a64e51c8f386f46ac'50import { RELOADED_KEY, RELOAD_GIVE_UP_MS, clientNavigated, reloadFixes, reloadedBefore } from "./lib/stale-build";
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address51import { useNonce } from "./lib/nonce";
Merge main into Artifacts Phase 252import { isNeedsSignIn } from "./lib/website-token";
Merge branch 'socket-tickets'53import { setLiveViaToken } from "./lib/live-socket";
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)54import { LiveNotifications } from "./components/notifications/live-notifications";
You choose how g1t looks: Auto follows your system, Light and Dark stay put, set from the account menu, the phone's You and help sheet, Settings → Account or the command palette; the page is drawn in your choice from the first byte, every colour has a light value that reads at 4.5:1 or better, code and diffs are highlighted for both, diagrams and the document editor follow along, the product tour and the g1t mark's avatar stay dark, and Dark is what you see until you choose; the workspaces guide says how.55import { THEME_COLOR, THEME_COOKIE, readTheme, themeAttribute, useThemeChoice } from "./lib/theme";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look56
New workspaces are told to connect a model, before anything fails57
58export const links: Route.LinksFunction = () => [
The fleet: g1t's new mark59 { rel: "icon", href: "/favicon.ico", sizes: "32x32" },
New workspaces are told to connect a model, before anything fails60 { rel: "icon", type: "image/svg+xml", href: "/favicon.svg" },
The fleet: g1t's new mark61 { rel: "icon", type: "image/png", sizes: "192x192", href: "/icon-192.png" },
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers62 { rel: "icon", type: "image/png", sizes: "512x512", href: "/icon-512.png" },
63 // Home screens and password managers look for a square, opaque 180px tile;
64 // the "-precomposed" name is served too, for the ones that ask for it.
65 { rel: "apple-touch-icon", sizes: "180x180", href: "/apple-touch-icon.png" },
66 // Installable: name, icons and colours for "Add to Home Screen" (public/site.webmanifest;
67 // manifest.webmanifest is the same file, kept for installs that already point at it).
68 { rel: "manifest", href: "/site.webmanifest" },
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look69 // The text and headline faces are wanted on every page, so they start
70 // loading with the stylesheet; mono waits until something uses it.
71 { rel: "preload", href: sansFont, as: "font", type: "font/woff2", crossOrigin: "anonymous" },
72 { rel: "preload", href: displayFont, as: "font", type: "font/woff2", crossOrigin: "anonymous" },
New workspaces are told to connect a model, before anything fails73];
74
75export const middleware: Route.MiddlewareFunction[] = [viewerMiddleware];
76
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put77export async function loader({ context, params, request }: Route.LoaderArgs) {
New workspaces are told to connect a model, before anything fails78 const user = getViewer(context);
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.79 const cookies = request.headers.get("cookie");
80 const chosen = readCookie(cookies, WORKSPACE_COOKIE);
81 const dock = readCookie(cookies, DOCK_COOKIE);
Merge two kinds of invite, kept apart: an invite to g1t (Settings, invite-only only, no workspace unless asked) and an invitation to a workspace (its People page)82 // Whether sign-up takes an invite: the sign-up page says so, and
83 // Settings → Invites offers invites to g1t only then. Cached per isolate.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look84 const [shell, mode] = await Promise.all([
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)85 // An account still confirming its address sees only the pages that
86 // allows (lib/confirm-gate.ts), in the visitor's frame.
87 user && !awaitsConfirmation(user)
Usage, Billing settings and prepaid AI credit; fixes from the UX audit88 ? shellFor(user, params, chosen, context).catch((error: unknown) => {
89 // A sidebar that could not be read is no reason to lose the page,
90 // or to draw it as if they were signed out.
91 console.error("root: the sidebar could not be read", error);
92 return bareShell(user, params, chosen);
93 })
94 : visitorShell(params, context),
Merge two kinds of invite, kept apart: an invite to g1t (Settings, invite-only only, no workspace unless asked) and an invitation to a workspace (its People page)95 registrationMode(),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look96 ]);
The apps you pin to your dock are kept with your account, per workspace and in your order, so the dock is the same on every device: identity keeps them in dock_pins and answers dock_pins and set_dock_pins, the dock reads them with the rest of the page, pins kept only on this device carry over with your first change, this device's copy still draws the dock when identity can't be reached, a pin that can't be saved says so, and they go when you or the workspace do; the workspaces guide says how.97 // The apps this person pinned to their dock here: as their account keeps
98 // them (read with the rest of the shell), else as this device's cookie
99 // remembers them (lib/apps.ts).
100 if (shell.workspace) shell.pins = pinsToShow(shell.pins, dock, shell.workspace.slug);
Merge branch 'worktree-agent-aaf03bdceac799c89'101 // Where this g1t lives, for clone lines, agent setup and link previews.
Merge site-audit: SEO routes, analytics with consent first in Europe, security page, founder and X account, docs header; the tour opens on the pull request and leads with Code, on the real shell, with Agents and Docs working today102 return {
103 user,
104 shell,
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.105 // Whether they folded the sidebar away, so the page is drawn that way from the start.
106 sidebarClosed: sidebarClosed(readCookie(cookies, SIDEBAR_COOKIE)),
You choose how g1t looks: Auto follows your system, Light and Dark stay put, set from the account menu, the phone's You and help sheet, Settings → Account or the command palette; the page is drawn in your choice from the first byte, every colour has a light value that reads at 4.5:1 or better, code and diffs are highlighted for both, diagrams and the document editor follow along, the product tour and the g1t mark's avatar stay dark, and Dark is what you see until you choose; the workspaces guide says how.107 // Their Appearance, so the page is drawn in it from the first byte (lib/theme.ts).
108 theme: readTheme(readCookie(cookies, THEME_COOKIE)),
Merge site-audit: SEO routes, analytics with consent first in Europe, security page, founder and X account, docs header; the tour opens on the pull request and leads with Code, on the real shell, with Agents and Docs working today109 inviteOnly: mode !== "open",
110 addresses: addresses(),
111 // Visitors from where the law asks first are asked before analytics runs.
112 analyticsConsent: visitorAsksFirst(request),
113 };
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put114}
115
116/**
117 * The sidebar for someone not signed in: nothing of their own, only the
118 * open counts of the project being looked at. One call, and only in a
119 * project; it answers the same for a private project as a missing one.
120 */
Fast pages, required checks on the branch, self-hosted runners, honest incidents121async function visitorShell(params: { owner?: string; repo?: string }, context: Route.LoaderArgs["context"]): Promise<ShellData> {
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put122 const path = params.owner && params.repo ? { namespace: params.owner, name: params.repo } : null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents123 const counts = path ? await countsFor(context, params) : null;
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put124 return {
125 workspace: null,
126 repos: [],
127 repo: path && counts?.ok ? { ...path, member: false, issues: counts.value.issues, pulls: counts.value.pulls } : null,
128 limit: null,
129 monthUsageMicros: null,
130 };
New workspaces are told to connect a model, before anything fails131}
132
Usage, Billing settings and prepaid AI credit; fixes from the UX audit133/** The sidebar from what the session already says, with no service asked. */
134function bareShell(user: User, params: { owner?: string; repo?: string }, chosen: string | null): ShellData {
135 return {
136 workspace: workspaceFor(user.workspaces ?? [], chosen, params),
137 repos: [],
138 repo: null,
139 limit: null,
140 monthUsageMicros: null,
141 };
142}
143
New workspaces are told to connect a model, before anything fails144/**
145 * The sidebar only changes with the workspace or repository being looked
146 * at, or after something was submitted: not on every page within them.
147 */
148export function shouldRevalidate({
149 currentParams,
150 nextParams,
151 formMethod,
152 defaultShouldRevalidate,
153}: ShouldRevalidateFunctionArgs) {
154 if (formMethod && formMethod !== "GET") return defaultShouldRevalidate;
155 return currentParams.owner !== nextParams.owner || currentParams.repo !== nextParams.repo;
156}
157
158/**
Usage, Billing settings and prepaid AI credit; fixes from the UX audit159 * The sidebar: the workspace whose pages or project these are when it is
160 * one of yours, else the one you chose (lib/workspace-choice.ts); its
161 * projects; and the repository being looked at.
New workspaces are told to connect a model, before anything fails162 */
163async function shellFor(
164 user: User,
165 params: { owner?: string; repo?: string },
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put166 chosen: string | null,
Fast pages, required checks on the branch, self-hosted runners, honest incidents167 context: Route.LoaderArgs["context"],
New workspaces are told to connect a model, before anything fails168): Promise<ShellData> {
169 const memberships = user.workspaces ?? [];
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put170 const workspace = workspaceFor(memberships, chosen, params);
New workspaces are told to connect a model, before anything fails171 const path = params.owner && params.repo ? { namespace: params.owner, name: params.repo } : null;
172 const now = new Date();
173 const monthStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1)).toISOString();
Fast pages, required checks on the branch, self-hosted runners, honest incidents174 // What the sidebar shows of the workspace changes rarely: kept for a few
175 // seconds per person and workspace, except just after they changed
176 // something (lib/cache.server.ts).
177 const kept = <T,>(what: string, load: () => Promise<T>) =>
178 workspace ? shortCache(`shell:${what}:${user.id}:${workspace.slug}`, SHELL_TTL_MS, load) : Promise.resolve(null);
The apps you pin to your dock are kept with your account, per workspace and in your order, so the dock is the same on every device: identity keeps them in dock_pins and answers dock_pins and set_dock_pins, the dock reads them with the rest of the page, pins kept only on this device carry over with your first change, this device's copy still draws the dock when identity can't be reached, a pin that can't be saved says so, and they go when you or the workspace do; the workspaces guide says how.179 const [listed, counts, status, usage, limit, entitlements, shared, unread, shortcuts, chatUnread, teamAgents, dockPins] = await Promise.all([
Navigation: the workspace's own page with tabs, pins and recent in the sidebar180 // Every project, for the palette and the count; the sidebar lists only
181 // the person's pinned and recent ones (lib/pins.ts).
182 workspace ? workspaceProjects(workspace.slug, user) : Promise.resolve(null),
Fast pages, required checks on the branch, self-hosted runners, honest incidents183 path ? countsFor(context, params) : Promise.resolve(null),
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor184 // Whether billing is on, without reading the account: that asks the
185 // card processor about the workspace's cards, too slow for every page.
186 kept("status", () => billing.status()).catch(() => null),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit187 kept(`usage:${monthStart}`, () => billing.usage(workspace!.slug, user, monthStart)).catch(() => null),
Fast pages, required checks on the branch, self-hosted runners, honest incidents188 kept("limit", () => billing.limit(workspace!.slug, user)).catch(() => null),
189 kept("entitlements", () => billing.entitlements(workspace!.slug)).catch(() => null),
190 sharedRepos(user),
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all191 // The bell's count: one query, never kept, so marking an item shows at once.
192 inbox.counts(user.username).catch(() => null),
Navigation: the workspace's own page with tabs, pins and recent in the sidebar193 // Never kept: opening a project moves it up Recent.
194 workspace ? projects.shortcuts(workspace.slug, user).catch(() => null) : Promise.resolve(null),
Chat and workspace agents: channels, DMs and named agents you talk to195 // The rail's Chat badge: never kept, and never waited on for long.
196 workspace ? chatUnreadFor(workspace.slug, user) : Promise.resolve(null),
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)197 // Home's Recent and the Agents sidebar, on every page: never waited on for long.
198 workspace ? agentsFor(workspace.slug, user) : Promise.resolve(null),
The apps you pin to your dock are kept with your account, per workspace and in your order, so the dock is the same on every device: identity keeps them in dock_pins and answers dock_pins and set_dock_pins, the dock reads them with the rest of the page, pins kept only on this device carry over with your first change, this device's copy still draws the dock when identity can't be reached, a pin that can't be saved says so, and they go when you or the workspace do; the workspaces guide says how.199 // The dock's pins, kept with the account so every device shows the same
200 // dock; never kept here, so a pin shows the moment it is made. Null
201 // falls back to the cookie (lib/dock.server.ts).
202 workspace ? savedPins(user, workspace.slug) : Promise.resolve(null),
New workspaces are told to connect a model, before anything fails203 ]);
204 return {
205 workspace,
Projects: what a workspace builds and runs, first on every page206 // Projects are what the sidebar lists: what the workspace builds and runs.
Navigation: the workspace's own page with tabs, pins and recent in the sidebar207 repos: listed?.ok ? listed.value.map(shortcutOf) : [],
208 pinned: (shortcuts?.pinned ?? []).map(shortcutOf),
209 recent: (shortcuts?.recent ?? []).map(shortcutOf),
New workspaces are told to connect a model, before anything fails210 repo:
211 path && counts?.ok
212 ? {
213 ...path,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look214 // Until the repository's own page says what their role is.
215 member: hasAccessIn(user, path.namespace),
New workspaces are told to connect a model, before anything fails216 issues: counts.value.issues,
217 pulls: counts.value.pulls,
218 }
219 : null,
Billing accounts, terms and enterprises; g1t is no longer free220 // Where the workspace stands against its usage limit, once billing is on.
221 limit:
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor222 status?.enabled && limit?.ok
Billing accounts, terms and enterprises; g1t is no longer free223 ? {
224 exposureMicros: limit.value.exposureMicros,
225 ceilingMicros: limit.value.ceilingMicros,
226 state: limit.value.state,
227 comped: limit.value.trust === "internal",
228 }
229 : null,
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor230 free: Boolean(status?.free),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look231 // A spend spike or a hold pauses new compute: the shell says so on every page.
232 compute:
233 workspace && entitlements && (entitlements.paused || entitlements.spike?.status === "open")
234 ? { paused: entitlements.paused, spike: entitlements.spike ?? null, owner: workspace.role === "owner" }
235 : null,
236 shared,
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all237 inbox: unread,
Chat and workspace agents: channels, DMs and named agents you talk to238 chat: chatUnread,
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)239 agents: teamAgents,
The apps you pin to your dock are kept with your account, per workspace and in your order, so the dock is the same on every device: identity keeps them in dock_pins and answers dock_pins and set_dock_pins, the dock reads them with the rest of the page, pins kept only on this device carry over with your first change, this device's copy still draws the dock when identity can't be reached, a pin that can't be saved says so, and they go when you or the workspace do; the workspaces guide says how.240 pins: dockPins ?? undefined,
New workspaces are told to connect a model, before anything fails241 // While g1t is free every charge is zero, so usage is shown at cost.
Usage, Billing settings and prepaid AI credit; fixes from the UX audit242 // Usage at price, the one figure every page shows.
243 monthUsageMicros: usage?.ok ? (usage.value.free ? usage.value.usedMicros : (usage.value.priceMicros ?? usage.value.spentMicros)) : null,
New workspaces are told to connect a model, before anything fails244 };
245}
246
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)247/** Starred and recent conversations Home's sidebar lists. */
248const SHELL_CONVERSATIONS = 8;
249
250/**
251 * What is unread in chat, for the rail, and the starred and latest
252 * conversations, for Home's sidebar; null when chat is slow to answer or
253 * does not.
254 */
Chat and workspace agents: channels, DMs and named agents you talk to255async function chatUnreadFor(slug: string, user: User): Promise<ShellData["chat"]> {
256 const timeout = new Promise<null>((resolve) => setTimeout(() => resolve(null), CHAT_BADGE_WAIT_MS));
257 const read = chat
258 .sidebar(slug, user)
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)259 .then((result) => {
260 if (!result.ok) return null;
261 const entries = result.value.entries;
262 const latest = [...entries].sort((a, b) => (b.channel.last_message_at ?? "").localeCompare(a.channel.last_message_at ?? ""));
263 return {
264 ...unreadTotals(entries),
265 starred: entries.filter((e) => e.starred).slice(0, SHELL_CONVERSATIONS),
266 recent: latest.slice(0, SHELL_CONVERSATIONS),
267 };
268 })
269 .catch(() => null);
270 return Promise.race([read, timeout]);
271}
272
273/** The workspace's agents, as the shell lists them; null when the agents service is slow or down. */
274async function agentsFor(slug: string, user: User): Promise<ShellData["agents"]> {
275 const timeout = new Promise<null>((resolve) => setTimeout(() => resolve(null), CHAT_BADGE_WAIT_MS));
An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how.276 // The workspace's agents and the person's own personal ones, for "Yours",
277 // with the teams each is on, for the Agents sidebar's groups.
278 const read = Promise.all([workspaceAgents.list(slug, user, { personal: "mine" }), identity.teamAgentIndex(slug).catch(() => null)])
279 .then(([result, index]) => {
280 if (!result.ok) return null;
281 const teams = index ? teamsByAgent(index) : null;
282 return result.value
283 .filter((agent) => !agent.archived_at)
284 .map((agent) => ({
285 id: agent.id,
286 handle: agent.handle,
287 display_name: agent.display_name,
288 avatar: agent.avatar,
289 avatar_seed: agent.avatar_seed,
290 role: agent.role,
291 title: agent.title,
292 status: agent.status,
293 builtin: agent.builtin === true,
294 scope: agent.scope,
295 ...(teams ? { teams: teams[agent.id] ?? [] } : {}),
296 }));
297 })
Chat and workspace agents: channels, DMs and named agents you talk to298 .catch(() => null);
299 return Promise.race([read, timeout]);
300}
301
302/** The longest the rail's Chat badge holds up a page. */
303const CHAT_BADGE_WAIT_MS = 300;
304
Usage, Billing settings and prepaid AI credit; fixes from the UX audit305/**
306 * How long the sidebar's workspace answers are kept: as long as someone's
307 * own changes read fresh anyway (lib/perf.ts `PRIMARY_WINDOW_SECONDS`).
308 */
309const SHELL_TTL_MS = 30_000;
Fast pages, required checks on the branch, self-hosted runners, honest incidents310
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look311/** Most repositories listed under Shared with you. */
312const MAX_SHARED = 20;
313
314/**
315 * Repositories shared with the user in workspaces they do not belong to,
316 * by their grants: only those, never the workspace's other repositories.
317 */
318async function sharedRepos(user: User): Promise<ShellData["shared"]> {
319 const outside = new Set(sharedWorkspaces(user));
320 if (outside.size === 0) return [];
321 const ids = (user.grants ?? []).filter((grant) => outside.has(grant.workspace)).map((grant) => grant.repo_id).slice(0, MAX_SHARED);
Fast pages, required checks on the branch, self-hosted runners, honest incidents322 const found = await readableRepos(ids, user);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look323 return found
Fast pages, required checks on the branch, self-hosted runners, honest incidents324 .map((repo) => ({ namespace: repo.namespace, name: repo.name, isPrivate: repo.isPrivate }))
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look325 .sort((a, b) => `${a.namespace}/${a.name}`.localeCompare(`${b.namespace}/${b.name}`));
326}
327
Usage, Billing settings and prepaid AI credit; fixes from the UX audit328/**
329 * The root's data as the browser last had it. When a navigation fails in a
330 * way that takes the root's data with it, the error is still drawn in the
331 * person's own frame, signed in, not the visitor's. Never kept on the
332 * server, where one module serves everyone.
333 */
334let lastRoot: Awaited<ReturnType<typeof loader>> | undefined;
335
New workspaces are told to connect a model, before anything fails336export function Layout({ children }: { children: React.ReactNode }) {
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address337 const nonce = useNonce();
New workspaces are told to connect a model, before anything fails338 // Undefined when the root loader itself failed.
Usage, Billing settings and prepaid AI credit; fixes from the UX audit339 const loaded = useRouteLoaderData<typeof loader>("root");
340 const inBrowser = typeof document !== "undefined";
341 if (loaded && inBrowser) lastRoot = loaded;
342 const root = loaded ?? (inBrowser ? lastRoot : undefined);
New workspaces are told to connect a model, before anything fails343 const user = root?.user;
Merge branch 'socket-tickets'344 // A page opened with an access token signs its live sockets in with tickets (lib/live-socket.ts).
345 if (inBrowser) setLiveViaToken(Boolean(user?.token?.website));
Merge branch 'worktree-agent-a985c01f43538af01'346 const { pathname, search } = useLocation();
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas347 // Drawn around the error page too: a 404 keeps the sidebar out of a
348 // project or workspace the viewer cannot see.
349 const error = useRouteError();
350 const missing = isRouteErrorResponse(error) && error.status === 404;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look351 // The billing page shows the full banner itself.
352 const paused = root?.shell?.compute && root.shell.workspace && !pathname.endsWith("/-/billing") && (
Fast pages, required checks on the branch, self-hosted runners, honest incidents353 <SpikeBanner
354 compact
355 slug={root.shell.workspace.slug}
356 entitlements={{ paused: root.shell.compute.paused, spike: root.shell.compute.spike }}
357 owner={root.shell.compute.owner}
358 />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look359 );
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)360 // On the few pages an account still confirming its address can open
361 // besides /confirm-email (policies, support, signing in): the way back.
362 const verify = user && awaitsConfirmation(user) && pathname !== "/confirm-email" && (
363 <p
364 role="status"
New workspaces are told to connect a model, before anything fails365 className="flex flex-wrap items-center justify-center gap-x-3 gap-y-1 border-b border-warn/30 bg-warn/10 px-4 py-2 text-sm"
366 >
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)367 <span>Confirm your email address to start using g1t.</span>
368 <Link to="/confirm-email" className="font-medium underline underline-offset-4">
369 Enter your code
370 </Link>
371 </p>
New workspaces are told to connect a model, before anything fails372 );
Merge main (membership, two-factor, GitHub repo roles) into tokens373 // A workspace that requires two-factor authentication of someone who
374 // has not turned it on: they keep their place, and cannot use it yet.
375 const held = user?.held && user.held.length > 0 && pathname !== "/settings/two-factor" && <PolicyNotice held={user.held} />;
Merge branch 'worktree-agent-aca4790ea223aa235'376 const leaving = useLeaving();
You choose how g1t looks: Auto follows your system, Light and Dark stay put, set from the account menu, the phone's You and help sheet, Settings → Account or the command palette; the page is drawn in your choice from the first byte, every colour has a light value that reads at 4.5:1 or better, code and diffs are highlighted for both, diagrams and the document editor follow along, the product tour and the g1t mark's avatar stay dark, and Dark is what you see until you choose; the workspaces guide says how.377 // Auto, Light or Dark: as the cookie said when the page was drawn, or as just switched.
378 const theme = useThemeChoice(root?.theme);
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.379 // Which frame the page is drawn in (lib/chrome.ts): on its own, the app's, or the public one.
380 const frame = frameOf(pathname, {
381 signedIn: user != null && !awaitsConfirmation(user),
382 workspace: (user?.workspaces ?? []).length > 0,
383 });
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look384 const banner =
Merge main (membership, two-factor, GitHub repo roles) into tokens385 paused || verify || held ? (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look386 <>
387 {paused}
388 {verify}
Merge main (membership, two-factor, GitHub repo roles) into tokens389 {held}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look390 </>
391 ) : null;
New workspaces are told to connect a model, before anything fails392 return (
You choose how g1t looks: Auto follows your system, Light and Dark stay put, set from the account menu, the phone's You and help sheet, Settings → Account or the command palette; the page is drawn in your choice from the first byte, every colour has a light value that reads at 4.5:1 or better, code and diffs are highlighted for both, diagrams and the document editor follow along, the product tour and the g1t mark's avatar stay dark, and Dark is what you see until you choose; the workspaces guide says how.393 <html lang="en" data-theme={themeAttribute(theme)}>
New workspaces are told to connect a model, before anything fails394 <head>
395 <meta charSet="utf-8" />
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)396 <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
You choose how g1t looks: Auto follows your system, Light and Dark stay put, set from the account menu, the phone's You and help sheet, Settings → Account or the command palette; the page is drawn in your choice from the first byte, every colour has a light value that reads at 4.5:1 or better, code and diffs are highlighted for both, diagrams and the document editor follow along, the product tour and the g1t mark's avatar stay dark, and Dark is what you see until you choose; the workspaces guide says how.397 {/* The browser bar matches the page: one colour for a chosen theme, the system's for Auto. */}
398 {theme === "auto" ? (
399 <>
400 <meta name="theme-color" media="(prefers-color-scheme: light)" content={THEME_COLOR.light} />
401 <meta name="theme-color" media="(prefers-color-scheme: dark)" content={THEME_COLOR.dark} />
402 </>
403 ) : (
404 <meta name="theme-color" content={THEME_COLOR[theme]} />
405 )}
Merge branch 'mirroring' into artifacts-mode406 <meta name="mobile-web-app-capable" content="yes" />
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)407 <meta name="apple-mobile-web-app-capable" content="yes" />
408 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
Put the stylesheet at the top of the head, before React Router's preloads409 {/* The stylesheet before everything React Router preloads, so a slow
The docs folder is gone, and what it held lives where people read it: how a self-hosted g1t runs and how to deploy g1t to Cloudflare are pages on docs.g1t.sh under Run g1t yourself, and speed, rate limits and operating g1t.sh are sections of CONTRIBUTING.md; code that cited a file in docs/ now points to the page or section that covers it, or says what it means itself, and applied migrations and the runner images are left as they were.410 connection paints sooner. */}
Give the stylesheet a precedence so React places it before preloads411 <link rel="stylesheet" href={appCss} precedence="default" />
Merge site-audit: SEO routes, analytics with consent first in Europe, security page, founder and X account, docs header; the tour opens on the pull request and leads with Code, on the real shell, with Agents and Docs working today412 {root?.analyticsConsent && <meta name="g1t-analytics" content="consent" />}
New workspaces are told to connect a model, before anything fails413 <Meta />
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address414 <Links nonce={nonce} />
New workspaces are told to connect a model, before anything fails415 </head>
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.416 <body className="flex min-h-screen flex-col" data-frame={frame === "app" && user && root?.shell ? "app" : frame}>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit417 {/* The first stop for the keyboard: past the menus, to the page. */}
418 <a
419 href="#content"
420 className="sr-only rounded-md bg-fg px-3 py-2 text-sm font-medium text-bg focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-[70]"
421 >
422 Skip to content
423 </a>
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.424 {frame === "standalone" ? (
425 <StandaloneFrame>{children}</StandaloneFrame>
426 ) : frame === "app" && user && root?.shell ? (
427 <AppShell user={user} shell={root.shell} missing={missing} banner={banner} sidebarClosed={root.sidebarClosed}>
New workspaces are told to connect a model, before anything fails428 {children}
429 </AppShell>
430 ) : (
431 <>
432 <Progress />
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.433 <PublicHeader user={user} />
New workspaces are told to connect a model, before anything fails434 {banner}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit435 <div id="content" tabIndex={-1} {...leaving} className={`grow outline-none ${leaving.className}`}>
Merge branch 'worktree-agent-aca4790ea223aa235'436 {children}
437 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look438 <SiteFooter user={user} />
New workspaces are told to connect a model, before anything fails439 </>
440 )}
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)441 {/* Live notifications for the signed-in: the feed socket, toasts, the tab's count (components/notifications). */}
442 {user && !awaitsConfirmation(user) && (
443 <LiveNotifications workspace={root?.shell?.workspace?.slug ?? null} inbox={root?.shell?.inbox?.unread ?? null} />
444 )}
Merge site-audit: SEO routes, analytics with consent first in Europe, security page, founder and X account, docs header; the tour opens on the pull request and leads with Code, on the real shell, with Agents and Docs working today445 <AnalyticsConsent />
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address446 <ScrollRestoration nonce={nonce} />
447 <Scripts nonce={nonce} />
New workspaces are told to connect a model, before anything fails448 </body>
449 </html>
450 );
451}
452
453export default function App() {
Merge site-audit: SEO routes, analytics with consent first in Europe, security page, founder and X account, docs header; the tour opens on the pull request and leads with Code, on the real shell, with Agents and Docs working today454 const userId = useRouteLoaderData<typeof loader>("root")?.user?.id ?? null;
455 // Tell analytics who is signed in, once per change; signing out forgets them.
456 const lastUserId = useRef<string | null>(null);
457 useEffect(() => {
458 identify(userId, lastUserId.current);
459 lastUserId.current = userId;
460 }, [userId]);
New workspaces are told to connect a model, before anything fails461 return <Outlet />;
462}
463
464export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
Usage, Billing settings and prepaid AI credit; fixes from the UX audit465 const location = useLocation();
466 const matches = useMatches();
Merge branch 'worktree-agent-a64e51c8f386f46ac'467 const href = location.pathname + location.search + location.hash;
Usage, Billing settings and prepaid AI credit; fixes from the UX audit468 // A tab left open across a deploy: load the address again as a whole
Merge branch 'worktree-agent-a64e51c8f386f46ac'469 // page, once, rather than show an error the reload fixes. A document load
470 // never reloads, so the server and the hydrating client render the same.
471 const reloadable = reloadFixes({
Usage, Billing settings and prepaid AI credit; fixes from the UX audit472 error,
473 status: isRouteErrorResponse(error) ? error.status : undefined,
Merge branch 'worktree-agent-a64e51c8f386f46ac'474 clientNavigation: clientNavigated(location.key),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit475 caughtByCatchAll: matches.at(-1)?.id === "routes/not-found",
476 });
Merge branch 'worktree-agent-a64e51c8f386f46ac'477 // Already loaded again once, or the reload never started: show the page.
478 const tried = useMemo(() => reloadable && reloadedBefore(href), [reloadable, href]);
479 const [gaveUp, setGaveUp] = useState<string | null>(null);
480 const reload = reloadable && !tried && gaveUp !== href;
Usage, Billing settings and prepaid AI credit; fixes from the UX audit481 useEffect(() => {
482 if (!reload) return;
483 try {
484 sessionStorage.setItem(RELOADED_KEY, href);
485 } catch {
486 // No session storage: reload anyway; a document load never asks again.
487 }
488 window.location.assign(href);
Merge branch 'worktree-agent-a64e51c8f386f46ac'489 const timer = window.setTimeout(() => setGaveUp(href), RELOAD_GIVE_UP_MS);
490 return () => window.clearTimeout(timer);
Usage, Billing settings and prepaid AI credit; fixes from the UX audit491 }, [reload, href]);
492 if (reload) {
493 return (
Not found and error pages have one main landmark: their own when signed out, the shell's when signed in494 <PageMain className="mx-auto max-w-xl px-4 py-32 text-center text-sm text-muted" aria-busy="true">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit495 <title>Loading · g1t</title>
496 Loading the latest version of g1t…
Not found and error pages have one main landmark: their own when signed out, the shell's when signed in497 </PageMain>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit498 );
499 }
500
New workspaces are told to connect a model, before anything fails501 let title = "Something went wrong";
502 let details = "An unexpected error occurred. Try again in a moment.";
503 let stack: string | undefined;
504
505 if (isRouteErrorResponse(error)) {
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put506 // The same page for something private and something missing.
Usage, Billing settings and prepaid AI credit; fixes from the UX audit507 if (error.status === 404) {
508 return (
509 <>
510 <title>Not found · g1t</title>
511 <NotFound data={error.data} />
512 </>
513 );
514 }
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put515 if (typeof error.data === "string" && error.data) {
New workspaces are told to connect a model, before anything fails516 details = error.data;
517 }
Merge main into Artifacts Phase 2518 // A token asked for what needs a real sign-in (lib/website-token.ts).
519 if (isNeedsSignIn(error.data)) {
520 title = "This needs you to sign in";
521 details = error.data.message;
522 }
New workspaces are told to connect a model, before anything fails523 } else if (import.meta.env.DEV && error instanceof Error) {
524 details = error.message;
525 stack = error.stack;
526 }
527
528 return (
Not found and error pages have one main landmark: their own when signed out, the shell's when signed in529 <PageMain className="mx-auto max-w-xl px-4 py-32 text-center">
New workspaces are told to connect a model, before anything fails530 <h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
531 <p className="mt-3 text-muted">{details}</p>
532 <div className="mt-8">
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.533 <ButtonLink to="/" variant="outline">
New workspaces are told to connect a model, before anything fails534 Back to home
535 </ButtonLink>
536 </div>
537 {stack && (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.538 <Card asChild tone="plain" radius="lg" className="mt-8 overflow-x-auto p-4 text-left text-xs">
539 <pre>
540 <code>{stack}</code>
541 </pre>
542 </Card>
New workspaces are told to connect a model, before anything fails543 )}
Not found and error pages have one main landmark: their own when signed out, the shell's when signed in544 </PageMain>
New workspaces are told to connect a model, before anything fails545 );
546}

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