flagon-io/g1t

public

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

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

157 lines6,700 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.

Integrations: your own model provider, alerts that open issues, tickets agents read1import { CreditCard, KeyRound, LayoutGrid, Plug, Plus, Settings, Users } from "lucide-react";
A catalogue of model providers, and settings that feel like settings2import { Outlet, data, useLocation, useRouteLoaderData } 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";
Agents as a team: lifecycle, merge queue, billing and a new shell6import { Avatar, ButtonLink, Pill, TabLink } from "../../components/ui";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains7import { redirectIfRenamed } from "../../lib/renamed.server";
Agents as a team: lifecycle, merge queue, billing and a new shell8import { identity } from "../../lib/services.server";
9import { getViewer, roleIn } from "../../lib/session.server";
10
Social cards for every page: og.g1t.sh11export function meta({ loaderData, params, ...args }: Route.MetaArgs) {
12 return page(args, { title: `${loaderData?.workspace.name ?? params.owner} · g1t` });
Agents as a team: lifecycle, merge queue, billing and a new shell13}
14
Agents and memory, checks and conflicts, profiles, slug renames, custom domains15export async function loader({ params, context, request }: Route.LoaderArgs) {
Agents as a team: lifecycle, merge queue, billing and a new shell16 const workspace = await identity.getWorkspace(params.owner);
Agents and memory, checks and conflicts, profiles, slug renames, custom domains17 if (!workspace) {
18 // A workspace's old name, after a rename: its pages are at the new one.
19 await redirectIfRenamed(request, params.owner);
20 throw data(null, { status: 404 });
21 }
Agents as a team: lifecycle, merge queue, billing and a new shell22 return { workspace, role: roleIn(getViewer(context), workspace.slug) };
23}
24
A catalogue of model providers, and settings that feel like settings25/** A workspace's own pages, each with its title and what it is for. */
26const PAGES: Record<string, { title: string; about: string }> = {
Workspace names and icons, and a component kit for every control27 settings: { title: "General", about: "The workspace's name, icon, address and description." },
A catalogue of model providers, and settings that feel like settings28 people: {
29 title: "Members",
30 about: "Members create repositories, push, manage issues and merge pull requests. Owners also manage members, tokens, billing and integrations.",
31 },
32 tokens: {
33 title: "Access tokens",
34 about: "Tokens that belong to the workspace, not a person: for CI, integrations and agents that work for the whole team.",
35 },
36 usage: { title: "Usage", about: "What the workspace's agents cost, run by run, by repository, pull request and model." },
Secrets and variables: one list, rows per environment, for workflows and deployments37 billing: { title: "Billing and plans", about: "Paid plans, agent credit, and every charge against it." },
Agents and memory, checks and conflicts, profiles, slug renames, custom domains38 agents: {
39 title: "Agent fleet",
40 about: "Every agent at work across the workspace's projects: what each holds, what it is doing now, and what it has cost.",
41 },
42 memory: {
43 title: "Workspace memory",
44 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.",
45 },
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API46 context: {
47 title: "Context",
48 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.",
49 },
50 audit: {
51 title: "Audit log",
52 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.",
53 },
54 guardrails: {
55 title: "Guardrails",
56 about: "What agents may reach, run, spend and take in every project's sandboxes. Each project can override these under its settings.",
57 },
Webhooks: every event, to your own addresses, signed and retried58 webhooks: {
59 title: "Webhooks",
60 about: "Every repository's events, sent to your own addresses as they happen. A repository can also have its own, under its settings.",
61 },
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs62 secrets: {
63 title: "Secrets and variables",
Secrets and variables: one list, rows per environment, for workflows and deployments64 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 docs65 },
A catalogue of model providers, and settings that feel like settings66 integrations: {
67 title: "Integrations",
68 about: "Model providers, alerts and trackers. Secrets are sealed when saved, and agents never see them.",
69 },
70};
71
Agents as a team: lifecycle, merge queue, billing and a new shell72export default function WorkspaceLayout({ loaderData }: Route.ComponentProps) {
73 const { workspace, role } = loaderData;
74 const base = `/${workspace.slug}`;
75 const signedIn = useRouteLoaderData("root")?.user != null;
A catalogue of model providers, and settings that feel like settings76 // The sidebar finds the workspace's pages for someone signed in, so its
77 // pages need a title, not the workspace's whole header again.
78 const page = PAGES[useLocation().pathname.split("/-/")[1]?.split("/")[0] ?? ""];
79 if (signedIn && page) {
80 return (
81 <div className="mx-auto max-w-5xl px-4 py-10 sm:px-8">
82 <header className="mb-8 border-b border-line pb-6">
83 <h1 className="text-2xl font-semibold tracking-tight">{page.title}</h1>
84 <p className="mt-1.5 text-sm text-muted">{page.about}</p>
85 </header>
86 <Outlet />
87 </div>
88 );
89 }
Agents as a team: lifecycle, merge queue, billing and a new shell90 return (
91 <>
92 {/* The workspace's own header band, under the site header. */}
93 <div className="border-b border-line bg-surface/60">
94 <div className="mx-auto max-w-6xl px-4 pt-8">
95 <div className="flex flex-wrap items-center gap-4">
Workspace names and icons, and a component kit for every control96 <Avatar name={workspace.slug} image={workspace.avatar} size={52} square />
Agents as a team: lifecycle, merge queue, billing and a new shell97 <div className="min-w-0 grow">
98 <div className="flex flex-wrap items-center gap-2.5">
99 <h1 className="truncate text-2xl font-semibold tracking-tight">
100 {workspace.name}
101 </h1>
102 {role && <Pill>{role}</Pill>}
103 </div>
104 <p className="font-mono text-sm text-muted">g1t.sh/{workspace.slug}</p>
105 </div>
106 {role && (
107 <ButtonLink to={`/new?workspace=${workspace.slug}`}>
108 <Plus size={15} />
Projects live: fixes from walking them end to end109 New project
Agents as a team: lifecycle, merge queue, billing and a new shell110 </ButtonLink>
111 )}
112 </div>
113 {workspace.description && (
114 <p className="mt-4 max-w-2xl text-sm text-muted">{workspace.description}</p>
115 )}
116 {signedIn ? (
117 <div className="pb-8" />
118 ) : (
119 <nav className="mt-6 flex flex-wrap gap-x-6">
120 <TabLink to={base} end icon={<LayoutGrid size={15} />}>
121 Overview
122 </TabLink>
123 {role && (
124 <>
125 <TabLink
126 to={`${base}/-/people`}
127 icon={<Users size={15} />}
128 count={workspace.memberCount}
129 >
Secrets and variables: one list, rows per environment, for workflows and deployments130 Members
Agents as a team: lifecycle, merge queue, billing and a new shell131 </TabLink>
132 <TabLink to={`${base}/-/tokens`} icon={<KeyRound size={15} />}>
133 Access tokens
134 </TabLink>
135 <TabLink to={`${base}/-/billing`} icon={<CreditCard size={15} />}>
136 Billing
137 </TabLink>
Integrations: your own model provider, alerts that open issues, tickets agents read138 <TabLink to={`${base}/-/integrations`} icon={<Plug size={15} />}>
139 Integrations
140 </TabLink>
Agents as a team: lifecycle, merge queue, billing and a new shell141 </>
142 )}
143 {role === "owner" && (
144 <TabLink to={`${base}/-/settings`} icon={<Settings size={15} />}>
145 Settings
146 </TabLink>
147 )}
148 </nav>
149 )}
150 </div>
151 </div>
152 <div className="mx-auto max-w-6xl px-4 py-8">
153 <Outlet />
154 </div>
155 </>
156 );
157}