g1t/apps/web/app/routes/workspace/layout.tsx

161 lines7,245 bytesCodeBlame

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.

Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1import { Plus } from "lucide-react";
2import { Outlet, data, useLocation } from "react-router";
Agents as a team: lifecycle, merge queue, billing and a new shell3
4import type { Route } from "./+types/layout";
Social cards for every page: og.g1t.sh5import { page } from "../../lib/meta";
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put6import { Avatar, ButtonLink, Pill } from "../../components/ui";
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas7import { WelcomeBanner } from "../../components/welcome";
8import { clearWelcome, welcomes } from "../../lib/invites";
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put9import { notFound } from "../../lib/not-found.server";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains10import { redirectIfRenamed } from "../../lib/renamed.server";
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put11import { rememberWorkspace } from "../../lib/workspace-choice";
Agents as a team: lifecycle, merge queue, billing and a new shell12import { identity } from "../../lib/services.server";
13import { getViewer, roleIn } from "../../lib/session.server";
14
Social cards for every page: og.g1t.sh15export function meta({ loaderData, params, ...args }: Route.MetaArgs) {
16 return page(args, { title: `${loaderData?.workspace.name ?? params.owner} · g1t` });
Agents as a team: lifecycle, merge queue, billing and a new shell17}
18
Agents and memory, checks and conflicts, profiles, slug renames, custom domains19export async function loader({ params, context, request }: Route.LoaderArgs) {
Agents as a team: lifecycle, merge queue, billing and a new shell20 const workspace = await identity.getWorkspace(params.owner);
Agents and memory, checks and conflicts, profiles, slug renames, custom domains21 if (!workspace) {
22 // A workspace's old name, after a rename: its pages are at the new one.
23 await redirectIfRenamed(request, params.owner);
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put24 throw notFound("workspace");
Agents and memory, checks and conflicts, profiles, slug renames, custom domains25 }
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put26 const role = roleIn(getViewer(context), workspace.slug);
27 // Opening one of your workspaces makes it the one you are in.
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas28 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 put29 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 quotas30 const headers = new Headers({ "Set-Cookie": rememberWorkspace(workspace.slug, secure) });
31 // Just joined with an invite: welcomed once (routes/invite.tsx).
32 const welcome = welcomes(request.headers.get("cookie"), workspace.slug);
33 if (welcome) headers.append("Set-Cookie", clearWelcome(secure));
34 return data({ workspace, role, welcome }, { headers });
Agents as a team: lifecycle, merge queue, billing and a new shell35}
36
A catalogue of model providers, and settings that feel like settings37/** A workspace's own pages, each with its title and what it is for. */
38const PAGES: Record<string, { title: string; about: string }> = {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look39 settings: { title: "General", about: "The workspace's name, icon, address and description, and deleting it." },
A catalogue of model providers, and settings that feel like settings40 people: {
41 title: "Members",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look42 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.",
43 },
44 repositories: {
45 title: "Repositories",
46 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 settings47 },
48 tokens: {
49 title: "Access tokens",
50 about: "Tokens that belong to the workspace, not a person: for CI, integrations and agents that work for the whole team.",
51 },
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member52 packages: {
53 title: "Packages",
Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start54 about: "What the workspace publishes and installs, beside its code: container images, npm, Composer and Go, 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 member55 },
A catalogue of model providers, and settings that feel like settings56 usage: { title: "Usage", about: "What the workspace's agents cost, run by run, by repository, pull request and model." },
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look57 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 domains58 agents: {
59 title: "Agent fleet",
60 about: "Every agent at work across the workspace's projects: what each holds, what it is doing now, and what it has cost.",
61 },
62 memory: {
63 title: "Workspace memory",
64 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.",
65 },
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API66 context: {
67 title: "Context",
68 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.",
69 },
70 audit: {
71 title: "Audit log",
72 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.",
73 },
74 guardrails: {
75 title: "Guardrails",
76 about: "What agents may reach, run, spend and take in every project's sandboxes. Each project can override these under its settings.",
77 },
Webhooks: every event, to your own addresses, signed and retried78 webhooks: {
79 title: "Webhooks",
80 about: "Every repository's events, sent to your own addresses as they happen. A repository can also have its own, under its settings.",
81 },
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs82 secrets: {
83 title: "Secrets and variables",
Secrets and variables: one list, rows per environment, for workflows and deployments84 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 docs85 },
Fast pages, required checks on the branch, self-hosted runners, honest incidents86 runners: {
87 title: "Self-hosted runners",
88 about: "Your own machines, which run workflow jobs, and if you choose agents' work, for free. They connect out to g1t; nothing reaches in.",
89 },
A catalogue of model providers, and settings that feel like settings90 integrations: {
91 title: "Integrations",
92 about: "Model providers, alerts and trackers. Secrets are sealed when saved, and agents never see them.",
93 },
94};
95
Agents as a team: lifecycle, merge queue, billing and a new shell96export default function WorkspaceLayout({ loaderData }: Route.ComponentProps) {
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas97 const { workspace, role, welcome } = loaderData;
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put98 // The sidebar finds the workspace's pages, for everyone, so its pages
99 // need a title, not the workspace's whole header again.
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member100 const parts = (useLocation().pathname.split("/-/")[1] ?? "").split("/").filter(Boolean);
101 const page = PAGES[parts[0] ?? ""];
102 // A page within one (a single package) has its own heading.
103 if (page && parts.length > 1) {
104 return (
105 <div className="mx-auto max-w-5xl px-4 py-10 sm:px-8">
106 <Outlet />
107 </div>
108 );
109 }
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put110 if (page) {
A catalogue of model providers, and settings that feel like settings111 return (
112 <div className="mx-auto max-w-5xl px-4 py-10 sm:px-8">
113 <header className="mb-8 border-b border-line pb-6">
114 <h1 className="text-2xl font-semibold tracking-tight">{page.title}</h1>
115 <p className="mt-1.5 text-sm text-muted">{page.about}</p>
116 </header>
117 <Outlet />
118 </div>
119 );
120 }
Agents as a team: lifecycle, merge queue, billing and a new shell121 return (
122 <>
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put123 {/* The workspace's own header band. */}
Agents as a team: lifecycle, merge queue, billing and a new shell124 <div className="border-b border-line bg-surface/60">
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put125 <div className="mx-auto max-w-6xl px-4 pt-8 pb-8">
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas126 {welcome && (
127 <div className="mb-6">
128 <WelcomeBanner title={`You're in ${workspace.name}`}>
129 You joined as a {role}. Its projects, issues and agents are all here.
130 </WelcomeBanner>
131 </div>
132 )}
Agents as a team: lifecycle, merge queue, billing and a new shell133 <div className="flex flex-wrap items-center gap-4">
Workspace names and icons, and a component kit for every control134 <Avatar name={workspace.slug} image={workspace.avatar} size={52} square />
Agents as a team: lifecycle, merge queue, billing and a new shell135 <div className="min-w-0 grow">
136 <div className="flex flex-wrap items-center gap-2.5">
137 <h1 className="truncate text-2xl font-semibold tracking-tight">
138 {workspace.name}
139 </h1>
140 {role && <Pill>{role}</Pill>}
141 </div>
142 <p className="font-mono text-sm text-muted">g1t.sh/{workspace.slug}</p>
143 </div>
144 {role && (
145 <ButtonLink to={`/new?workspace=${workspace.slug}`}>
146 <Plus size={15} />
Projects live: fixes from walking them end to end147 New project
Agents as a team: lifecycle, merge queue, billing and a new shell148 </ButtonLink>
149 )}
150 </div>
151 {workspace.description && (
152 <p className="mt-4 max-w-2xl text-sm text-muted">{workspace.description}</p>
153 )}
154 </div>
155 </div>
156 <div className="mx-auto max-w-6xl px-4 py-8">
157 <Outlet />
158 </div>
159 </>
160 );
161}