Skip to content
445 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.

Navigation: the workspace's own page with tabs, pins and recent in the sidebar1import { ArrowRight, ArrowUpRight, Box, CircleDot, GitBranch, GitPullRequest, KeyRound, Lock, Pin, Plus, Rocket } from "lucide-react";
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)2import { Link, redirect } from "react-router";
Commit pages credit co-authors instead of printing their trailers3
A project is an app or a library: libraries show their package and how to ship a release, not production4import type { PackageSummary, Project, ProjectDeploys, User } from "@g1t/contracts";
Projects: what a workspace builds and runs, first on every page5
Commit pages credit co-authors instead of printing their trailers6import type { Route } from "./+types/overview";
Merge branch 'site-fixes' into fast-push7import { DeployLink, host, StatusDot } from "../../components/deploy";
Commit pages credit co-authors instead of printing their trailers8import { Avatar, ButtonLink, CopyLine, Pill, TimeAgo } from "../../components/ui";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look9import { UsageCard } from "../../components/usage-card";
10import { PullIcon } from "../../components/work-icons";
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent11import { openedBy } from "../../lib/opened-by";
Merge branch 'worktree-agent-aaf03bdceac799c89'12import { cloneUrl, useAddresses } from "../../lib/addresses";
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9713import { kindLabel, libraryPackages, packageLine, packagePath, primaryLink } from "../../lib/project-kind";
Navigation: the workspace's own page with tabs, pins and recent in the sidebar14import { PinButton } from "../../components/pin-button";
15import { sortProjects } from "../../lib/project-list";
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)16import { usageFor } from "../../lib/workspace-usage.server";
17import { deployments, identity, packages, projects as projectsApi, work } from "../../lib/services.server";
Commit pages credit co-authors instead of printing their trailers18import { getViewer, roleIn } from "../../lib/session.server";
Navigation: the workspace's own page with tabs, pins and recent in the sidebar19import { workspaceProjects } from "../../lib/workspace-projects.server";
Commit pages credit co-authors instead of printing their trailers20
Navigation: the workspace's own page with tabs, pins and recent in the sidebar21/** Projects whose open issues and pull requests are counted: the most active. */
Commit pages credit co-authors instead of printing their trailers22const MAX_COUNTED = 30;
Navigation: the workspace's own page with tabs, pins and recent in the sidebar23/** Projects shown under Recently active, after the pins. */
24const MAX_ACTIVE = 6;
Projects: what a workspace builds and runs, first on every page25/** Projects whose pull requests are listed under "In progress". */
Commit pages credit co-authors instead of printing their trailers26const MAX_LISTED = 8;
27const MAX_PULLS = 8;
28const MAX_FACES = 8;
29export async function loader({ params, context }: Route.LoaderArgs) {
30 const viewer = getViewer(context);
31 const slug = params.owner.toLowerCase();
32 const role = roleIn(viewer, slug);
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.33 // A member's way in is Today; this page
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)34 // is the workspace as a visitor sees it.
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.35 if (role) throw redirect(`/${slug}/-/today`);
Navigation: the workspace's own page with tabs, pins and recent in the sidebar36 const [listed, members, deploys, usage, published, shortcuts] = await Promise.all([
37 // The listing the sidebar and the Projects tab share.
38 workspaceProjects(slug, viewer),
Commit pages credit co-authors instead of printing their trailers39 role ? identity.listMembers(slug, viewer) : null,
Projects: what a workspace builds and runs, first on every page40 role ? deployments.overview(slug, viewer) : null,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look41 role ? usageFor(slug, viewer) : null,
A project is an app or a library: libraries show their package and how to ship a release, not production42 // One listing for every card: a library's card shows its package where an app's shows production.
43 packages.list(slug, viewer).catch(() => null),
Navigation: the workspace's own page with tabs, pins and recent in the sidebar44 // The viewer's pins lead the page, as they lead the sidebar.
45 role ? projectsApi.shortcuts(slug, viewer).catch(() => null) : null,
Commit pages credit co-authors instead of printing their trailers46 ]);
Projects: what a workspace builds and runs, first on every page47 const projects = listed.ok ? listed.value : [];
Navigation: the workspace's own page with tabs, pins and recent in the sidebar48 const pinnedIds = new Set((shortcuts?.pinned ?? []).map((project) => project.id));
49 const byId = new Map(projects.map((project) => [project.id, project]));
50 const pinned = (shortcuts?.pinned ?? []).map((pin) => byId.get(pin.id)).filter((project): project is Project => project != null);
51 // However many there are: the most active first, archived ones last.
52 const byActivity = sortProjects(
53 projects.map((project) => ({ ...project, pushedAt: project.pushedAt ?? null, activity: project.activity ?? 0, deploying: false })),
54 "active",
55 )
56 .sort((a, b) => Number(a.archived) - Number(b.archived))
57 .map((listedProject) => byId.get(listedProject.id)!);
58 const active = byActivity.filter((project) => !pinnedIds.has(project.id)).slice(0, MAX_ACTIVE);
Commit pages credit co-authors instead of printing their trailers59
Projects: what a workspace builds and runs, first on every page60 const pathOf = (project: Project) =>
61 project.source.kind === "hosted" ? project.source.repo : { namespace: project.workspace, name: project.slug };
Navigation: the workspace's own page with tabs, pins and recent in the sidebar62 const counted = [...new Map([...pinned, ...active, ...byActivity].map((project) => [project.id, project])).values()].slice(0, Math.max(MAX_COUNTED, pinned.length + active.length));
Projects: what a workspace builds and runs, first on every page63 const counts = await Promise.all(counted.map((project) => work.counts(pathOf(project), viewer)));
Commit pages credit co-authors instead of printing their trailers64 const open: Record<string, { issues: number; pulls: number }> = {};
Projects: what a workspace builds and runs, first on every page65 counted.forEach((project, i) => {
Commit pages credit co-authors instead of printing their trailers66 const result = counts[i];
Projects: what a workspace builds and runs, first on every page67 if (result.ok) open[project.id] = result.value;
Commit pages credit co-authors instead of printing their trailers68 });
69
Projects: what a workspace builds and runs, first on every page70 const busy = counted.filter((project) => (open[project.id]?.pulls ?? 0) > 0).slice(0, MAX_LISTED);
71 const lists = await Promise.all(busy.map((project) => work.listPulls(pathOf(project), viewer, "open")));
Commit pages credit co-authors instead of printing their trailers72 const pulls = lists
Projects: what a workspace builds and runs, first on every page73 .flatMap((result, i) => (result.ok ? result.value.map((pull) => ({ pull, project: busy[i] })) : []))
Commit pages credit co-authors instead of printing their trailers74 .sort((a, b) => b.pull.updatedAt.localeCompare(a.pull.updatedAt))
75 .slice(0, MAX_PULLS);
76
Projects: what a workspace builds and runs, first on every page77 const bySlug: Record<string, ProjectDeploys> = {};
78 for (const entry of deploys?.ok ? deploys.value : []) bySlug[entry.slug] = entry;
79
A project is an app or a library: libraries show their package and how to ship a release, not production80 const byRepo = new Map<string, PackageSummary[]>();
81 for (const pkg of published?.ok ? published.value : []) {
82 if (pkg.repo) byRepo.set(pkg.repo.id, [...(byRepo.get(pkg.repo.id) ?? []), pkg]);
83 }
84 const packageOf: Record<string, PackageSummary> = {};
Navigation: the workspace's own page with tabs, pins and recent in the sidebar85 for (const project of [...pinned, ...active]) {
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9786 if ((project.kind !== "library" && project.kind !== "tool") || project.source.kind !== "hosted") continue;
A project is an app or a library: libraries show their package and how to ship a release, not production87 const first = libraryPackages(byRepo.get(project.source.repoId) ?? [])[0];
88 if (first) packageOf[project.id] = first;
89 }
90
Projects: what a workspace builds and runs, first on every page91 return {
92 slug,
93 role,
Navigation: the workspace's own page with tabs, pins and recent in the sidebar94 total: projects.length,
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9795 elsewhere: projects.filter((project) => project.runs === "elsewhere" && project.productionUrl).length,
Navigation: the workspace's own page with tabs, pins and recent in the sidebar96 pinned,
97 active,
Projects: what a workspace builds and runs, first on every page98 open,
99 deploys: bySlug,
A project is an app or a library: libraries show their package and how to ship a release, not production100 packageOf,
Projects: what a workspace builds and runs, first on every page101 pulls: pulls.map(({ pull, project }) => ({ pull, slug: project.slug, name: project.name })),
102 members: members?.ok ? members.value : null,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look103 usage,
Projects: what a workspace builds and runs, first on every page104 };
Commit pages credit co-authors instead of printing their trailers105}
106
107function Stat({ value, label }: { value: number; label: string }) {
108 return (
109 <div className="rounded-xl border border-line bg-surface px-4 py-3">
110 <p className="text-xl font-semibold tabular-nums tracking-tight">{value}</p>
111 <p className="text-xs text-muted">{label}</p>
112 </div>
113 );
114}
115
Projects: what a workspace builds and runs, first on every page116/** What a member sees in a workspace with no projects yet. */
Commit pages credit co-authors instead of printing their trailers117function GetStarted({ slug }: { slug: string }) {
Merge branch 'worktree-agent-aaf03bdceac799c89'118 const addresses = useAddresses();
Commit pages credit co-authors instead of printing their trailers119 return (
Projects: what a workspace builds and runs, first on every page120 <div className="rounded-2xl border border-line bg-surface p-8">
121 <span className="flex size-10 items-center justify-center rounded-xl bg-accent/10 text-accent ring-1 ring-accent/30">
122 <Box size={18} />
123 </span>
124 <h2 className="mt-4 text-lg font-semibold tracking-tight">Create your first project</h2>
Commit pages credit co-authors instead of printing their trailers125 <p className="mt-1.5 max-w-xl text-sm text-muted">
Projects: what a workspace builds and runs, first on every page126 A project is what you build and run: its code, the issues that say what should change, the agents and
127 people who change it, and where it is deployed.
Commit pages credit co-authors instead of printing their trailers128 </p>
Projects: what a workspace builds and runs, first on every page129 <div className="mt-6 flex flex-wrap gap-3">
130 <ButtonLink to={`/new?workspace=${slug}`} variant="accent">
131 <Plus size={14} />
132 New project
133 </ButtonLink>
Commit pages credit co-authors instead of printing their trailers134 </div>
Projects: what a workspace builds and runs, first on every page135 <p className="mt-6 text-sm text-muted">Or push code you already have, and it becomes a project:</p>
Commit pages credit co-authors instead of printing their trailers136 <div className="mt-2 max-w-xl">
Merge branch 'worktree-agent-aaf03bdceac799c89'137 <CopyLine prompt text={`git push ${cloneUrl(addresses, `${slug}/my-project`)} main`} />
Commit pages credit co-authors instead of printing their trailers138 </div>
139 </div>
140 );
141}
142
Projects: what a workspace builds and runs, first on every page143function ProjectCard({
144 project,
145 open,
146 deploys,
A project is an app or a library: libraries show their package and how to ship a release, not production147 pkg,
Projects: what a workspace builds and runs, first on every page148 member,
Navigation: the workspace's own page with tabs, pins and recent in the sidebar149 pinned,
Projects: what a workspace builds and runs, first on every page150}: {
151 project: Project;
152 open: { issues: number; pulls: number } | undefined;
153 deploys: ProjectDeploys | undefined;
A project is an app or a library: libraries show their package and how to ship a release, not production154 /** For a library, the package it publishes. */
155 pkg: PackageSummary | undefined;
Projects: what a workspace builds and runs, first on every page156 member: boolean;
Navigation: the workspace's own page with tabs, pins and recent in the sidebar157 /** Whether the viewer pinned it; null when they cannot pin. */
158 pinned: boolean | null;
Projects: what a workspace builds and runs, first on every page159}) {
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97160 // A library or a tool shows the package it publishes.
161 const library = project.kind === "library" || project.kind === "tool";
Projects: what a workspace builds and runs, first on every page162 const base = `/${project.workspace}/${project.slug}`;
163 const production = deploys?.production ?? null;
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97164 // Production where g1t serves it or elsewhere, else its homepage or docs.
165 const link = primaryLink(project, production?.url);
Projects: what a workspace builds and runs, first on every page166 const latest = deploys?.latest ?? null;
167 const source = project.source.kind === "hosted" ? project.source : null;
168 return (
169 <li className="group relative flex flex-col rounded-2xl border border-line bg-surface p-5 transition-colors hover:border-line-strong">
170 <div className="flex items-start gap-3">
171 <span className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-raised text-muted ring-1 ring-line">
172 {project.private ? <Lock size={15} /> : <Box size={15} />}
173 </span>
174 <div className="min-w-0 grow">
175 <Link to={base} prefetch="intent" className="font-medium after:absolute after:inset-0 hover:underline">
176 {project.name}
177 </Link>
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97178 {link && !(library && pkg && !production) ? (
Merge branch 'site-fixes' into fast-push179 <DeployLink
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97180 href={link}
Projects: what a workspace builds and runs, first on every page181 className="relative z-10 mt-0.5 flex items-center gap-1 truncate font-mono text-xs text-muted hover:text-accent"
182 >
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97183 {host(link)}
Projects: what a workspace builds and runs, first on every page184 <ArrowUpRight size={11} className="shrink-0" />
Merge branch 'site-fixes' into fast-push185 </DeployLink>
A project is an app or a library: libraries show their package and how to ship a release, not production186 ) : library && pkg ? (
187 <Link
188 to={packagePath(pkg)}
189 className="relative z-10 mt-0.5 block truncate font-mono text-xs text-muted hover:text-accent"
190 >
191 {packageLine(pkg)}
192 </Link>
Projects: what a workspace builds and runs, first on every page193 ) : (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look194 <p className="mt-0.5 truncate text-xs text-faint">
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97195 {project.description ??
196 (library
197 ? "Not published yet"
198 : project.runs === "g1t"
199 ? deploys?.enabled
200 ? "Not deployed yet"
201 : "Deployments are off"
202 : kindLabel(project))}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look203 </p>
Projects: what a workspace builds and runs, first on every page204 )}
205 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look206 {(project.private || project.archived) && (
207 <span className="flex shrink-0 gap-1.5">
208 {project.private && <Pill>private</Pill>}
209 {project.archived && <Pill>archived</Pill>}
210 </span>
211 )}
Navigation: the workspace's own page with tabs, pins and recent in the sidebar212 {pinned != null && (
213 <PinButton workspace={project.workspace} slug={project.slug} name={project.name} pinned={pinned} compact className="-mt-1.5 -mr-2" />
214 )}
Projects: what a workspace builds and runs, first on every page215 </div>
216
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97217 {project.description && (link || (library && pkg)) && <p className="mt-3 line-clamp-2 text-sm text-muted">{project.description}</p>}
Projects: what a workspace builds and runs, first on every page218
219 <div className="mt-auto pt-5">
220 {member && latest && (
221 <p className="mb-3 flex items-center gap-2 text-xs text-muted">
222 <StatusDot status={latest.status} />
223 <span className="truncate">
224 {latest.kind === "production" ? "Production" : `Preview of ${latest.branch}`}
225 </span>
226 <span className="ml-auto shrink-0 text-faint">
227 <TimeAgo at={latest.createdAt} />
228 </span>
229 </p>
230 )}
231 <div className="flex items-center gap-4 border-t border-line pt-3 text-xs text-muted">
232 {source && (
233 <span className="flex min-w-0 items-center gap-1.5 font-mono">
234 <GitBranch size={12} className="shrink-0 text-faint" />
235 <span className="truncate">{source.defaultBranch}</span>
236 </span>
237 )}
238 <span className="flex items-center gap-1">
239 <CircleDot size={12} className="text-faint" />
240 {open?.issues ?? 0}
241 </span>
242 <span className="flex items-center gap-1">
243 <GitPullRequest size={12} className="text-faint" />
244 {open?.pulls ?? 0}
245 </span>
246 {member && deploys?.enabled && (deploys.previews ?? 0) > 0 && (
Merge branch 'worktree-agent-ad7c6d88d93adc817'247 <span className="ml-auto flex items-center gap-1 text-success">
Projects: what a workspace builds and runs, first on every page248 <Rocket size={12} />
249 {deploys.previews} {deploys.previews === 1 ? "preview" : "previews"}
250 </span>
251 )}
252 </div>
253 </div>
254 </li>
255 );
256}
257
Commit pages credit co-authors instead of printing their trailers258export default function WorkspaceOverview({ loaderData }: Route.ComponentProps) {
Navigation: the workspace's own page with tabs, pins and recent in the sidebar259 const { slug, role, total, pinned, active, open, deploys, pulls, members, usage } = loaderData;
260 const card = (project: Project, isPinned: boolean) => (
261 <ProjectCard
262 key={project.id}
263 project={project}
264 open={open[project.id]}
265 deploys={deploys[project.slug]}
266 pkg={loaderData.packageOf[project.id]}
267 member={role != null}
268 pinned={role ? isPinned : null}
269 />
270 );
Commit pages credit co-authors instead of printing their trailers271 const totals = Object.values(open).reduce(
Projects: what a workspace builds and runs, first on every page272 (sum, counts) => ({ issues: sum.issues + counts.issues, pulls: sum.pulls + counts.pulls }),
Commit pages credit co-authors instead of printing their trailers273 { issues: 0, pulls: 0 },
274 );
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97275 // Live on g1t, or deployed elsewhere with an address.
276 const liveApps =
277 Object.values(deploys).filter((entry) => entry.production).length +
278 loaderData.elsewhere;
Commit pages credit co-authors instead of printing their trailers279 return (
280 <div className="grid gap-10 lg:grid-cols-[1fr_18rem]">
281 <div className="min-w-0 space-y-10">
Navigation: the workspace's own page with tabs, pins and recent in the sidebar282 {total === 0 && role ? (
Commit pages credit co-authors instead of printing their trailers283 <GetStarted slug={slug} />
284 ) : (
285 <>
Projects: what a workspace builds and runs, first on every page286 <div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
Navigation: the workspace's own page with tabs, pins and recent in the sidebar287 <Stat value={total} label={total === 1 ? "Project" : "Projects"} />
Projects: what a workspace builds and runs, first on every page288 <Stat value={liveApps} label="In production" />
Commit pages credit co-authors instead of printing their trailers289 <Stat value={totals.issues} label="Open issues" />
290 <Stat value={totals.pulls} label="Pull requests in progress" />
291 </div>
292
Navigation: the workspace's own page with tabs, pins and recent in the sidebar293 {role && (
294 <section aria-labelledby="pinned">
295 <h2 id="pinned" className="text-sm font-medium text-muted">
296 Pinned
297 </h2>
298 {pinned.length > 0 ? (
299 <ul className="mt-3 grid gap-4 sm:grid-cols-2">{pinned.map((project) => card(project, true))}</ul>
300 ) : (
301 <p className="mt-3 flex items-center gap-2 rounded-xl border border-dashed border-line px-4 py-3 text-sm text-muted">
302 <Pin size={14} className="shrink-0 text-faint" />
303 Pin the projects you use most, from their page or from Projects, to keep them here and in your sidebar.
304 </p>
Projects: what a workspace builds and runs, first on every page305 )}
Navigation: the workspace's own page with tabs, pins and recent in the sidebar306 </section>
307 )}
308
309 {active.length > 0 && (
310 <section aria-labelledby="active">
311 <div className="flex items-center justify-between">
312 <h2 id="active" className="text-sm font-medium text-muted">
313 {role ? "Recently active" : "Projects"}
314 </h2>
315 {role && (
316 <Link to={`/new?workspace=${slug}`} className="inline-flex items-center gap-1 text-xs text-muted hover:text-fg">
317 <Plus size={13} />
318 New project
319 </Link>
320 )}
321 </div>
322 <ul className="mt-3 grid gap-4 sm:grid-cols-2">{active.map((project) => card(project, false))}</ul>
323 </section>
324 )}
325
326 <Link
327 to={`/${slug}/-/projects`}
328 prefetch="intent"
329 className="flex items-center justify-between rounded-xl border border-line bg-surface px-4 py-3 text-sm transition-colors hover:border-line-strong"
330 >
331 <span>
332 All projects <span className="tabular-nums text-muted">({total})</span>
333 </span>
334 <span className="flex items-center gap-1 text-xs text-muted">
335 Search, filter and sort
336 <ArrowRight size={13} />
337 </span>
338 </Link>
Projects: what a workspace builds and runs, first on every page339
Commit pages credit co-authors instead of printing their trailers340 {pulls.length > 0 && (
341 <section>
342 <h2 className="text-sm font-medium text-muted">In progress</h2>
343 <ul className="mt-3 divide-y divide-line overflow-hidden rounded-xl border border-line bg-surface">
Projects: what a workspace builds and runs, first on every page344 {pulls.map(({ pull, slug: project, name }) => (
Commit pages credit co-authors instead of printing their trailers345 <li key={pull.id}>
346 <Link
347 prefetch="intent"
Projects: what a workspace builds and runs, first on every page348 to={`/${slug}/${project}/pull/${pull.number}`}
Commit pages credit co-authors instead of printing their trailers349 className="flex items-center gap-3 px-4 py-3 transition-colors hover:bg-raised"
350 >
351 <PullIcon status={pull.status} />
352 <span className="min-w-0 grow">
353 <span className="block truncate font-medium">{pull.title}</span>
Projects: what a workspace builds and runs, first on every page354 <span className="text-xs text-muted">
355 {name} <span className="font-mono">#{pull.number}</span>
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent356 {pull.issue != null && ` · for #${pull.issue}`} · {openedBy(pull).name}
Commit pages credit co-authors instead of printing their trailers357 </span>
358 </span>
359 <span className="hidden shrink-0 text-xs text-muted sm:block">
360 {pull.status === "draft" ? "Being worked on" : "Ready for review"}
361 </span>
362 <span className="w-14 shrink-0 text-right text-xs text-faint">
363 <TimeAgo at={pull.updatedAt} />
364 </span>
365 </Link>
366 </li>
367 ))}
368 </ul>
369 </section>
370 )}
371 </>
372 )}
373 </div>
374
375 <aside className="space-y-6">
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look376 {role && usage && <UsageCard slug={slug} glance={usage} owner={role === "owner"} />}
377
Commit pages credit co-authors instead of printing their trailers378 {members ? (
379 <section>
380 <div className="flex items-center justify-between">
Projects: what a workspace builds and runs, first on every page381 <h2 className="text-sm font-medium">Members</h2>
Commit pages credit co-authors instead of printing their trailers382 <Link to={`/${slug}/-/people`} className="text-xs text-muted hover:text-fg">
383 {role === "owner" ? "Manage" : "See all"}
384 </Link>
385 </div>
386 <ul className="mt-3 space-y-1.5">
387 {members.slice(0, MAX_FACES).map((member) => (
388 <li key={member.username} className="flex items-center gap-2 text-sm">
389 <Avatar name={member.username} />
390 <span className="grow truncate font-mono">{member.username}</span>
391 {member.role === "owner" && <Pill>owner</Pill>}
392 </li>
393 ))}
394 </ul>
Projects: what a workspace builds and runs, first on every page395 {members.length > MAX_FACES && <p className="mt-2 text-xs text-faint">and {members.length - MAX_FACES} more</p>}
Commit pages credit co-authors instead of printing their trailers396 </section>
397 ) : (
398 <p className="text-sm text-muted">
Projects: what a workspace builds and runs, first on every page399 Members of this workspace build its projects. Anyone can open an issue or a pull request on a public one.
Commit pages credit co-authors instead of printing their trailers400 </p>
401 )}
402
403 {role && (
404 <section className="rounded-xl border border-line bg-surface p-5">
405 <h2 className="flex items-center gap-2 font-medium">
Projects: what a workspace builds and runs, first on every page406 <Rocket size={15} className="text-faint" />
407 Deploy on g1t.page
408 </h2>
409 <p className="mt-1.5 text-sm text-muted">
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look410 Off until you turn them on for a project, under its Settings. Then production builds from its default
411 branch, with a live preview for every pull request. Apps cost nothing while no one visits.
Projects: what a workspace builds and runs, first on every page412 </p>
413 <Link
414 prefetch="intent"
415 to={`/${slug}/-/billing`}
416 className="mt-3 inline-flex items-center gap-1 text-sm text-accent hover:underline"
417 >
418 Billing and plans <ArrowRight size={13} />
419 </Link>
420 </section>
421 )}
422
423 {role && (
424 <section className="rounded-xl border border-line bg-surface p-5">
425 <h2 className="flex items-center gap-2 font-medium">
Commit pages credit co-authors instead of printing their trailers426 <KeyRound size={15} className="text-faint" />
427 Automate without a service account
428 </h2>
429 <p className="mt-1.5 text-sm text-muted">
Projects: what a workspace builds and runs, first on every page430 A workspace has access tokens of its own for CI, integrations and agents. They act as the workspace,
431 not as a person.
Commit pages credit co-authors instead of printing their trailers432 </p>
433 <Link
434 prefetch="intent"
435 to={`/${slug}/-/tokens`}
436 className="mt-3 inline-flex items-center gap-1 text-sm text-accent hover:underline"
437 >
438 Access tokens <ArrowRight size={13} />
439 </Link>
440 </section>
441 )}
442 </aside>
443 </div>
444 );
445}

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