Skip to content
542 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";
The docs' Copy page menu keeps its own spacing, with an icon per item2import {
3 isRouteErrorResponse,
4 Link,
5 Links,
6 Meta,
7 Outlet,
8 Scripts,
9 ScrollRestoration,
10 type ShouldRevalidateFunctionArgs,
11 useLocation,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit12 useMatches,
The docs' Copy page menu keeps its own spacing, with an icon per item13 useRouteError,
14 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";
The docs' Copy page menu keeps its own spacing, with an icon per item18
19import type { Route } from "./+types/root";
20import appCss from "./app.css?url";
21import 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";
The docs' Copy page menu keeps its own spacing, with an icon per item26import { 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";
The docs' Copy page menu keeps its own spacing, with an icon per item28import { 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";
The docs' Copy page menu keeps its own spacing, with an icon per item33import { 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";
The docs' Copy page menu keeps its own spacing, with an icon per item36import { 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";
The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404.41import { billing, chat, identity, inbox, projects } from "./lib/services.server";
Chat and workspace agents: channels, DMs and named agents you talk to42import { unreadTotals } from "./lib/chat";
The docs' Copy page menu keeps its own spacing, with an icon per item43import { countsFor, readableRepos } from "./lib/access.server";
44import { shortCache } from "./lib/cache.server";
45import { getViewer, viewerMiddleware } from "./lib/session.server";
Navigation: the workspace's own page with tabs, pins and recent in the sidebar46import { shortcutOf, workspaceProjects } from "./lib/workspace-projects.server";
Merge branch 'worktree-agent-aaf03bdceac799c89'47import { addresses } from "./lib/addresses.server";
Merge branch 'worktree-agent-a64e51c8f386f46ac'48import { 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 address49import { useNonce } from "./lib/nonce";
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r950import { useStuckNavigationRescue } from "./lib/stuck";
Merge main into Artifacts Phase 251import { isNeedsSignIn } from "./lib/website-token";
A doc opens at once: the page carries the document's saved state, so the editor mounts without waiting for its live room, which connects in the background and sends only what changed, with a quiet dot that turns green when it's synced; edits made before then are kept and sent once. The editor's code is preloaded from the page and fetched early when you hover a doc, the app's shared code ships in a few chunks instead of a hundred small ones, socket tickets come with the page instead of a round trip, and the artifacts service answers a page, a sidebar and a room in parallel lookups instead of a dozen in a row, reporting its database round trips in Server-Timing. The artifacts guide says how a doc opens, and CONTRIBUTING's Speed section has the numbers.52import { offerTicket, setLiveViaToken } from "./lib/live-socket";
53import { socketTicketFor } from "./lib/socket-ticket.server";
Merge branch 'main' into rm-agents54import { DesktopApp } from "./components/desktop-app";
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.55import { DesktopRunnerLink } from "./components/desktop-runner-link";
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)56import { 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.57import { THEME_COLOR, THEME_COOKIE, readTheme, themeAttribute, useThemeChoice } from "./lib/theme";
The docs' Copy page menu keeps its own spacing, with an icon per item58
59
60export const links: Route.LinksFunction = () => [
61 { rel: "icon", href: "/favicon.ico", sizes: "32x32" },
62 { rel: "icon", type: "image/svg+xml", href: "/favicon.svg" },
63 { 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 managers64 { rel: "icon", type: "image/png", sizes: "512x512", href: "/icon-512.png" },
65 // Home screens and password managers look for a square, opaque 180px tile;
66 // the "-precomposed" name is served too, for the ones that ask for it.
67 { rel: "apple-touch-icon", sizes: "180x180", href: "/apple-touch-icon.png" },
68 // Installable: name, icons and colours for "Add to Home Screen" (public/site.webmanifest;
69 // manifest.webmanifest is the same file, kept for installs that already point at it).
70 { rel: "manifest", href: "/site.webmanifest" },
The docs' Copy page menu keeps its own spacing, with an icon per item71 // The text and headline faces are wanted on every page, so they start
72 // loading with the stylesheet; mono waits until something uses it.
73 { rel: "preload", href: sansFont, as: "font", type: "font/woff2", crossOrigin: "anonymous" },
74 { rel: "preload", href: displayFont, as: "font", type: "font/woff2", crossOrigin: "anonymous" },
75];
76
77export const middleware: Route.MiddlewareFunction[] = [viewerMiddleware];
78
79export async function loader({ context, params, request }: Route.LoaderArgs) {
80 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.81 const cookies = request.headers.get("cookie");
82 const chosen = readCookie(cookies, WORKSPACE_COOKIE);
83 const dock = readCookie(cookies, DOCK_COOKIE);
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.84 const [shell, sounds] = 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),
Chat has sounds. A short warm cue plays for a message in a conversation you have open but aren't looking at, a direct message, a mention of you and an agent finishing something for you, with a soft tick for sending that is off until you turn it on; never for your own messages, a muted conversation, a conversation you are looking at in a window that has the front, or while you have paused notifications, and never twice for one message or more than once in a second and a half. The cues are made by the browser itself in two sets, Soft and Bright, so nothing is downloaded; the Sounds and notifications section of your notification settings turns them on and off, picks the set and the volume, plays each one, and offers desktop notifications for when the window is behind; Do not disturb is the one pause everyone sees, set from the chat sidebar's bell, the settings, or your avatar menu, now with a two-hour choice, and the top bar's bell shows a dot while it's on. Your choices are kept with your account. The sound layer sits behind one small interface so the desktop app can play them natively later. The chat and notifications guides say the rules.95 // Their chat sounds, with the page, so the first live event already
96 // respects them (lib/sounds.ts); the defaults when identity is slow.
97 user && !awaitsConfirmation(user) ? identity.soundSettings(user).catch(() => null) : Promise.resolve(null),
The docs' Copy page menu keeps its own spacing, with an icon per item98 ]);
The left-hand navigation takes less room and reads at a glance. The dock becomes a rail: a 56-pixel column of icons with the g1t mark on top, the current app marked by a filled square, hover hints naming each with its shortcut, the apps you pinned in a scrolling band, and Apps, People, Workspace and your account at the foot. Each mode's sidebar sits flush against it, 240 pixels wide, opening on Search or jump to and listing its groups under small labels in 36-pixel rows; Ctrl B hides it and the page grows into the room. Every mode's list is built on the shadcn Sidebar now in the kit, so Workspace, Code, People, Notifications, account settings, Chat, Artifacts and Agents all share one frame; the phone keeps its bottom bar and drawer. The workspaces guide describes the rail, the sidebar and the page.99 // The apps this person pinned to their rail here: as their account keeps
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.100 // them (read with the rest of the shell), else as this device's cookie
101 // remembers them (lib/apps.ts).
102 if (shell.workspace) shell.pins = pinsToShow(shell.pins, dock, shell.workspace.slug);
Merge branch 'worktree-agent-aaf03bdceac799c89'103 // 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 today104 return {
105 user,
106 shell,
A doc opens at once: the page carries the document's saved state, so the editor mounts without waiting for its live room, which connects in the background and sends only what changed, with a quiet dot that turns green when it's synced; edits made before then are kept and sent once. The editor's code is preloaded from the page and fetched early when you hover a doc, the app's shared code ships in a few chunks instead of a hundred small ones, socket tickets come with the page instead of a round trip, and the artifacts service answers a page, a sidebar and a room in parallel lookups instead of a dozen in a row, reporting its database round trips in Server-Timing. The artifacts guide says how a doc opens, and CONTRIBUTING's Speed section has the numbers.107 // For a page opened with an access token: the feed socket's ticket,
108 // minted here so the feed opens without asking for one (lib/live-socket.ts).
109 liveTicket: user?.token?.website ? await socketTicketFor(context, request, "/-/live") : null,
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.110 // Whether they folded the sidebar away, so the page is drawn that way from the start.
111 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.112 // Their Appearance, so the page is drawn in it from the first byte (lib/theme.ts).
113 theme: readTheme(readCookie(cookies, THEME_COOKIE)),
Chat has sounds. A short warm cue plays for a message in a conversation you have open but aren't looking at, a direct message, a mention of you and an agent finishing something for you, with a soft tick for sending that is off until you turn it on; never for your own messages, a muted conversation, a conversation you are looking at in a window that has the front, or while you have paused notifications, and never twice for one message or more than once in a second and a half. The cues are made by the browser itself in two sets, Soft and Bright, so nothing is downloaded; the Sounds and notifications section of your notification settings turns them on and off, picks the set and the volume, plays each one, and offers desktop notifications for when the window is behind; Do not disturb is the one pause everyone sees, set from the chat sidebar's bell, the settings, or your avatar menu, now with a two-hour choice, and the top bar's bell shows a dot while it's on. Your choices are kept with your account. The sound layer sits behind one small interface so the desktop app can play them natively later. The chat and notifications guides say the rules.114 // What they hear when chat moves (components/notifications/sounds.tsx).
115 sounds,
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 today116 addresses: addresses(),
117 // Visitors from where the law asks first are asked before analytics runs.
118 analyticsConsent: visitorAsksFirst(request),
119 };
The docs' Copy page menu keeps its own spacing, with an icon per item120}
121
122/**
123 * The sidebar for someone not signed in: nothing of their own, only the
124 * open counts of the project being looked at. One call, and only in a
125 * project; it answers the same for a private project as a missing one.
126 */
127async function visitorShell(params: { owner?: string; repo?: string }, context: Route.LoaderArgs["context"]): Promise<ShellData> {
128 const path = params.owner && params.repo ? { namespace: params.owner, name: params.repo } : null;
129 const counts = path ? await countsFor(context, params) : null;
130 return {
131 workspace: null,
132 repos: [],
133 repo: path && counts?.ok ? { ...path, member: false, issues: counts.value.issues, pulls: counts.value.pulls } : null,
134 limit: null,
135 monthUsageMicros: null,
136 };
137}
138
Usage, Billing settings and prepaid AI credit; fixes from the UX audit139/** The sidebar from what the session already says, with no service asked. */
140function bareShell(user: User, params: { owner?: string; repo?: string }, chosen: string | null): ShellData {
141 return {
142 workspace: workspaceFor(user.workspaces ?? [], chosen, params),
143 repos: [],
144 repo: null,
145 limit: null,
146 monthUsageMicros: null,
147 };
148}
149
The docs' Copy page menu keeps its own spacing, with an icon per item150/**
151 * The sidebar only changes with the workspace or repository being looked
152 * at, or after something was submitted: not on every page within them.
153 */
154export function shouldRevalidate({
155 currentParams,
156 nextParams,
157 formMethod,
158 defaultShouldRevalidate,
159}: ShouldRevalidateFunctionArgs) {
160 if (formMethod && formMethod !== "GET") return defaultShouldRevalidate;
161 return currentParams.owner !== nextParams.owner || currentParams.repo !== nextParams.repo;
162}
163
164/**
Usage, Billing settings and prepaid AI credit; fixes from the UX audit165 * The sidebar: the workspace whose pages or project these are when it is
166 * one of yours, else the one you chose (lib/workspace-choice.ts); its
167 * projects; and the repository being looked at.
The docs' Copy page menu keeps its own spacing, with an icon per item168 */
169async function shellFor(
170 user: User,
171 params: { owner?: string; repo?: string },
172 chosen: string | null,
173 context: Route.LoaderArgs["context"],
174): Promise<ShellData> {
175 const memberships = user.workspaces ?? [];
176 const workspace = workspaceFor(memberships, chosen, params);
177 const path = params.owner && params.repo ? { namespace: params.owner, name: params.repo } : null;
178 const now = new Date();
179 const monthStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1)).toISOString();
180 // What the sidebar shows of the workspace changes rarely: kept for a few
181 // seconds per person and workspace, except just after they changed
182 // something (lib/cache.server.ts).
183 const kept = <T,>(what: string, load: () => Promise<T>) =>
184 workspace ? shortCache(`shell:${what}:${user.id}:${workspace.slug}`, SHELL_TTL_MS, load) : Promise.resolve(null);
The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404.185 const [listed, counts, status, usage, limit, entitlements, shared, unread, shortcuts, chatUnread, dockPins] = await Promise.all([
Navigation: the workspace's own page with tabs, pins and recent in the sidebar186 // Every project, for the palette and the count; the sidebar lists only
187 // the person's pinned and recent ones (lib/pins.ts).
188 workspace ? workspaceProjects(workspace.slug, user) : Promise.resolve(null),
The docs' Copy page menu keeps its own spacing, with an icon per item189 path ? countsFor(context, params) : Promise.resolve(null),
190 // Whether billing is on, without reading the account: that asks the
191 // card processor about the workspace's cards, too slow for every page.
192 kept("status", () => billing.status()).catch(() => null),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit193 kept(`usage:${monthStart}`, () => billing.usage(workspace!.slug, user, monthStart)).catch(() => null),
The docs' Copy page menu keeps its own spacing, with an icon per item194 kept("limit", () => billing.limit(workspace!.slug, user)).catch(() => null),
195 kept("entitlements", () => billing.entitlements(workspace!.slug)).catch(() => null),
196 sharedRepos(user),
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all197 // The bell's count: one query, never kept, so marking an item shows at once.
198 inbox.counts(user.username).catch(() => null),
Navigation: the workspace's own page with tabs, pins and recent in the sidebar199 // Never kept: opening a project moves it up Recent.
200 workspace ? projects.shortcuts(workspace.slug, user).catch(() => null) : Promise.resolve(null),
Chat and workspace agents: channels, DMs and named agents you talk to201 // The rail's Chat badge: never kept, and never waited on for long.
202 workspace ? chatUnreadFor(workspace.slug, user) : Promise.resolve(null),
The left-hand navigation takes less room and reads at a glance. The dock becomes a rail: a 56-pixel column of icons with the g1t mark on top, the current app marked by a filled square, hover hints naming each with its shortcut, the apps you pinned in a scrolling band, and Apps, People, Workspace and your account at the foot. Each mode's sidebar sits flush against it, 240 pixels wide, opening on Search or jump to and listing its groups under small labels in 36-pixel rows; Ctrl B hides it and the page grows into the room. Every mode's list is built on the shadcn Sidebar now in the kit, so Workspace, Code, People, Notifications, account settings, Chat, Artifacts and Agents all share one frame; the phone keeps its bottom bar and drawer. The workspaces guide describes the rail, the sidebar and the page.203 // The rail's pins, kept with the account so every device shows the same
204 // rail; never kept here, so a pin shows the moment it is made. 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.205 // falls back to the cookie (lib/dock.server.ts).
206 workspace ? savedPins(user, workspace.slug) : Promise.resolve(null),
The docs' Copy page menu keeps its own spacing, with an icon per item207 ]);
208 return {
209 workspace,
210 // 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 sidebar211 repos: listed?.ok ? listed.value.map(shortcutOf) : [],
212 pinned: (shortcuts?.pinned ?? []).map(shortcutOf),
213 recent: (shortcuts?.recent ?? []).map(shortcutOf),
The docs' Copy page menu keeps its own spacing, with an icon per item214 repo:
215 path && counts?.ok
216 ? {
217 ...path,
218 // Until the repository's own page says what their role is.
219 member: hasAccessIn(user, path.namespace),
220 issues: counts.value.issues,
221 pulls: counts.value.pulls,
222 }
223 : null,
224 // Where the workspace stands against its usage limit, once billing is on.
225 limit:
226 status?.enabled && limit?.ok
227 ? {
228 exposureMicros: limit.value.exposureMicros,
229 ceilingMicros: limit.value.ceilingMicros,
230 state: limit.value.state,
231 comped: limit.value.trust === "internal",
232 }
233 : null,
234 free: Boolean(status?.free),
235 // A spend spike or a hold pauses new compute: the shell says so on every page.
236 compute:
237 workspace && entitlements && (entitlements.paused || entitlements.spike?.status === "open")
238 ? { paused: entitlements.paused, spike: entitlements.spike ?? null, owner: workspace.role === "owner" }
239 : null,
240 shared,
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all241 inbox: unread,
Chat and workspace agents: channels, DMs and named agents you talk to242 chat: chatUnread,
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.243 pins: dockPins ?? undefined,
The docs' Copy page menu keeps its own spacing, with an icon per item244 // 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 audit245 // Usage at price, the one figure every page shows.
246 monthUsageMicros: usage?.ok ? (usage.value.free ? usage.value.usedMicros : (usage.value.priceMicros ?? usage.value.spentMicros)) : null,
The docs' Copy page menu keeps its own spacing, with an icon per item247 };
248}
249
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)250/** Starred and recent conversations Home's sidebar lists. */
251const SHELL_CONVERSATIONS = 8;
252
253/**
254 * What is unread in chat, for the rail, and the starred and latest
255 * conversations, for Home's sidebar; null when chat is slow to answer or
256 * does not.
257 */
Chat and workspace agents: channels, DMs and named agents you talk to258async function chatUnreadFor(slug: string, user: User): Promise<ShellData["chat"]> {
259 const timeout = new Promise<null>((resolve) => setTimeout(() => resolve(null), CHAT_BADGE_WAIT_MS));
260 const read = chat
261 .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)262 .then((result) => {
263 if (!result.ok) return null;
264 const entries = result.value.entries;
265 const latest = [...entries].sort((a, b) => (b.channel.last_message_at ?? "").localeCompare(a.channel.last_message_at ?? ""));
266 return {
267 ...unreadTotals(entries),
268 starred: entries.filter((e) => e.starred).slice(0, SHELL_CONVERSATIONS),
269 recent: latest.slice(0, SHELL_CONVERSATIONS),
270 };
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.271 })
Chat and workspace agents: channels, DMs and named agents you talk to272 .catch(() => null);
273 return Promise.race([read, timeout]);
274}
275
276/** The longest the rail's Chat badge holds up a page. */
277const CHAT_BADGE_WAIT_MS = 300;
278
Usage, Billing settings and prepaid AI credit; fixes from the UX audit279/**
280 * How long the sidebar's workspace answers are kept: as long as someone's
281 * own changes read fresh anyway (lib/perf.ts `PRIMARY_WINDOW_SECONDS`).
282 */
283const SHELL_TTL_MS = 30_000;
The docs' Copy page menu keeps its own spacing, with an icon per item284
285/** Most repositories listed under Shared with you. */
286const MAX_SHARED = 20;
287
288/**
289 * Repositories shared with the user in workspaces they do not belong to,
290 * by their grants: only those, never the workspace's other repositories.
291 */
292async function sharedRepos(user: User): Promise<ShellData["shared"]> {
293 const outside = new Set(sharedWorkspaces(user));
294 if (outside.size === 0) return [];
295 const ids = (user.grants ?? []).filter((grant) => outside.has(grant.workspace)).map((grant) => grant.repo_id).slice(0, MAX_SHARED);
296 const found = await readableRepos(ids, user);
297 return found
298 .map((repo) => ({ namespace: repo.namespace, name: repo.name, isPrivate: repo.isPrivate }))
299 .sort((a, b) => `${a.namespace}/${a.name}`.localeCompare(`${b.namespace}/${b.name}`));
300}
301
Usage, Billing settings and prepaid AI credit; fixes from the UX audit302/**
303 * The root's data as the browser last had it. When a navigation fails in a
304 * way that takes the root's data with it, the error is still drawn in the
305 * person's own frame, signed in, not the visitor's. Never kept on the
306 * server, where one module serves everyone.
307 */
308let lastRoot: Awaited<ReturnType<typeof loader>> | undefined;
309
The docs' Copy page menu keeps its own spacing, with an icon per item310export 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 address311 const nonce = useNonce();
The docs' Copy page menu keeps its own spacing, with an icon per item312 // Undefined when the root loader itself failed.
Usage, Billing settings and prepaid AI credit; fixes from the UX audit313 const loaded = useRouteLoaderData<typeof loader>("root");
314 const inBrowser = typeof document !== "undefined";
315 if (loaded && inBrowser) lastRoot = loaded;
316 const root = loaded ?? (inBrowser ? lastRoot : undefined);
The docs' Copy page menu keeps its own spacing, with an icon per item317 const user = root?.user;
Merge branch 'socket-tickets'318 // A page opened with an access token signs its live sockets in with tickets (lib/live-socket.ts).
A doc opens at once: the page carries the document's saved state, so the editor mounts without waiting for its live room, which connects in the background and sends only what changed, with a quiet dot that turns green when it's synced; edits made before then are kept and sent once. The editor's code is preloaded from the page and fetched early when you hover a doc, the app's shared code ships in a few chunks instead of a hundred small ones, socket tickets come with the page instead of a round trip, and the artifacts service answers a page, a sidebar and a room in parallel lookups instead of a dozen in a row, reporting its database round trips in Server-Timing. The artifacts guide says how a doc opens, and CONTRIBUTING's Speed section has the numbers.319 if (inBrowser) {
320 setLiveViaToken(Boolean(user?.token?.website));
321 offerTicket("/-/live", root?.liveTicket);
322 }
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9323 const { pathname } = useLocation();
The docs' Copy page menu keeps its own spacing, with an icon per item324 // Drawn around the error page too: a 404 keeps the sidebar out of a
325 // project or workspace the viewer cannot see.
326 const error = useRouteError();
327 const missing = isRouteErrorResponse(error) && error.status === 404;
328 // The billing page shows the full banner itself.
329 const paused = root?.shell?.compute && root.shell.workspace && !pathname.endsWith("/-/billing") && (
330 <SpikeBanner
331 compact
332 slug={root.shell.workspace.slug}
333 entitlements={{ paused: root.shell.compute.paused, spike: root.shell.compute.spike }}
334 owner={root.shell.compute.owner}
335 />
336 );
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)337 // On the few pages an account still confirming its address can open
338 // besides /confirm-email (policies, support, signing in): the way back.
339 const verify = user && awaitsConfirmation(user) && pathname !== "/confirm-email" && (
340 <p
341 role="status"
The docs' Copy page menu keeps its own spacing, with an icon per item342 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"
343 >
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)344 <span>Confirm your email address to start using g1t.</span>
345 <Link to="/confirm-email" className="font-medium underline underline-offset-4">
346 Enter your code
347 </Link>
348 </p>
The docs' Copy page menu keeps its own spacing, with an icon per item349 );
Merge main (membership, two-factor, GitHub repo roles) into tokens350 // A workspace that requires two-factor authentication of someone who
351 // has not turned it on: they keep their place, and cannot use it yet.
352 const held = user?.held && user.held.length > 0 && pathname !== "/settings/two-factor" && <PolicyNotice held={user.held} />;
Merge branch 'worktree-agent-aca4790ea223aa235'353 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.354 // Auto, Light or Dark: as the cookie said when the page was drawn, or as just switched.
355 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.356 // Which frame the page is drawn in (lib/chrome.ts): on its own, the app's, or the public one.
357 const frame = frameOf(pathname, {
358 signedIn: user != null && !awaitsConfirmation(user),
359 workspace: (user?.workspaces ?? []).length > 0,
360 });
The docs' Copy page menu keeps its own spacing, with an icon per item361 const banner =
Merge main (membership, two-factor, GitHub repo roles) into tokens362 paused || verify || held ? (
The docs' Copy page menu keeps its own spacing, with an icon per item363 <>
364 {paused}
365 {verify}
Merge main (membership, two-factor, GitHub repo roles) into tokens366 {held}
The docs' Copy page menu keeps its own spacing, with an icon per item367 </>
368 ) : null;
369 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.370 <html lang="en" data-theme={themeAttribute(theme)}>
The docs' Copy page menu keeps its own spacing, with an icon per item371 <head>
372 <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)373 <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.374 {/* The browser bar matches the page: one colour for a chosen theme, the system's for Auto. */}
375 {theme === "auto" ? (
376 <>
377 <meta name="theme-color" media="(prefers-color-scheme: light)" content={THEME_COLOR.light} />
378 <meta name="theme-color" media="(prefers-color-scheme: dark)" content={THEME_COLOR.dark} />
379 </>
380 ) : (
381 <meta name="theme-color" content={THEME_COLOR[theme]} />
382 )}
Merge branch 'mirroring' into artifacts-mode383 <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)384 <meta name="apple-mobile-web-app-capable" content="yes" />
385 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
The docs' Copy page menu keeps its own spacing, with an icon per item386 {/* 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.387 connection paints sooner. */}
The docs' Copy page menu keeps its own spacing, with an icon per item388 <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 today389 {root?.analyticsConsent && <meta name="g1t-analytics" content="consent" />}
The docs' Copy page menu keeps its own spacing, with an icon per item390 <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 address391 <Links nonce={nonce} />
The docs' Copy page menu keeps its own spacing, with an icon per item392 </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.393 <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 audit394 {/* The first stop for the keyboard: past the menus, to the page. */}
395 <a
396 href="#content"
397 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]"
398 >
399 Skip to content
400 </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.401 {frame === "standalone" ? (
402 <StandaloneFrame>{children}</StandaloneFrame>
403 ) : frame === "app" && user && root?.shell ? (
404 <AppShell user={user} shell={root.shell} missing={missing} banner={banner} sidebarClosed={root.sidebarClosed}>
The docs' Copy page menu keeps its own spacing, with an icon per item405 {children}
406 </AppShell>
407 ) : (
408 <>
409 <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.410 <PublicHeader user={user} />
The docs' Copy page menu keeps its own spacing, with an icon per item411 {banner}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit412 <div id="content" tabIndex={-1} {...leaving} className={`grow outline-none ${leaving.className}`}>
Merge branch 'worktree-agent-aca4790ea223aa235'413 {children}
414 </div>
The docs' Copy page menu keeps its own spacing, with an icon per item415 <SiteFooter user={user} />
416 </>
417 )}
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)418 {/* Live notifications for the signed-in: the feed socket, toasts, the tab's count (components/notifications). */}
419 {user && !awaitsConfirmation(user) && (
Chat has sounds. A short warm cue plays for a message in a conversation you have open but aren't looking at, a direct message, a mention of you and an agent finishing something for you, with a soft tick for sending that is off until you turn it on; never for your own messages, a muted conversation, a conversation you are looking at in a window that has the front, or while you have paused notifications, and never twice for one message or more than once in a second and a half. The cues are made by the browser itself in two sets, Soft and Bright, so nothing is downloaded; the Sounds and notifications section of your notification settings turns them on and off, picks the set and the volume, plays each one, and offers desktop notifications for when the window is behind; Do not disturb is the one pause everyone sees, set from the chat sidebar's bell, the settings, or your avatar menu, now with a two-hour choice, and the top bar's bell shows a dot while it's on. Your choices are kept with your account. The sound layer sits behind one small interface so the desktop app can play them natively later. The chat and notifications guides say the rules.420 <LiveNotifications
421 workspace={root?.shell?.workspace?.slug ?? null}
422 inbox={root?.shell?.inbox?.unread ?? null}
423 sounds={root?.sounds ?? null}
424 me={{ id: user.id, username: user.username }}
425 />
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)426 )}
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.427 {/* Inside the desktop app: who its runner runs for, and its registration (lib/desktop-runner.ts). */}
428 {(!user || !awaitsConfirmation(user)) && (
429 <DesktopRunnerLink
430 offer={{
431 signedIn: user != null,
432 workspaces: (user?.workspaces ?? []).filter((m) => m.role === "owner").map((m) => ({ slug: m.slug, name: m.name ?? null })),
433 current: root?.shell?.workspace?.slug ?? null,
434 }}
435 />
436 )}
Merge branch 'main' into rm-agents437 {/* Inside the desktop app: its settings dialog, and the runner's menu under the title bar's dot. */}
438 <DesktopApp />
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 today439 <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 address440 <ScrollRestoration nonce={nonce} />
441 <Scripts nonce={nonce} />
The docs' Copy page menu keeps its own spacing, with an icon per item442 </body>
443 </html>
444 );
445}
446
447export 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 today448 const userId = useRouteLoaderData<typeof loader>("root")?.user?.id ?? null;
449 // Tell analytics who is signed in, once per change; signing out forgets them.
450 const lastUserId = useRef<string | null>(null);
451 useEffect(() => {
452 identify(userId, lastUserId.current);
453 lastUserId.current = userId;
454 }, [userId]);
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9455 // A navigation that never arrives is loaded as a whole page (lib/stuck.ts).
456 useStuckNavigationRescue();
The docs' Copy page menu keeps its own spacing, with an icon per item457 return <Outlet />;
458}
459
460export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
Usage, Billing settings and prepaid AI credit; fixes from the UX audit461 const location = useLocation();
462 const matches = useMatches();
Merge branch 'worktree-agent-a64e51c8f386f46ac'463 const href = location.pathname + location.search + location.hash;
Usage, Billing settings and prepaid AI credit; fixes from the UX audit464 // A tab left open across a deploy: load the address again as a whole
Merge branch 'worktree-agent-a64e51c8f386f46ac'465 // page, once, rather than show an error the reload fixes. A document load
466 // never reloads, so the server and the hydrating client render the same.
467 const reloadable = reloadFixes({
Usage, Billing settings and prepaid AI credit; fixes from the UX audit468 error,
469 status: isRouteErrorResponse(error) ? error.status : undefined,
Merge branch 'worktree-agent-a64e51c8f386f46ac'470 clientNavigation: clientNavigated(location.key),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit471 caughtByCatchAll: matches.at(-1)?.id === "routes/not-found",
472 });
Merge branch 'worktree-agent-a64e51c8f386f46ac'473 // Already loaded again once, or the reload never started: show the page.
474 const tried = useMemo(() => reloadable && reloadedBefore(href), [reloadable, href]);
475 const [gaveUp, setGaveUp] = useState<string | null>(null);
476 const reload = reloadable && !tried && gaveUp !== href;
Usage, Billing settings and prepaid AI credit; fixes from the UX audit477 useEffect(() => {
478 if (!reload) return;
479 try {
480 sessionStorage.setItem(RELOADED_KEY, href);
481 } catch {
482 // No session storage: reload anyway; a document load never asks again.
483 }
484 window.location.assign(href);
Merge branch 'worktree-agent-a64e51c8f386f46ac'485 const timer = window.setTimeout(() => setGaveUp(href), RELOAD_GIVE_UP_MS);
486 return () => window.clearTimeout(timer);
Usage, Billing settings and prepaid AI credit; fixes from the UX audit487 }, [reload, href]);
488 if (reload) {
489 return (
Not found and error pages have one main landmark: their own when signed out, the shell's when signed in490 <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 audit491 <title>Loading · g1t</title>
492 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 in493 </PageMain>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit494 );
495 }
496
The docs' Copy page menu keeps its own spacing, with an icon per item497 let title = "Something went wrong";
498 let details = "An unexpected error occurred. Try again in a moment.";
499 let stack: string | undefined;
500
501 if (isRouteErrorResponse(error)) {
502 // The same page for something private and something missing.
Usage, Billing settings and prepaid AI credit; fixes from the UX audit503 if (error.status === 404) {
504 return (
505 <>
506 <title>Not found · g1t</title>
507 <NotFound data={error.data} />
508 </>
509 );
510 }
The docs' Copy page menu keeps its own spacing, with an icon per item511 if (typeof error.data === "string" && error.data) {
512 details = error.data;
513 }
Merge main into Artifacts Phase 2514 // A token asked for what needs a real sign-in (lib/website-token.ts).
515 if (isNeedsSignIn(error.data)) {
516 title = "This needs you to sign in";
517 details = error.data.message;
518 }
The docs' Copy page menu keeps its own spacing, with an icon per item519 } else if (import.meta.env.DEV && error instanceof Error) {
520 details = error.message;
521 stack = error.stack;
522 }
523
524 return (
Not found and error pages have one main landmark: their own when signed out, the shell's when signed in525 <PageMain className="mx-auto max-w-xl px-4 py-32 text-center">
The docs' Copy page menu keeps its own spacing, with an icon per item526 <h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
527 <p className="mt-3 text-muted">{details}</p>
528 <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.529 <ButtonLink to="/" variant="outline">
The docs' Copy page menu keeps its own spacing, with an icon per item530 Back to home
531 </ButtonLink>
532 </div>
533 {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.534 <Card asChild tone="plain" radius="lg" className="mt-8 overflow-x-auto p-4 text-left text-xs">
535 <pre>
536 <code>{stack}</code>
537 </pre>
538 </Card>
The docs' Copy page menu keeps its own spacing, with an icon per item539 )}
Not found and error pages have one main landmark: their own when signed out, the shell's when signed in540 </PageMain>
The docs' Copy page menu keeps its own spacing, with an icon per item541 );
542}

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