Skip to content

Compare changes

Choose two branches to see what one has that the other does not, then open a pull request for it.

Open a pull request

1 commit

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+}