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

165 lines7,518 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",
The Packages page names every registry it has54 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 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 },
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail90 security: {
91 title: "Security",
92 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.",
93 },
A catalogue of model providers, and settings that feel like settings94 integrations: {
95 title: "Integrations",
96 about: "Model providers, alerts and trackers. Secrets are sealed when saved, and agents never see them.",
97 },
98};
99
Agents as a team: lifecycle, merge queue, billing and a new shell100export default function WorkspaceLayout({ loaderData }: Route.ComponentProps) {
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas101 const { workspace, role, welcome } = loaderData;
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put102 // The sidebar finds the workspace's pages, for everyone, so its pages
103 // 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 member104 const parts = (useLocation().pathname.split("/-/")[1] ?? "").split("/").filter(Boolean);
105 const page = PAGES[parts[0] ?? ""];
106 // A page within one (a single package) has its own heading.
107 if (page && parts.length > 1) {
108 return (
109 <div className="mx-auto max-w-5xl px-4 py-10 sm:px-8">
110 <Outlet />
111 </div>
112 );
113 }
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put114 if (page) {
A catalogue of model providers, and settings that feel like settings115 return (
116 <div className="mx-auto max-w-5xl px-4 py-10 sm:px-8">
117 <header className="mb-8 border-b border-line pb-6">
118 <h1 className="text-2xl font-semibold tracking-tight">{page.title}</h1>
119 <p className="mt-1.5 text-sm text-muted">{page.about}</p>
120 </header>
121 <Outlet />
122 </div>
123 );
124 }
Agents as a team: lifecycle, merge queue, billing and a new shell125 return (
126 <>
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put127 {/* The workspace's own header band. */}
Agents as a team: lifecycle, merge queue, billing and a new shell128 <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 put129 <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 quotas130 {welcome && (
131 <div className="mb-6">
132 <WelcomeBanner title={`You're in ${workspace.name}`}>
133 You joined as a {role}. Its projects, issues and agents are all here.
134 </WelcomeBanner>
135 </div>
136 )}
Agents as a team: lifecycle, merge queue, billing and a new shell137 <div className="flex flex-wrap items-center gap-4">
Workspace names and icons, and a component kit for every control138 <Avatar name={workspace.slug} image={workspace.avatar} size={52} square />
Agents as a team: lifecycle, merge queue, billing and a new shell139 <div className="min-w-0 grow">
140 <div className="flex flex-wrap items-center gap-2.5">
141 <h1 className="truncate text-2xl font-semibold tracking-tight">
142 {workspace.name}
143 </h1>
144 {role && <Pill>{role}</Pill>}
145 </div>
146 <p className="font-mono text-sm text-muted">g1t.sh/{workspace.slug}</p>
147 </div>
148 {role && (
149 <ButtonLink to={`/new?workspace=${workspace.slug}`}>
150 <Plus size={15} />
Projects live: fixes from walking them end to end151 New project
Agents as a team: lifecycle, merge queue, billing and a new shell152 </ButtonLink>
153 )}
154 </div>
155 {workspace.description && (
156 <p className="mt-4 max-w-2xl text-sm text-muted">{workspace.description}</p>
157 )}
158 </div>
159 </div>
160 <div className="mx-auto max-w-6xl px-4 py-8">
161 <Outlet />
162 </div>
163 </>
164 );
165}