flagon-io/g1t

public

Git for AI scale: a forge for thousands of agents working on the same code at once.

Commit

Pull requests say where they are live; the project menu shows what is coming

- A pull request's conversation gets a deployment card above the merge box, as GitHub shows one: "This branch is live" with its address and Visit preview, or that it is deploying (the last preview still up) or failed and why; its commit and age; the build log; and the previews of projects built against it with "Preview them against this change". - The project menu shows, marked Soon, what projects will have: Boards, Wiki, Agents at work, Sessions, Security and quality, Dependency updates, Environments, Releases, Packages, Feature flags, Logs and metrics, Errors and incidents, Insights. Each says in its tooltip what it will do.

syntaqxcommitted Parent389eaefBrowse files
2 files+211−30/2 viewed
+80−0
3030 Users,
3131 Webhook,
3232 PlayCircle,
33+ Activity,
34+ Bot,
35+ Kanban,
36+ Package,
37+ PackageCheck,
38+ ScrollText,
39+ Server,
40+ ShieldCheck,
41+ Siren,
42+ Tag,
43+ ToggleRight,
3344 Rocket,
3445 X,
3546 } from "lucide-react";
118129 );
119130 }
120131
132+/**
133+ * Something a project will have, shown where it will live so people can
134+ * see where g1t is going. Not a link: nothing is behind it yet.
135+ */
136+function SidebarSoon({ icon, children, about }: { icon: ReactNode; children: ReactNode; about: string }) {
137+ return (
138+ <div
139+ title={about}
140+ aria-disabled="true"
141+ className="flex h-8 cursor-default items-center gap-2.5 rounded-md px-2 text-[0.8125rem] text-faint"
142+ >
143+ <span className="shrink-0 opacity-70">{icon}</span>
144+ <span className="grow truncate">{children}</span>
145+ <span className="rounded-full px-1.5 py-px text-[0.625rem] font-medium tracking-wide text-muted uppercase ring-1 ring-line">
146+ Soon
147+ </span>
148+ </div>
149+ );
150+}
151+
121152 function SidebarGroup({ title, action, children }: { title: string; action?: ReactNode; children: ReactNode }) {
122153 return (
123154 <section className="mt-6">
397428 <SidebarLink to={`${base}/queue`} icon={<Layers size={15} />}>
398429 Merge queue
399430 </SidebarLink>
431+ <SidebarSoon icon={<Kanban size={15} />} about="Issues and pull requests on boards, by state, owner or outcome.">
432+ Boards
433+ </SidebarSoon>
434+ <SidebarSoon icon={<BookOpen size={15} />} about="Pages about the project that agents keep current as the code changes.">
435+ Wiki
436+ </SidebarSoon>
437+ </SidebarGroup>
438+ <SidebarGroup title="Agents">
439+ <SidebarSoon icon={<Bot size={15} />} about="Every agent at work on this project now: what it is doing, what it knows, and a way to steer it.">
440+ Agents at work
441+ </SidebarSoon>
442+ <SidebarSoon icon={<ScrollText size={15} />} about="Every agent session that changed this project, searchable, with why-blame back to the lines it wrote.">
443+ Sessions
444+ </SidebarSoon>
400445 </SidebarGroup>
401446 <SidebarGroup title="Automate">
402447 <SidebarLink to={`${base}/actions`} icon={<PlayCircle size={15} />}>
403448 Actions
404449 </SidebarLink>
405450 </SidebarGroup>
451+ <SidebarGroup title="Secure">
452+ <SidebarSoon icon={<ShieldCheck size={15} />} about="Secret scanning, vulnerable dependencies and code scanning, each finding fixed by an agent through the queue.">
453+ Security and quality
454+ </SidebarSoon>
455+ <SidebarSoon icon={<PackageCheck size={15} />} about="Outdated packages updated by agents, tested and landed through the queue.">
456+ Dependency updates
457+ </SidebarSoon>
458+ </SidebarGroup>
459+ <SidebarGroup title="Deploy">
460+ <SidebarSoon icon={<Server size={15} />} about="Staging and other environments, with required approvers and branch rules.">
461+ Environments
462+ </SidebarSoon>
463+ <SidebarSoon icon={<Tag size={15} />} about="Tagged releases with notes written from what landed.">
464+ Releases
465+ </SidebarSoon>
466+ <SidebarSoon icon={<Package size={15} />} about="npm, container and other packages published from the project.">
467+ Packages
468+ </SidebarSoon>
469+ <SidebarSoon icon={<ToggleRight size={15} />} about="Turn features on per environment or per user, without a deploy.">
470+ Feature flags
471+ </SidebarSoon>
472+ </SidebarGroup>
473+ <SidebarGroup title="Operate">
474+ <SidebarSoon icon={<Activity size={15} />} about="Requests, errors and CPU time of each deployment, and its logs.">
475+ Logs and metrics
476+ </SidebarSoon>
477+ <SidebarSoon icon={<Siren size={15} />} about="Errors and incidents from the running app, each becoming an issue an agent can take.">
478+ Errors and incidents
479+ </SidebarSoon>
480+ </SidebarGroup>
481+ <SidebarGroup title="Insights">
482+ <SidebarSoon icon={<BarChart3 size={15} />} about="How work flows: lead time, agents' share of changes, review time, what costs most.">
483+ Insights
484+ </SidebarSoon>
485+ </SidebarGroup>
406486 {repo.member && (
407487 <SidebarGroup title="Project">
408488 <SidebarLink to={`${base}/settings`} icon={<Settings size={15} />}>
+131−3
1515 Radar,
1616 Sparkles,
1717 MessageSquare,
18+ ArrowUpRight,
1819 MessagesSquare,
20+ Rocket,
1921 Network,
2022 StickyNote,
2123 User,
2426 import { useEffect, useState } from "react";
2527 import { Form, Link, redirect, useRevalidator } from "react-router";
2628
27−import { type Comparison, type SessionEntry, pullComparison } from "@g1t/contracts";
29+import {
30+ type Comparison,
31+ type Deployment,
32+ type LiveApp,
33+ type SessionEntry,
34+ type Viewer,
35+ pullComparison,
36+} from "@g1t/contracts";
2837
2938 import type { Route } from "./+types/pull";
3039 import { DiffView } from "../../components/diff-view";
124133 canIgnoreChecks: !settings.ok || settings.value.allowIgnoringChecks,
125134 defaultBranch: repo.ok ? repo.value.defaultBranch : "main",
126135 affects: deps.ok ? deps.value.usedBy : [],
127− // This pull request's preview, if it is up: a stack builds on it.
128− preview: deployed?.ok ? (deployed.value.live.find((app) => app.kind === "preview" && app.number === number) ?? null) : null,
136+ ...(await deploymentOf(deployed?.ok ? deployed.value : null, number, params.owner, deps.ok ? deps.value.usedBy : [], viewer)),
129137 };
130138 }
131139
140+/**
141+ * Where this pull request is live: its preview (or its latest build, while
142+ * one is going or after one failed), and the previews of the projects
143+ * that use this one, built against it.
144+ */
145+async function deploymentOf(
146+ list: { deployments: Deployment[]; live: LiveApp[] } | null,
147+ number: number,
148+ owner: string,
149+ affects: { slug: string; name: string }[],
150+ viewer: Viewer,
151+) {
152+ if (!list) return { preview: null, build: null, stacked: [] };
153+ const preview = list.live.find((app) => app.kind === "preview" && app.number === number) ?? null;
154+ const build = list.deployments.find((d) => d.kind === "preview" && d.number === number) ?? null;
155+ const branch = preview?.branch ?? build?.branch ?? null;
156+ const stacked = branch
157+ ? (
158+ await Promise.all(
159+ affects.slice(0, 5).map(async (project) => {
160+ const theirs = await deployments.list({ workspace: owner, slug: project.slug }, viewer);
161+ const app = theirs.ok ? theirs.value.live.find((a) => a.kind === "preview" && a.branch === branch) : undefined;
162+ return app ? { name: project.name, slug: project.slug, url: app.url } : null;
163+ }),
164+ )
165+ ).filter((entry) => entry != null)
166+ : [];
167+ return { preview, build, stacked };
168+}
169+
132170 export async function action({ request, params, context }: Route.ActionArgs) {
133171 assertSameOrigin(request);
134172 const user = requireUser(context, request);
337375 statuses = [],
338376 affects,
339377 preview,
378+ build,
379+ stacked,
340380 landing,
341381 stalled,
342382 requireUpToDate,
668708
669709 <CommentList comments={comments} review={review} base={base} />
670710
711+ {(preview || build) && (
712+ <DeploymentCard preview={preview} build={build} stacked={stacked} base={base} />
713+ )}
714+
671715 {pull.status === "draft" && (
672716 <StatusBox>
673717 <StatusRow icon={<Loader size={16} className="text-faint" />} title="This is a draft">
10741118 </div>
10751119 );
10761120 }
1121+
1122+/** Where the pull request's change is live, as GitHub shows a deployment. */
1123+function DeploymentCard({
1124+ preview,
1125+ build,
1126+ stacked,
1127+ base,
1128+}: {
1129+ preview: LiveApp | null;
1130+ build: Deployment | null;
1131+ stacked: { name: string; slug: string; url: string }[];
1132+ base: string;
1133+}) {
1134+ const building = build?.status === "queued" || build?.status === "building";
1135+ const failed = build?.status === "failed";
1136+ return (
1137+ <section className="ml-12 overflow-hidden rounded-xl border border-line bg-surface">
1138+ <div className="flex flex-wrap items-center gap-3 px-4 py-3">
1139+ <span
1140+ className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ${
1141+ failed ? "text-danger ring-danger/40" : building ? "text-warn ring-warn/40" : "text-accent ring-accent/40"
1142+ }`}
1143+ >
1144+ {building ? <Loader size={15} className="animate-spin" /> : <Rocket size={15} />}
1145+ </span>
1146+ <div className="min-w-0 grow">
1147+ <p className="text-sm font-medium">
1148+ {building
1149+ ? preview
1150+ ? "Deploying the latest push. The last preview is still live."
1151+ : "Deploying a preview"
1152+ : failed
1153+ ? "The preview failed to deploy"
1154+ : "This branch is live"}
1155+ </p>
1156+ {preview ? (
1157+ <a href={preview.url} className="mt-0.5 block truncate font-mono text-xs text-muted hover:text-accent">
1158+ {preview.url.replace(/^https?:[/][/]/, "")}
1159+ </a>
1160+ ) : (
1161+ build?.error && <p className="mt-0.5 truncate text-xs text-muted">{build.error}</p>
1162+ )}
1163+ </div>
1164+ <div className="flex shrink-0 items-center gap-2">
1165+ {build && (
1166+ <Link to={`${base}/deployments/${build.id}`} className="text-xs text-muted hover:text-fg">
1167+ {failed ? "See why" : "Build log"}
1168+ </Link>
1169+ )}
1170+ {preview && (
1171+ <a
1172+ href={preview.url}
1173+ className="inline-flex items-center gap-1.5 rounded-md bg-accent px-3 py-1.5 text-xs font-medium text-bg hover:opacity-90"
1174+ >
1175+ Visit preview
1176+ <ArrowUpRight size={13} />
1177+ </a>
1178+ )}
1179+ </div>
1180+ </div>
1181+ {preview && (
1182+ <p className="border-t border-line px-4 py-2 text-xs text-faint">
1183+ <span className="font-mono">{preview.commit.slice(0, 7)}</span> · deployed <TimeAgo at={preview.deployedAt} />
1184+ {preview.branch && ` · from ${preview.branch}`}
1185+ </p>
1186+ )}
1187+ {stacked.length > 0 && (
1188+ <div className="border-t border-line px-4 py-2.5">
1189+ <p className="text-xs text-muted">Built against this change:</p>
1190+ <ul className="mt-1.5 space-y-1">
1191+ {stacked.map((entry) => (
1192+ <li key={entry.slug} className="flex items-center gap-2 text-xs">
1193+ <span className="font-medium">{entry.name}</span>
1194+ <a href={entry.url} className="truncate font-mono text-muted hover:text-accent">
1195+ {entry.url.replace(/^https?:[/][/]/, "")}
1196+ </a>
1197+ </li>
1198+ ))}
1199+ </ul>
1200+ </div>
1201+ )}
1202+ </section>
1203+ );
1204+}