Skip to content
1,507 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.

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,
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb976 BookOpen,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API7 Bot,
8 Brain,
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb979 Globe,
10 Tag,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API11 Code2,
12 GitBranch,
13 GitCommitHorizontal,
14 GitMerge,
15 Hand,
16 HeartPulse,
17 Loader2,
18 Lock,
19 Network,
A project is an app or a library: libraries show their package and how to ship a release, not production20 Package as PackageGlyph,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API21 Pin,
22 Rocket,
23 RotateCw,
24} from "lucide-react";
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 2525import { waitUntil } from "cloudflare:workers";
26import { isbot } from "isbot";
Fast pages, required checks on the branch, self-hosted runners, honest incidents27import { type ReactNode, Suspense } from "react";
Merge branch 'worktree-agent-a985c01f43538af01'28import { Await, Form, Link } from "react-router";
Projects: what a workspace builds and runs, first on every page29
A project is an app or a library: libraries show their package and how to ship a release, not production30import {
31 type AgentRun,
32 type G1tEvent,
33 type Memory,
34 type PackageSummary,
35 type Project,
36 type Pull,
37 RUN_KIND_LABEL,
38 isActiveRun,
39 workOwner,
40} from "@g1t/contracts";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API41
Projects: what a workspace builds and runs, first on every page42import type { Route } from "./+types/overview";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit43import { InlineMarkdown } from "../../components/inline-markdown";
Projects: what a workspace builds and runs, first on every page44import { host, StatusDot } from "../../components/deploy";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API45import { CheckBadge } from "../../components/checks";
46import { Elapsed, formatCost, useLiveRefresh } from "../../components/agents";
47import { 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 look48import { type ActiveBranch, ActiveBranches } from "../../components/branches";
49import { ProductionChecklist } from "../../components/checklist";
A project is an app or a library: libraries show their package and how to ship a release, not production50import { PackageIcon } from "../../components/package-icon";
Merge branch 'worktree-agent-ae1299e92e4462012'51import { Hint } from "../../components/ui/hint";
Merge branch 'worktree-agent-aca4790ea223aa235'52import { Loading, Skeleton, SkeletonRows } from "../../components/ui/skeleton";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look53import { ProductionShot } from "../../components/production-shot";
54import { GithubLinkStrip } from "../../components/github";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit55import { distinctFacts } from "../../lib/memory-facts";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look56import { githubApp } from "../../lib/github.server";
Merge branch 'worktree-agent-a985c01f43538af01'57import { Avatar, ButtonLink, CopyLine, SubmitButton, TimeAgo } from "../../components/ui";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API58import { ChangeSize } from "../../components/work";
59import {
60 type ActivityItem,
61 type Need,
62 type PipelineStage,
63 PIPELINE,
64 TIME,
Site: a project's activity names people, not their account ids65 actorIds,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API66 ageBuckets,
67 eventItem,
68 firstPassRate,
69 groupActivity,
Site: a project's activity names people, not their account ids70 nameActor,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API71 passRate,
72 pipelineStage,
73 queuedNumbers,
74 rankNeeds,
75 stuckMinutes,
76} from "../../lib/mission";
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9777import { agentWasAssigned, checklistPlan, hasInstructions, productionChecklist, releaseChecklist, startChecklist } from "../../lib/checklist";
A project is an app or a library: libraries show their package and how to ship a release, not production78import { ECOSYSTEM_LABEL, installCommands } from "../../lib/packages";
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9779import {
80 PUBLISH_GUIDES,
81 bare,
82 hasRelease,
83 kindLabel,
84 linksToShow,
85 latestTag,
86 libraryPackages,
87 packageName,
88 packagePath,
89 projectChanges,
90 publishGuide,
91} from "../../lib/project-kind";
92import { AboutEditor, KindMenu, LinkList } from "../../components/project-about";
93import { DocsHead, ElsewhereHead, type ExternalDeployment, OtherHead, WhereItRuns } from "../../components/project-head";
Site: a project's activity names people, not their account ids94import { actions, agents, deployments, events as eventLog, identity, 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-agent95import { madeByG1t } from "../../lib/opened-by";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look96import { assertSameOrigin, getViewer, requireUser } from "../../lib/session.server";
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 2597import { accessTo, countsFor, projectFor, refusal, repoFor } from "../../lib/access.server";
Production thumbnails follow each deploy, not each commit, and old ones are cleared daily98import { shotVersion } from "./production-screenshot";
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9799import { DeploymentsPanel } from "../../components/deployments-panel";
100import { environmentUrl, productionEnvironment } from "../../lib/deployments";
Merge branch 'worktree-agent-aaf03bdceac799c89'101import { cloneUrl, useAddresses } from "../../lib/addresses";
Branches and Tags pages, each file's last commit, and the branch menu on files102import { readBranches } from "../../lib/branches.server";
Merge checks: statuses and check runs on every commit103import { commitChecksFor } from "../../lib/commit-checks.server";
104import { CommitChecksBadge } from "../../components/commit-checks";
Projects: what a workspace builds and runs, first on every page105
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API106const MAX_LANDED = 6;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look107/** Branches read for the Active branches list, and shown. */
108const BRANCHES_READ = 10;
109const BRANCHES_SHOWN = 5;
A project is an app or a library: libraries show their package and how to ship a release, not production110/** A library's packages shown on its overview; the rest are a link away. */
111const PACKAGES_SHOWN = 3;
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97112/** The default branch's latest commits listed. */
113const COMMITS_SHOWN = 5;
114/** How long Active branches may take before the section links to Branches instead. */
115const BRANCHES_WAIT_MS = 3_500;
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 25116/**
117 * The same for a crawler, which gets the page only once everything in it
118 * has settled (entry.server.tsx): past this, it gets the link to Branches.
119 */
120const BRANCHES_WAIT_CRAWLER_MS = 700;
Projects: what a workspace builds and runs, first on every page121
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily122/**
123 * The overview streams: the layout's header and tabs (one repository
124 * lookup, one project) go out at once with a skeleton below them, and
125 * everything here, seventeen calls across six services, follows in the
126 * same response as it settles. Crawlers wait for all of it
127 * (entry.server.tsx). Before, the first byte waited on the slowest of them.
128 */
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 25129export function loader({ params, context, request }: Route.LoaderArgs) {
130 const userAgent = request.headers.get("user-agent");
131 return { overview: overviewData({ params, context }, Boolean(userAgent && isbot(userAgent))) };
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily132}
133
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 25134async function overviewData({ params, context }: Pick<Route.LoaderArgs, "params" | "context">, crawler: boolean) {
Projects: what a workspace builds and runs, first on every page135 const viewer = getViewer(context);
136 const path = { namespace: params.owner, name: params.repo };
137 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 API138 const now = Date.now();
139 // A section whose service fails shows its own empty state; the page stays up.
140 const soft = <T,>(promise: Promise<T> | null): Promise<T | null> =>
141 promise ? promise.catch((error) => (console.warn("overview:", error), null)) : Promise.resolve(null);
Fast pages, required checks on the branch, self-hosted runners, honest incidents142 // People with a role of their own here see its running parts: deployments,
143 // memory, setup; everyone who can read it sees the rest. Not awaited: only
144 // those sections wait on the repository lookup, which the layout makes in
145 // this request too.
146 const accessP = accessTo(context, params);
147 const memberP = accessP.then((access) => access.insider);
148 const forMembers = <T,>(start: () => Promise<T>): Promise<T | null> =>
149 memberP.then((member) => (member ? soft(start()) : null));
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look150 const repoP = soft(repoFor(context, params));
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 25151 const projectP = soft(projectFor(context, params));
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97152 // A library or a tool shows its packages where an app shows production,
153 // and every project g1t does not deploy counts a workflow in its checklist.
154 // Deployments from anywhere (g1t.page, g1t Actions, the API), by environment, for anyone who can read it.
155 const environmentsP = projectP.then((found) =>
156 soft(deployments.environments(found?.ok && found.value.source.kind === "hosted" ? found.value.source.repo : path, viewer)),
A project is an app or a library: libraries show their package and how to ship a release, not production157 );
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97158 const planP = projectP.then((project) => (project?.ok ? checklistPlan(project.value).plan : null));
159 const libraryRepoP = Promise.all([planP, repoP]).then(([plan, repo]) => (plan === "release" && repo?.ok ? repo.value : null));
A project is an app or a library: libraries show their package and how to ship a release, not production160 const packagesP = libraryRepoP.then((repo) => (repo ? soft(packages.list(params.owner, viewer, { repoId: repo.id })) : null));
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97161 const workflowsP = planP.then((plan) => (plan && plan !== "production" ? 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 API162 const eventsP = repoP.then((repo) => (repo?.ok ? soft(eventLog.list({ repoId: repo.value.id, limit: 150 })) : null));
163 const minePullsP = repoP.then((repo) =>
164 repo?.ok && viewer
165 ? soft(work.listActivePulls(viewer)).then((list) => (list ?? []).filter((item) => item.pull.repoId === repo.value.id))
166 : null,
167 );
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look168 const openP = soft(work.listPulls(path, viewer, "open"));
169 // Where its code came from on GitHub, for members.
Fast pages, required checks on the branch, self-hosted runners, honest incidents170 const githubP = repoP.then((repo) => (repo?.ok ? forMembers(() => githubApp.link(repo.value.id)) : null));
171 const listP = forMembers(() => deployments.list(ref, viewer));
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look172 // Active branches: the newest few besides the default, with how far each
173 // has moved. Branches with an open pull request are read first.
174 const branchesP = Promise.all([repoP, soft(repos.branches(path, viewer)), openP, listP]).then(
175 async ([repo, branchList, pulls, deploys]): Promise<{ main: string; total: number; shown: ActiveBranch[] } | null> => {
176 if (!repo?.ok || !branchList?.ok) return null;
177 const main = repo.value.defaultBranch;
Branches and Tags pages, each file's last commit, and the branch menu on files178 if (branchList.value.every((branch) => branch.name === main)) return { main, total: 0, shown: [] };
179 const read = await readBranches(
180 path,
181 viewer,
182 {
183 defaultBranch: main,
184 branches: branchList.value,
185 pulls: pulls?.ok ? pulls.value : [],
186 previews: deploys?.ok ? deploys.value.live.filter((app) => app.kind === "preview") : [],
187 },
188 BRANCHES_READ,
189 );
190 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 look191 },
192 );
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 25193 // Active branches walk history when a branch or the default branch has
194 // moved: streamed, so the rest shows first. Bounded well inside the
195 // response's stream timeout (entry.server.tsx): a promise still pending
196 // when the stream ends never settles in the browser, and its skeleton
197 // would stay. The walk finishes after the page if it must (waitUntil),
198 // so repos keeps the answer and the next view has it. Crawlers, which
199 // wait for the whole page, wait for it only briefly.
200 waitUntil(branchesP.then(() => undefined, () => undefined));
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97201 const branches = Promise.race([
202 branchesP.catch(() => null),
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 25203 new Promise<"slow">((resolve) => setTimeout(() => resolve("slow"), crawler ? BRANCHES_WAIT_CRAWLER_MS : BRANCHES_WAIT_MS)),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97204 ]);
205 const [{ insider: member, can }, project, settings, list, open, closed, log, counts, deps, runs, queue, issues, memories, recent, mine, domains, root, packageList, workflows, tagList] = await Promise.all([
Fast pages, required checks on the branch, self-hosted runners, honest incidents206 accessP,
A project is an app or a library: libraries show their package and how to ship a release, not production207 projectP,
Fast pages, required checks on the branch, self-hosted runners, honest incidents208 forMembers(() => deployments.settings(ref, viewer)),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look209 listP,
210 openP,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API211 soft(work.listPulls(path, viewer, "closed")),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97212 soft(repos.log(path, viewer, null, COMMITS_SHOWN)),
Fast pages, required checks on the branch, self-hosted runners, honest incidents213 soft(countsFor(context, params)),
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API214 soft(projects.dependencies(params.owner, params.repo, viewer)),
215 soft(agents.listRuns(viewer, { repo: path, limit: 60 })),
216 soft(work.queue(path, viewer)),
217 soft(work.listIssues(path, viewer, { state: "open" })),
Fast pages, required checks on the branch, self-hosted runners, honest incidents218 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 API219 eventsP,
220 minePullsP,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look221 // For the checklist, which only members see.
Fast pages, required checks on the branch, self-hosted runners, honest incidents222 forMembers(() => deployments.domains(ref, viewer)),
223 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 production224 packagesP,
225 workflowsP,
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97226 // The latest release, for About.
227 soft(repos.tags(path, viewer)),
Projects: what a workspace builds and runs, first on every page228 ]);
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API229 const ok = <T,>(result: { ok: true; value: T } | { ok: false } | null): T | null => (result?.ok ? result.value : null);
230
231 const openPulls = ok(open) ?? [];
232 const runList = ok(runs) ?? [];
233 const live = runList.filter((run) => isActiveRun(run.status));
234 const builds = ok(list)?.deployments ?? [];
235 const landed = (ok(closed) ?? [])
236 .filter((pull) => pull.status === "merged")
237 .sort((a, b) => Date.parse(b.mergedAt ?? b.updatedAt) - Date.parse(a.mergedAt ?? a.updatedAt));
238 const queued = queuedNumbers(ok(queue)?.active ?? []);
239 const runOn = new Map<number, AgentRun>();
240 for (const run of live) if (run.number != null && !runOn.has(run.number)) runOn.set(run.number, run);
241 const lifecycleOf = new Map((mine ?? []).map((item) => [item.pull.number, item.lifecycle]));
242
243 // --- The pipeline -----------------------------------------------------------
244 type Card = {
245 number: number;
246 title: string;
247 agent: string;
248 status: Pull["status"];
249 checkStatus: Pull["checkStatus"];
250 step: string | null;
251 runKind: string | null;
252 runStarted: string | null;
253 needsYou: boolean;
254 at: string;
255 };
256 const columns: Record<PipelineStage, Card[]> = { working: [], checking: [], reviewing: [], queue: [], landed: [] };
257 for (const pull of openPulls.slice(0, 40)) {
258 const run = runOn.get(pull.number);
Fast pages, required checks on the branch, self-hosted runners, honest incidents259 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 API260 number: pull.number,
261 title: pull.title,
262 agent: pull.agent,
263 status: pull.status,
264 checkStatus: pull.checkStatus,
265 step: run?.step ?? null,
266 runKind: run ? RUN_KIND_LABEL[run.kind] : null,
267 runStarted: run ? (run.startedAt ?? run.createdAt) : null,
268 needsYou: lifecycleOf.get(pull.number)?.stage === "needs_you",
269 at: pull.updatedAt,
270 });
271 }
272 for (const pull of landed.filter((p) => now - Date.parse(p.mergedAt ?? p.updatedAt) < 3 * TIME.DAY).slice(0, 5)) {
273 columns.landed.push({
274 number: pull.number,
275 title: pull.title,
276 agent: pull.agent,
277 status: pull.status,
278 checkStatus: pull.checkStatus,
279 step: null,
280 runKind: null,
281 runStarted: null,
282 needsYou: false,
283 at: pull.mergedAt ?? pull.updatedAt,
284 });
285 }
286
287 // --- Needs you ---------------------------------------------------------------
288 const base = `/${params.owner}/${params.repo}`;
289 const needs: Need[] = [];
290 const latestProduction = builds.find((build) => build.kind === "production") ?? null;
291 if (latestProduction?.status === "failed") {
292 needs.push({
293 key: "deploy",
294 kind: "deploy",
295 title: "The production build failed",
296 detail: latestProduction.error ?? "Production still serves the build before it.",
297 to: `${base}/deployments/${latestProduction.id}`,
298 action: "See the build",
299 at: Date.parse(latestProduction.finishedAt ?? latestProduction.createdAt),
300 where: latestProduction.commit.slice(0, 7),
301 });
302 }
303 const me = viewer?.username.toLowerCase();
304 for (const pull of openPulls) {
305 const to = `${base}/pull/${pull.number}`;
306 const where = `#${pull.number}`;
307 const lifecycle = lifecycleOf.get(pull.number);
308 if (lifecycle?.stage === "needs_you") {
309 const conflict = /conflict/i.test(lifecycle.detail);
310 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 });
311 } else if (lifecycle?.stage === "ready") {
312 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 });
313 }
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights314 // Never your own, nor one g1t made for you; whoever it is for asked.
315 const owner = workOwner(pull).username;
316 if (me && pull.status === "open" && owner.toLowerCase() !== me && pull.reviewers.some((name) => name.toLowerCase() === me)) {
317 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 API318 }
319 if (member && pull.status === "open" && pull.checkStatus === "failed" && !runOn.has(pull.number) && !lifecycle) {
Fast pages, required checks on the branch, self-hosted runners, honest incidents320 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 API321 }
322 }
323 for (const run of live) {
324 const minutes = stuckMinutes(run, now);
325 if (minutes == null) continue;
326 needs.push({
327 key: `run:${run.id}`,
328 kind: "stuck",
329 title: run.title ?? `${run.agent}'s run`,
330 detail: `${run.agent} has reported nothing for ${minutes} min${run.step ? `. Last: ${run.step}` : ""}.`,
331 to: `${base}/agents/runs/${run.id}`,
332 action: "Look",
333 at: Date.parse(run.updatedAt),
334 where: run.number != null ? `#${run.number}` : null,
335 });
336 }
337
338 // --- Activity and health -------------------------------------------------------
339 const eventList: G1tEvent[] = recent ?? [];
Site: a project's activity names people, not their account ids340 const logged = eventList
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API341 .map((event) => eventItem(event, path))
342 .filter((item): item is ActivityItem => item != null);
Site: a project's activity names people, not their account ids343 // The log names people by account id: their usernames, in one lookup.
344 const ids = actorIds(logged.map((item) => item.actor));
345 const names = ids.length > 0 ? await soft(identity.usernames(ids)) : {};
346 const items = logged.map((item) => ({ ...item, actor: nameActor(item.actor, names) }));
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API347 const checkEvents = eventList.flatMap((event) =>
348 event.type === "checks.completed" ? [{ repo: "", number: event.data.number, at: Date.parse(event.time), passed: event.data.status === "passed" }] : [],
349 );
350 const memoryList = ok(memories)?.project ?? [];
Usage, Billing settings and prepaid AI credit; fixes from the UX audit351 // Pinned first, then the newest; a fact remembered twice, once.
352 const knows = distinctFacts([...memoryList.filter((m) => m.pinned), ...memoryList.filter((m) => !m.pinned).sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt))]).slice(0, 5);
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API353 const openIssues = ok(issues);
354
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look355 // --- Getting to production ----------------------------------------------------
356 const wentLive = (status: string) => status === "ready" || status === "replaced" || status === "down";
357 const liveApps = ok(list)?.live ?? [];
358 const commitNow = ok(log)?.[0] ?? null;
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97359 // Production deployed somewhere other than g1t.page (by g1t Actions, or
360 // reported through the API): the slot an app deployed elsewhere shows.
361 const environments = await environmentsP;
362 const environmentList = environments?.ok ? environments.value : null;
363 const reported = productionEnvironment(environmentList?.environments ?? []);
364 const deployment: ExternalDeployment | null =
365 reported?.latest && reported.latest.source !== "g1t_page"
366 ? {
367 environment: reported.name,
368 url: environmentUrl(reported),
369 state: reported.latest.state,
370 sha: reported.latest.sha,
371 created_at: reported.latest.updated_at,
372 }
373 : null;
374 // An app that has not said where it runs, but reports production from
375 // elsewhere, runs elsewhere.
376 const found = ok(project);
377 const projectValue = found && deployment && found.kind === "app" && found.runs == null ? { ...found, runs: "elsewhere" as const } : found;
378 const plan = projectValue ? checklistPlan(projectValue) : { plan: "production" as const, title: "Get to production" };
379 const isLibrary = plan.plan === "release";
A project is an app or a library: libraries show their package and how to ship a release, not production380 const published = libraryPackages(ok(packageList) ?? []);
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97381 const hasWorkflow = workflows?.ok ? workflows.value.length > 0 : null;
A project is an app or a library: libraries show their package and how to ship a release, not production382 const firstSteps = {
383 base,
384 hasCode: commitNow != null || projectValue?.source.kind === "mirror",
385 instructions: commitNow == null ? false : root?.ok ? hasInstructions(root.value.entries.map((entry) => entry.name)) : null,
386 agentAssigned: agentWasAssigned({
387 runAgents: runList.map((run) => run.agent),
388 pullAgents: [...openPulls, ...(ok(closed) ?? [])].map((pull) => pull.agent),
389 issues: openIssues ?? [],
390 }),
391 };
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97392 const links = projectValue?.links;
A project is an app or a library: libraries show their package and how to ship a release, not production393 const checklist = !member
394 ? null
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97395 : plan.plan !== "production" && plan.plan !== "release"
396 ? startChecklist(plan.plan, {
397 ...firstSteps,
398 hasWorkflow,
399 productionUrl: projectValue?.productionUrl ?? null,
400 hasLinks: !!links && (links.homepage != null || links.docs != null || links.custom.length > 0),
401 hasDocsLink: !!links && (links.docs != null || links.homepage != null),
402 })
A project is an app or a library: libraries show their package and how to ship a release, not production403 : isLibrary
404 ? releaseChecklist({
405 ...firstSteps,
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97406 hasWorkflow,
A project is an app or a library: libraries show their package and how to ship a release, not production407 released: hasRelease(published),
408 releaseTo: published[0]
409 ? packagePath(published[0])
410 : (publishGuide(projectValue?.ecosystem ?? null)?.guide ?? "https://docs.g1t.sh/guides/packages/"),
411 })
412 : productionChecklist({
413 ...firstSteps,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look414 deploysEnabled: ok(settings)?.enabled ?? false,
415 productionDeployed:
416 liveApps.some((app) => app.kind === "production") ||
417 builds.some((build) => build.kind === "production" && wentLive(build.status)),
418 domains: ok(domains)?.domains.length ?? null,
419 previewOpened:
420 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 production421 });
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look422
Merge checks: statuses and check runs on every commit423 // The checks on the latest commit and each active branch's head, in
424 // one call once the branches are read: streamed in beside them.
425 const checks = branches.then((found) =>
426 commitChecksFor(path, viewer, [commitNow?.hash, ...(found && found !== "slow" ? found.shown : []).map((branch) => branch.commit?.hash)]),
427 );
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look428 const github = await githubP;
Projects: what a workspace builds and runs, first on every page429 return {
430 member,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look431 can,
432 github,
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97433 project: projectValue,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API434 settings: ok(settings),
435 builds: builds.slice(0, 30),
436 live: ok(list)?.live ?? [],
437 commit: ok(log)?.[0] ?? null,
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97438 commits: (ok(log) ?? []).map((one) => ({ hash: one.hash, message: one.message.split("\n")[0] ?? "", author: one.author.name, at: one.authoredAt })),
439 release: latestTag(ok(tagList) ?? []),
440 checklistTitle: plan.title,
441 // Production as reported from outside g1t, for an app deployed elsewhere.
442 deployment,
443 // Every environment's latest deployment, for the Deployments panel.
444 environments: environmentList,
445 // About's languages and contributors, filled in by the files' About work.
446 languages: null as { name: string; share: number }[] | null,
447 contributors: null as { username: string }[] | null,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API448 open: ok(counts) ?? { issues: openIssues?.length ?? 0, pulls: openPulls.length },
449 dependencies: ok(deps),
450 agentsLive: live.slice(0, 6),
451 runsLoaded: runs?.ok ?? false,
452 pullsLoaded: open?.ok ?? false,
453 columns,
454 needs: rankNeeds(needs),
455 landed: landed.slice(0, MAX_LANDED),
456 groups: groupActivity(items).slice(0, 30),
457 eventsLoaded: recent != null,
458 health: {
459 passRate: passRate(checkEvents.map((c) => c.passed)),
460 firstPass: firstPassRate(checkEvents),
461 checkRuns: checkEvents.length,
462 ages: openIssues ? ageBuckets(openIssues.map((issue) => issue.createdAt), now) : null,
463 },
464 knows: knows as Memory[],
465 memoryCount: memoryList.length,
466 memoriesLoaded: memories?.ok ?? false,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look467 checklist,
A project is an app or a library: libraries show their package and how to ship a release, not production468 library: isLibrary
469 ? { packages: published.slice(0, PACKAGES_SHOWN), total: published.length, loaded: packageList?.ok ?? false }
470 : null,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look471 branches,
Merge checks: statuses and check runs on every commit472 checks,
Projects: what a workspace builds and runs, first on every page473 };
474}
475
476export async function action({ request, params, context }: Route.ActionArgs) {
477 assertSameOrigin(request);
478 const user = requireUser(context, request);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look479 const form = await request.formData().catch(() => null);
480 const intent = form?.get("intent");
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97481 // What it is, where it runs, its description and links: its settings.
482 if (form && (intent === "kind" || intent === "about")) {
483 const refused = await refusal(context, params, "manage_settings");
484 if (refused) return { error: refused };
485 const saved = await projects.update(user, params.owner, params.repo, projectChanges(form));
486 return saved.ok ? { notice: "Saved." } : { error: saved.error.message };
487 }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look488 // Syncing from GitHub is pushing; stopping it is an integration; deploying is compute.
489 const refused = await refusal(context, params, intent === "github-sync" ? "push" : intent === "github-stop" ? "manage_integrations" : "run");
490 if (refused) return { error: refused };
491 if (intent === "github-sync" || intent === "github-stop") {
492 const repo = await repos.get({ namespace: params.owner, name: params.repo }, user);
493 if (!repo.ok) return { error: repo.error.message };
494 const done =
495 intent === "github-sync" ? await githubApp.sync(user, repo.value.id) : await githubApp.unlinkRepo(user, repo.value.id);
496 return done.ok
497 ? { notice: intent === "github-sync" ? "Synced with GitHub." : "It is no longer kept in step with GitHub." }
498 : { error: done.error.message };
499 }
Projects: what a workspace builds and runs, first on every page500 const started = await deployments.redeploy(user, { workspace: params.owner, slug: params.repo }, null);
501 return started.ok ? { notice: "Production is building." } : { error: started.error.message };
502}
503
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily504type Loaded = Awaited<ReturnType<typeof overviewData>>;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API505type Card = Loaded["columns"]["working"][number];
506
507const STAGE_TONE: Record<PipelineStage, string> = {
Merge branch 'worktree-agent-ad7c6d88d93adc817'508 working: "bg-accent",
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API509 checking: "bg-info",
510 reviewing: "bg-warn",
Merge branch 'worktree-agent-ad7c6d88d93adc817'511 queue: "bg-success-dim",
512 landed: "bg-success",
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API513};
514
515function PipelineCard({ card, base, stage }: { card: Card; base: string; stage: PipelineStage }) {
516 return (
517 <li>
518 <Link
519 to={`${base}/pull/${card.number}`}
520 prefetch="intent"
521 className={`block rounded-lg border bg-bg/60 p-2.5 transition-colors hover:border-line-strong hover:bg-raised ${
Merge branch 'worktree-agent-ad7c6d88d93adc817'522 card.needsYou ? "border-warn/50" : card.step ? "border-accent/30" : "border-line"
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API523 }`}
524 >
525 <span className="line-clamp-2 text-[0.8125rem] leading-snug font-medium">{card.title}</span>
526 <span className="mt-1.5 flex items-center gap-1.5 text-[0.6875rem] text-muted">
527 <Avatar name={card.agent} size={14} />
528 <span className="font-mono text-faint">#{card.number}</span>
529 <span className="grow" />
530 {card.needsYou ? (
Merge branch 'worktree-agent-ae1299e92e4462012'531 <Hint label="Stopped: a person decides what happens next">
532 <span className="inline-flex items-center gap-1 text-warn">
533 <Hand size={11} /> you
534 <span className="sr-only">: stopped, a person decides what happens next</span>
535 </span>
536 </Hint>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API537 ) : stage === "landed" ? (
538 <TimeAgo at={card.at} />
539 ) : card.runStarted ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'540 <span className="inline-flex items-center gap-1 text-accent">
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API541 <Loader2 size={11} className="animate-spin" />
542 <Elapsed from={card.runStarted} />
543 </span>
544 ) : (
545 <CheckBadge status={card.checkStatus} />
546 )}
547 </span>
548 {card.step && (
Merge branch 'worktree-agent-ae1299e92e4462012'549 <Hint label={card.step}>
550 <span className="mt-1.5 block truncate font-mono text-[0.6875rem] text-fg/70">
551 {card.runKind}: {card.step}
552 </span>
553 </Hint>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API554 )}
555 </Link>
556 </li>
557 );
558}
559
560/** Open pull requests moving through Working → Checking → Reviewing → Queue → Landed. */
561function Pipeline({ columns, base }: { columns: Loaded["columns"]; base: string }) {
562 return (
563 <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 look564 {/* Each stage as tall as what is in it: an empty one stays short
565 instead of stretching to the busiest. */}
566 <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 API567 {PIPELINE.map(({ stage, label }, index) => {
568 const cards = columns[stage];
569 return (
570 <li key={stage} className="flex min-w-0 flex-col rounded-xl border border-line bg-surface p-2">
571 <p className="flex items-center gap-2 px-1 pt-0.5 pb-2 text-xs font-medium text-muted">
572 <span className={`size-1.5 rounded-full ${STAGE_TONE[stage]} ${cards.length && stage !== "landed" ? "animate-pulse" : ""}`} />
573 {label}
574 <span className="tabular-nums text-faint">{cards.length}</span>
575 {index < PIPELINE.length - 1 && <ArrowRight size={11} className="ml-auto text-line-strong" />}
576 </p>
577 {cards.length === 0 ? (
578 <p className="rounded-lg border border-dashed border-line/70 px-2 py-3 text-center text-[0.6875rem] text-faint">Empty</p>
579 ) : (
580 <ul className="space-y-1.5">
581 {cards.slice(0, 6).map((card) => (
582 <PipelineCard key={card.number} card={card} base={base} stage={stage} />
583 ))}
584 {cards.length > 6 && (
585 <li className="px-1 text-[0.6875rem] text-muted">
586 <Link to={`${base}/pulls`} className="hover:text-fg">
587 {cards.length - 6} more
588 </Link>
589 </li>
590 )}
591 </ul>
592 )}
593 </li>
594 );
595 })}
596 </ol>
597 </div>
598 );
599}
600
A project is an app or a library: libraries show their package and how to ship a release, not production601/**
602 * Where an app's overview shows production, a library's shows what it
603 * publishes: each package with its latest version and how to install it,
604 * or, before there is one, how to publish a first version.
605 */
606function LibraryHead({
607 library,
608 project,
609 base,
610 workspace,
611}: {
612 library: { packages: PackageSummary[]; total: number; loaded: boolean };
613 project: Project | null;
614 base: string;
615 workspace: string;
616}) {
617 const guide = publishGuide(project?.ecosystem ?? null);
618 const name = project?.name ?? base.split("/").pop();
619 return (
620 <div className="p-5 sm:p-6">
621 <p className="flex items-center gap-2 text-xs font-medium tracking-wide text-muted uppercase">
622 <PackageGlyph size={13} className="text-accent" />
623 Packages
624 </p>
625 {library.packages.length > 0 ? (
626 <>
627 <ul className="mt-3 space-y-4">
628 {library.packages.map((pkg) => {
629 const commands = installCommands(pkg, pkg.latest, "you");
630 return (
631 <li key={pkg.id} className="flex flex-col gap-2.5 lg:flex-row lg:items-start lg:gap-5">
632 <Link to={packagePath(pkg)} className="group flex min-w-0 items-center gap-3 lg:w-72 lg:shrink-0">
633 <PackageIcon ecosystem={pkg.ecosystem} size={32} />
634 <span className="min-w-0">
635 <span className="block truncate font-mono text-[0.9375rem] font-medium group-hover:text-accent">{packageName(pkg)}</span>
636 <span className="block text-xs text-muted">
637 {ECOSYSTEM_LABEL[pkg.ecosystem]} · {pkg.latest ?? "no versions yet"}
638 {pkg.versions > 1 && ` · ${pkg.versions} versions`}
639 </span>
640 </span>
641 </Link>
642 <div className="min-w-0 grow space-y-1.5">
643 {/* The workspace's registry first, or the tool installs a package of the same name from elsewhere. */}
644 {commands.registry && <CopyLine prompt text={commands.registry} />}
645 <CopyLine prompt text={commands.install} />
646 </div>
647 </li>
648 );
649 })}
650 </ul>
651 {library.total > library.packages.length && (
652 <Link to={`/${workspace}/-/packages`} className="mt-3 inline-block text-xs text-muted hover:text-fg">
653 All {library.total} packages
654 </Link>
655 )}
656 </>
657 ) : (
658 <>
659 <p className="mt-2 text-lg font-medium">{library.loaded ? "Not published yet" : "Packages could not be loaded just now"}</p>
660 {guide ? (
661 <>
662 <p className="mt-1 max-w-lg text-sm text-muted">
663 Publish a first version of {name} to the workspace's {guide.label} registry, for others to install
664 {guide.start ? ":" : "."}
665 </p>
666 {guide.start && (
667 <div className="mt-3 max-w-lg">
668 <CopyLine prompt text={guide.start} />
669 </div>
670 )}
671 <a href={guide.guide} className="mt-3 inline-flex items-center gap-1 text-sm text-accent hover:underline">
672 Publishing to {guide.label} <ArrowUpRight size={13} />
673 </a>
674 </>
675 ) : (
676 <>
677 <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>
678 <p className="mt-2 flex flex-wrap gap-x-4 gap-y-1 text-sm">
679 {PUBLISH_GUIDES.map((one) => (
680 <a key={one.label} href={one.guide} className="inline-flex items-center gap-1 text-accent hover:underline">
681 {one.label} <ArrowUpRight size={13} />
682 </a>
683 ))}
684 </p>
685 </>
686 )}
687 </>
688 )}
689 </div>
690 );
691}
692
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API693function Stat({ label, value, to }: { label: string; value: ReactNode; to?: string }) {
694 const body = (
695 <>
696 <span className="block text-[0.6875rem] text-faint">{label}</span>
697 <span className="mt-0.5 block truncate text-[0.8125rem] font-medium">{value}</span>
698 </>
699 );
700 return to ? (
701 <Link to={to} className="min-w-0 px-5 py-3 transition-colors hover:bg-raised/50">
702 {body}
703 </Link>
704 ) : (
705 <div className="min-w-0 px-5 py-3">{body}</div>
706 );
707}
708
Projects: what a workspace builds and runs, first on every page709export default function ProjectOverview({ loaderData, actionData, params }: Route.ComponentProps) {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily710 return (
711 <Suspense fallback={<OverviewSkeleton />}>
712 <Await resolve={loaderData.overview} errorElement={<OverviewFailed />}>
Merge branch 'worktree-agent-aca4790ea223aa235'713 {(overview) => (
714 // Faded in over its outline, not switched for it.
715 <div className="animate-fade-in">
716 <Overview loaderData={overview} actionData={actionData} params={params} />
717 </div>
718 )}
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily719 </Await>
720 </Suspense>
721 );
722}
723
724/** The overview's sections as they will sit, while they stream in. */
725function OverviewSkeleton() {
726 return (
Merge branch 'worktree-agent-aca4790ea223aa235'727 <Loading className="space-y-8" aria-label="Loading the overview">
728 {/* Production: its screenshot beside where it is live, then the facts under them. */}
729 <section aria-hidden="true" className="overflow-hidden rounded-2xl border border-line bg-surface">
730 <div className="flex flex-col gap-5 p-5 sm:flex-row sm:items-start sm:p-6">
731 <Skeleton className="aspect-[16/10] w-full shrink-0 rounded-lg sm:w-72" />
732 <div className="min-w-0 grow">
733 <Skeleton className="h-3 w-24" />
734 <Skeleton className="mt-3 h-5 w-72 max-w-full" />
735 <Skeleton className="mt-3 h-3 w-48 max-w-full" />
736 </div>
737 </div>
738 <div className="grid grid-cols-2 gap-px border-t border-line bg-line sm:grid-cols-4">
739 {Array.from({ length: 4 }, (_, index) => (
740 <div key={index} className="bg-surface px-5 py-4">
741 <Skeleton className="h-3 w-16" />
742 <Skeleton className="mt-2.5 h-4 w-28" />
743 </div>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily744 ))}
745 </div>
746 </section>
Merge branch 'worktree-agent-aca4790ea223aa235'747 {/* Right now: the work, column by column. */}
748 <div aria-hidden="true" className="grid grid-cols-2 gap-2 sm:grid-cols-5">
749 {Array.from({ length: 5 }, (_, index) => (
750 <div key={index} className="rounded-xl border border-line bg-surface p-3">
751 <Skeleton className="h-3 w-20" />
752 <Skeleton className="mt-3 h-14 rounded-lg" />
753 </div>
754 ))}
755 </div>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily756 <div className="grid gap-6 lg:grid-cols-2">
757 <section className="rounded-2xl border border-line bg-surface">
758 <SkeletonRows rows={4} />
759 </section>
760 <section className="rounded-2xl border border-line bg-surface">
761 <SkeletonRows rows={4} />
762 </section>
763 </div>
Merge branch 'worktree-agent-aca4790ea223aa235'764 </Loading>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily765 );
766}
767
768function OverviewFailed() {
769 return (
770 <p className="rounded-2xl border border-line bg-surface p-6 text-sm text-muted">
771 The overview could not be loaded just now. Reload the page to try again.
772 </p>
773 );
774}
775
776function Overview({
777 loaderData,
778 actionData,
779 params,
780}: {
781 loaderData: Loaded;
782 actionData: Route.ComponentProps["actionData"];
783 params: Route.ComponentProps["params"];
784}) {
Merge checks: statuses and check runs on every commit785 const { member, project, settings, builds, live, commit, open, dependencies, agentsLive, columns, needs, landed, groups, health, knows, checklist, branches, library, checks } =
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look786 loaderData;
Projects: what a workspace builds and runs, first on every page787 const base = `/${params.owner}/${params.repo}`;
788 const production = live.find((app) => app.kind === "production") ?? null;
Agents and memory, checks and conflicts, profiles, slug renames, custom domains789 // The project's own domain, once it is active, is where production is visited.
790 const productionUrl = production ? (settings?.primaryDomain ? `https://${settings.primaryDomain}` : production.url) : null;
Projects: what a workspace builds and runs, first on every page791 const previews = live.filter((app) => app.kind === "preview");
792 const latestProduction = builds.find((build) => build.kind === "production") ?? null;
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97793 const plan = project ? checklistPlan(project).plan : "production";
794 const canChange = member && loaderData.can.manage_settings;
795 // Its homepage and docs beside what it is, unless the card below already links there.
796 const headUrl =
797 plan === "production"
798 ? (productionUrl ?? settings?.productionUrl)
799 : plan === "elsewhere"
800 ? (loaderData.deployment?.url ?? project?.productionUrl)
801 : plan === "docs"
802 ? (project?.links.docs ?? project?.productionUrl ?? project?.links.homepage)
803 : plan === "other"
804 ? (project?.links.homepage ?? project?.links.docs)
805 : null;
806 const stripLinks = project ? linksToShow({ ...project.links, custom: [] }, [headUrl]) : [];
Merge branch 'worktree-agent-aaf03bdceac799c89'807 const addresses = useAddresses();
Projects: what a workspace builds and runs, first on every page808 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 API809 const moving = agentsLive.length > 0 || columns.working.length + columns.checking.length > 0;
810 useLiveRefresh(moving);
811 const ages = health.ages;
812 const oldest = ages ? Math.max(1, ...ages.map((a) => a.count)) : 1;
Projects: what a workspace builds and runs, first on every page813
814 return (
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API815 <div className="space-y-8">
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look816 {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 API817 {/* What the project is, running, and where its code is. */}
818 <section className="overflow-hidden rounded-2xl border border-line bg-surface">
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97819 {project && (
820 <div className="flex flex-wrap items-center gap-x-4 gap-y-2 border-b border-line px-5 py-2.5 text-xs sm:px-6">
821 <KindMenu project={project} label={kindLabel(project)} canChange={canChange} />
822 {stripLinks.map((link) => (
823 <a key={link.key} href={link.url} rel="noopener noreferrer" className="inline-flex min-w-0 items-center gap-1.5 text-muted hover:text-fg">
824 {link.type === "docs" ? <BookOpen size={12} className="shrink-0 text-faint" /> : <Globe size={12} className="shrink-0 text-faint" />}
825 <span className="truncate">{link.type === "docs" ? `Docs · ${bare(link.url)}` : link.label}</span>
826 </a>
827 ))}
828 </div>
829 )}
830 {plan === "elsewhere" && project ? (
831 <ElsewhereHead
832 project={project}
833 base={base}
834 canChange={canChange}
835 canDeploy={member && loaderData.can.manage_integrations}
836 deployment={loaderData.deployment}
837 commit={commit}
Merge checks: statuses and check runs on every commit838 checks={commit ? <CommitChecksBadge checks={checks} sha={commit.hash} className="size-5" /> : undefined}
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97839 />
840 ) : plan === "unknown" && project ? (
841 <WhereItRuns project={project} base={base} canChange={canChange} canDeploy={member && loaderData.can.manage_integrations} />
842 ) : plan === "docs" && project ? (
843 <DocsHead project={project} base={base} canChange={canChange} canDeploy={member && loaderData.can.manage_integrations} />
844 ) : plan === "other" && project ? (
845 <OtherHead project={project} base={base} canChange={canChange} />
846 ) : library ? (
847 <LibraryHead library={library} project={project} base={base} workspace={params.owner} />
A project is an app or a library: libraries show their package and how to ship a release, not production848 ) : (
849 <div className="flex flex-col gap-5 p-5 sm:flex-row sm:items-start sm:p-6">
850 {member && (production || settings?.enabled) && (
851 <ProductionShot
852 src={production ? `${base}/production.jpg?v=${shotVersion(production)}` : null}
853 href={production ? (productionUrl ?? production.url) : null}
854 label={host(production ? (productionUrl ?? production.url) : (settings?.productionUrl ?? ""))}
855 className="w-full shrink-0 sm:w-60 lg:w-72"
856 />
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API857 )}
A project is an app or a library: libraries show their package and how to ship a release, not production858 <div className="flex min-w-0 grow flex-wrap items-start justify-between gap-4">
859 <div className="min-w-0">
860 <p className="flex items-center gap-2 text-xs font-medium tracking-wide text-muted uppercase">
861 <Rocket size={13} className="text-accent" />
862 Production
863 </p>
864 {production ? (
865 <>
866 <a
867 href={productionUrl ?? production.url}
868 className="mt-2 flex items-center gap-1.5 truncate font-mono text-lg font-medium hover:text-accent"
869 >
870 {host(productionUrl ?? production.url)}
871 <ArrowUpRight size={16} className="shrink-0 text-faint" />
872 </a>
873 <p className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted">
874 {latestProduction && <StatusDot status={latestProduction.status} label={latestProduction.status === "ready" ? "Live" : undefined} />}
875 <span className="inline-flex items-center gap-1 font-mono">
876 <GitCommitHorizontal size={13} className="text-faint" />
877 {production.commit.slice(0, 7)}
878 </span>
879 <span>
880 deployed <TimeAgo at={production.deployedAt} />
881 </span>
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97882 {settings?.primaryDomain && <span className="text-faint">also at {host(production.url)}</span>} </p>
A project is an app or a library: libraries show their package and how to ship a release, not production883 </>
884 ) : settings?.enabled ? (
885 <>
886 <p className="mt-2 font-mono text-lg text-muted">{host(settings.productionUrl)}</p>
887 <p className="mt-1.5 text-xs text-muted">
888 {latestProduction ? (
889 <StatusDot status={latestProduction.status} label={latestProduction.status === "failed" ? "The last build failed" : undefined} />
890 ) : (
891 "Not deployed yet. Push to the default branch, or deploy it now."
892 )}
893 </p>
894 </>
895 ) : (
896 <>
897 <p className="mt-2 text-lg font-medium">Deployments are off</p>
898 <p className="mt-1 max-w-lg text-sm text-muted">
899 Nothing builds or runs until you turn them on. Then {project?.name ?? params.repo} goes up on g1t.page:
900 production from the default branch, and a live preview for every pull request. It runs only while
901 someone visits.
902 </p>
903 </>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API904 )}
Projects: what a workspace builds and runs, first on every page905 </div>
A project is an app or a library: libraries show their package and how to ship a release, not production906 {member && (
907 <div className="flex shrink-0 items-center gap-2">
908 {production && (
909 <ButtonLink to={productionUrl ?? production.url} variant="accent" reloadDocument>
910 Visit
911 <ArrowUpRight size={14} />
912 </ButtonLink>
913 )}
914 {settings?.enabled ? (
915 loaderData.can.run && <Form method="post">
Merge branch 'worktree-agent-ae1299e92e4462012'916 <Hint label="Build production again from the default branch">
917 <SubmitButton variant="quiet" name="intent" value="redeploy" pending="Redeploying…">
918 <RotateCw size={14} />
919 Redeploy
920 </SubmitButton>
921 </Hint>
A project is an app or a library: libraries show their package and how to ship a release, not production922 </Form>
923 ) : loaderData.can.manage_integrations && (
924 <ButtonLink to={`${base}/settings/deployments`} variant="accent">
925 <Rocket size={14} />
926 Turn on deployments
927 </ButtonLink>
928 )}
929 </div>
930 )}
931 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look932 </div>
A project is an app or a library: libraries show their package and how to ship a release, not production933 )}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API934 {actionData && (
Merge branch 'worktree-agent-ad7c6d88d93adc817'935 <p className={`border-t border-line px-6 py-2.5 text-sm ${"error" in actionData ? "text-danger" : "text-success"}`}>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API936 {"error" in actionData ? actionData.error : actionData.notice}
937 </p>
938 )}
939 <div className="grid grid-cols-2 divide-line border-t border-line text-sm sm:grid-cols-4 sm:divide-x">
940 <Stat
941 label="Source"
942 to={`${base}/code`}
943 value={
944 source ? (
945 <span className="inline-flex items-center gap-1.5 font-mono">
946 {project?.private ? <Lock size={12} className="text-faint" /> : <Code2 size={12} className="text-faint" />}
947 {source.repo.name}
948 {source.rootDir ? `/${source.rootDir}` : ""}
949 </span>
950 ) : (
951 "—"
952 )
953 }
954 />
955 <Stat
956 label="Default branch"
957 to={`${base}/commits`}
958 value={
959 <span className="inline-flex items-center gap-1.5 font-mono">
960 <GitBranch size={12} className="text-faint" />
961 {source?.defaultBranch ?? "main"}
962 </span>
963 }
964 />
965 <Stat
966 label="Latest commit"
967 value={
968 commit ? (
Merge checks: statuses and check runs on every commit969 <span className="flex min-w-0 items-center gap-1">
970 <Hint label={commit.message.split("\n")[0]}>
971 <Link to={`${base}/commit/${commit.hash}`} className="min-w-0 truncate hover:text-accent">
972 <span className="font-mono">{commit.hash.slice(0, 7)}</span>{" "}
973 <span className="font-normal text-muted">
974 <TimeAgo at={commit.authoredAt} />
975 </span>
976 </Link>
977 </Hint>
978 {/* A button, so beside the link rather than in it. */}
979 <CommitChecksBadge checks={checks} sha={commit.hash} className="size-5" />
980 </span>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API981 ) : (
982 "No commits yet"
983 )
984 }
985 />
986 <Stat
A project is an app or a library: libraries show their package and how to ship a release, not production987 label={member && settings?.enabled && !library ? "Last deploy" : "Open"}
988 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 API989 value={
A project is an app or a library: libraries show their package and how to ship a release, not production990 member && settings?.enabled && !library ? (
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API991 latestProduction ? (
992 <span className="inline-flex items-center gap-2">
993 <StatusDot status={latestProduction.status} />
994 <span className="font-normal text-muted">
995 <TimeAgo at={latestProduction.createdAt} />
Projects: what a workspace builds and runs, first on every page996 </span>
997 </span>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API998 ) : (
999 "None yet"
1000 )
1001 ) : (
1002 `${open.pulls} pull requests · ${open.issues} issues`
1003 )
1004 }
1005 />
1006 </div>
1007 {commit && (
1008 <p className="truncate border-t border-line px-5 py-2.5 text-xs text-muted sm:px-6">
1009 <GitCommitHorizontal size={12} className="mr-1.5 inline text-faint" />
1010 {commit.message.split("\n")[0]} <span className="text-faint">· {commit.author.name}</span>
1011 </p>
1012 )}
1013 </section>
1014
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971015 {checklist && <ProductionChecklist base={base} items={checklist} title={loaderData.checklistTitle} />}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1016
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1017 <Panel
1018 title="Right now"
Merge branch 'worktree-agent-ad7c6d88d93adc817'1019 icon={<span className={`block size-2 rounded-full ${agentsLive.length > 0 ? "animate-pulse bg-accent" : "bg-line-strong"}`} />}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1020 count={agentsLive.length}
1021 all={{ to: `${base}/agents`, label: "Agents" }}
1022 >
1023 {agentsLive.length > 0 && (
1024 <ul className="mb-3 grid grid-cols-1 gap-2 sm:grid-cols-2 xl:grid-cols-3">
1025 {agentsLive.map((run) => (
Merge branch 'worktree-agent-ad7c6d88d93adc817'1026 <li key={run.id} className="min-w-0 rounded-xl border border-accent/30 bg-surface px-3.5 py-3">
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1027 <div className="flex items-center gap-2 text-sm">
1028 <Avatar name={run.agent} size={18} />
1029 <span className="font-medium">{run.agent}</span>
1030 <span className="text-xs text-muted">{RUN_KIND_LABEL[run.kind].toLowerCase()}</span>
1031 <span className="grow" />
1032 <span className="text-xs text-muted">
1033 <Elapsed from={run.startedAt ?? run.createdAt} />
Projects: what a workspace builds and runs, first on every page1034 </span>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1035 </div>
1036 <Link
1037 to={run.number != null ? `${base}/pull/${run.number}` : `${base}/agents/runs/${run.id}`}
1038 className="mt-1 block truncate text-[0.8125rem] hover:text-accent"
1039 >
1040 {run.title ?? "A run"} {run.number != null && <span className="text-faint">#{run.number}</span>}
1041 </Link>
1042 {run.step && (
Merge branch 'worktree-agent-ae1299e92e4462012'1043 <Hint label={run.step}>
1044 <p className="mt-1 truncate font-mono text-[0.6875rem] text-fg/70">
Merge branch 'worktree-agent-ad7c6d88d93adc817'1045 <span className="mr-1.5 inline-block size-1.5 animate-pulse rounded-full bg-accent align-middle" />
Merge branch 'worktree-agent-ae1299e92e4462012'1046 {run.step}
1047 </p>
1048 </Hint>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1049 )}
1050 <p className="mt-1.5 flex items-center gap-3 text-[0.6875rem] text-faint">
1051 {formatCost(run.costUsd) && <span>{formatCost(run.costUsd)} so far</span>}
1052 <Link to={`${base}/agents/runs/${run.id}`} className="hover:text-fg">
1053 {run.stepCount} steps
1054 </Link>
1055 {run.number != null && (
1056 <Link to={`${base}/sessions/${run.number}`} className="hover:text-fg">
1057 Session
1058 </Link>
1059 )}
1060 </p>
1061 </li>
1062 ))}
1063 </ul>
1064 )}
1065 {!loaderData.pullsLoaded ? (
1066 <Unavailable what="Pull requests" />
1067 ) : Object.values(columns).every((c) => c.length === 0) ? (
1068 <Quiet
1069 action={
1070 member ? (
1071 <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">
1072 <Bot size={12} /> Open an issue for an agent
1073 </Link>
1074 ) : null
1075 }
1076 >
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1077 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 API1078 </Quiet>
1079 ) : (
1080 <Pipeline columns={columns} base={base} />
Projects: what a workspace builds and runs, first on every page1081 )}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1082 </Panel>
Projects: what a workspace builds and runs, first on every page1083
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1084 <div className="grid gap-x-8 gap-y-8 lg:grid-cols-[minmax(0,1fr)_20rem]">
1085 <div className="min-w-0 space-y-8">
1086 {needs.length > 0 && (
1087 <Panel title="Needs you" icon={<Hand size={14} className="text-warn" />} count={needs.length}>
1088 <NeedsList needs={needs} limit={5} />
1089 </Panel>
1090 )}
1091
Fast pages, required checks on the branch, self-hosted runners, honest incidents1092 <Suspense
1093 fallback={
1094 <Panel title="Active branches" icon={<GitBranch size={14} />}>
1095 <div aria-busy="true">
1096 <SkeletonRows rows={3} rowClassName="h-12" />
1097 </div>
1098 </Panel>
1099 }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1100 >
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971101 <Await
1102 resolve={branches}
1103 errorElement={
1104 <Panel title="Active branches" icon={<GitBranch size={14} />}>
1105 <Unavailable what="Branches" />
1106 </Panel>
1107 }
1108 >
Fast pages, required checks on the branch, self-hosted runners, honest incidents1109 {(branches) => (
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971110 <Panel
1111 title="Active branches"
1112 icon={<GitBranch size={14} />}
1113 count={branches && branches !== "slow" ? branches.total || null : null}
1114 all={{ to: `${base}/branches`, label: "All branches" }}
1115 >
1116 {branches === "slow" ? (
1117 <Quiet>
1118 Its branches are taking a while to compare.{" "}
1119 <Link to={`${base}/branches`} className="text-accent hover:underline">
1120 See them on Branches
1121 </Link>
1122 .
1123 </Quiet>
1124 ) : !branches ? (
Fast pages, required checks on the branch, self-hosted runners, honest incidents1125 <Unavailable what="Branches" />
1126 ) : branches.shown.length === 0 ? (
1127 <Quiet>
1128 Only {branches.main} so far. Branches pushed here show with how far each has moved from {branches.main}, and
1129 its pull request and preview.
1130 </Quiet>
1131 ) : (
1132 <>
Merge checks: statuses and check runs on every commit1133 <ActiveBranches branches={branches.shown} base={base} main={branches.main} checks={checks} />
Fast pages, required checks on the branch, self-hosted runners, honest incidents1134 {branches.total > branches.shown.length && (
1135 <p className="mt-2 px-1 text-xs text-faint">
1136 The {branches.shown.length} most recently changed of {branches.total} branches.
1137 </p>
1138 )}
1139 </>
1140 )}
1141 </Panel>
1142 )}
1143 </Await>
1144 </Suspense>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1145
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1146 <Panel title="Recent changes" icon={<GitMerge size={14} />} all={{ to: `${base}/pulls?state=closed`, label: "All landed" }}>
1147 {landed.length === 0 ? (
1148 <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 page1149 ) : (
1150 <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 API1151 {landed.map((pull) => {
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1152 const byAgent = pull.runtime === "hosted" || madeByG1t(pull);
Projects: what a workspace builds and runs, first on every page1153 return (
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1154 <li key={pull.id} className="flex items-start gap-3 px-4 py-3">
1155 <GitMerge size={15} className="mt-0.5 shrink-0 text-merged" />
1156 <span className="min-w-0 grow">
1157 <Link to={`${base}/pull/${pull.number}`} prefetch="intent" className="block truncate text-sm font-medium hover:text-accent">
1158 {pull.title}
1159 </Link>
1160 <span className="mt-0.5 flex flex-wrap items-center gap-x-1.5 text-xs text-muted">
1161 <span className="font-mono text-faint">#{pull.number}</span>
1162 <span>·</span>
1163 <span className="inline-flex items-center gap-1">
1164 <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 rights1165 {pull.requestedBy
1166 ? `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-agent1167 : byAgent
1168 ? `made by ${pull.agent}`
1169 : `by ${pull.author.username}`}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1170 </span>
1171 {pull.issue != null && (
1172 <>
1173 <span>· for</span>
1174 <Link to={`${base}/issues/${pull.issue}`} className="hover:text-fg">
1175 #{pull.issue}
1176 </Link>
1177 </>
1178 )}
1179 {pull.mergedBy && <span>· landed by {pull.mergedBy}</span>}
1180 <span>
1181 · <TimeAgo at={pull.mergedAt ?? pull.updatedAt} />
Projects: what a workspace builds and runs, first on every page1182 </span>
1183 </span>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1184 </span>
1185 <span className="hidden shrink-0 text-xs text-faint sm:block">
1186 <ChangeSize files={pull.files} />
1187 </span>
1188 {byAgent && (
1189 <Link to={`${base}/sessions/${pull.number}`} className="shrink-0 text-xs text-muted hover:text-fg">
1190 Session
1191 </Link>
1192 )}
Projects: what a workspace builds and runs, first on every page1193 </li>
1194 );
1195 })}
1196 </ul>
1197 )}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1198 </Panel>
Projects: what a workspace builds and runs, first on every page1199
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971200 {loaderData.commits.length > 0 && (
1201 <Panel
1202 title={`Latest on ${source?.defaultBranch ?? "main"}`}
1203 icon={<GitCommitHorizontal size={14} />}
1204 all={{ to: `${base}/commits`, label: "All commits" }}
1205 >
1206 <ul className="divide-y divide-line overflow-hidden rounded-xl border border-line bg-surface">
1207 {loaderData.commits.map((one) => (
1208 <li key={one.hash} className="flex items-center gap-3 px-4 py-2.5 text-sm">
1209 <Avatar name={one.author} size={18} />
1210 <span className="min-w-0 grow">
1211 <Link to={`${base}/commit/${one.hash}`} className="block truncate hover:text-accent">
1212 {one.message}
1213 </Link>
1214 <span className="text-xs text-muted">{one.author}</span>
1215 </span>
1216 <Link to={`${base}/commit/${one.hash}`} className="hidden shrink-0 font-mono text-xs text-faint hover:text-fg sm:block">
1217 {one.hash.slice(0, 7)}
1218 </Link>
1219 <span className="w-16 shrink-0 text-right text-xs text-faint">
1220 <TimeAgo at={one.at} />
1221 </span>
1222 </li>
1223 ))}
1224 </ul>
1225 </Panel>
1226 )}
1227
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1228 <Panel title="Activity" icon={<ActivityIcon size={14} />}>
1229 {loaderData.eventsLoaded ? (
1230 <ActivityFeed groups={groups} showRepo={false} limit={8} empty="Nothing has happened here yet." />
1231 ) : (
1232 <Unavailable what="Activity" />
1233 )}
1234 </Panel>
1235
1236 {previews.length > 0 && (
1237 <Panel title="Previews" icon={<GitBranch size={14} />} count={previews.length} all={{ to: `${base}/deployments`, label: "Deployments" }}>
1238 <ul className="divide-y divide-line overflow-hidden rounded-xl border border-line bg-surface">
1239 {previews.map((app) => (
1240 <li key={app.url} className="flex items-center gap-3 px-4 py-2.5 text-sm">
1241 <span className="min-w-0 grow">
1242 <a href={app.url} className="block truncate font-mono text-[0.8125rem] hover:text-accent">
1243 {host(app.url)}
1244 </a>
1245 <span className="text-xs text-muted">
1246 {app.branch}
1247 {app.number != null && (
1248 <>
1249 {" · "}
1250 <Link to={`${base}/pull/${app.number}`} className="hover:underline">
1251 #{app.number}
1252 </Link>
1253 </>
1254 )}
1255 </span>
Projects: what a workspace builds and runs, first on every page1256 </span>
1257 <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 API1258 <TimeAgo at={app.deployedAt} />
Projects: what a workspace builds and runs, first on every page1259 </span>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1260 </li>
1261 ))}
1262 </ul>
1263 </Panel>
1264 )}
1265 </div>
Projects: what a workspace builds and runs, first on every page1266
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1267 <aside className="space-y-4 text-sm">
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971268 {/* Deployments panel (deployments-panel.tsx): each environment's latest deployment. */}
1269 <DeploymentsPanel base={base} summary={loaderData.environments} className="rounded-xl border border-line bg-surface p-5" />
1270 {project && (
1271 <section className="rounded-xl border border-line bg-surface p-5">
1272 <div className="flex items-center justify-between">
1273 <h2 className="text-sm font-semibold">About</h2>
1274 {canChange && <AboutEditor project={project} />}
1275 </div>
1276 <p className={`mt-2.5 text-sm ${project.description ? "text-fg-soft" : "text-faint"}`}>
1277 {project.description ?? "No description."}
1278 </p>
1279 <LinkList links={project.links} className="mt-3" />
1280 <ul className="mt-4 space-y-2 text-xs text-muted">
1281 <li className="flex items-center gap-2">
1282 {project.kind === "library" || project.kind === "tool" ? (
1283 <PackageGlyph size={13} className="shrink-0 text-faint" />
1284 ) : project.kind === "docs" ? (
1285 <BookOpen size={13} className="shrink-0 text-faint" />
1286 ) : (
1287 <Rocket size={13} className="shrink-0 text-faint" />
1288 )}
1289 <Hint label={project.kindReason.detail}>
1290 <span>{kindLabel(project)}</span>
1291 </Hint>
1292 </li>
1293 {loaderData.release && (
1294 <li className="flex items-center gap-2">
1295 <Tag size={13} className="shrink-0 text-faint" />
1296 <Link to={`${base}/tags`} className="hover:text-fg">
1297 <span className="font-mono text-fg-soft">{loaderData.release.name}</span>
1298 {loaderData.release.at && (
1299 <span className="text-faint">
1300 {" · "}
1301 <TimeAgo at={loaderData.release.at} />
1302 </span>
1303 )}
1304 </Link>
1305 </li>
1306 )}
1307 {loaderData.languages && loaderData.languages.length > 0 && (
1308 <li className="flex items-center gap-2">
1309 <Code2 size={13} className="shrink-0 text-faint" />
1310 <span>
1311 {loaderData.languages
1312 .slice(0, 3)
1313 .map((language) => `${language.name} ${Math.round(language.share * 100)}%`)
1314 .join(" · ")}
1315 </span>
1316 </li>
1317 )}
1318 </ul>
1319 {loaderData.contributors && loaderData.contributors.length > 0 && (
1320 <div className="mt-4 flex flex-wrap gap-1">
1321 {loaderData.contributors.slice(0, 12).map((person) => (
1322 <Hint key={person.username} label={person.username}>
1323 <Link to={`/${person.username}`}>
1324 <Avatar name={person.username} size={22} />
1325 </Link>
1326 </Hint>
1327 ))}
1328 </div>
1329 )}
1330 </section>
1331 )}
1332
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1333 {member && (
1334 <section className="rounded-xl border border-line bg-surface p-5">
1335 <div className="flex items-center justify-between">
1336 <h2 className="flex items-center gap-1.5 text-sm font-semibold">
Merge branch 'worktree-agent-ad7c6d88d93adc817'1337 <Brain size={14} className="text-accent" />
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1338 What agents know here
1339 </h2>
1340 <Link to={`${base}/memory`} className="text-xs text-muted hover:text-fg">
1341 Memory
1342 </Link>
Projects: what a workspace builds and runs, first on every page1343 </div>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1344 {!loaderData.memoriesLoaded ? (
1345 <p className="mt-3 text-xs text-muted">Memory could not be loaded just now.</p>
1346 ) : knows.length === 0 ? (
1347 <p className="mt-3 text-xs leading-5 text-muted">
1348 Nothing yet. Agents note conventions, decisions and traps as they work, and every agent starting here reads them.
1349 </p>
1350 ) : (
1351 <ul className="mt-3 space-y-2.5">
1352 {knows.map((memory) => (
1353 <li key={memory.id} className="text-xs leading-5">
1354 <p className="line-clamp-3 text-fg-soft">
1355 {memory.pinned && <Pin size={11} className="mr-1 inline text-accent" />}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1356 <InlineMarkdown text={memory.text} />
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1357 </p>
1358 <p className="text-faint">
1359 {memory.kind} · {memory.createdBy} · <TimeAgo at={memory.createdAt} />
1360 </p>
1361 </li>
1362 ))}
1363 {loaderData.memoryCount > knows.length && (
1364 <li>
1365 <Link to={`${base}/memory`} className="text-xs text-muted hover:text-fg">
1366 {loaderData.memoryCount - knows.length} more
1367 </Link>
1368 </li>
1369 )}
1370 </ul>
1371 )}
1372 </section>
Projects: what a workspace builds and runs, first on every page1373 )}
1374
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1375 <section className="rounded-xl border border-line bg-surface p-5">
1376 <h2 className="flex items-center gap-1.5 text-sm font-semibold">
1377 <HeartPulse size={14} className="text-faint" />
1378 Health
Project dependencies: addresses, preview stacks, Affects, and agents who know1379 </h2>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1380 <div className="mt-4 space-y-4 text-xs">
1381 <div>
1382 <div className="flex items-baseline justify-between">
1383 <span className="text-muted">Checks passing</span>
1384 <span className="font-medium tabular-nums">{percent(health.passRate)}</span>
1385 </div>
1386 <div className="mt-1.5">
Merge branch 'worktree-agent-ad7c6d88d93adc817'1387 <Meter value={health.passRate} tone={health.passRate != null && health.passRate < 0.7 ? "bg-warn" : "bg-success"} />
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1388 </div>
1389 <p className="mt-1 text-faint">
1390 {health.checkRuns
1391 ? `${health.checkRuns} recent runs · ${percent(health.firstPass.rate)} pass on the first try`
1392 : "No checks have run recently."}
1393 </p>
1394 </div>
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971395 {member && (plan === "production" || builds.length > 0) && (
Project dependencies: addresses, preview stacks, Affects, and agents who know1396 <div>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1397 <div className="flex items-baseline justify-between">
1398 <span className="text-muted">Deploys</span>
1399 <Link to={`${base}/deployments`} className="text-faint hover:text-fg">
1400 {builds.length ? `last ${Math.min(builds.length, 20)}` : "none yet"}
1401 </Link>
1402 </div>
1403 <div className="mt-1.5">
1404 <DeployStrip builds={builds} base={base} />
1405 </div>
Project dependencies: addresses, preview stacks, Affects, and agents who know1406 </div>
1407 )}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1408 <div>
1409 <div className="flex items-baseline justify-between">
1410 <span className="text-muted">Open issues by age</span>
1411 <Link to={`${base}/issues`} className="font-medium tabular-nums hover:text-accent">
1412 {open.issues}
1413 </Link>
1414 </div>
1415 {ages ? (
1416 <ul className="mt-2 space-y-1.5">
1417 {ages.map((bucket) => (
1418 <li key={bucket.label} className="grid grid-cols-[6.5rem_1fr_1.5rem] items-center gap-2">
1419 <span className="text-faint">{bucket.label}</span>
1420 <span className="block h-1.5 overflow-hidden rounded-full bg-line">
1421 <span
1422 className={`block h-full rounded-full ${bucket.label === "Older" ? "bg-warn" : "bg-fg-soft/60"}`}
1423 style={{ width: `${(bucket.count / oldest) * 100}%` }}
1424 />
1425 </span>
1426 <span className="text-right tabular-nums text-muted">{bucket.count}</span>
Project dependencies: addresses, preview stacks, Affects, and agents who know1427 </li>
1428 ))}
1429 </ul>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1430 ) : (
1431 <p className="mt-1 text-faint">Issues could not be loaded just now.</p>
1432 )}
1433 </div>
Project dependencies: addresses, preview stacks, Affects, and agents who know1434 </div>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1435 </section>
Project dependencies: addresses, preview stacks, Affects, and agents who know1436
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1437 <section className="rounded-xl border border-line bg-surface p-5">
1438 <div className="flex items-center justify-between">
1439 <h2 className="flex items-center gap-1.5 text-sm font-semibold">
1440 <Network size={14} className="text-faint" />
1441 Dependencies
1442 </h2>
1443 {member && (
1444 <Link to={`${base}/settings/dependencies`} className="text-xs text-muted hover:text-fg">
1445 Manage
1446 </Link>
1447 )}
1448 </div>
1449 {!dependencies ? (
1450 <p className="mt-3 text-xs text-muted">Dependencies could not be loaded just now.</p>
1451 ) : dependencies.dependsOn.length === 0 && dependencies.usedBy.length === 0 ? (
1452 <p className="mt-3 text-xs leading-5 text-muted">
1453 Uses no other project, and none uses it. Declare one, and builds get its address and agents know what depends on
1454 what.
1455 </p>
1456 ) : (
1457 <div className="mt-3 space-y-3 text-xs">
1458 {dependencies.dependsOn.length > 0 && (
1459 <div>
1460 <p className="flex items-center gap-1 text-muted">
1461 <ArrowUpRight size={12} /> Uses
1462 </p>
1463 <ul className="mt-1.5 space-y-1">
1464 {dependencies.dependsOn.map((d) => (
1465 <li key={d.slug} className="flex items-center justify-between gap-2">
1466 <Link to={`/${params.owner}/${d.slug}`} className="font-medium hover:underline">
1467 {d.name}
1468 </Link>
1469 {d.as && <code className="font-mono text-faint">{d.as}</code>}
1470 </li>
1471 ))}
1472 </ul>
1473 </div>
1474 )}
1475 {dependencies.usedBy.length > 0 && (
1476 <div>
1477 <p className="flex items-center gap-1 text-muted">
1478 <ArrowDownLeft size={12} /> Used by
1479 </p>
1480 <ul className="mt-1.5 space-y-1">
1481 {dependencies.usedBy.map((d) => (
1482 <li key={d.slug}>
1483 <Link to={`/${params.owner}/${d.slug}`} className="font-medium hover:underline">
1484 {d.name}
1485 </Link>
1486 </li>
1487 ))}
1488 </ul>
1489 </div>
1490 )}
1491 </div>
1492 )}
Projects: what a workspace builds and runs, first on every page1493 </section>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1494
1495 {source && (
1496 <section className="rounded-xl border border-line bg-surface p-5">
1497 <h2 className="text-sm font-semibold">Clone</h2>
1498 <div className="mt-3">
Merge branch 'worktree-agent-aaf03bdceac799c89'1499 <CopyLine text={`git clone ${cloneUrl(addresses, `${source.repo.namespace}/${source.repo.name}`)}`} />
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1500 </div>
1501 </section>
1502 )}
1503 </aside>
1504 </div>
Projects: what a workspace builds and runs, first on every page1505 </div>
1506 );
1507}

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