Skip to content
520 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";
Commit pages credit co-authors instead of printing their trailers2import {
3 isRouteErrorResponse,
4 Link,
5 Links,
6 Meta,
7 Outlet,
8 Scripts,
9 ScrollRestoration,
10 type ShouldRevalidateFunctionArgs,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put11 useLocation,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit12 useMatches,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas13 useRouteError,
Commit pages credit co-authors instead of printing their trailers14 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";
Commit pages credit co-authors instead of printing their trailers18
19import type { Route } from "./+types/root";
Load the stylesheet first; plain legal contact details20import appCss from "./app.css?url";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look21import displayFont from "@g1t/theme/fonts/bricolage-grotesque-latin.woff2?url";
22import sansFont from "@g1t/theme/fonts/hanken-grotesk-latin.woff2?url";
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.23import { ButtonLink } from "./components/ui";
Merge branch 'worktree-agent-aca4790ea223aa235'24import { AppShell, Progress, type ShellData, useLeaving } from "./components/shell";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look25import { 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.26import { PublicHeader } from "./components/public-header";
Fast pages, required checks on the branch, self-hosted runners, honest incidents27import { SpikeBanner } from "./components/spike-banner";
Merge main (membership, two-factor, GitHub repo roles) into tokens28import { 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 today29import { identify } from "./lib/analytics.client";
30import { visitorAsksFirst } from "./lib/analytics-consent";
31import { AnalyticsConsent } from "./components/analytics-consent";
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put32import { readCookie } from "./lib/mission";
33import { 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 in34import { PageMain } from "./components/landmark";
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put35import { 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.36import { frameOf } from "./lib/chrome";
37import { DOCK_COOKIE, SIDEBAR_COOKIE, pinsIn, sidebarClosed } from "./lib/apps";
38import { StandaloneFrame } from "./components/standalone";
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)39import { billing, chat, inbox, projects, workspaceAgents } from "./lib/services.server";
Chat and workspace agents: channels, DMs and named agents you talk to40import { unreadTotals } from "./lib/chat";
Fast pages, required checks on the branch, self-hosted runners, honest incidents41import { countsFor, readableRepos } from "./lib/access.server";
42import { shortCache } from "./lib/cache.server";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look43import { getViewer, viewerMiddleware } from "./lib/session.server";
Navigation: the workspace's own page with tabs, pins and recent in the sidebar44import { shortcutOf, workspaceProjects } from "./lib/workspace-projects.server";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look45import { registrationMode } from "./lib/registration.server";
Merge branch 'worktree-agent-aaf03bdceac799c89'46import { addresses } from "./lib/addresses.server";
Merge branch 'worktree-agent-a64e51c8f386f46ac'47import { 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 address48import { useNonce } from "./lib/nonce";
Merge main into Artifacts Phase 249import { isNeedsSignIn } from "./lib/website-token";
Merge branch 'socket-tickets'50import { setLiveViaToken } from "./lib/live-socket";
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)51import { LiveNotifications } from "./components/notifications/live-notifications";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look52
Commit pages credit co-authors instead of printing their trailers53
54export const links: Route.LinksFunction = () => [
The fleet: g1t's new mark55 { rel: "icon", href: "/favicon.ico", sizes: "32x32" },
Commit pages credit co-authors instead of printing their trailers56 { rel: "icon", type: "image/svg+xml", href: "/favicon.svg" },
The fleet: g1t's new mark57 { 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 managers58 { rel: "icon", type: "image/png", sizes: "512x512", href: "/icon-512.png" },
59 // Home screens and password managers look for a square, opaque 180px tile;
60 // the "-precomposed" name is served too, for the ones that ask for it.
61 { rel: "apple-touch-icon", sizes: "180x180", href: "/apple-touch-icon.png" },
62 // Installable: name, icons and colours for "Add to Home Screen" (public/site.webmanifest;
63 // manifest.webmanifest is the same file, kept for installs that already point at it).
64 { rel: "manifest", href: "/site.webmanifest" },
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look65 // The text and headline faces are wanted on every page, so they start
66 // loading with the stylesheet; mono waits until something uses it.
67 { rel: "preload", href: sansFont, as: "font", type: "font/woff2", crossOrigin: "anonymous" },
68 { rel: "preload", href: displayFont, as: "font", type: "font/woff2", crossOrigin: "anonymous" },
Commit pages credit co-authors instead of printing their trailers69];
70
71export const middleware: Route.MiddlewareFunction[] = [viewerMiddleware];
72
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put73export async function loader({ context, params, request }: Route.LoaderArgs) {
Commit pages credit co-authors instead of printing their trailers74 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.75 const cookies = request.headers.get("cookie");
76 const chosen = readCookie(cookies, WORKSPACE_COOKIE);
77 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)78 // Whether sign-up takes an invite: the sign-up page says so, and
79 // Settings → Invites offers invites to g1t only then. Cached per isolate.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look80 const [shell, mode] = await Promise.all([
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)81 // An account still confirming its address sees only the pages that
82 // allows (lib/confirm-gate.ts), in the visitor's frame.
83 user && !awaitsConfirmation(user)
Usage, Billing settings and prepaid AI credit; fixes from the UX audit84 ? shellFor(user, params, chosen, context).catch((error: unknown) => {
85 // A sidebar that could not be read is no reason to lose the page,
86 // or to draw it as if they were signed out.
87 console.error("root: the sidebar could not be read", error);
88 return bareShell(user, params, chosen);
89 })
90 : 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)91 registrationMode(),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look92 ]);
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.93 // The apps this person pinned to their dock here, from their cookie (lib/apps.ts).
94 if (shell.workspace) shell.pins = pinsIn(dock, shell.workspace.slug);
Merge branch 'worktree-agent-aaf03bdceac799c89'95 // 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 today96 return {
97 user,
98 shell,
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.99 // Whether they folded the sidebar away, so the page is drawn that way from the start.
100 sidebarClosed: sidebarClosed(readCookie(cookies, SIDEBAR_COOKIE)),
Merge site-audit: SEO routes, analytics with consent first in Europe, security page, founder and X account, docs header; the tour opens on the pull request and leads with Code, on the real shell, with Agents and Docs working today101 inviteOnly: mode !== "open",
102 addresses: addresses(),
103 // Visitors from where the law asks first are asked before analytics runs.
104 analyticsConsent: visitorAsksFirst(request),
105 };
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put106}
107
108/**
109 * The sidebar for someone not signed in: nothing of their own, only the
110 * open counts of the project being looked at. One call, and only in a
111 * project; it answers the same for a private project as a missing one.
112 */
Fast pages, required checks on the branch, self-hosted runners, honest incidents113async function visitorShell(params: { owner?: string; repo?: string }, context: Route.LoaderArgs["context"]): Promise<ShellData> {
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put114 const path = params.owner && params.repo ? { namespace: params.owner, name: params.repo } : null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents115 const counts = path ? await countsFor(context, params) : null;
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put116 return {
117 workspace: null,
118 repos: [],
119 repo: path && counts?.ok ? { ...path, member: false, issues: counts.value.issues, pulls: counts.value.pulls } : null,
120 limit: null,
121 monthUsageMicros: null,
122 };
Commit pages credit co-authors instead of printing their trailers123}
124
Usage, Billing settings and prepaid AI credit; fixes from the UX audit125/** The sidebar from what the session already says, with no service asked. */
126function bareShell(user: User, params: { owner?: string; repo?: string }, chosen: string | null): ShellData {
127 return {
128 workspace: workspaceFor(user.workspaces ?? [], chosen, params),
129 repos: [],
130 repo: null,
131 limit: null,
132 monthUsageMicros: null,
133 };
134}
135
Commit pages credit co-authors instead of printing their trailers136/**
137 * The sidebar only changes with the workspace or repository being looked
138 * at, or after something was submitted: not on every page within them.
139 */
140export function shouldRevalidate({
141 currentParams,
142 nextParams,
143 formMethod,
144 defaultShouldRevalidate,
145}: ShouldRevalidateFunctionArgs) {
146 if (formMethod && formMethod !== "GET") return defaultShouldRevalidate;
147 return currentParams.owner !== nextParams.owner || currentParams.repo !== nextParams.repo;
148}
149
150/**
Usage, Billing settings and prepaid AI credit; fixes from the UX audit151 * The sidebar: the workspace whose pages or project these are when it is
152 * one of yours, else the one you chose (lib/workspace-choice.ts); its
153 * projects; and the repository being looked at.
Commit pages credit co-authors instead of printing their trailers154 */
155async function shellFor(
156 user: User,
157 params: { owner?: string; repo?: string },
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put158 chosen: string | null,
Fast pages, required checks on the branch, self-hosted runners, honest incidents159 context: Route.LoaderArgs["context"],
Commit pages credit co-authors instead of printing their trailers160): Promise<ShellData> {
161 const memberships = user.workspaces ?? [];
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put162 const workspace = workspaceFor(memberships, chosen, params);
Commit pages credit co-authors instead of printing their trailers163 const path = params.owner && params.repo ? { namespace: params.owner, name: params.repo } : null;
164 const now = new Date();
165 const monthStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1)).toISOString();
Fast pages, required checks on the branch, self-hosted runners, honest incidents166 // What the sidebar shows of the workspace changes rarely: kept for a few
167 // seconds per person and workspace, except just after they changed
168 // something (lib/cache.server.ts).
169 const kept = <T,>(what: string, load: () => Promise<T>) =>
170 workspace ? shortCache(`shell:${what}:${user.id}:${workspace.slug}`, SHELL_TTL_MS, load) : 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)171 const [listed, counts, status, usage, limit, entitlements, shared, unread, shortcuts, chatUnread, teamAgents] = await Promise.all([
Navigation: the workspace's own page with tabs, pins and recent in the sidebar172 // Every project, for the palette and the count; the sidebar lists only
173 // the person's pinned and recent ones (lib/pins.ts).
174 workspace ? workspaceProjects(workspace.slug, user) : Promise.resolve(null),
Fast pages, required checks on the branch, self-hosted runners, honest incidents175 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 processor176 // Whether billing is on, without reading the account: that asks the
177 // card processor about the workspace's cards, too slow for every page.
178 kept("status", () => billing.status()).catch(() => null),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit179 kept(`usage:${monthStart}`, () => billing.usage(workspace!.slug, user, monthStart)).catch(() => null),
Fast pages, required checks on the branch, self-hosted runners, honest incidents180 kept("limit", () => billing.limit(workspace!.slug, user)).catch(() => null),
181 kept("entitlements", () => billing.entitlements(workspace!.slug)).catch(() => null),
182 sharedRepos(user),
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all183 // The bell's count: one query, never kept, so marking an item shows at once.
184 inbox.counts(user.username).catch(() => null),
Navigation: the workspace's own page with tabs, pins and recent in the sidebar185 // Never kept: opening a project moves it up Recent.
186 workspace ? projects.shortcuts(workspace.slug, user).catch(() => null) : Promise.resolve(null),
Chat and workspace agents: channels, DMs and named agents you talk to187 // The rail's Chat badge: never kept, and never waited on for long.
188 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)189 // Home's Recent and the Agents sidebar, on every page: never waited on for long.
190 workspace ? agentsFor(workspace.slug, user) : Promise.resolve(null),
Commit pages credit co-authors instead of printing their trailers191 ]);
192 return {
193 workspace,
Projects: what a workspace builds and runs, first on every page194 // 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 sidebar195 repos: listed?.ok ? listed.value.map(shortcutOf) : [],
196 pinned: (shortcuts?.pinned ?? []).map(shortcutOf),
197 recent: (shortcuts?.recent ?? []).map(shortcutOf),
Commit pages credit co-authors instead of printing their trailers198 repo:
199 path && counts?.ok
200 ? {
201 ...path,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look202 // Until the repository's own page says what their role is.
203 member: hasAccessIn(user, path.namespace),
Commit pages credit co-authors instead of printing their trailers204 issues: counts.value.issues,
205 pulls: counts.value.pulls,
206 }
207 : null,
Billing accounts, terms and enterprises; g1t is no longer free208 // Where the workspace stands against its usage limit, once billing is on.
209 limit:
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor210 status?.enabled && limit?.ok
Billing accounts, terms and enterprises; g1t is no longer free211 ? {
212 exposureMicros: limit.value.exposureMicros,
213 ceilingMicros: limit.value.ceilingMicros,
214 state: limit.value.state,
215 comped: limit.value.trust === "internal",
216 }
217 : null,
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor218 free: Boolean(status?.free),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look219 // A spend spike or a hold pauses new compute: the shell says so on every page.
220 compute:
221 workspace && entitlements && (entitlements.paused || entitlements.spike?.status === "open")
222 ? { paused: entitlements.paused, spike: entitlements.spike ?? null, owner: workspace.role === "owner" }
223 : null,
224 shared,
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all225 inbox: unread,
Chat and workspace agents: channels, DMs and named agents you talk to226 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)227 agents: teamAgents,
Usage while free is shown at cost; agents get rustfmt and clippy228 // 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 audit229 // Usage at price, the one figure every page shows.
230 monthUsageMicros: usage?.ok ? (usage.value.free ? usage.value.usedMicros : (usage.value.priceMicros ?? usage.value.spentMicros)) : null,
Commit pages credit co-authors instead of printing their trailers231 };
232}
233
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)234/** Starred and recent conversations Home's sidebar lists. */
235const SHELL_CONVERSATIONS = 8;
236
237/**
238 * What is unread in chat, for the rail, and the starred and latest
239 * conversations, for Home's sidebar; null when chat is slow to answer or
240 * does not.
241 */
Chat and workspace agents: channels, DMs and named agents you talk to242async function chatUnreadFor(slug: string, user: User): Promise<ShellData["chat"]> {
243 const timeout = new Promise<null>((resolve) => setTimeout(() => resolve(null), CHAT_BADGE_WAIT_MS));
244 const read = chat
245 .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)246 .then((result) => {
247 if (!result.ok) return null;
248 const entries = result.value.entries;
249 const latest = [...entries].sort((a, b) => (b.channel.last_message_at ?? "").localeCompare(a.channel.last_message_at ?? ""));
250 return {
251 ...unreadTotals(entries),
252 starred: entries.filter((e) => e.starred).slice(0, SHELL_CONVERSATIONS),
253 recent: latest.slice(0, SHELL_CONVERSATIONS),
254 };
255 })
256 .catch(() => null);
257 return Promise.race([read, timeout]);
258}
259
260/** The workspace's agents, as the shell lists them; null when the agents service is slow or down. */
261async function agentsFor(slug: string, user: User): Promise<ShellData["agents"]> {
262 const timeout = new Promise<null>((resolve) => setTimeout(() => resolve(null), CHAT_BADGE_WAIT_MS));
263 const read = workspaceAgents
264 .list(slug, user)
265 .then((result) =>
266 result.ok
267 ? result.value
268 .filter((agent) => !agent.archived_at)
269 .map((agent) => ({
270 id: agent.id,
271 handle: agent.handle,
272 display_name: agent.display_name,
273 avatar: agent.avatar,
274 avatar_seed: agent.avatar_seed,
275 role: agent.role,
276 title: agent.title,
277 team: agent.team,
278 department: agent.department,
279 status: agent.status,
280 builtin: agent.builtin === true,
281 }))
282 : null,
283 )
Chat and workspace agents: channels, DMs and named agents you talk to284 .catch(() => null);
285 return Promise.race([read, timeout]);
286}
287
288/** The longest the rail's Chat badge holds up a page. */
289const CHAT_BADGE_WAIT_MS = 300;
290
Usage, Billing settings and prepaid AI credit; fixes from the UX audit291/**
292 * How long the sidebar's workspace answers are kept: as long as someone's
293 * own changes read fresh anyway (lib/perf.ts `PRIMARY_WINDOW_SECONDS`).
294 */
295const SHELL_TTL_MS = 30_000;
Fast pages, required checks on the branch, self-hosted runners, honest incidents296
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look297/** Most repositories listed under Shared with you. */
298const MAX_SHARED = 20;
299
300/**
301 * Repositories shared with the user in workspaces they do not belong to,
302 * by their grants: only those, never the workspace's other repositories.
303 */
304async function sharedRepos(user: User): Promise<ShellData["shared"]> {
305 const outside = new Set(sharedWorkspaces(user));
306 if (outside.size === 0) return [];
307 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 incidents308 const found = await readableRepos(ids, user);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look309 return found
Fast pages, required checks on the branch, self-hosted runners, honest incidents310 .map((repo) => ({ namespace: repo.namespace, name: repo.name, isPrivate: repo.isPrivate }))
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look311 .sort((a, b) => `${a.namespace}/${a.name}`.localeCompare(`${b.namespace}/${b.name}`));
312}
313
Usage, Billing settings and prepaid AI credit; fixes from the UX audit314/**
315 * The root's data as the browser last had it. When a navigation fails in a
316 * way that takes the root's data with it, the error is still drawn in the
317 * person's own frame, signed in, not the visitor's. Never kept on the
318 * server, where one module serves everyone.
319 */
320let lastRoot: Awaited<ReturnType<typeof loader>> | undefined;
321
Commit pages credit co-authors instead of printing their trailers322export 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 address323 const nonce = useNonce();
Commit pages credit co-authors instead of printing their trailers324 // Undefined when the root loader itself failed.
Usage, Billing settings and prepaid AI credit; fixes from the UX audit325 const loaded = useRouteLoaderData<typeof loader>("root");
326 const inBrowser = typeof document !== "undefined";
327 if (loaded && inBrowser) lastRoot = loaded;
328 const root = loaded ?? (inBrowser ? lastRoot : undefined);
Commit pages credit co-authors instead of printing their trailers329 const user = root?.user;
Merge branch 'socket-tickets'330 // A page opened with an access token signs its live sockets in with tickets (lib/live-socket.ts).
331 if (inBrowser) setLiveViaToken(Boolean(user?.token?.website));
Merge branch 'worktree-agent-a985c01f43538af01'332 const { pathname, search } = useLocation();
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas333 // Drawn around the error page too: a 404 keeps the sidebar out of a
334 // project or workspace the viewer cannot see.
335 const error = useRouteError();
336 const missing = isRouteErrorResponse(error) && error.status === 404;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look337 // The billing page shows the full banner itself.
338 const paused = root?.shell?.compute && root.shell.workspace && !pathname.endsWith("/-/billing") && (
Fast pages, required checks on the branch, self-hosted runners, honest incidents339 <SpikeBanner
340 compact
341 slug={root.shell.workspace.slug}
342 entitlements={{ paused: root.shell.compute.paused, spike: root.shell.compute.spike }}
343 owner={root.shell.compute.owner}
344 />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look345 );
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)346 // On the few pages an account still confirming its address can open
347 // besides /confirm-email (policies, support, signing in): the way back.
348 const verify = user && awaitsConfirmation(user) && pathname !== "/confirm-email" && (
349 <p
350 role="status"
Commit pages credit co-authors instead of printing their trailers351 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"
352 >
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)353 <span>Confirm your email address to start using g1t.</span>
354 <Link to="/confirm-email" className="font-medium underline underline-offset-4">
355 Enter your code
356 </Link>
357 </p>
Commit pages credit co-authors instead of printing their trailers358 );
Merge main (membership, two-factor, GitHub repo roles) into tokens359 // A workspace that requires two-factor authentication of someone who
360 // has not turned it on: they keep their place, and cannot use it yet.
361 const held = user?.held && user.held.length > 0 && pathname !== "/settings/two-factor" && <PolicyNotice held={user.held} />;
Merge branch 'worktree-agent-aca4790ea223aa235'362 const leaving = useLeaving();
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.363 // Which frame the page is drawn in (lib/chrome.ts): on its own, the app's, or the public one.
364 const frame = frameOf(pathname, {
365 signedIn: user != null && !awaitsConfirmation(user),
366 workspace: (user?.workspaces ?? []).length > 0,
367 });
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look368 const banner =
Merge main (membership, two-factor, GitHub repo roles) into tokens369 paused || verify || held ? (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look370 <>
371 {paused}
372 {verify}
Merge main (membership, two-factor, GitHub repo roles) into tokens373 {held}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look374 </>
375 ) : null;
Commit pages credit co-authors instead of printing their trailers376 return (
377 <html lang="en">
378 <head>
379 <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)380 <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
Commit pages credit co-authors instead of printing their trailers381 <meta name="theme-color" content="#0f0f11" />
Merge branch 'mirroring' into artifacts-mode382 <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)383 <meta name="apple-mobile-web-app-capable" content="yes" />
384 <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 preloads385 {/* The stylesheet before everything React Router preloads, so a slow
386 connection paints sooner (docs/research/css-shipping.md). */}
Give the stylesheet a precedence so React places it before preloads387 <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 today388 {root?.analyticsConsent && <meta name="g1t-analytics" content="consent" />}
Commit pages credit co-authors instead of printing their trailers389 <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 address390 <Links nonce={nonce} />
Commit pages credit co-authors instead of printing their trailers391 </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.392 <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 audit393 {/* The first stop for the keyboard: past the menus, to the page. */}
394 <a
395 href="#content"
396 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]"
397 >
398 Skip to content
399 </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.400 {frame === "standalone" ? (
401 <StandaloneFrame>{children}</StandaloneFrame>
402 ) : frame === "app" && user && root?.shell ? (
403 <AppShell user={user} shell={root.shell} missing={missing} banner={banner} sidebarClosed={root.sidebarClosed}>
Commit pages credit co-authors instead of printing their trailers404 {children}
405 </AppShell>
406 ) : (
407 <>
408 <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.409 <PublicHeader user={user} />
Commit pages credit co-authors instead of printing their trailers410 {banner}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit411 <div id="content" tabIndex={-1} {...leaving} className={`grow outline-none ${leaving.className}`}>
Merge branch 'worktree-agent-aca4790ea223aa235'412 {children}
413 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look414 <SiteFooter user={user} />
Commit pages credit co-authors instead of printing their trailers415 </>
416 )}
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)417 {/* Live notifications for the signed-in: the feed socket, toasts, the tab's count (components/notifications). */}
418 {user && !awaitsConfirmation(user) && (
419 <LiveNotifications workspace={root?.shell?.workspace?.slug ?? null} inbox={root?.shell?.inbox?.unread ?? null} />
420 )}
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 today421 <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 address422 <ScrollRestoration nonce={nonce} />
423 <Scripts nonce={nonce} />
Commit pages credit co-authors instead of printing their trailers424 </body>
425 </html>
426 );
427}
428
429export 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 today430 const userId = useRouteLoaderData<typeof loader>("root")?.user?.id ?? null;
431 // Tell analytics who is signed in, once per change; signing out forgets them.
432 const lastUserId = useRef<string | null>(null);
433 useEffect(() => {
434 identify(userId, lastUserId.current);
435 lastUserId.current = userId;
436 }, [userId]);
Commit pages credit co-authors instead of printing their trailers437 return <Outlet />;
438}
439
440export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
Usage, Billing settings and prepaid AI credit; fixes from the UX audit441 const location = useLocation();
442 const matches = useMatches();
Merge branch 'worktree-agent-a64e51c8f386f46ac'443 const href = location.pathname + location.search + location.hash;
Usage, Billing settings and prepaid AI credit; fixes from the UX audit444 // A tab left open across a deploy: load the address again as a whole
Merge branch 'worktree-agent-a64e51c8f386f46ac'445 // page, once, rather than show an error the reload fixes. A document load
446 // never reloads, so the server and the hydrating client render the same.
447 const reloadable = reloadFixes({
Usage, Billing settings and prepaid AI credit; fixes from the UX audit448 error,
449 status: isRouteErrorResponse(error) ? error.status : undefined,
Merge branch 'worktree-agent-a64e51c8f386f46ac'450 clientNavigation: clientNavigated(location.key),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit451 caughtByCatchAll: matches.at(-1)?.id === "routes/not-found",
452 });
Merge branch 'worktree-agent-a64e51c8f386f46ac'453 // Already loaded again once, or the reload never started: show the page.
454 const tried = useMemo(() => reloadable && reloadedBefore(href), [reloadable, href]);
455 const [gaveUp, setGaveUp] = useState<string | null>(null);
456 const reload = reloadable && !tried && gaveUp !== href;
Usage, Billing settings and prepaid AI credit; fixes from the UX audit457 useEffect(() => {
458 if (!reload) return;
459 try {
460 sessionStorage.setItem(RELOADED_KEY, href);
461 } catch {
462 // No session storage: reload anyway; a document load never asks again.
463 }
464 window.location.assign(href);
Merge branch 'worktree-agent-a64e51c8f386f46ac'465 const timer = window.setTimeout(() => setGaveUp(href), RELOAD_GIVE_UP_MS);
466 return () => window.clearTimeout(timer);
Usage, Billing settings and prepaid AI credit; fixes from the UX audit467 }, [reload, href]);
468 if (reload) {
469 return (
Not found and error pages have one main landmark: their own when signed out, the shell's when signed in470 <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 audit471 <title>Loading · g1t</title>
472 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 in473 </PageMain>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit474 );
475 }
476
Commit pages credit co-authors instead of printing their trailers477 let title = "Something went wrong";
478 let details = "An unexpected error occurred. Try again in a moment.";
479 let stack: string | undefined;
480
481 if (isRouteErrorResponse(error)) {
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put482 // The same page for something private and something missing.
Usage, Billing settings and prepaid AI credit; fixes from the UX audit483 if (error.status === 404) {
484 return (
485 <>
486 <title>Not found · g1t</title>
487 <NotFound data={error.data} />
488 </>
489 );
490 }
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put491 if (typeof error.data === "string" && error.data) {
Commit pages credit co-authors instead of printing their trailers492 details = error.data;
493 }
Merge main into Artifacts Phase 2494 // A token asked for what needs a real sign-in (lib/website-token.ts).
495 if (isNeedsSignIn(error.data)) {
496 title = "This needs you to sign in";
497 details = error.data.message;
498 }
Commit pages credit co-authors instead of printing their trailers499 } else if (import.meta.env.DEV && error instanceof Error) {
500 details = error.message;
501 stack = error.stack;
502 }
503
504 return (
Not found and error pages have one main landmark: their own when signed out, the shell's when signed in505 <PageMain className="mx-auto max-w-xl px-4 py-32 text-center">
Commit pages credit co-authors instead of printing their trailers506 <h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
507 <p className="mt-3 text-muted">{details}</p>
508 <div className="mt-8">
509 <ButtonLink to="/" variant="quiet">
510 Back to home
511 </ButtonLink>
512 </div>
513 {stack && (
514 <pre className="mt-8 overflow-x-auto rounded-lg border border-line p-4 text-left text-xs">
515 <code>{stack}</code>
516 </pre>
517 )}
Not found and error pages have one main landmark: their own when signed out, the shell's when signed in518 </PageMain>
Commit pages credit co-authors instead of printing their trailers519 );
520}

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