g1t/apps/web/app/routes/repo/overview.tsx

1,218 lines54,968 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.

Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1import {
2 Activity as ActivityIcon,
3 ArrowDownLeft,
4 ArrowRight,
5 ArrowUpRight,
6 Bot,
7 Brain,
8 Code2,
9 GitBranch,
10 GitCommitHorizontal,
11 GitMerge,
12 Hand,
13 HeartPulse,
14 Loader2,
15 Lock,
16 Network,
A project is an app or a library: libraries show their package and how to ship a release, not production17 Package as PackageGlyph,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API18 Pin,
19 Rocket,
20 RotateCw,
21} from "lucide-react";
Fast pages, required checks on the branch, self-hosted runners, honest incidents22import { type ReactNode, Suspense } from "react";
23import { Await, Form, Link, useNavigation } from "react-router";
Projects: what a workspace builds and runs, first on every page24
A project is an app or a library: libraries show their package and how to ship a release, not production25import {
26 type AgentRun,
27 type G1tEvent,
28 type Memory,
29 type PackageSummary,
30 type Project,
31 type Pull,
32 RUN_KIND_LABEL,
33 isActiveRun,
34 workOwner,
35} from "@g1t/contracts";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API36
Projects: what a workspace builds and runs, first on every page37import type { Route } from "./+types/overview";
38import { host, StatusDot } from "../../components/deploy";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API39import { CheckBadge } from "../../components/checks";
40import { Elapsed, formatCost, useLiveRefresh } from "../../components/agents";
41import { ActivityFeed, DeployStrip, Meter, NeedsList, Panel, Quiet, Unavailable, percent } from "../../components/mission";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look42import { type ActiveBranch, ActiveBranches } from "../../components/branches";
43import { ProductionChecklist } from "../../components/checklist";
A project is an app or a library: libraries show their package and how to ship a release, not production44import { PackageIcon } from "../../components/package-icon";
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily45import { Skeleton, SkeletonRows } from "../../components/ui/skeleton";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look46import { ProductionShot } from "../../components/production-shot";
47import { GithubLinkStrip } from "../../components/github";
48import { githubApp } from "../../lib/github.server";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API49import { Avatar, Button, ButtonLink, CopyLine, TimeAgo } from "../../components/ui";
50import { ChangeSize } from "../../components/work";
51import {
52 type ActivityItem,
53 type Need,
54 type PipelineStage,
55 PIPELINE,
56 TIME,
57 ageBuckets,
58 eventItem,
59 firstPassRate,
60 groupActivity,
61 passRate,
62 pipelineStage,
63 queuedNumbers,
64 rankNeeds,
65 stuckMinutes,
66} from "../../lib/mission";
A project is an app or a library: libraries show their package and how to ship a release, not production67import { agentWasAssigned, hasInstructions, productionChecklist, releaseChecklist } from "../../lib/checklist";
68import { ECOSYSTEM_LABEL, installCommands } from "../../lib/packages";
69import { PUBLISH_GUIDES, hasRelease, libraryPackages, packageName, packagePath, publishGuide } from "../../lib/project-kind";
70import { actions, agents, deployments, events as eventLog, packages, projects, repos, work } from "../../lib/services.server";
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent71import { madeByG1t } from "../../lib/opened-by";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look72import { assertSameOrigin, getViewer, requireUser } from "../../lib/session.server";
Fast pages, required checks on the branch, self-hosted runners, honest incidents73import { accessTo, countsFor, refusal, repoFor } from "../../lib/access.server";
Production thumbnails follow each deploy, not each commit, and old ones are cleared daily74import { shotVersion } from "./production-screenshot";
Branches and Tags pages, each file's last commit, and the branch menu on files75import { readBranches } from "../../lib/branches.server";
Projects: what a workspace builds and runs, first on every page76
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API77const MAX_LANDED = 6;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look78/** Branches read for the Active branches list, and shown. */
79const BRANCHES_READ = 10;
80const BRANCHES_SHOWN = 5;
A project is an app or a library: libraries show their package and how to ship a release, not production81/** A library's packages shown on its overview; the rest are a link away. */
82const PACKAGES_SHOWN = 3;
Projects: what a workspace builds and runs, first on every page83
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily84/**
85 * The overview streams: the layout's header and tabs (one repository
86 * lookup, one project) go out at once with a skeleton below them, and
87 * everything here, seventeen calls across six services, follows in the
88 * same response as it settles. Crawlers wait for all of it
89 * (entry.server.tsx). Before, the first byte waited on the slowest of them.
90 */
91export function loader({ params, context }: Route.LoaderArgs) {
92 return { overview: overviewData({ params, context }) };
93}
94
95async function overviewData({ params, context }: Pick<Route.LoaderArgs, "params" | "context">) {
Projects: what a workspace builds and runs, first on every page96 const viewer = getViewer(context);
97 const path = { namespace: params.owner, name: params.repo };
98 const ref = { workspace: params.owner, slug: params.repo };
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API99 const now = Date.now();
100 // A section whose service fails shows its own empty state; the page stays up.
101 const soft = <T,>(promise: Promise<T> | null): Promise<T | null> =>
102 promise ? promise.catch((error) => (console.warn("overview:", error), null)) : Promise.resolve(null);
Fast pages, required checks on the branch, self-hosted runners, honest incidents103 // People with a role of their own here see its running parts: deployments,
104 // memory, setup; everyone who can read it sees the rest. Not awaited: only
105 // those sections wait on the repository lookup, which the layout makes in
106 // this request too.
107 const accessP = accessTo(context, params);
108 const memberP = accessP.then((access) => access.insider);
109 const forMembers = <T,>(start: () => Promise<T>): Promise<T | null> =>
110 memberP.then((member) => (member ? soft(start()) : null));
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look111 const repoP = soft(repoFor(context, params));
A project is an app or a library: libraries show their package and how to ship a release, not production112 const projectP = soft(projects.get(params.owner, params.repo, viewer));
113 // A library shows its packages where an app shows production, and its
114 // checklist counts a workflow and a release instead of deploys.
115 const libraryRepoP = Promise.all([projectP, repoP]).then(([project, repo]) =>
116 project?.ok && project.value.kind === "library" && repo?.ok ? repo.value : null,
117 );
118 const packagesP = libraryRepoP.then((repo) => (repo ? soft(packages.list(params.owner, viewer, { repoId: repo.id })) : null));
119 const workflowsP = libraryRepoP.then((repo) => (repo ? forMembers(() => actions.workflows(path, viewer)) : null));
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API120 const eventsP = repoP.then((repo) => (repo?.ok ? soft(eventLog.list({ repoId: repo.value.id, limit: 150 })) : null));
121 const minePullsP = repoP.then((repo) =>
122 repo?.ok && viewer
123 ? soft(work.listActivePulls(viewer)).then((list) => (list ?? []).filter((item) => item.pull.repoId === repo.value.id))
124 : null,
125 );
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look126 const openP = soft(work.listPulls(path, viewer, "open"));
127 // Where its code came from on GitHub, for members.
Fast pages, required checks on the branch, self-hosted runners, honest incidents128 const githubP = repoP.then((repo) => (repo?.ok ? forMembers(() => githubApp.link(repo.value.id)) : null));
129 const listP = forMembers(() => deployments.list(ref, viewer));
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look130 // Active branches: the newest few besides the default, with how far each
131 // has moved. Branches with an open pull request are read first.
132 const branchesP = Promise.all([repoP, soft(repos.branches(path, viewer)), openP, listP]).then(
133 async ([repo, branchList, pulls, deploys]): Promise<{ main: string; total: number; shown: ActiveBranch[] } | null> => {
134 if (!repo?.ok || !branchList?.ok) return null;
135 const main = repo.value.defaultBranch;
Branches and Tags pages, each file's last commit, and the branch menu on files136 if (branchList.value.every((branch) => branch.name === main)) return { main, total: 0, shown: [] };
137 const read = await readBranches(
138 path,
139 viewer,
140 {
141 defaultBranch: main,
142 branches: branchList.value,
143 pulls: pulls?.ok ? pulls.value : [],
144 previews: deploys?.ok ? deploys.value.live.filter((app) => app.kind === "preview") : [],
145 },
146 BRANCHES_READ,
147 );
148 return { main, total: read.total, shown: read.shown.slice(0, BRANCHES_SHOWN) };
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look149 },
150 );
Fast pages, required checks on the branch, self-hosted runners, honest incidents151 // Active branches read several logs each: streamed, so the rest shows first.
152 const branches = branchesP.catch(() => null);
A project is an app or a library: libraries show their package and how to ship a release, not production153 const [{ insider: member, can }, project, settings, list, open, closed, log, counts, deps, runs, queue, issues, memories, recent, mine, domains, root, packageList, workflows] = await Promise.all([
Fast pages, required checks on the branch, self-hosted runners, honest incidents154 accessP,
A project is an app or a library: libraries show their package and how to ship a release, not production155 projectP,
Fast pages, required checks on the branch, self-hosted runners, honest incidents156 forMembers(() => deployments.settings(ref, viewer)),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look157 listP,
158 openP,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API159 soft(work.listPulls(path, viewer, "closed")),
160 soft(repos.log(path, viewer, null, 1)),
Fast pages, required checks on the branch, self-hosted runners, honest incidents161 soft(countsFor(context, params)),
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API162 soft(projects.dependencies(params.owner, params.repo, viewer)),
163 soft(agents.listRuns(viewer, { repo: path, limit: 60 })),
164 soft(work.queue(path, viewer)),
165 soft(work.listIssues(path, viewer, { state: "open" })),
Fast pages, required checks on the branch, self-hosted runners, honest incidents166 forMembers(() => agents.listMemories(viewer, params.owner, path)),
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API167 eventsP,
168 minePullsP,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look169 // For the checklist, which only members see.
Fast pages, required checks on the branch, self-hosted runners, honest incidents170 forMembers(() => deployments.domains(ref, viewer)),
171 forMembers(() => repos.tree(path, viewer, null, "")),
A project is an app or a library: libraries show their package and how to ship a release, not production172 packagesP,
173 workflowsP,
Projects: what a workspace builds and runs, first on every page174 ]);
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API175 const ok = <T,>(result: { ok: true; value: T } | { ok: false } | null): T | null => (result?.ok ? result.value : null);
176
177 const openPulls = ok(open) ?? [];
178 const runList = ok(runs) ?? [];
179 const live = runList.filter((run) => isActiveRun(run.status));
180 const builds = ok(list)?.deployments ?? [];
181 const landed = (ok(closed) ?? [])
182 .filter((pull) => pull.status === "merged")
183 .sort((a, b) => Date.parse(b.mergedAt ?? b.updatedAt) - Date.parse(a.mergedAt ?? a.updatedAt));
184 const queued = queuedNumbers(ok(queue)?.active ?? []);
185 const runOn = new Map<number, AgentRun>();
186 for (const run of live) if (run.number != null && !runOn.has(run.number)) runOn.set(run.number, run);
187 const lifecycleOf = new Map((mine ?? []).map((item) => [item.pull.number, item.lifecycle]));
188
189 // --- The pipeline -----------------------------------------------------------
190 type Card = {
191 number: number;
192 title: string;
193 agent: string;
194 status: Pull["status"];
195 checkStatus: Pull["checkStatus"];
196 step: string | null;
197 runKind: string | null;
198 runStarted: string | null;
199 needsYou: boolean;
200 at: string;
201 };
202 const columns: Record<PipelineStage, Card[]> = { working: [], checking: [], reviewing: [], queue: [], landed: [] };
203 for (const pull of openPulls.slice(0, 40)) {
204 const run = runOn.get(pull.number);
Fast pages, required checks on the branch, self-hosted runners, honest incidents205 columns[pipelineStage(pull, run, queued, lifecycleOf.get(pull.number))].push({
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API206 number: pull.number,
207 title: pull.title,
208 agent: pull.agent,
209 status: pull.status,
210 checkStatus: pull.checkStatus,
211 step: run?.step ?? null,
212 runKind: run ? RUN_KIND_LABEL[run.kind] : null,
213 runStarted: run ? (run.startedAt ?? run.createdAt) : null,
214 needsYou: lifecycleOf.get(pull.number)?.stage === "needs_you",
215 at: pull.updatedAt,
216 });
217 }
218 for (const pull of landed.filter((p) => now - Date.parse(p.mergedAt ?? p.updatedAt) < 3 * TIME.DAY).slice(0, 5)) {
219 columns.landed.push({
220 number: pull.number,
221 title: pull.title,
222 agent: pull.agent,
223 status: pull.status,
224 checkStatus: pull.checkStatus,
225 step: null,
226 runKind: null,
227 runStarted: null,
228 needsYou: false,
229 at: pull.mergedAt ?? pull.updatedAt,
230 });
231 }
232
233 // --- Needs you ---------------------------------------------------------------
234 const base = `/${params.owner}/${params.repo}`;
235 const needs: Need[] = [];
236 const latestProduction = builds.find((build) => build.kind === "production") ?? null;
237 if (latestProduction?.status === "failed") {
238 needs.push({
239 key: "deploy",
240 kind: "deploy",
241 title: "The production build failed",
242 detail: latestProduction.error ?? "Production still serves the build before it.",
243 to: `${base}/deployments/${latestProduction.id}`,
244 action: "See the build",
245 at: Date.parse(latestProduction.finishedAt ?? latestProduction.createdAt),
246 where: latestProduction.commit.slice(0, 7),
247 });
248 }
249 const me = viewer?.username.toLowerCase();
250 for (const pull of openPulls) {
251 const to = `${base}/pull/${pull.number}`;
252 const where = `#${pull.number}`;
253 const lifecycle = lifecycleOf.get(pull.number);
254 if (lifecycle?.stage === "needs_you") {
255 const conflict = /conflict/i.test(lifecycle.detail);
256 needs.push({ key: `pull:${pull.number}`, kind: conflict ? "conflict" : "stalled", title: pull.title, detail: lifecycle.detail, to, action: conflict ? "Resolve" : "Decide", at: Date.parse(pull.updatedAt), where });
257 } else if (lifecycle?.stage === "ready") {
258 needs.push({ key: `pull:${pull.number}`, kind: "ready", title: pull.title, detail: "Ready to land when you merge it.", to, action: "Merge", at: Date.parse(pull.updatedAt), where });
259 }
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights260 // Never your own, nor one g1t made for you; whoever it is for asked.
261 const owner = workOwner(pull).username;
262 if (me && pull.status === "open" && owner.toLowerCase() !== me && pull.reviewers.some((name) => name.toLowerCase() === me)) {
263 needs.push({ key: `review:${pull.number}`, kind: "review", title: pull.title, detail: `${owner} asked for your review.`, to, action: "Review", at: Date.parse(pull.updatedAt), where });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API264 }
265 if (member && pull.status === "open" && pull.checkStatus === "failed" && !runOn.has(pull.number) && !lifecycle) {
Fast pages, required checks on the branch, self-hosted runners, honest incidents266 needs.push({ key: `checks:${pull.number}`, kind: "checks", title: pull.title, detail: "It failed in the merge queue and no agent is fixing it.", to, action: "See checks", at: Date.parse(pull.updatedAt), where });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API267 }
268 }
269 for (const run of live) {
270 const minutes = stuckMinutes(run, now);
271 if (minutes == null) continue;
272 needs.push({
273 key: `run:${run.id}`,
274 kind: "stuck",
275 title: run.title ?? `${run.agent}'s run`,
276 detail: `${run.agent} has reported nothing for ${minutes} min${run.step ? `. Last: ${run.step}` : ""}.`,
277 to: `${base}/agents/runs/${run.id}`,
278 action: "Look",
279 at: Date.parse(run.updatedAt),
280 where: run.number != null ? `#${run.number}` : null,
281 });
282 }
283
284 // --- Activity and health -------------------------------------------------------
285 const eventList: G1tEvent[] = recent ?? [];
286 const items = eventList
287 .map((event) => eventItem(event, path))
288 .filter((item): item is ActivityItem => item != null);
289 const checkEvents = eventList.flatMap((event) =>
290 event.type === "checks.completed" ? [{ repo: "", number: event.data.number, at: Date.parse(event.time), passed: event.data.status === "passed" }] : [],
291 );
292 const memoryList = ok(memories)?.project ?? [];
293 const knows = [...memoryList.filter((m) => m.pinned), ...memoryList.filter((m) => !m.pinned).sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt))].slice(0, 5);
294 const openIssues = ok(issues);
295
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look296 // --- Getting to production ----------------------------------------------------
297 const wentLive = (status: string) => status === "ready" || status === "replaced" || status === "down";
298 const liveApps = ok(list)?.live ?? [];
299 const commitNow = ok(log)?.[0] ?? null;
300 const projectValue = ok(project);
A project is an app or a library: libraries show their package and how to ship a release, not production301 const isLibrary = projectValue?.kind === "library";
302 const published = libraryPackages(ok(packageList) ?? []);
303 const firstSteps = {
304 base,
305 hasCode: commitNow != null || projectValue?.source.kind === "mirror",
306 instructions: commitNow == null ? false : root?.ok ? hasInstructions(root.value.entries.map((entry) => entry.name)) : null,
307 agentAssigned: agentWasAssigned({
308 runAgents: runList.map((run) => run.agent),
309 pullAgents: [...openPulls, ...(ok(closed) ?? [])].map((pull) => pull.agent),
310 issues: openIssues ?? [],
311 }),
312 };
313 const checklist = !member
314 ? null
315 : isLibrary
316 ? releaseChecklist({
317 ...firstSteps,
318 hasWorkflow: workflows?.ok ? workflows.value.length > 0 : null,
319 released: hasRelease(published),
320 releaseTo: published[0]
321 ? packagePath(published[0])
322 : (publishGuide(projectValue?.ecosystem ?? null)?.guide ?? "https://docs.g1t.sh/guides/packages/"),
323 })
324 : productionChecklist({
325 ...firstSteps,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look326 deploysEnabled: ok(settings)?.enabled ?? false,
327 productionDeployed:
328 liveApps.some((app) => app.kind === "production") ||
329 builds.some((build) => build.kind === "production" && wentLive(build.status)),
330 domains: ok(domains)?.domains.length ?? null,
331 previewOpened:
332 liveApps.some((app) => app.kind === "preview") || builds.some((build) => build.kind === "preview" && wentLive(build.status)),
A project is an app or a library: libraries show their package and how to ship a release, not production333 });
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look334
335 const github = await githubP;
Projects: what a workspace builds and runs, first on every page336 return {
337 member,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look338 can,
339 github,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API340 project: ok(project),
341 settings: ok(settings),
342 builds: builds.slice(0, 30),
343 live: ok(list)?.live ?? [],
344 commit: ok(log)?.[0] ?? null,
345 open: ok(counts) ?? { issues: openIssues?.length ?? 0, pulls: openPulls.length },
346 dependencies: ok(deps),
347 agentsLive: live.slice(0, 6),
348 runsLoaded: runs?.ok ?? false,
349 pullsLoaded: open?.ok ?? false,
350 columns,
351 needs: rankNeeds(needs),
352 landed: landed.slice(0, MAX_LANDED),
353 groups: groupActivity(items).slice(0, 30),
354 eventsLoaded: recent != null,
355 health: {
356 passRate: passRate(checkEvents.map((c) => c.passed)),
357 firstPass: firstPassRate(checkEvents),
358 checkRuns: checkEvents.length,
359 ages: openIssues ? ageBuckets(openIssues.map((issue) => issue.createdAt), now) : null,
360 },
361 knows: knows as Memory[],
362 memoryCount: memoryList.length,
363 memoriesLoaded: memories?.ok ?? false,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look364 checklist,
A project is an app or a library: libraries show their package and how to ship a release, not production365 library: isLibrary
366 ? { packages: published.slice(0, PACKAGES_SHOWN), total: published.length, loaded: packageList?.ok ?? false }
367 : null,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look368 branches,
Projects: what a workspace builds and runs, first on every page369 };
370}
371
372export async function action({ request, params, context }: Route.ActionArgs) {
373 assertSameOrigin(request);
374 const user = requireUser(context, request);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look375 const form = await request.formData().catch(() => null);
376 const intent = form?.get("intent");
377 // Syncing from GitHub is pushing; stopping it is an integration; deploying is compute.
378 const refused = await refusal(context, params, intent === "github-sync" ? "push" : intent === "github-stop" ? "manage_integrations" : "run");
379 if (refused) return { error: refused };
380 if (intent === "github-sync" || intent === "github-stop") {
381 const repo = await repos.get({ namespace: params.owner, name: params.repo }, user);
382 if (!repo.ok) return { error: repo.error.message };
383 const done =
384 intent === "github-sync" ? await githubApp.sync(user, repo.value.id) : await githubApp.unlinkRepo(user, repo.value.id);
385 return done.ok
386 ? { notice: intent === "github-sync" ? "Synced with GitHub." : "It is no longer kept in step with GitHub." }
387 : { error: done.error.message };
388 }
Projects: what a workspace builds and runs, first on every page389 const started = await deployments.redeploy(user, { workspace: params.owner, slug: params.repo }, null);
390 return started.ok ? { notice: "Production is building." } : { error: started.error.message };
391}
392
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily393type Loaded = Awaited<ReturnType<typeof overviewData>>;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API394type Card = Loaded["columns"]["working"][number];
395
396const STAGE_TONE: Record<PipelineStage, string> = {
397 working: "bg-merged",
398 checking: "bg-info",
399 reviewing: "bg-warn",
400 queue: "bg-accent-dim",
401 landed: "bg-accent",
402};
403
404function PipelineCard({ card, base, stage }: { card: Card; base: string; stage: PipelineStage }) {
405 return (
406 <li>
407 <Link
408 to={`${base}/pull/${card.number}`}
409 prefetch="intent"
410 className={`block rounded-lg border bg-bg/60 p-2.5 transition-colors hover:border-line-strong hover:bg-raised ${
411 card.needsYou ? "border-warn/50" : card.step ? "border-merged/30" : "border-line"
412 }`}
413 >
414 <span className="line-clamp-2 text-[0.8125rem] leading-snug font-medium">{card.title}</span>
415 <span className="mt-1.5 flex items-center gap-1.5 text-[0.6875rem] text-muted">
416 <Avatar name={card.agent} size={14} />
417 <span className="font-mono text-faint">#{card.number}</span>
418 <span className="grow" />
419 {card.needsYou ? (
420 <span className="inline-flex items-center gap-1 text-warn" title="Stopped: a person decides what happens next">
421 <Hand size={11} /> you
422 </span>
423 ) : stage === "landed" ? (
424 <TimeAgo at={card.at} />
425 ) : card.runStarted ? (
426 <span className="inline-flex items-center gap-1 text-merged">
427 <Loader2 size={11} className="animate-spin" />
428 <Elapsed from={card.runStarted} />
429 </span>
430 ) : (
431 <CheckBadge status={card.checkStatus} />
432 )}
433 </span>
434 {card.step && (
435 <span className="mt-1.5 block truncate font-mono text-[0.6875rem] text-fg/70" title={card.step}>
436 {card.runKind}: {card.step}
437 </span>
438 )}
439 </Link>
440 </li>
441 );
442}
443
444/** Open pull requests moving through Working → Checking → Reviewing → Queue → Landed. */
445function Pipeline({ columns, base }: { columns: Loaded["columns"]; base: string }) {
446 return (
447 <div className="relative -mx-4 overflow-x-auto px-4 pb-1 sm:mx-0 sm:px-0">
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look448 {/* Each stage as tall as what is in it: an empty one stays short
449 instead of stretching to the busiest. */}
450 <ol className="grid min-w-176 grid-cols-5 items-start gap-2">
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API451 {PIPELINE.map(({ stage, label }, index) => {
452 const cards = columns[stage];
453 return (
454 <li key={stage} className="flex min-w-0 flex-col rounded-xl border border-line bg-surface p-2">
455 <p className="flex items-center gap-2 px-1 pt-0.5 pb-2 text-xs font-medium text-muted">
456 <span className={`size-1.5 rounded-full ${STAGE_TONE[stage]} ${cards.length && stage !== "landed" ? "animate-pulse" : ""}`} />
457 {label}
458 <span className="tabular-nums text-faint">{cards.length}</span>
459 {index < PIPELINE.length - 1 && <ArrowRight size={11} className="ml-auto text-line-strong" />}
460 </p>
461 {cards.length === 0 ? (
462 <p className="rounded-lg border border-dashed border-line/70 px-2 py-3 text-center text-[0.6875rem] text-faint">Empty</p>
463 ) : (
464 <ul className="space-y-1.5">
465 {cards.slice(0, 6).map((card) => (
466 <PipelineCard key={card.number} card={card} base={base} stage={stage} />
467 ))}
468 {cards.length > 6 && (
469 <li className="px-1 text-[0.6875rem] text-muted">
470 <Link to={`${base}/pulls`} className="hover:text-fg">
471 {cards.length - 6} more
472 </Link>
473 </li>
474 )}
475 </ul>
476 )}
477 </li>
478 );
479 })}
480 </ol>
481 </div>
482 );
483}
484
A project is an app or a library: libraries show their package and how to ship a release, not production485/**
486 * Where an app's overview shows production, a library's shows what it
487 * publishes: each package with its latest version and how to install it,
488 * or, before there is one, how to publish a first version.
489 */
490function LibraryHead({
491 library,
492 project,
493 base,
494 canChange,
495 workspace,
496}: {
497 library: { packages: PackageSummary[]; total: number; loaded: boolean };
498 project: Project | null;
499 base: string;
500 canChange: boolean;
501 workspace: string;
502}) {
503 const guide = publishGuide(project?.ecosystem ?? null);
504 const name = project?.name ?? base.split("/").pop();
505 return (
506 <div className="p-5 sm:p-6">
507 <p className="flex items-center gap-2 text-xs font-medium tracking-wide text-muted uppercase">
508 <PackageGlyph size={13} className="text-accent" />
509 Packages
510 </p>
511 {library.packages.length > 0 ? (
512 <>
513 <ul className="mt-3 space-y-4">
514 {library.packages.map((pkg) => {
515 const commands = installCommands(pkg, pkg.latest, "you");
516 return (
517 <li key={pkg.id} className="flex flex-col gap-2.5 lg:flex-row lg:items-start lg:gap-5">
518 <Link to={packagePath(pkg)} className="group flex min-w-0 items-center gap-3 lg:w-72 lg:shrink-0">
519 <PackageIcon ecosystem={pkg.ecosystem} size={32} />
520 <span className="min-w-0">
521 <span className="block truncate font-mono text-[0.9375rem] font-medium group-hover:text-accent">{packageName(pkg)}</span>
522 <span className="block text-xs text-muted">
523 {ECOSYSTEM_LABEL[pkg.ecosystem]} · {pkg.latest ?? "no versions yet"}
524 {pkg.versions > 1 && ` · ${pkg.versions} versions`}
525 </span>
526 </span>
527 </Link>
528 <div className="min-w-0 grow space-y-1.5">
529 {/* The workspace's registry first, or the tool installs a package of the same name from elsewhere. */}
530 {commands.registry && <CopyLine prompt text={commands.registry} />}
531 <CopyLine prompt text={commands.install} />
532 </div>
533 </li>
534 );
535 })}
536 </ul>
537 {library.total > library.packages.length && (
538 <Link to={`/${workspace}/-/packages`} className="mt-3 inline-block text-xs text-muted hover:text-fg">
539 All {library.total} packages
540 </Link>
541 )}
542 </>
543 ) : (
544 <>
545 <p className="mt-2 text-lg font-medium">{library.loaded ? "Not published yet" : "Packages could not be loaded just now"}</p>
546 {guide ? (
547 <>
548 <p className="mt-1 max-w-lg text-sm text-muted">
549 Publish a first version of {name} to the workspace's {guide.label} registry, for others to install
550 {guide.start ? ":" : "."}
551 </p>
552 {guide.start && (
553 <div className="mt-3 max-w-lg">
554 <CopyLine prompt text={guide.start} />
555 </div>
556 )}
557 <a href={guide.guide} className="mt-3 inline-flex items-center gap-1 text-sm text-accent hover:underline">
558 Publishing to {guide.label} <ArrowUpRight size={13} />
559 </a>
560 </>
561 ) : (
562 <>
563 <p className="mt-1 max-w-lg text-sm text-muted">Publish a first version of {name} to one of the workspace's registries:</p>
564 <p className="mt-2 flex flex-wrap gap-x-4 gap-y-1 text-sm">
565 {PUBLISH_GUIDES.map((one) => (
566 <a key={one.label} href={one.guide} className="inline-flex items-center gap-1 text-accent hover:underline">
567 {one.label} <ArrowUpRight size={13} />
568 </a>
569 ))}
570 </p>
571 </>
572 )}
573 </>
574 )}
575 {canChange && (
576 <p className="mt-4 text-xs text-faint">
577 Deploys?{" "}
578 <Link to={`${base}/settings#deploys`} className="underline-offset-4 hover:text-fg hover:underline">
579 Change it in Settings.
580 </Link>
581 </p>
582 )}
583 </div>
584 );
585}
586
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API587function Stat({ label, value, to }: { label: string; value: ReactNode; to?: string }) {
588 const body = (
589 <>
590 <span className="block text-[0.6875rem] text-faint">{label}</span>
591 <span className="mt-0.5 block truncate text-[0.8125rem] font-medium">{value}</span>
592 </>
593 );
594 return to ? (
595 <Link to={to} className="min-w-0 px-5 py-3 transition-colors hover:bg-raised/50">
596 {body}
597 </Link>
598 ) : (
599 <div className="min-w-0 px-5 py-3">{body}</div>
600 );
601}
602
Projects: what a workspace builds and runs, first on every page603export default function ProjectOverview({ loaderData, actionData, params }: Route.ComponentProps) {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily604 return (
605 <Suspense fallback={<OverviewSkeleton />}>
606 <Await resolve={loaderData.overview} errorElement={<OverviewFailed />}>
607 {(overview) => <Overview loaderData={overview} actionData={actionData} params={params} />}
608 </Await>
609 </Suspense>
610 );
611}
612
613/** The overview's sections as they will sit, while they stream in. */
614function OverviewSkeleton() {
615 return (
616 <div className="space-y-8" aria-busy="true" aria-label="Loading the overview">
617 <section className="rounded-2xl border border-line bg-surface p-5 sm:p-6">
618 <Skeleton className="h-3 w-24" />
619 <Skeleton className="mt-3 h-5 w-64 max-w-full" />
620 <Skeleton className="mt-3 h-3 w-48 max-w-full" />
621 </section>
622 <section className="rounded-2xl border border-line bg-surface p-2">
623 <div className="grid grid-cols-2 gap-2 sm:grid-cols-5">
624 {Array.from({ length: 5 }, (_, index) => (
625 <Skeleton key={index} className="h-24 rounded-xl" />
626 ))}
627 </div>
628 </section>
629 <div className="grid gap-6 lg:grid-cols-2">
630 <section className="rounded-2xl border border-line bg-surface">
631 <SkeletonRows rows={4} />
632 </section>
633 <section className="rounded-2xl border border-line bg-surface">
634 <SkeletonRows rows={4} />
635 </section>
636 </div>
637 </div>
638 );
639}
640
641function OverviewFailed() {
642 return (
643 <p className="rounded-2xl border border-line bg-surface p-6 text-sm text-muted">
644 The overview could not be loaded just now. Reload the page to try again.
645 </p>
646 );
647}
648
649function Overview({
650 loaderData,
651 actionData,
652 params,
653}: {
654 loaderData: Loaded;
655 actionData: Route.ComponentProps["actionData"];
656 params: Route.ComponentProps["params"];
657}) {
A project is an app or a library: libraries show their package and how to ship a release, not production658 const { member, project, settings, builds, live, commit, open, dependencies, agentsLive, columns, needs, landed, groups, health, knows, checklist, branches, library } =
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look659 loaderData;
Projects: what a workspace builds and runs, first on every page660 const base = `/${params.owner}/${params.repo}`;
661 const production = live.find((app) => app.kind === "production") ?? null;
Agents and memory, checks and conflicts, profiles, slug renames, custom domains662 // The project's own domain, once it is active, is where production is visited.
663 const productionUrl = production ? (settings?.primaryDomain ? `https://${settings.primaryDomain}` : production.url) : null;
Projects: what a workspace builds and runs, first on every page664 const previews = live.filter((app) => app.kind === "preview");
665 const latestProduction = builds.find((build) => build.kind === "production") ?? null;
666 const busy = useNavigation().state === "submitting";
667 const source = project?.source.kind === "hosted" ? project.source : null;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API668 const moving = agentsLive.length > 0 || columns.working.length + columns.checking.length > 0;
669 useLiveRefresh(moving);
670 const ages = health.ages;
671 const oldest = ages ? Math.max(1, ...ages.map((a) => a.count)) : 1;
Projects: what a workspace builds and runs, first on every page672
673 return (
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API674 <div className="space-y-8">
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look675 {loaderData.github && <GithubLinkStrip link={loaderData.github} />}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API676 {/* What the project is, running, and where its code is. */}
677 <section className="overflow-hidden rounded-2xl border border-line bg-surface">
A project is an app or a library: libraries show their package and how to ship a release, not production678 {library ? (
679 <LibraryHead library={library} project={project} base={base} canChange={loaderData.can.manage_settings} workspace={params.owner} />
680 ) : (
681 <div className="flex flex-col gap-5 p-5 sm:flex-row sm:items-start sm:p-6">
682 {member && (production || settings?.enabled) && (
683 <ProductionShot
684 src={production ? `${base}/production.jpg?v=${shotVersion(production)}` : null}
685 href={production ? (productionUrl ?? production.url) : null}
686 label={host(production ? (productionUrl ?? production.url) : (settings?.productionUrl ?? ""))}
687 className="w-full shrink-0 sm:w-60 lg:w-72"
688 />
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API689 )}
A project is an app or a library: libraries show their package and how to ship a release, not production690 <div className="flex min-w-0 grow flex-wrap items-start justify-between gap-4">
691 <div className="min-w-0">
692 <p className="flex items-center gap-2 text-xs font-medium tracking-wide text-muted uppercase">
693 <Rocket size={13} className="text-accent" />
694 Production
695 </p>
696 {production ? (
697 <>
698 <a
699 href={productionUrl ?? production.url}
700 className="mt-2 flex items-center gap-1.5 truncate font-mono text-lg font-medium hover:text-accent"
701 >
702 {host(productionUrl ?? production.url)}
703 <ArrowUpRight size={16} className="shrink-0 text-faint" />
704 </a>
705 <p className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted">
706 {latestProduction && <StatusDot status={latestProduction.status} label={latestProduction.status === "ready" ? "Live" : undefined} />}
707 <span className="inline-flex items-center gap-1 font-mono">
708 <GitCommitHorizontal size={13} className="text-faint" />
709 {production.commit.slice(0, 7)}
710 </span>
711 <span>
712 deployed <TimeAgo at={production.deployedAt} />
713 </span>
714 {settings?.primaryDomain && <span className="text-faint">also at {host(production.url)}</span>}
715 </p>
716 </>
717 ) : settings?.enabled ? (
718 <>
719 <p className="mt-2 font-mono text-lg text-muted">{host(settings.productionUrl)}</p>
720 <p className="mt-1.5 text-xs text-muted">
721 {latestProduction ? (
722 <StatusDot status={latestProduction.status} label={latestProduction.status === "failed" ? "The last build failed" : undefined} />
723 ) : (
724 "Not deployed yet. Push to the default branch, or deploy it now."
725 )}
726 </p>
727 </>
728 ) : (
729 <>
730 <p className="mt-2 text-lg font-medium">Deployments are off</p>
731 <p className="mt-1 max-w-lg text-sm text-muted">
732 Nothing builds or runs until you turn them on. Then {project?.name ?? params.repo} goes up on g1t.page:
733 production from the default branch, and a live preview for every pull request. It runs only while
734 someone visits.
735 </p>
736 </>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API737 )}
Projects: what a workspace builds and runs, first on every page738 </div>
A project is an app or a library: libraries show their package and how to ship a release, not production739 {member && (
740 <div className="flex shrink-0 items-center gap-2">
741 {production && (
742 <ButtonLink to={productionUrl ?? production.url} variant="accent" reloadDocument>
743 Visit
744 <ArrowUpRight size={14} />
745 </ButtonLink>
746 )}
747 {settings?.enabled ? (
748 loaderData.can.run && <Form method="post">
749 <Button type="submit" variant="quiet" disabled={busy} title="Build production again from the default branch">
750 <RotateCw size={14} />
751 Redeploy
752 </Button>
753 </Form>
754 ) : loaderData.can.manage_integrations && (
755 <ButtonLink to={`${base}/settings/deployments`} variant="accent">
756 <Rocket size={14} />
757 Turn on deployments
758 </ButtonLink>
759 )}
760 </div>
761 )}
762 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look763 </div>
A project is an app or a library: libraries show their package and how to ship a release, not production764 )}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API765 {actionData && (
766 <p className={`border-t border-line px-6 py-2.5 text-sm ${"error" in actionData ? "text-danger" : "text-accent"}`}>
767 {"error" in actionData ? actionData.error : actionData.notice}
768 </p>
769 )}
770 <div className="grid grid-cols-2 divide-line border-t border-line text-sm sm:grid-cols-4 sm:divide-x">
771 <Stat
772 label="Source"
773 to={`${base}/code`}
774 value={
775 source ? (
776 <span className="inline-flex items-center gap-1.5 font-mono">
777 {project?.private ? <Lock size={12} className="text-faint" /> : <Code2 size={12} className="text-faint" />}
778 {source.repo.name}
779 {source.rootDir ? `/${source.rootDir}` : ""}
780 </span>
781 ) : (
782 "—"
783 )
784 }
785 />
786 <Stat
787 label="Default branch"
788 to={`${base}/commits`}
789 value={
790 <span className="inline-flex items-center gap-1.5 font-mono">
791 <GitBranch size={12} className="text-faint" />
792 {source?.defaultBranch ?? "main"}
793 </span>
794 }
795 />
796 <Stat
797 label="Latest commit"
798 to={commit ? `${base}/commit/${commit.hash}` : undefined}
799 value={
800 commit ? (
801 <span title={commit.message.split("\n")[0]}>
802 <span className="font-mono">{commit.hash.slice(0, 7)}</span>{" "}
803 <span className="font-normal text-muted">
804 <TimeAgo at={commit.authoredAt} />
805 </span>
806 </span>
807 ) : (
808 "No commits yet"
809 )
810 }
811 />
812 <Stat
A project is an app or a library: libraries show their package and how to ship a release, not production813 label={member && settings?.enabled && !library ? "Last deploy" : "Open"}
814 to={member && settings?.enabled && !library ? `${base}/deployments` : `${base}/pulls`}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API815 value={
A project is an app or a library: libraries show their package and how to ship a release, not production816 member && settings?.enabled && !library ? (
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API817 latestProduction ? (
818 <span className="inline-flex items-center gap-2">
819 <StatusDot status={latestProduction.status} />
820 <span className="font-normal text-muted">
821 <TimeAgo at={latestProduction.createdAt} />
Projects: what a workspace builds and runs, first on every page822 </span>
823 </span>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API824 ) : (
825 "None yet"
826 )
827 ) : (
828 `${open.pulls} pull requests · ${open.issues} issues`
829 )
830 }
831 />
832 </div>
833 {commit && (
834 <p className="truncate border-t border-line px-5 py-2.5 text-xs text-muted sm:px-6">
835 <GitCommitHorizontal size={12} className="mr-1.5 inline text-faint" />
836 {commit.message.split("\n")[0]} <span className="text-faint">· {commit.author.name}</span>
837 </p>
838 )}
839 </section>
840
A project is an app or a library: libraries show their package and how to ship a release, not production841 {checklist && <ProductionChecklist base={base} items={checklist} title={library ? "Ship a release" : undefined} />}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look842
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API843 <Panel
844 title="Right now"
845 icon={<span className={`block size-2 rounded-full ${agentsLive.length > 0 ? "animate-pulse bg-merged" : "bg-line-strong"}`} />}
846 count={agentsLive.length}
847 all={{ to: `${base}/agents`, label: "Agents" }}
848 >
849 {agentsLive.length > 0 && (
850 <ul className="mb-3 grid grid-cols-1 gap-2 sm:grid-cols-2 xl:grid-cols-3">
851 {agentsLive.map((run) => (
852 <li key={run.id} className="min-w-0 rounded-xl border border-merged/30 bg-surface px-3.5 py-3">
853 <div className="flex items-center gap-2 text-sm">
854 <Avatar name={run.agent} size={18} />
855 <span className="font-medium">{run.agent}</span>
856 <span className="text-xs text-muted">{RUN_KIND_LABEL[run.kind].toLowerCase()}</span>
857 <span className="grow" />
858 <span className="text-xs text-muted">
859 <Elapsed from={run.startedAt ?? run.createdAt} />
Projects: what a workspace builds and runs, first on every page860 </span>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API861 </div>
862 <Link
863 to={run.number != null ? `${base}/pull/${run.number}` : `${base}/agents/runs/${run.id}`}
864 className="mt-1 block truncate text-[0.8125rem] hover:text-accent"
865 >
866 {run.title ?? "A run"} {run.number != null && <span className="text-faint">#{run.number}</span>}
867 </Link>
868 {run.step && (
869 <p className="mt-1 truncate font-mono text-[0.6875rem] text-fg/70" title={run.step}>
870 <span className="mr-1.5 inline-block size-1.5 animate-pulse rounded-full bg-merged align-middle" />
871 {run.step}
872 </p>
873 )}
874 <p className="mt-1.5 flex items-center gap-3 text-[0.6875rem] text-faint">
875 {formatCost(run.costUsd) && <span>{formatCost(run.costUsd)} so far</span>}
876 <Link to={`${base}/agents/runs/${run.id}`} className="hover:text-fg">
877 {run.stepCount} steps
878 </Link>
879 {run.number != null && (
880 <Link to={`${base}/sessions/${run.number}`} className="hover:text-fg">
881 Session
882 </Link>
883 )}
884 </p>
885 </li>
886 ))}
887 </ul>
888 )}
889 {!loaderData.pullsLoaded ? (
890 <Unavailable what="Pull requests" />
891 ) : Object.values(columns).every((c) => c.length === 0) ? (
892 <Quiet
893 action={
894 member ? (
895 <Link to={`${base}/issues/new`} className="inline-flex items-center gap-1.5 rounded-md bg-fg px-3 py-1.5 text-xs font-medium text-bg hover:bg-white">
896 <Bot size={12} /> Open an issue for an agent
897 </Link>
898 ) : null
899 }
900 >
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent901 Nothing is moving. Open an issue and hand it to g1t, or push a branch and open a pull request.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API902 </Quiet>
903 ) : (
904 <Pipeline columns={columns} base={base} />
Projects: what a workspace builds and runs, first on every page905 )}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API906 </Panel>
Projects: what a workspace builds and runs, first on every page907
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API908 <div className="grid gap-x-8 gap-y-8 lg:grid-cols-[minmax(0,1fr)_20rem]">
909 <div className="min-w-0 space-y-8">
910 {needs.length > 0 && (
911 <Panel title="Needs you" icon={<Hand size={14} className="text-warn" />} count={needs.length}>
912 <NeedsList needs={needs} limit={5} />
913 </Panel>
914 )}
915
Fast pages, required checks on the branch, self-hosted runners, honest incidents916 <Suspense
917 fallback={
918 <Panel title="Active branches" icon={<GitBranch size={14} />}>
919 <div aria-busy="true">
920 <SkeletonRows rows={3} rowClassName="h-12" />
921 </div>
922 </Panel>
923 }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look924 >
Fast pages, required checks on the branch, self-hosted runners, honest incidents925 <Await resolve={branches}>
926 {(branches) => (
927 <Panel title="Active branches" icon={<GitBranch size={14} />} count={branches?.total || null}>
928 {!branches ? (
929 <Unavailable what="Branches" />
930 ) : branches.shown.length === 0 ? (
931 <Quiet>
932 Only {branches.main} so far. Branches pushed here show with how far each has moved from {branches.main}, and
933 its pull request and preview.
934 </Quiet>
935 ) : (
936 <>
937 <ActiveBranches branches={branches.shown} base={base} main={branches.main} />
938 {branches.total > branches.shown.length && (
939 <p className="mt-2 px-1 text-xs text-faint">
940 The {branches.shown.length} most recently changed of {branches.total} branches.
941 </p>
942 )}
943 </>
944 )}
945 </Panel>
946 )}
947 </Await>
948 </Suspense>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look949
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API950 <Panel title="Recent changes" icon={<GitMerge size={14} />} all={{ to: `${base}/pulls?state=closed`, label: "All landed" }}>
951 {landed.length === 0 ? (
952 <Quiet>Nothing has landed yet. Merged pull requests show here with who made them and why.</Quiet>
Projects: what a workspace builds and runs, first on every page953 ) : (
954 <ul className="divide-y divide-line overflow-hidden rounded-xl border border-line bg-surface">
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API955 {landed.map((pull) => {
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent956 const byAgent = pull.runtime === "hosted" || madeByG1t(pull);
Projects: what a workspace builds and runs, first on every page957 return (
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API958 <li key={pull.id} className="flex items-start gap-3 px-4 py-3">
959 <GitMerge size={15} className="mt-0.5 shrink-0 text-merged" />
960 <span className="min-w-0 grow">
961 <Link to={`${base}/pull/${pull.number}`} prefetch="intent" className="block truncate text-sm font-medium hover:text-accent">
962 {pull.title}
963 </Link>
964 <span className="mt-0.5 flex flex-wrap items-center gap-x-1.5 text-xs text-muted">
965 <span className="font-mono text-faint">#{pull.number}</span>
966 <span>·</span>
967 <span className="inline-flex items-center gap-1">
968 <Avatar name={pull.agent} size={13} />
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights969 {pull.requestedBy
970 ? `made by ${pull.author.username} for ${pull.requestedBy.username}`
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent971 : byAgent
972 ? `made by ${pull.agent}`
973 : `by ${pull.author.username}`}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API974 </span>
975 {pull.issue != null && (
976 <>
977 <span>· for</span>
978 <Link to={`${base}/issues/${pull.issue}`} className="hover:text-fg">
979 #{pull.issue}
980 </Link>
981 </>
982 )}
983 {pull.mergedBy && <span>· landed by {pull.mergedBy}</span>}
984 <span>
985 · <TimeAgo at={pull.mergedAt ?? pull.updatedAt} />
Projects: what a workspace builds and runs, first on every page986 </span>
987 </span>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API988 </span>
989 <span className="hidden shrink-0 text-xs text-faint sm:block">
990 <ChangeSize files={pull.files} />
991 </span>
992 {byAgent && (
993 <Link to={`${base}/sessions/${pull.number}`} className="shrink-0 text-xs text-muted hover:text-fg">
994 Session
995 </Link>
996 )}
Projects: what a workspace builds and runs, first on every page997 </li>
998 );
999 })}
1000 </ul>
1001 )}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1002 </Panel>
Projects: what a workspace builds and runs, first on every page1003
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1004 <Panel title="Activity" icon={<ActivityIcon size={14} />}>
1005 {loaderData.eventsLoaded ? (
1006 <ActivityFeed groups={groups} showRepo={false} limit={8} empty="Nothing has happened here yet." />
1007 ) : (
1008 <Unavailable what="Activity" />
1009 )}
1010 </Panel>
1011
1012 {previews.length > 0 && (
1013 <Panel title="Previews" icon={<GitBranch size={14} />} count={previews.length} all={{ to: `${base}/deployments`, label: "Deployments" }}>
1014 <ul className="divide-y divide-line overflow-hidden rounded-xl border border-line bg-surface">
1015 {previews.map((app) => (
1016 <li key={app.url} className="flex items-center gap-3 px-4 py-2.5 text-sm">
1017 <span className="min-w-0 grow">
1018 <a href={app.url} className="block truncate font-mono text-[0.8125rem] hover:text-accent">
1019 {host(app.url)}
1020 </a>
1021 <span className="text-xs text-muted">
1022 {app.branch}
1023 {app.number != null && (
1024 <>
1025 {" · "}
1026 <Link to={`${base}/pull/${app.number}`} className="hover:underline">
1027 #{app.number}
1028 </Link>
1029 </>
1030 )}
1031 </span>
Projects: what a workspace builds and runs, first on every page1032 </span>
1033 <span className="shrink-0 text-xs text-faint">
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1034 <TimeAgo at={app.deployedAt} />
Projects: what a workspace builds and runs, first on every page1035 </span>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1036 </li>
1037 ))}
1038 </ul>
1039 </Panel>
1040 )}
1041 </div>
Projects: what a workspace builds and runs, first on every page1042
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1043 <aside className="space-y-4 text-sm">
1044 {member && (
1045 <section className="rounded-xl border border-line bg-surface p-5">
1046 <div className="flex items-center justify-between">
1047 <h2 className="flex items-center gap-1.5 text-sm font-semibold">
1048 <Brain size={14} className="text-merged" />
1049 What agents know here
1050 </h2>
1051 <Link to={`${base}/memory`} className="text-xs text-muted hover:text-fg">
1052 Memory
1053 </Link>
Projects: what a workspace builds and runs, first on every page1054 </div>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1055 {!loaderData.memoriesLoaded ? (
1056 <p className="mt-3 text-xs text-muted">Memory could not be loaded just now.</p>
1057 ) : knows.length === 0 ? (
1058 <p className="mt-3 text-xs leading-5 text-muted">
1059 Nothing yet. Agents note conventions, decisions and traps as they work, and every agent starting here reads them.
1060 </p>
1061 ) : (
1062 <ul className="mt-3 space-y-2.5">
1063 {knows.map((memory) => (
1064 <li key={memory.id} className="text-xs leading-5">
1065 <p className="line-clamp-3 text-fg-soft">
1066 {memory.pinned && <Pin size={11} className="mr-1 inline text-accent" />}
1067 {memory.text}
1068 </p>
1069 <p className="text-faint">
1070 {memory.kind} · {memory.createdBy} · <TimeAgo at={memory.createdAt} />
1071 </p>
1072 </li>
1073 ))}
1074 {loaderData.memoryCount > knows.length && (
1075 <li>
1076 <Link to={`${base}/memory`} className="text-xs text-muted hover:text-fg">
1077 {loaderData.memoryCount - knows.length} more
1078 </Link>
1079 </li>
1080 )}
1081 </ul>
1082 )}
1083 </section>
Projects: what a workspace builds and runs, first on every page1084 )}
1085
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1086 <section className="rounded-xl border border-line bg-surface p-5">
1087 <h2 className="flex items-center gap-1.5 text-sm font-semibold">
1088 <HeartPulse size={14} className="text-faint" />
1089 Health
Project dependencies: addresses, preview stacks, Affects, and agents who know1090 </h2>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1091 <div className="mt-4 space-y-4 text-xs">
1092 <div>
1093 <div className="flex items-baseline justify-between">
1094 <span className="text-muted">Checks passing</span>
1095 <span className="font-medium tabular-nums">{percent(health.passRate)}</span>
1096 </div>
1097 <div className="mt-1.5">
1098 <Meter value={health.passRate} tone={health.passRate != null && health.passRate < 0.7 ? "bg-warn" : "bg-accent"} />
1099 </div>
1100 <p className="mt-1 text-faint">
1101 {health.checkRuns
1102 ? `${health.checkRuns} recent runs · ${percent(health.firstPass.rate)} pass on the first try`
1103 : "No checks have run recently."}
1104 </p>
1105 </div>
A project is an app or a library: libraries show their package and how to ship a release, not production1106 {member && (!library || builds.length > 0) && (
Project dependencies: addresses, preview stacks, Affects, and agents who know1107 <div>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1108 <div className="flex items-baseline justify-between">
1109 <span className="text-muted">Deploys</span>
1110 <Link to={`${base}/deployments`} className="text-faint hover:text-fg">
1111 {builds.length ? `last ${Math.min(builds.length, 20)}` : "none yet"}
1112 </Link>
1113 </div>
1114 <div className="mt-1.5">
1115 <DeployStrip builds={builds} base={base} />
1116 </div>
Project dependencies: addresses, preview stacks, Affects, and agents who know1117 </div>
1118 )}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1119 <div>
1120 <div className="flex items-baseline justify-between">
1121 <span className="text-muted">Open issues by age</span>
1122 <Link to={`${base}/issues`} className="font-medium tabular-nums hover:text-accent">
1123 {open.issues}
1124 </Link>
1125 </div>
1126 {ages ? (
1127 <ul className="mt-2 space-y-1.5">
1128 {ages.map((bucket) => (
1129 <li key={bucket.label} className="grid grid-cols-[6.5rem_1fr_1.5rem] items-center gap-2">
1130 <span className="text-faint">{bucket.label}</span>
1131 <span className="block h-1.5 overflow-hidden rounded-full bg-line">
1132 <span
1133 className={`block h-full rounded-full ${bucket.label === "Older" ? "bg-warn" : "bg-fg-soft/60"}`}
1134 style={{ width: `${(bucket.count / oldest) * 100}%` }}
1135 />
1136 </span>
1137 <span className="text-right tabular-nums text-muted">{bucket.count}</span>
Project dependencies: addresses, preview stacks, Affects, and agents who know1138 </li>
1139 ))}
1140 </ul>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1141 ) : (
1142 <p className="mt-1 text-faint">Issues could not be loaded just now.</p>
1143 )}
1144 </div>
Project dependencies: addresses, preview stacks, Affects, and agents who know1145 </div>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1146 </section>
Project dependencies: addresses, preview stacks, Affects, and agents who know1147
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1148 <section className="rounded-xl border border-line bg-surface p-5">
1149 <div className="flex items-center justify-between">
1150 <h2 className="flex items-center gap-1.5 text-sm font-semibold">
1151 <Network size={14} className="text-faint" />
1152 Dependencies
1153 </h2>
1154 {member && (
1155 <Link to={`${base}/settings/dependencies`} className="text-xs text-muted hover:text-fg">
1156 Manage
1157 </Link>
1158 )}
1159 </div>
1160 {!dependencies ? (
1161 <p className="mt-3 text-xs text-muted">Dependencies could not be loaded just now.</p>
1162 ) : dependencies.dependsOn.length === 0 && dependencies.usedBy.length === 0 ? (
1163 <p className="mt-3 text-xs leading-5 text-muted">
1164 Uses no other project, and none uses it. Declare one, and builds get its address and agents know what depends on
1165 what.
1166 </p>
1167 ) : (
1168 <div className="mt-3 space-y-3 text-xs">
1169 {dependencies.dependsOn.length > 0 && (
1170 <div>
1171 <p className="flex items-center gap-1 text-muted">
1172 <ArrowUpRight size={12} /> Uses
1173 </p>
1174 <ul className="mt-1.5 space-y-1">
1175 {dependencies.dependsOn.map((d) => (
1176 <li key={d.slug} className="flex items-center justify-between gap-2">
1177 <Link to={`/${params.owner}/${d.slug}`} className="font-medium hover:underline">
1178 {d.name}
1179 </Link>
1180 {d.as && <code className="font-mono text-faint">{d.as}</code>}
1181 </li>
1182 ))}
1183 </ul>
1184 </div>
1185 )}
1186 {dependencies.usedBy.length > 0 && (
1187 <div>
1188 <p className="flex items-center gap-1 text-muted">
1189 <ArrowDownLeft size={12} /> Used by
1190 </p>
1191 <ul className="mt-1.5 space-y-1">
1192 {dependencies.usedBy.map((d) => (
1193 <li key={d.slug}>
1194 <Link to={`/${params.owner}/${d.slug}`} className="font-medium hover:underline">
1195 {d.name}
1196 </Link>
1197 </li>
1198 ))}
1199 </ul>
1200 </div>
1201 )}
1202 </div>
1203 )}
Projects: what a workspace builds and runs, first on every page1204 </section>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1205
1206 {source && (
1207 <section className="rounded-xl border border-line bg-surface p-5">
1208 <h2 className="text-sm font-semibold">Clone</h2>
1209 <div className="mt-3">
1210 <CopyLine text={`git clone https://g1t.sh/${source.repo.namespace}/${source.repo.name}.git`} />
1211 </div>
1212 </section>
1213 )}
1214 </aside>
1215 </div>
Projects: what a workspace builds and runs, first on every page1216 </div>
1217 );
1218}