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-ad7c6d88d93adc817' | 1 | import { Package, Plus } from "lucide-react"; |
| 2 | import { Outlet, data, redirect, useLocation, useNavigation } from "react-router"; | |
| Agents as a team: lifecycle, merge queue, billing and a new shell | 3 | |
| 4 | import type { Route } from "./+types/layout"; | |
| Social cards for every page: og.g1t.sh | 5 | import { page } from "../../lib/meta"; |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 6 | import { Avatar, ButtonLink, Pill } from "../../components/ui"; |
| status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas | 7 | import { WelcomeBanner } from "../../components/welcome"; |
| 8 | import { clearWelcome, welcomes } from "../../lib/invites"; | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 9 | import { notFound } from "../../lib/not-found.server"; |
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 10 | import { redirectIfRenamed } from "../../lib/renamed.server"; |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 11 | import { rememberWorkspace } from "../../lib/workspace-choice"; |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 12 | import { workspacePage, workspaceRedirect } from "../../lib/workspace-nav"; |
| 13 | import { WorkspacePageSkeleton } from "../../components/workspace-skeletons"; | |
| Agents as a team: lifecycle, merge queue, billing and a new shell | 14 | import { identity } from "../../lib/services.server"; |
| 15 | import { getViewer, roleIn } from "../../lib/session.server"; | |
| 16 | ||
| Social cards for every page: og.g1t.sh | 17 | export function meta({ loaderData, params, ...args }: Route.MetaArgs) { |
| 18 | return page(args, { title: `${loaderData?.workspace.name ?? params.owner} · g1t` }); | |
| Agents as a team: lifecycle, merge queue, billing and a new shell | 19 | } |
| 20 | ||
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 21 | export async function loader({ params, context, request }: Route.LoaderArgs) { |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 22 | const url = new URL(request.url); |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 23 | // `?tab=projects` and the like, from the tabs this page once had: that |
| 24 | // page's own address. | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 25 | const moved = url.search ? workspaceRedirect(url.pathname, url.search) : null; |
| 26 | if (moved) throw redirect(moved); | |
| 27 | const viewer = getViewer(context); | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 28 | const workspace = await identity.getWorkspace(params.owner); |
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 29 | if (!workspace) { |
| 30 | // A workspace's old name, after a rename: its pages are at the new one. | |
| 31 | await redirectIfRenamed(request, params.owner); | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 32 | throw notFound("workspace"); |
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 33 | } |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 34 | const role = roleIn(viewer, workspace.slug); |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 35 | // Opening one of your workspaces makes it the one you are in. |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 36 | if (!role) return { workspace, role, welcome: false }; |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 37 | const secure = new URL(request.url).protocol === "https:"; |
| status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas | 38 | const headers = new Headers({ "Set-Cookie": rememberWorkspace(workspace.slug, secure) }); |
| 39 | // Just joined with an invite: welcomed once (routes/invite.tsx). | |
| 40 | const welcome = welcomes(request.headers.get("cookie"), workspace.slug); | |
| 41 | if (welcome) headers.append("Set-Cookie", clearWelcome(secure)); | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 42 | return data({ workspace, role, welcome }, { headers }); |
| Agents as a team: lifecycle, merge queue, billing and a new shell | 43 | } |
| 44 | ||
| A catalogue of model providers, and settings that feel like settings | 45 | /** A workspace's own pages, each with its title and what it is for. */ |
| 46 | const PAGES: Record<string, { title: string; about: string }> = { | |
| 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) | 47 | workspace: { |
| 48 | title: "Workspace", | |
| 49 | about: "Who is in it, what it is on, and what it has spent this month. Everything about the workspace itself is in this sidebar.", | |
| 50 | }, | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 51 | settings: { title: "General", about: "The workspace's name, icon, address and description, who can create teams, and deleting it." }, |
| A catalogue of model providers, and settings that feel like settings | 52 | people: { |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 53 | title: "People", |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 54 | about: "Members create repositories and have the base permission on each one. Owners are Admins on every repository, and also manage members, tokens, billing and integrations.", |
| 55 | }, | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 56 | projects: { |
| 57 | title: "Projects", | |
| 58 | about: "Everything the workspace builds and runs. Search, filter and sort them, and pin the ones you use most to keep them in your sidebar.", | |
| 59 | }, | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 60 | teams: { |
| 61 | title: "Teams", | |
| 62 | about: "Groups of members, given roles on repositories together, mentioned as @workspace/team and asked to review together. Child teams inherit their parent's access.", | |
| 63 | }, | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 64 | repositories: { |
| 65 | title: "Repositories", | |
| 66 | about: "Every repository in the workspace: who can see it, whether it is archived, and the ones deleted recently.", | |
| A catalogue of model providers, and settings that feel like settings | 67 | }, |
| 68 | tokens: { | |
| 69 | title: "Access tokens", | |
| One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers | 70 | about: "Tokens that belong to the workspace, not a person: for CI, integrations and agents that work for the whole team. Each has the permissions an owner gives it, in all of the workspace's repositories or the ones chosen.", |
| Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules | 71 | }, |
| 72 | "personal-access-tokens": { | |
| 73 | title: "Personal access tokens", | |
| One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers | 74 | about: "Which of your members' own tokens may reach the workspace and for how long, the tokens made for it that wait for an owner's approval, and every token that can reach it.", |
| A catalogue of model providers, and settings that feel like settings | 75 | }, |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 76 | packages: { |
| 77 | title: "Packages", | |
| The Packages page names every registry it has | 78 | about: "What the workspace publishes and installs, beside its code: container images, npm, Composer, Go, Cargo, Maven, NuGet and RubyGems, with the same members, roles and tokens.", |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 79 | }, |
| A catalogue of model providers, and settings that feel like settings | 80 | usage: { title: "Usage", about: "What the workspace's agents cost, run by run, by repository, pull request and model." }, |
| Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens | 81 | gateway: { |
| 82 | title: "AI Gateway", | |
| 83 | about: "Your own code's model requests, sent with the workspace's access tokens: each one's model, tokens, cost and status.", | |
| 84 | }, | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 85 | billing: { title: "Billing and plans", about: "The g1t plan, the trial, your spend limit and caps, prepaying, and every charge." }, |
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 86 | agents: { |
| Agents mode: an overview of every agent's work and spend, and each agent's sessions, memory, routines, spend, activity and versions | 87 | title: "Agents", |
| 88 | about: "The workspace's agents together: what they are working on now, what waits on you, and where this month's agent budget went.", | |
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 89 | }, |
| 90 | memory: { | |
| 91 | title: "Workspace memory", | |
| 92 | about: "What holds across all of the workspace's projects, given to every agent in every one of them, beside each project's own memory.", | |
| 93 | }, | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 94 | context: { |
| 95 | title: "Context", | |
| 96 | about: "Everything the workspace builds and runs, and what it knows: a catalog built from its projects, memory with a review queue, one search over all of it, and a scorecard for each project.", | |
| 97 | }, | |
| 98 | audit: { | |
| 99 | title: "Audit log", | |
| 100 | about: "Every action agents took with their run credentials, and every change people and tokens made: by whom, on whose behalf, to what, and whether it was allowed.", | |
| 101 | }, | |
| 102 | guardrails: { | |
| 103 | title: "Guardrails", | |
| 104 | about: "What agents may reach, run, spend and take in every project's sandboxes. Each project can override these under its settings.", | |
| 105 | }, | |
| Webhooks: every event, to your own addresses, signed and retried | 106 | webhooks: { |
| 107 | title: "Webhooks", | |
| 108 | about: "Every repository's events, sent to your own addresses as they happen. A repository can also have its own, under its settings.", | |
| 109 | }, | |
| GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs | 110 | secrets: { |
| 111 | title: "Secrets and variables", | |
| Secrets and variables: one list, rows per environment, for workflows and deployments | 112 | about: "Shared with every repository, or the ones you link: read by workflows, deployments, or both. A repository's own row of the same key wins.", |
| GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs | 113 | }, |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 114 | runners: { |
| 115 | title: "Self-hosted runners", | |
| 116 | about: "Your own machines, which run workflow jobs, and if you choose agents' work, for free. They connect out to g1t; nothing reaches in.", | |
| 117 | }, | |
| Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail | 118 | security: { |
| 119 | title: "Security", | |
| 120 | about: "Open alerts in every project: secrets found in pushes and history, and vulnerable dependencies. Each project's Security page has the details and the security update g1t opened for each.", | |
| 121 | }, | |
| A catalogue of model providers, and settings that feel like settings | 122 | integrations: { |
| 123 | title: "Integrations", | |
| Integrations are a directory of connectors, for a workspace and for each person | 124 | about: |
| 125 | "The tools your team works with, connected once for everyone in the workspace and its agents. Secrets are sealed when saved, and agents never see them.", | |
| A catalogue of model providers, and settings that feel like settings | 126 | }, |
| 127 | }; | |
| 128 | ||
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 129 | /** A workspace page's heading: its title and what it is for. */ |
| 130 | function PageHeader({ title, about }: { title: string; about: string }) { | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 131 | return ( |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 132 | <header className="mb-8 border-b border-line pb-6"> |
| 133 | <h1 className="text-2xl font-semibold tracking-tight">{title}</h1> | |
| 134 | <p className="mt-1.5 text-sm text-muted">{about}</p> | |
| 135 | </header> | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 136 | ); |
| 137 | } | |
| 138 | ||
| Agents as a team: lifecycle, merge queue, billing and a new shell | 139 | export default function WorkspaceLayout({ loaderData }: Route.ComponentProps) { |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 140 | const { workspace, role, welcome } = loaderData; |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 141 | const { pathname } = useLocation(); |
| 142 | const going = useNavigation().location; | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 143 | // The workspace's own page is its overview. Its other pages are found |
| 144 | // from the sidebar, which lights the one you are on, and the top bar's | |
| 145 | // trail leads back here; each has a heading of its own. | |
| 146 | const here = workspacePage(pathname, workspace.slug); | |
| 147 | // On the way to another of its pages: that page's heading and shape | |
| 148 | // until it arrives. | |
| 149 | const next = going && going.pathname !== pathname ? workspacePage(going.pathname, workspace.slug) : null; | |
| 150 | const shown = next ?? here; | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 151 | const parts = (pathname.split("/-/")[1] ?? "").split("/").filter(Boolean); |
| Chat and workspace agents: channels, DMs and named agents you talk to | 152 | // Chat fills the page edge to edge: its conversation, thread and details |
| 153 | // are columns of their own (components/chat/channel.tsx). | |
| 154 | if (parts[0] === "chat" && !next) return <Outlet />; | |
| 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) | 155 | // Code's Overview lays itself out, as Mission control did. |
| 156 | if (parts[0] === "overview" && parts.length === 1 && !next) return <Outlet />; | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 157 | if (shown !== "overview") { |
| 158 | const key = shown ?? parts[0] ?? ""; | |
| 159 | const heading = PAGES[key]; | |
| 160 | // A page within one (a single package or team) and a page that is | |
| 161 | // coming (Insights) have headings of their own. | |
| 162 | const titled = heading && (shown != null || parts.length === 1); | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 163 | return ( |
| 164 | <div className="mx-auto max-w-5xl px-4 py-10 sm:px-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) | 165 | {titled && ( |
| 166 | // A phone's Agents tab opens on its list of agents (agents/layout.tsx); the fleet's heading would sit above it. | |
| 167 | <div className={key === "agents" ? "max-md:hidden" : undefined}> | |
| 168 | <PageHeader title={heading.title} about={heading.about} /> | |
| 169 | </div> | |
| 170 | )} | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 171 | {next ? <WorkspacePageSkeleton page={next} /> : <Outlet />} |
| A catalogue of model providers, and settings that feel like settings | 172 | </div> |
| 173 | ); | |
| 174 | } | |
| Agents as a team: lifecycle, merge queue, billing and a new shell | 175 | return ( |
| 176 | <> | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 177 | {/* The workspace's own header band, above its overview. */} |
| Agents as a team: lifecycle, merge queue, billing and a new shell | 178 | <div className="border-b border-line bg-surface/60"> |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 179 | <div className="mx-auto max-w-6xl px-4 py-8"> |
| status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas | 180 | {welcome && ( |
| 181 | <div className="mb-6"> | |
| 182 | <WelcomeBanner title={`You're in ${workspace.name}`}> | |
| 183 | You joined as a {role}. Its projects, issues and agents are all here. | |
| 184 | </WelcomeBanner> | |
| 185 | </div> | |
| 186 | )} | |
| Agents as a team: lifecycle, merge queue, billing and a new shell | 187 | <div className="flex flex-wrap items-center gap-4"> |
| Workspace names and icons, and a component kit for every control | 188 | <Avatar name={workspace.slug} image={workspace.avatar} size={52} square /> |
| Agents as a team: lifecycle, merge queue, billing and a new shell | 189 | <div className="min-w-0 grow"> |
| 190 | <div className="flex flex-wrap items-center gap-2.5"> | |
| 191 | <h1 className="truncate text-2xl font-semibold tracking-tight"> | |
| 192 | {workspace.name} | |
| 193 | </h1> | |
| 194 | {role && <Pill>{role}</Pill>} | |
| 195 | </div> | |
| 196 | <p className="font-mono text-sm text-muted">g1t.sh/{workspace.slug}</p> | |
| 197 | </div> | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 198 | {role ? ( |
| Agents as a team: lifecycle, merge queue, billing and a new shell | 199 | <ButtonLink to={`/new?workspace=${workspace.slug}`}> |
| 200 | <Plus size={15} /> | |
| Projects live: fixes from walking them end to end | 201 | New project |
| Agents as a team: lifecycle, merge queue, billing and a new shell | 202 | </ButtonLink> |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 203 | ) : ( |
| 204 | // A visitor's sidebar is not this workspace's: its packages | |
| 205 | // are a link here, as its projects are below. | |
| 206 | <ButtonLink to={`/${workspace.slug}/-/packages`} variant="quiet" prefetch="intent"> | |
| 207 | <Package size={15} /> | |
| 208 | Packages | |
| 209 | </ButtonLink> | |
| Agents as a team: lifecycle, merge queue, billing and a new shell | 210 | )} |
| 211 | </div> | |
| 212 | {workspace.description && ( | |
| 213 | <p className="mt-4 max-w-2xl text-sm text-muted">{workspace.description}</p> | |
| 214 | )} | |
| 215 | </div> | |
| 216 | </div> | |
| 217 | <div className="mx-auto max-w-6xl px-4 py-8"> | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 218 | {next ? <WorkspacePageSkeleton page="overview" /> : <Outlet />} |
| Agents as a team: lifecycle, merge queue, billing and a new shell | 219 | </div> |
| 220 | </> | |
| 221 | ); | |
| 222 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.