Skip to content
578 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";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms2import {
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,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms13 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";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms18
19import type { Route } from "./+types/root";
Load the stylesheet first; plain legal contact details20import appCss from "./app.css?url";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms21import 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";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms26import { 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";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms33import { 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";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms36import { 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";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms46import { 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";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms48import { 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 remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r952import { useStuckNavigationRescue } from "./lib/stuck";
Merge main into Artifacts Phase 253import { 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.54import { offerTicket, setLiveViaToken } from "./lib/live-socket";
55import { socketTicketFor } from "./lib/socket-ticket.server";
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.56import { 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)57import { 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.58import { THEME_COLOR, THEME_COOKIE, readTheme, themeAttribute, useThemeChoice } from "./lib/theme";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms59
60
61export const links: Route.LinksFunction = () => [
62 { rel: "icon", href: "/favicon.ico", sizes: "32x32" },
63 { rel: "icon", type: "image/svg+xml", href: "/favicon.svg" },
64 { 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 managers65 { rel: "icon", type: "image/png", sizes: "512x512", href: "/icon-512.png" },
66 // Home screens and password managers look for a square, opaque 180px tile;
67 // the "-precomposed" name is served too, for the ones that ask for it.
68 { rel: "apple-touch-icon", sizes: "180x180", href: "/apple-touch-icon.png" },
69 // Installable: name, icons and colours for "Add to Home Screen" (public/site.webmanifest;
70 // manifest.webmanifest is the same file, kept for installs that already point at it).
71 { rel: "manifest", href: "/site.webmanifest" },
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms72 // The text and headline faces are wanted on every page, so they start
73 // loading with the stylesheet; mono waits until something uses it.
74 { rel: "preload", href: sansFont, as: "font", type: "font/woff2", crossOrigin: "anonymous" },
75 { rel: "preload", href: displayFont, as: "font", type: "font/woff2", crossOrigin: "anonymous" },
76];
77
78export const middleware: Route.MiddlewareFunction[] = [viewerMiddleware];
79
80export async function loader({ context, params, request }: Route.LoaderArgs) {
81 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.82 const cookies = request.headers.get("cookie");
83 const chosen = readCookie(cookies, WORKSPACE_COOKIE);
84 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)85 // Whether sign-up takes an invite: the sign-up page says so, and
86 // Settings → Invites offers invites to g1t only then. Cached per isolate.
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.87 const [shell, mode, sounds] = await Promise.all([
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)88 // An account still confirming its address sees only the pages that
89 // allows (lib/confirm-gate.ts), in the visitor's frame.
90 user && !awaitsConfirmation(user)
Usage, Billing settings and prepaid AI credit; fixes from the UX audit91 ? shellFor(user, params, chosen, context).catch((error: unknown) => {
92 // A sidebar that could not be read is no reason to lose the page,
93 // or to draw it as if they were signed out.
94 console.error("root: the sidebar could not be read", error);
95 return bareShell(user, params, chosen);
96 })
97 : 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)98 registrationMode(),
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.99 // Their chat sounds, with the page, so the first live event already
100 // respects them (lib/sounds.ts); the defaults when identity is slow.
101 user && !awaitsConfirmation(user) ? identity.soundSettings(user).catch(() => null) : Promise.resolve(null),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms102 ]);
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.103 // 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.104 // them (read with the rest of the shell), else as this device's cookie
105 // remembers them (lib/apps.ts).
106 if (shell.workspace) shell.pins = pinsToShow(shell.pins, dock, shell.workspace.slug);
Merge branch 'worktree-agent-aaf03bdceac799c89'107 // 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 today108 return {
109 user,
110 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.111 // For a page opened with an access token: the feed socket's ticket,
112 // minted here so the feed opens without asking for one (lib/live-socket.ts).
113 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.114 // Whether they folded the sidebar away, so the page is drawn that way from the start.
115 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.116 // Their Appearance, so the page is drawn in it from the first byte (lib/theme.ts).
117 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.118 // What they hear when chat moves (components/notifications/sounds.tsx).
119 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 today120 inviteOnly: mode !== "open",
121 addresses: addresses(),
122 // Visitors from where the law asks first are asked before analytics runs.
123 analyticsConsent: visitorAsksFirst(request),
124 };
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms125}
126
127/**
128 * The sidebar for someone not signed in: nothing of their own, only the
129 * open counts of the project being looked at. One call, and only in a
130 * project; it answers the same for a private project as a missing one.
131 */
Fast pages, required checks on the branch, self-hosted runners, honest incidents132async function visitorShell(params: { owner?: string; repo?: string }, context: Route.LoaderArgs["context"]): Promise<ShellData> {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms133 const path = params.owner && params.repo ? { namespace: params.owner, name: params.repo } : null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents134 const counts = path ? await countsFor(context, params) : null;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms135 return {
136 workspace: null,
137 repos: [],
138 repo: path && counts?.ok ? { ...path, member: false, issues: counts.value.issues, pulls: counts.value.pulls } : null,
139 limit: null,
140 monthUsageMicros: null,
141 };
142}
143
Usage, Billing settings and prepaid AI credit; fixes from the UX audit144/** The sidebar from what the session already says, with no service asked. */
145function bareShell(user: User, params: { owner?: string; repo?: string }, chosen: string | null): ShellData {
146 return {
147 workspace: workspaceFor(user.workspaces ?? [], chosen, params),
148 repos: [],
149 repo: null,
150 limit: null,
151 monthUsageMicros: null,
152 };
153}
154
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms155/**
156 * The sidebar only changes with the workspace or repository being looked
157 * at, or after something was submitted: not on every page within them.
158 */
159export function shouldRevalidate({
160 currentParams,
161 nextParams,
162 formMethod,
163 defaultShouldRevalidate,
164}: ShouldRevalidateFunctionArgs) {
165 if (formMethod && formMethod !== "GET") return defaultShouldRevalidate;
166 return currentParams.owner !== nextParams.owner || currentParams.repo !== nextParams.repo;
167}
168
169/**
Usage, Billing settings and prepaid AI credit; fixes from the UX audit170 * The sidebar: the workspace whose pages or project these are when it is
171 * one of yours, else the one you chose (lib/workspace-choice.ts); its
172 * projects; and the repository being looked at.
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms173 */
174async function shellFor(
175 user: User,
176 params: { owner?: string; repo?: string },
177 chosen: string | null,
Fast pages, required checks on the branch, self-hosted runners, honest incidents178 context: Route.LoaderArgs["context"],
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms179): Promise<ShellData> {
180 const memberships = user.workspaces ?? [];
181 const workspace = workspaceFor(memberships, chosen, params);
182 const path = params.owner && params.repo ? { namespace: params.owner, name: params.repo } : null;
183 const now = new Date();
184 const monthStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1)).toISOString();
Fast pages, required checks on the branch, self-hosted runners, honest incidents185 // What the sidebar shows of the workspace changes rarely: kept for a few
186 // seconds per person and workspace, except just after they changed
187 // something (lib/cache.server.ts).
188 const kept = <T,>(what: string, load: () => Promise<T>) =>
189 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.190 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 sidebar191 // Every project, for the palette and the count; the sidebar lists only
192 // the person's pinned and recent ones (lib/pins.ts).
193 workspace ? workspaceProjects(workspace.slug, user) : Promise.resolve(null),
Fast pages, required checks on the branch, self-hosted runners, honest incidents194 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 processor195 // Whether billing is on, without reading the account: that asks the
196 // card processor about the workspace's cards, too slow for every page.
197 kept("status", () => billing.status()).catch(() => null),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit198 kept(`usage:${monthStart}`, () => billing.usage(workspace!.slug, user, monthStart)).catch(() => null),
Fast pages, required checks on the branch, self-hosted runners, honest incidents199 kept("limit", () => billing.limit(workspace!.slug, user)).catch(() => null),
200 kept("entitlements", () => billing.entitlements(workspace!.slug)).catch(() => null),
201 sharedRepos(user),
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all202 // The bell's count: one query, never kept, so marking an item shows at once.
203 inbox.counts(user.username).catch(() => null),
Navigation: the workspace's own page with tabs, pins and recent in the sidebar204 // Never kept: opening a project moves it up Recent.
205 workspace ? projects.shortcuts(workspace.slug, user).catch(() => null) : Promise.resolve(null),
Chat and workspace agents: channels, DMs and named agents you talk to206 // The rail's Chat badge: never kept, and never waited on for long.
207 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)208 // Home's Recent and the Agents sidebar, on every page: never waited on for long.
209 workspace ? agentsFor(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.210 // The rail's pins, kept with the account so every device shows the same
211 // 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.212 // falls back to the cookie (lib/dock.server.ts).
213 workspace ? savedPins(user, workspace.slug) : Promise.resolve(null),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms214 ]);
215 return {
216 workspace,
217 // 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 sidebar218 repos: listed?.ok ? listed.value.map(shortcutOf) : [],
219 pinned: (shortcuts?.pinned ?? []).map(shortcutOf),
220 recent: (shortcuts?.recent ?? []).map(shortcutOf),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms221 repo:
222 path && counts?.ok
223 ? {
224 ...path,
225 // Until the repository's own page says what their role is.
226 member: hasAccessIn(user, path.namespace),
227 issues: counts.value.issues,
228 pulls: counts.value.pulls,
229 }
230 : null,
231 // Where the workspace stands against its usage limit, once billing is on.
232 limit:
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor233 status?.enabled && limit?.ok
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms234 ? {
235 exposureMicros: limit.value.exposureMicros,
236 ceilingMicros: limit.value.ceilingMicros,
237 state: limit.value.state,
238 comped: limit.value.trust === "internal",
239 }
240 : null,
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor241 free: Boolean(status?.free),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms242 // A spend spike or a hold pauses new compute: the shell says so on every page.
243 compute:
244 workspace && entitlements && (entitlements.paused || entitlements.spike?.status === "open")
245 ? { paused: entitlements.paused, spike: entitlements.spike ?? null, owner: workspace.role === "owner" }
246 : null,
247 shared,
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all248 inbox: unread,
Chat and workspace agents: channels, DMs and named agents you talk to249 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)250 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.251 pins: dockPins ?? undefined,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms252 // 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 audit253 // Usage at price, the one figure every page shows.
254 monthUsageMicros: usage?.ok ? (usage.value.free ? usage.value.usedMicros : (usage.value.priceMicros ?? usage.value.spentMicros)) : null,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms255 };
256}
257
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)258/** Starred and recent conversations Home's sidebar lists. */
259const SHELL_CONVERSATIONS = 8;
260
261/**
262 * What is unread in chat, for the rail, and the starred and latest
263 * conversations, for Home's sidebar; null when chat is slow to answer or
264 * does not.
265 */
Chat and workspace agents: channels, DMs and named agents you talk to266async function chatUnreadFor(slug: string, user: User): Promise<ShellData["chat"]> {
267 const timeout = new Promise<null>((resolve) => setTimeout(() => resolve(null), CHAT_BADGE_WAIT_MS));
268 const read = chat
269 .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)270 .then((result) => {
271 if (!result.ok) return null;
272 const entries = result.value.entries;
273 const latest = [...entries].sort((a, b) => (b.channel.last_message_at ?? "").localeCompare(a.channel.last_message_at ?? ""));
274 return {
275 ...unreadTotals(entries),
276 starred: entries.filter((e) => e.starred).slice(0, SHELL_CONVERSATIONS),
277 recent: latest.slice(0, SHELL_CONVERSATIONS),
278 };
279 })
280 .catch(() => null);
281 return Promise.race([read, timeout]);
282}
283
284/** The workspace's agents, as the shell lists them; null when the agents service is slow or down. */
285async function agentsFor(slug: string, user: User): Promise<ShellData["agents"]> {
286 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.287 // The workspace's agents and the person's own personal ones, for "Yours",
288 // with the teams each is on, for the Agents sidebar's groups.
289 const read = Promise.all([workspaceAgents.list(slug, user, { personal: "mine" }), identity.teamAgentIndex(slug).catch(() => null)])
290 .then(([result, index]) => {
291 if (!result.ok) return null;
292 const teams = index ? teamsByAgent(index) : null;
293 return result.value
294 .filter((agent) => !agent.archived_at)
295 .map((agent) => ({
296 id: agent.id,
297 handle: agent.handle,
298 display_name: agent.display_name,
299 avatar: agent.avatar,
300 avatar_seed: agent.avatar_seed,
Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent.301 look: agent.look ?? null,
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.302 role: agent.role,
303 title: agent.title,
304 status: agent.status,
305 builtin: agent.builtin === true,
306 scope: agent.scope,
307 ...(teams ? { teams: teams[agent.id] ?? [] } : {}),
308 }));
309 })
Chat and workspace agents: channels, DMs and named agents you talk to310 .catch(() => null);
311 return Promise.race([read, timeout]);
312}
313
314/** The longest the rail's Chat badge holds up a page. */
315const CHAT_BADGE_WAIT_MS = 300;
316
Usage, Billing settings and prepaid AI credit; fixes from the UX audit317/**
318 * How long the sidebar's workspace answers are kept: as long as someone's
319 * own changes read fresh anyway (lib/perf.ts `PRIMARY_WINDOW_SECONDS`).
320 */
321const SHELL_TTL_MS = 30_000;
Fast pages, required checks on the branch, self-hosted runners, honest incidents322
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms323/** Most repositories listed under Shared with you. */
324const MAX_SHARED = 20;
325
326/**
327 * Repositories shared with the user in workspaces they do not belong to,
328 * by their grants: only those, never the workspace's other repositories.
329 */
330async function sharedRepos(user: User): Promise<ShellData["shared"]> {
331 const outside = new Set(sharedWorkspaces(user));
332 if (outside.size === 0) return [];
333 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 incidents334 const found = await readableRepos(ids, user);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms335 return found
Fast pages, required checks on the branch, self-hosted runners, honest incidents336 .map((repo) => ({ namespace: repo.namespace, name: repo.name, isPrivate: repo.isPrivate }))
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms337 .sort((a, b) => `${a.namespace}/${a.name}`.localeCompare(`${b.namespace}/${b.name}`));
338}
339
Usage, Billing settings and prepaid AI credit; fixes from the UX audit340/**
341 * The root's data as the browser last had it. When a navigation fails in a
342 * way that takes the root's data with it, the error is still drawn in the
343 * person's own frame, signed in, not the visitor's. Never kept on the
344 * server, where one module serves everyone.
345 */
346let lastRoot: Awaited<ReturnType<typeof loader>> | undefined;
347
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms348export 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 address349 const nonce = useNonce();
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms350 // Undefined when the root loader itself failed.
Usage, Billing settings and prepaid AI credit; fixes from the UX audit351 const loaded = useRouteLoaderData<typeof loader>("root");
352 const inBrowser = typeof document !== "undefined";
353 if (loaded && inBrowser) lastRoot = loaded;
354 const root = loaded ?? (inBrowser ? lastRoot : undefined);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms355 const user = root?.user;
Merge branch 'socket-tickets'356 // 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.357 if (inBrowser) {
358 setLiveViaToken(Boolean(user?.token?.website));
359 offerTicket("/-/live", root?.liveTicket);
360 }
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9361 const { pathname } = useLocation();
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms362 // Drawn around the error page too: a 404 keeps the sidebar out of a
363 // project or workspace the viewer cannot see.
364 const error = useRouteError();
365 const missing = isRouteErrorResponse(error) && error.status === 404;
366 // The billing page shows the full banner itself.
367 const paused = root?.shell?.compute && root.shell.workspace && !pathname.endsWith("/-/billing") && (
Fast pages, required checks on the branch, self-hosted runners, honest incidents368 <SpikeBanner
369 compact
370 slug={root.shell.workspace.slug}
371 entitlements={{ paused: root.shell.compute.paused, spike: root.shell.compute.spike }}
372 owner={root.shell.compute.owner}
373 />
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms374 );
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)375 // On the few pages an account still confirming its address can open
376 // besides /confirm-email (policies, support, signing in): the way back.
377 const verify = user && awaitsConfirmation(user) && pathname !== "/confirm-email" && (
378 <p
379 role="status"
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms380 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"
381 >
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)382 <span>Confirm your email address to start using g1t.</span>
383 <Link to="/confirm-email" className="font-medium underline underline-offset-4">
384 Enter your code
385 </Link>
386 </p>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms387 );
Merge main (membership, two-factor, GitHub repo roles) into tokens388 // A workspace that requires two-factor authentication of someone who
389 // has not turned it on: they keep their place, and cannot use it yet.
390 const held = user?.held && user.held.length > 0 && pathname !== "/settings/two-factor" && <PolicyNotice held={user.held} />;
Merge branch 'worktree-agent-aca4790ea223aa235'391 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.392 // Auto, Light or Dark: as the cookie said when the page was drawn, or as just switched.
393 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.394 // Which frame the page is drawn in (lib/chrome.ts): on its own, the app's, or the public one.
395 const frame = frameOf(pathname, {
396 signedIn: user != null && !awaitsConfirmation(user),
397 workspace: (user?.workspaces ?? []).length > 0,
398 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms399 const banner =
Merge main (membership, two-factor, GitHub repo roles) into tokens400 paused || verify || held ? (
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms401 <>
402 {paused}
403 {verify}
Merge main (membership, two-factor, GitHub repo roles) into tokens404 {held}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms405 </>
406 ) : null;
407 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.408 <html lang="en" data-theme={themeAttribute(theme)}>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms409 <head>
410 <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)411 <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.412 {/* The browser bar matches the page: one colour for a chosen theme, the system's for Auto. */}
413 {theme === "auto" ? (
414 <>
415 <meta name="theme-color" media="(prefers-color-scheme: light)" content={THEME_COLOR.light} />
416 <meta name="theme-color" media="(prefers-color-scheme: dark)" content={THEME_COLOR.dark} />
417 </>
418 ) : (
419 <meta name="theme-color" content={THEME_COLOR[theme]} />
420 )}
Merge branch 'mirroring' into artifacts-mode421 <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)422 <meta name="apple-mobile-web-app-capable" content="yes" />
423 <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 preloads424 {/* 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.425 connection paints sooner. */}
Give the stylesheet a precedence so React places it before preloads426 <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 today427 {root?.analyticsConsent && <meta name="g1t-analytics" content="consent" />}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms428 <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 address429 <Links nonce={nonce} />
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms430 </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.431 <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 audit432 {/* The first stop for the keyboard: past the menus, to the page. */}
433 <a
434 href="#content"
435 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]"
436 >
437 Skip to content
438 </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.439 {frame === "standalone" ? (
440 <StandaloneFrame>{children}</StandaloneFrame>
441 ) : frame === "app" && user && root?.shell ? (
442 <AppShell user={user} shell={root.shell} missing={missing} banner={banner} sidebarClosed={root.sidebarClosed}>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms443 {children}
444 </AppShell>
445 ) : (
446 <>
447 <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.448 <PublicHeader user={user} />
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms449 {banner}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit450 <div id="content" tabIndex={-1} {...leaving} className={`grow outline-none ${leaving.className}`}>
Merge branch 'worktree-agent-aca4790ea223aa235'451 {children}
452 </div>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms453 <SiteFooter user={user} />
454 </>
455 )}
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)456 {/* Live notifications for the signed-in: the feed socket, toasts, the tab's count (components/notifications). */}
457 {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.458 <LiveNotifications
459 workspace={root?.shell?.workspace?.slug ?? null}
460 inbox={root?.shell?.inbox?.unread ?? null}
461 sounds={root?.sounds ?? null}
462 me={{ id: user.id, username: user.username }}
463 />
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)464 )}
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.465 {/* Inside the desktop app: who its runner runs for, and its registration (lib/desktop-runner.ts). */}
466 {(!user || !awaitsConfirmation(user)) && (
467 <DesktopRunnerLink
468 offer={{
469 signedIn: user != null,
470 workspaces: (user?.workspaces ?? []).filter((m) => m.role === "owner").map((m) => ({ slug: m.slug, name: m.name ?? null })),
471 current: root?.shell?.workspace?.slug ?? null,
472 }}
473 />
474 )}
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 today475 <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 address476 <ScrollRestoration nonce={nonce} />
477 <Scripts nonce={nonce} />
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms478 </body>
479 </html>
480 );
481}
482
483export 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 today484 const userId = useRouteLoaderData<typeof loader>("root")?.user?.id ?? null;
485 // Tell analytics who is signed in, once per change; signing out forgets them.
486 const lastUserId = useRef<string | null>(null);
487 useEffect(() => {
488 identify(userId, lastUserId.current);
489 lastUserId.current = userId;
490 }, [userId]);
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9491 // A navigation that never arrives is loaded as a whole page (lib/stuck.ts).
492 useStuckNavigationRescue();
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms493 return <Outlet />;
494}
495
496export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
Usage, Billing settings and prepaid AI credit; fixes from the UX audit497 const location = useLocation();
498 const matches = useMatches();
Merge branch 'worktree-agent-a64e51c8f386f46ac'499 const href = location.pathname + location.search + location.hash;
Usage, Billing settings and prepaid AI credit; fixes from the UX audit500 // A tab left open across a deploy: load the address again as a whole
Merge branch 'worktree-agent-a64e51c8f386f46ac'501 // page, once, rather than show an error the reload fixes. A document load
502 // never reloads, so the server and the hydrating client render the same.
503 const reloadable = reloadFixes({
Usage, Billing settings and prepaid AI credit; fixes from the UX audit504 error,
505 status: isRouteErrorResponse(error) ? error.status : undefined,
Merge branch 'worktree-agent-a64e51c8f386f46ac'506 clientNavigation: clientNavigated(location.key),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit507 caughtByCatchAll: matches.at(-1)?.id === "routes/not-found",
508 });
Merge branch 'worktree-agent-a64e51c8f386f46ac'509 // Already loaded again once, or the reload never started: show the page.
510 const tried = useMemo(() => reloadable && reloadedBefore(href), [reloadable, href]);
511 const [gaveUp, setGaveUp] = useState<string | null>(null);
512 const reload = reloadable && !tried && gaveUp !== href;
Usage, Billing settings and prepaid AI credit; fixes from the UX audit513 useEffect(() => {
514 if (!reload) return;
515 try {
516 sessionStorage.setItem(RELOADED_KEY, href);
517 } catch {
518 // No session storage: reload anyway; a document load never asks again.
519 }
520 window.location.assign(href);
Merge branch 'worktree-agent-a64e51c8f386f46ac'521 const timer = window.setTimeout(() => setGaveUp(href), RELOAD_GIVE_UP_MS);
522 return () => window.clearTimeout(timer);
Usage, Billing settings and prepaid AI credit; fixes from the UX audit523 }, [reload, href]);
524 if (reload) {
525 return (
Not found and error pages have one main landmark: their own when signed out, the shell's when signed in526 <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 audit527 <title>Loading · g1t</title>
528 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 in529 </PageMain>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit530 );
531 }
532
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms533 let title = "Something went wrong";
534 let details = "An unexpected error occurred. Try again in a moment.";
535 let stack: string | undefined;
536
537 if (isRouteErrorResponse(error)) {
538 // The same page for something private and something missing.
Usage, Billing settings and prepaid AI credit; fixes from the UX audit539 if (error.status === 404) {
540 return (
541 <>
542 <title>Not found · g1t</title>
543 <NotFound data={error.data} />
544 </>
545 );
546 }
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms547 if (typeof error.data === "string" && error.data) {
548 details = error.data;
549 }
Merge main into Artifacts Phase 2550 // A token asked for what needs a real sign-in (lib/website-token.ts).
551 if (isNeedsSignIn(error.data)) {
552 title = "This needs you to sign in";
553 details = error.data.message;
554 }
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms555 } else if (import.meta.env.DEV && error instanceof Error) {
556 details = error.message;
557 stack = error.stack;
558 }
559
560 return (
Not found and error pages have one main landmark: their own when signed out, the shell's when signed in561 <PageMain className="mx-auto max-w-xl px-4 py-32 text-center">
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms562 <h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
563 <p className="mt-3 text-muted">{details}</p>
564 <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.565 <ButtonLink to="/" variant="outline">
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms566 Back to home
567 </ButtonLink>
568 </div>
569 {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.570 <Card asChild tone="plain" radius="lg" className="mt-8 overflow-x-auto p-4 text-left text-xs">
571 <pre>
572 <code>{stack}</code>
573 </pre>
574 </Card>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms575 )}
Not found and error pages have one main landmark: their own when signed out, the shell's when signed in576 </PageMain>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms577 );
578}

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