Skip to content

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

1,450 lines66,463 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1import {
2 Activity as ActivityIcon,
3 ArrowDownLeft,
4 ArrowRight,
5 ArrowUpRight,
Merge branch 'projects-kind-and-links'6 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 'projects-kind-and-links'9 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";
Fast pages, required checks on the branch, self-hosted runners, honest incidents25import { type ReactNode, Suspense } from "react";
Merge branch 'worktree-agent-a985c01f43538af01'26import { Await, Form, Link } from "react-router";
Projects: what a workspace builds and runs, first on every page27
A project is an app or a library: libraries show their package and how to ship a release, not production28import {
29 type AgentRun,
30 type G1tEvent,
31 type Memory,
32 type PackageSummary,
33 type Project,
34 type Pull,
35 RUN_KIND_LABEL,
36 isActiveRun,
37 workOwner,
38} from "@g1t/contracts";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API39
Projects: what a workspace builds and runs, first on every page40import type { Route } from "./+types/overview";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit41import { InlineMarkdown } from "../../components/inline-markdown";
Projects: what a workspace builds and runs, first on every page42import { host, StatusDot } from "../../components/deploy";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API43import { CheckBadge } from "../../components/checks";
44import { Elapsed, formatCost, useLiveRefresh } from "../../components/agents";
45import { 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 look46import { type ActiveBranch, ActiveBranches } from "../../components/branches";
47import { ProductionChecklist } from "../../components/checklist";
A project is an app or a library: libraries show their package and how to ship a release, not production48import { PackageIcon } from "../../components/package-icon";
Merge branch 'worktree-agent-ae1299e92e4462012'49import { Hint } from "../../components/ui/hint";
Merge branch 'worktree-agent-aca4790ea223aa235'50import { Loading, Skeleton, SkeletonRows } from "../../components/ui/skeleton";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look51import { ProductionShot } from "../../components/production-shot";
52import { GithubLinkStrip } from "../../components/github";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit53import { distinctFacts } from "../../lib/memory-facts";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look54import { githubApp } from "../../lib/github.server";
Merge branch 'worktree-agent-a985c01f43538af01'55import { 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 API56import { ChangeSize } from "../../components/work";
57import {
58 type ActivityItem,
59 type Need,
60 type PipelineStage,
61 PIPELINE,
62 TIME,
63 ageBuckets,
64 eventItem,
65 firstPassRate,
66 groupActivity,
67 passRate,
68 pipelineStage,
69 queuedNumbers,
70 rankNeeds,
71 stuckMinutes,
72} from "../../lib/mission";
Merge branch 'projects-kind-and-links'73import { 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 production74import { ECOSYSTEM_LABEL, installCommands } from "../../lib/packages";
Merge branch 'projects-kind-and-links'75import {
76 PUBLISH_GUIDES,
77 bare,
78 hasRelease,
79 kindLabel,
80 linksToShow,
81 latestTag,
82 libraryPackages,
83 packageName,
84 packagePath,
85 projectChanges,
86 publishGuide,
87} from "../../lib/project-kind";
88import { AboutEditor, KindMenu, LinkList } from "../../components/project-about";
89import { DocsHead, ElsewhereHead, type ExternalDeployment, OtherHead, WhereItRuns } from "../../components/project-head";
A project is an app or a library: libraries show their package and how to ship a release, not production90import { actions, agents, deployments, events as eventLog, packages, projects, repos, work } from "../../lib/services.server";
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent91import { madeByG1t } from "../../lib/opened-by";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look92import { assertSameOrigin, getViewer, requireUser } from "../../lib/session.server";
Fast pages, required checks on the branch, self-hosted runners, honest incidents93import { accessTo, countsFor, refusal, repoFor } from "../../lib/access.server";
Production thumbnails follow each deploy, not each commit, and old ones are cleared daily94import { shotVersion } from "./production-screenshot";
Merge branch 'worktree-agent-aaf03bdceac799c89'95import { cloneUrl, useAddresses } from "../../lib/addresses";
Branches and Tags pages, each file's last commit, and the branch menu on files96import { readBranches } from "../../lib/branches.server";
Projects: what a workspace builds and runs, first on every page97
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API98const MAX_LANDED = 6;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look99/** Branches read for the Active branches list, and shown. */
100const BRANCHES_READ = 10;
101const BRANCHES_SHOWN = 5;
A project is an app or a library: libraries show their package and how to ship a release, not production102/** A library's packages shown on its overview; the rest are a link away. */
103const PACKAGES_SHOWN = 3;
Merge branch 'projects-kind-and-links'104/** The default branch's latest commits listed. */
105const COMMITS_SHOWN = 5;
106/** How long Active branches may take before the section links to Branches instead. */
107const BRANCHES_WAIT_MS = 3_500;
Projects: what a workspace builds and runs, first on every page108
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily109/**
110 * The overview streams: the layout's header and tabs (one repository
111 * lookup, one project) go out at once with a skeleton below them, and
112 * everything here, seventeen calls across six services, follows in the
113 * same response as it settles. Crawlers wait for all of it
114 * (entry.server.tsx). Before, the first byte waited on the slowest of them.
115 */
116export function loader({ params, context }: Route.LoaderArgs) {
117 return { overview: overviewData({ params, context }) };
118}
119
120async function overviewData({ params, context }: Pick<Route.LoaderArgs, "params" | "context">) {
Projects: what a workspace builds and runs, first on every page121 const viewer = getViewer(context);
122 const path = { namespace: params.owner, name: params.repo };
123 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 API124 const now = Date.now();
125 // A section whose service fails shows its own empty state; the page stays up.
126 const soft = <T,>(promise: Promise<T> | null): Promise<T | null> =>
127 promise ? promise.catch((error) => (console.warn("overview:", error), null)) : Promise.resolve(null);
Fast pages, required checks on the branch, self-hosted runners, honest incidents128 // People with a role of their own here see its running parts: deployments,
129 // memory, setup; everyone who can read it sees the rest. Not awaited: only
130 // those sections wait on the repository lookup, which the layout makes in
131 // this request too.
132 const accessP = accessTo(context, params);
133 const memberP = accessP.then((access) => access.insider);
134 const forMembers = <T,>(start: () => Promise<T>): Promise<T | null> =>
135 memberP.then((member) => (member ? soft(start()) : null));
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look136 const repoP = soft(repoFor(context, params));
A project is an app or a library: libraries show their package and how to ship a release, not production137 const projectP = soft(projects.get(params.owner, params.repo, viewer));
Merge branch 'projects-kind-and-links'138 // A library or a tool shows its packages where an app shows production,
139 // and every project g1t does not deploy counts a workflow in its checklist.
140 const planP = projectP.then((project) => (project?.ok ? checklistPlan(project.value).plan : null));
141 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 production142 const packagesP = libraryRepoP.then((repo) => (repo ? soft(packages.list(params.owner, viewer, { repoId: repo.id })) : null));
Merge branch 'projects-kind-and-links'143 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 API144 const eventsP = repoP.then((repo) => (repo?.ok ? soft(eventLog.list({ repoId: repo.value.id, limit: 150 })) : null));
145 const minePullsP = repoP.then((repo) =>
146 repo?.ok && viewer
147 ? soft(work.listActivePulls(viewer)).then((list) => (list ?? []).filter((item) => item.pull.repoId === repo.value.id))
148 : null,
149 );
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look150 const openP = soft(work.listPulls(path, viewer, "open"));
151 // Where its code came from on GitHub, for members.
Fast pages, required checks on the branch, self-hosted runners, honest incidents152 const githubP = repoP.then((repo) => (repo?.ok ? forMembers(() => githubApp.link(repo.value.id)) : null));
153 const listP = forMembers(() => deployments.list(ref, viewer));
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look154 // Active branches: the newest few besides the default, with how far each
155 // has moved. Branches with an open pull request are read first.
156 const branchesP = Promise.all([repoP, soft(repos.branches(path, viewer)), openP, listP]).then(
157 async ([repo, branchList, pulls, deploys]): Promise<{ main: string; total: number; shown: ActiveBranch[] } | null> => {
158 if (!repo?.ok || !branchList?.ok) return null;
159 const main = repo.value.defaultBranch;
Branches and Tags pages, each file's last commit, and the branch menu on files160 if (branchList.value.every((branch) => branch.name === main)) return { main, total: 0, shown: [] };
161 const read = await readBranches(
162 path,
163 viewer,
164 {
165 defaultBranch: main,
166 branches: branchList.value,
167 pulls: pulls?.ok ? pulls.value : [],
168 previews: deploys?.ok ? deploys.value.live.filter((app) => app.kind === "preview") : [],
169 },
170 BRANCHES_READ,
171 );
172 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 look173 },
174 );
Merge branch 'projects-kind-and-links'175 // Active branches read several logs each: streamed, so the rest shows
176 // first. Bounded well inside the response's stream timeout
177 // (entry.server.tsx): a promise still pending when the stream ends never
178 // settles in the browser, and its skeleton would stay.
179 const branches = Promise.race([
180 branchesP.catch(() => null),
181 new Promise<"slow">((resolve) => setTimeout(() => resolve("slow"), BRANCHES_WAIT_MS)),
182 ]);
183 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 incidents184 accessP,
A project is an app or a library: libraries show their package and how to ship a release, not production185 projectP,
Fast pages, required checks on the branch, self-hosted runners, honest incidents186 forMembers(() => deployments.settings(ref, viewer)),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look187 listP,
188 openP,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API189 soft(work.listPulls(path, viewer, "closed")),
Merge branch 'projects-kind-and-links'190 soft(repos.log(path, viewer, null, COMMITS_SHOWN)),
Fast pages, required checks on the branch, self-hosted runners, honest incidents191 soft(countsFor(context, params)),
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API192 soft(projects.dependencies(params.owner, params.repo, viewer)),
193 soft(agents.listRuns(viewer, { repo: path, limit: 60 })),
194 soft(work.queue(path, viewer)),
195 soft(work.listIssues(path, viewer, { state: "open" })),
Fast pages, required checks on the branch, self-hosted runners, honest incidents196 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 API197 eventsP,
198 minePullsP,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look199 // For the checklist, which only members see.
Fast pages, required checks on the branch, self-hosted runners, honest incidents200 forMembers(() => deployments.domains(ref, viewer)),
201 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 production202 packagesP,
203 workflowsP,
Merge branch 'projects-kind-and-links'204 // The latest release, for About.
205 soft(repos.tags(path, viewer)),
Projects: what a workspace builds and runs, first on every page206 ]);
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API207 const ok = <T,>(result: { ok: true; value: T } | { ok: false } | null): T | null => (result?.ok ? result.value : null);
208
209 const openPulls = ok(open) ?? [];
210 const runList = ok(runs) ?? [];
211 const live = runList.filter((run) => isActiveRun(run.status));
212 const builds = ok(list)?.deployments ?? [];
213 const landed = (ok(closed) ?? [])
214 .filter((pull) => pull.status === "merged")
215 .sort((a, b) => Date.parse(b.mergedAt ?? b.updatedAt) - Date.parse(a.mergedAt ?? a.updatedAt));
216 const queued = queuedNumbers(ok(queue)?.active ?? []);
217 const runOn = new Map<number, AgentRun>();
218 for (const run of live) if (run.number != null && !runOn.has(run.number)) runOn.set(run.number, run);
219 const lifecycleOf = new Map((mine ?? []).map((item) => [item.pull.number, item.lifecycle]));
220
221 // --- The pipeline -----------------------------------------------------------
222 type Card = {
223 number: number;
224 title: string;
225 agent: string;
226 status: Pull["status"];
227 checkStatus: Pull["checkStatus"];
228 step: string | null;
229 runKind: string | null;
230 runStarted: string | null;
231 needsYou: boolean;
232 at: string;
233 };
234 const columns: Record<PipelineStage, Card[]> = { working: [], checking: [], reviewing: [], queue: [], landed: [] };
235 for (const pull of openPulls.slice(0, 40)) {
236 const run = runOn.get(pull.number);
Fast pages, required checks on the branch, self-hosted runners, honest incidents237 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 API238 number: pull.number,
239 title: pull.title,
240 agent: pull.agent,
241 status: pull.status,
242 checkStatus: pull.checkStatus,
243 step: run?.step ?? null,
244 runKind: run ? RUN_KIND_LABEL[run.kind] : null,
245 runStarted: run ? (run.startedAt ?? run.createdAt) : null,
246 needsYou: lifecycleOf.get(pull.number)?.stage === "needs_you",
247 at: pull.updatedAt,
248 });
249 }
250 for (const pull of landed.filter((p) => now - Date.parse(p.mergedAt ?? p.updatedAt) < 3 * TIME.DAY).slice(0, 5)) {
251 columns.landed.push({
252 number: pull.number,
253 title: pull.title,
254 agent: pull.agent,
255 status: pull.status,
256 checkStatus: pull.checkStatus,
257 step: null,
258 runKind: null,
259 runStarted: null,
260 needsYou: false,
261 at: pull.mergedAt ?? pull.updatedAt,
262 });
263 }
264
265 // --- Needs you ---------------------------------------------------------------
266 const base = `/${params.owner}/${params.repo}`;
267 const needs: Need[] = [];
268 const latestProduction = builds.find((build) => build.kind === "production") ?? null;
269 if (latestProduction?.status === "failed") {
270 needs.push({
271 key: "deploy",
272 kind: "deploy",
273 title: "The production build failed",
274 detail: latestProduction.error ?? "Production still serves the build before it.",
275 to: `${base}/deployments/${latestProduction.id}`,
276 action: "See the build",
277 at: Date.parse(latestProduction.finishedAt ?? latestProduction.createdAt),
278 where: latestProduction.commit.slice(0, 7),
279 });
280 }
281 const me = viewer?.username.toLowerCase();
282 for (const pull of openPulls) {
283 const to = `${base}/pull/${pull.number}`;
284 const where = `#${pull.number}`;
285 const lifecycle = lifecycleOf.get(pull.number);
286 if (lifecycle?.stage === "needs_you") {
287 const conflict = /conflict/i.test(lifecycle.detail);
288 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 });
289 } else if (lifecycle?.stage === "ready") {
290 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 });
291 }
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights292 // Never your own, nor one g1t made for you; whoever it is for asked.
293 const owner = workOwner(pull).username;
294 if (me && pull.status === "open" && owner.toLowerCase() !== me && pull.reviewers.some((name) => name.toLowerCase() === me)) {
295 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 API296 }
297 if (member && pull.status === "open" && pull.checkStatus === "failed" && !runOn.has(pull.number) && !lifecycle) {
Fast pages, required checks on the branch, self-hosted runners, honest incidents298 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 API299 }
300 }
301 for (const run of live) {
302 const minutes = stuckMinutes(run, now);
303 if (minutes == null) continue;
304 needs.push({
305 key: `run:${run.id}`,
306 kind: "stuck",
307 title: run.title ?? `${run.agent}'s run`,
308 detail: `${run.agent} has reported nothing for ${minutes} min${run.step ? `. Last: ${run.step}` : ""}.`,
309 to: `${base}/agents/runs/${run.id}`,
310 action: "Look",
311 at: Date.parse(run.updatedAt),
312 where: run.number != null ? `#${run.number}` : null,
313 });
314 }
315
316 // --- Activity and health -------------------------------------------------------
317 const eventList: G1tEvent[] = recent ?? [];
318 const items = eventList
319 .map((event) => eventItem(event, path))
320 .filter((item): item is ActivityItem => item != null);
321 const checkEvents = eventList.flatMap((event) =>
322 event.type === "checks.completed" ? [{ repo: "", number: event.data.number, at: Date.parse(event.time), passed: event.data.status === "passed" }] : [],
323 );
324 const memoryList = ok(memories)?.project ?? [];
Usage, Billing settings and prepaid AI credit; fixes from the UX audit325 // Pinned first, then the newest; a fact remembered twice, once.
326 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 API327 const openIssues = ok(issues);
328
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look329 // --- Getting to production ----------------------------------------------------
330 const wentLive = (status: string) => status === "ready" || status === "replaced" || status === "down";
331 const liveApps = ok(list)?.live ?? [];
332 const commitNow = ok(log)?.[0] ?? null;
333 const projectValue = ok(project);
Merge branch 'projects-kind-and-links'334 const plan = projectValue ? checklistPlan(projectValue) : { plan: "production" as const, title: "Get to production" };
335 const isLibrary = plan.plan === "release";
A project is an app or a library: libraries show their package and how to ship a release, not production336 const published = libraryPackages(ok(packageList) ?? []);
Merge branch 'projects-kind-and-links'337 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 production338 const firstSteps = {
339 base,
340 hasCode: commitNow != null || projectValue?.source.kind === "mirror",
341 instructions: commitNow == null ? false : root?.ok ? hasInstructions(root.value.entries.map((entry) => entry.name)) : null,
342 agentAssigned: agentWasAssigned({
343 runAgents: runList.map((run) => run.agent),
344 pullAgents: [...openPulls, ...(ok(closed) ?? [])].map((pull) => pull.agent),
345 issues: openIssues ?? [],
346 }),
347 };
Merge branch 'projects-kind-and-links'348 const links = projectValue?.links;
A project is an app or a library: libraries show their package and how to ship a release, not production349 const checklist = !member
350 ? null
Merge branch 'projects-kind-and-links'351 : plan.plan !== "production" && plan.plan !== "release"
352 ? startChecklist(plan.plan, {
353 ...firstSteps,
354 hasWorkflow,
355 productionUrl: projectValue?.productionUrl ?? null,
356 hasLinks: !!links && (links.homepage != null || links.docs != null || links.custom.length > 0),
357 hasDocsLink: !!links && (links.docs != null || links.homepage != null),
358 })
A project is an app or a library: libraries show their package and how to ship a release, not production359 : isLibrary
360 ? releaseChecklist({
361 ...firstSteps,
Merge branch 'projects-kind-and-links'362 hasWorkflow,
A project is an app or a library: libraries show their package and how to ship a release, not production363 released: hasRelease(published),
364 releaseTo: published[0]
365 ? packagePath(published[0])
366 : (publishGuide(projectValue?.ecosystem ?? null)?.guide ?? "https://docs.g1t.sh/guides/packages/"),
367 })
368 : productionChecklist({
369 ...firstSteps,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look370 deploysEnabled: ok(settings)?.enabled ?? false,
371 productionDeployed:
372 liveApps.some((app) => app.kind === "production") ||
373 builds.some((build) => build.kind === "production" && wentLive(build.status)),
374 domains: ok(domains)?.domains.length ?? null,
375 previewOpened:
376 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 production377 });
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look378
379 const github = await githubP;
Projects: what a workspace builds and runs, first on every page380 return {
381 member,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look382 can,
383 github,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API384 project: ok(project),
385 settings: ok(settings),
386 builds: builds.slice(0, 30),
387 live: ok(list)?.live ?? [],
388 commit: ok(log)?.[0] ?? null,
Merge branch 'projects-kind-and-links'389 commits: (ok(log) ?? []).map((one) => ({ hash: one.hash, message: one.message.split("\n")[0] ?? "", author: one.author.name, at: one.authoredAt })),
390 release: latestTag(ok(tagList) ?? []),
391 checklistTitle: plan.title,
392 // Production as reported from outside g1t, for an app deployed elsewhere:
393 // filled in once deployments can be reported through the API.
394 deployment: null as ExternalDeployment | null,
395 // About's languages and contributors, filled in by the files' About work.
396 languages: null as { name: string; share: number }[] | null,
397 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 API398 open: ok(counts) ?? { issues: openIssues?.length ?? 0, pulls: openPulls.length },
399 dependencies: ok(deps),
400 agentsLive: live.slice(0, 6),
401 runsLoaded: runs?.ok ?? false,
402 pullsLoaded: open?.ok ?? false,
403 columns,
404 needs: rankNeeds(needs),
405 landed: landed.slice(0, MAX_LANDED),
406 groups: groupActivity(items).slice(0, 30),
407 eventsLoaded: recent != null,
408 health: {
409 passRate: passRate(checkEvents.map((c) => c.passed)),
410 firstPass: firstPassRate(checkEvents),
411 checkRuns: checkEvents.length,
412 ages: openIssues ? ageBuckets(openIssues.map((issue) => issue.createdAt), now) : null,
413 },
414 knows: knows as Memory[],
415 memoryCount: memoryList.length,
416 memoriesLoaded: memories?.ok ?? false,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look417 checklist,
A project is an app or a library: libraries show their package and how to ship a release, not production418 library: isLibrary
419 ? { packages: published.slice(0, PACKAGES_SHOWN), total: published.length, loaded: packageList?.ok ?? false }
420 : null,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look421 branches,
Projects: what a workspace builds and runs, first on every page422 };
423}
424
425export async function action({ request, params, context }: Route.ActionArgs) {
426 assertSameOrigin(request);
427 const user = requireUser(context, request);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look428 const form = await request.formData().catch(() => null);
429 const intent = form?.get("intent");
Merge branch 'projects-kind-and-links'430 // What it is, where it runs, its description and links: its settings.
431 if (form && (intent === "kind" || intent === "about")) {
432 const refused = await refusal(context, params, "manage_settings");
433 if (refused) return { error: refused };
434 const saved = await projects.update(user, params.owner, params.repo, projectChanges(form));
435 return saved.ok ? { notice: "Saved." } : { error: saved.error.message };
436 }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look437 // Syncing from GitHub is pushing; stopping it is an integration; deploying is compute.
438 const refused = await refusal(context, params, intent === "github-sync" ? "push" : intent === "github-stop" ? "manage_integrations" : "run");
439 if (refused) return { error: refused };
440 if (intent === "github-sync" || intent === "github-stop") {
441 const repo = await repos.get({ namespace: params.owner, name: params.repo }, user);
442 if (!repo.ok) return { error: repo.error.message };
443 const done =
444 intent === "github-sync" ? await githubApp.sync(user, repo.value.id) : await githubApp.unlinkRepo(user, repo.value.id);
445 return done.ok
446 ? { notice: intent === "github-sync" ? "Synced with GitHub." : "It is no longer kept in step with GitHub." }
447 : { error: done.error.message };
448 }
Projects: what a workspace builds and runs, first on every page449 const started = await deployments.redeploy(user, { workspace: params.owner, slug: params.repo }, null);
450 return started.ok ? { notice: "Production is building." } : { error: started.error.message };
451}
452
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily453type Loaded = Awaited<ReturnType<typeof overviewData>>;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API454type Card = Loaded["columns"]["working"][number];
455
456const STAGE_TONE: Record<PipelineStage, string> = {
Merge branch 'worktree-agent-ad7c6d88d93adc817'457 working: "bg-accent",
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API458 checking: "bg-info",
459 reviewing: "bg-warn",
Merge branch 'worktree-agent-ad7c6d88d93adc817'460 queue: "bg-success-dim",
461 landed: "bg-success",
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API462};
463
464function PipelineCard({ card, base, stage }: { card: Card; base: string; stage: PipelineStage }) {
465 return (
466 <li>
467 <Link
468 to={`${base}/pull/${card.number}`}
469 prefetch="intent"
470 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'471 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 API472 }`}
473 >
474 <span className="line-clamp-2 text-[0.8125rem] leading-snug font-medium">{card.title}</span>
475 <span className="mt-1.5 flex items-center gap-1.5 text-[0.6875rem] text-muted">
476 <Avatar name={card.agent} size={14} />
477 <span className="font-mono text-faint">#{card.number}</span>
478 <span className="grow" />
479 {card.needsYou ? (
Merge branch 'worktree-agent-ae1299e92e4462012'480 <Hint label="Stopped: a person decides what happens next">
481 <span className="inline-flex items-center gap-1 text-warn">
482 <Hand size={11} /> you
483 <span className="sr-only">: stopped, a person decides what happens next</span>
484 </span>
485 </Hint>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API486 ) : stage === "landed" ? (
487 <TimeAgo at={card.at} />
488 ) : card.runStarted ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'489 <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 API490 <Loader2 size={11} className="animate-spin" />
491 <Elapsed from={card.runStarted} />
492 </span>
493 ) : (
494 <CheckBadge status={card.checkStatus} />
495 )}
496 </span>
497 {card.step && (
Merge branch 'worktree-agent-ae1299e92e4462012'498 <Hint label={card.step}>
499 <span className="mt-1.5 block truncate font-mono text-[0.6875rem] text-fg/70">
500 {card.runKind}: {card.step}
501 </span>
502 </Hint>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API503 )}
504 </Link>
505 </li>
506 );
507}
508
509/** Open pull requests moving through Working → Checking → Reviewing → Queue → Landed. */
510function Pipeline({ columns, base }: { columns: Loaded["columns"]; base: string }) {
511 return (
512 <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 look513 {/* Each stage as tall as what is in it: an empty one stays short
514 instead of stretching to the busiest. */}
515 <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 API516 {PIPELINE.map(({ stage, label }, index) => {
517 const cards = columns[stage];
518 return (
519 <li key={stage} className="flex min-w-0 flex-col rounded-xl border border-line bg-surface p-2">
520 <p className="flex items-center gap-2 px-1 pt-0.5 pb-2 text-xs font-medium text-muted">
521 <span className={`size-1.5 rounded-full ${STAGE_TONE[stage]} ${cards.length && stage !== "landed" ? "animate-pulse" : ""}`} />
522 {label}
523 <span className="tabular-nums text-faint">{cards.length}</span>
524 {index < PIPELINE.length - 1 && <ArrowRight size={11} className="ml-auto text-line-strong" />}
525 </p>
526 {cards.length === 0 ? (
527 <p className="rounded-lg border border-dashed border-line/70 px-2 py-3 text-center text-[0.6875rem] text-faint">Empty</p>
528 ) : (
529 <ul className="space-y-1.5">
530 {cards.slice(0, 6).map((card) => (
531 <PipelineCard key={card.number} card={card} base={base} stage={stage} />
532 ))}
533 {cards.length > 6 && (
534 <li className="px-1 text-[0.6875rem] text-muted">
535 <Link to={`${base}/pulls`} className="hover:text-fg">
536 {cards.length - 6} more
537 </Link>
538 </li>
539 )}
540 </ul>
541 )}
542 </li>
543 );
544 })}
545 </ol>
546 </div>
547 );
548}
549
A project is an app or a library: libraries show their package and how to ship a release, not production550/**
551 * Where an app's overview shows production, a library's shows what it
552 * publishes: each package with its latest version and how to install it,
553 * or, before there is one, how to publish a first version.
554 */
555function LibraryHead({
556 library,
557 project,
558 base,
559 workspace,
560}: {
561 library: { packages: PackageSummary[]; total: number; loaded: boolean };
562 project: Project | null;
563 base: string;
564 workspace: string;
565}) {
566 const guide = publishGuide(project?.ecosystem ?? null);
567 const name = project?.name ?? base.split("/").pop();
568 return (
569 <div className="p-5 sm:p-6">
570 <p className="flex items-center gap-2 text-xs font-medium tracking-wide text-muted uppercase">
571 <PackageGlyph size={13} className="text-accent" />
572 Packages
573 </p>
574 {library.packages.length > 0 ? (
575 <>
576 <ul className="mt-3 space-y-4">
577 {library.packages.map((pkg) => {
578 const commands = installCommands(pkg, pkg.latest, "you");
579 return (
580 <li key={pkg.id} className="flex flex-col gap-2.5 lg:flex-row lg:items-start lg:gap-5">
581 <Link to={packagePath(pkg)} className="group flex min-w-0 items-center gap-3 lg:w-72 lg:shrink-0">
582 <PackageIcon ecosystem={pkg.ecosystem} size={32} />
583 <span className="min-w-0">
584 <span className="block truncate font-mono text-[0.9375rem] font-medium group-hover:text-accent">{packageName(pkg)}</span>
585 <span className="block text-xs text-muted">
586 {ECOSYSTEM_LABEL[pkg.ecosystem]} · {pkg.latest ?? "no versions yet"}
587 {pkg.versions > 1 && ` · ${pkg.versions} versions`}
588 </span>
589 </span>
590 </Link>
591 <div className="min-w-0 grow space-y-1.5">
592 {/* The workspace's registry first, or the tool installs a package of the same name from elsewhere. */}
593 {commands.registry && <CopyLine prompt text={commands.registry} />}
594 <CopyLine prompt text={commands.install} />
595 </div>
596 </li>
597 );
598 })}
599 </ul>
600 {library.total > library.packages.length && (
601 <Link to={`/${workspace}/-/packages`} className="mt-3 inline-block text-xs text-muted hover:text-fg">
602 All {library.total} packages
603 </Link>
604 )}
605 </>
606 ) : (
607 <>
608 <p className="mt-2 text-lg font-medium">{library.loaded ? "Not published yet" : "Packages could not be loaded just now"}</p>
609 {guide ? (
610 <>
611 <p className="mt-1 max-w-lg text-sm text-muted">
612 Publish a first version of {name} to the workspace's {guide.label} registry, for others to install
613 {guide.start ? ":" : "."}
614 </p>
615 {guide.start && (
616 <div className="mt-3 max-w-lg">
617 <CopyLine prompt text={guide.start} />
618 </div>
619 )}
620 <a href={guide.guide} className="mt-3 inline-flex items-center gap-1 text-sm text-accent hover:underline">
621 Publishing to {guide.label} <ArrowUpRight size={13} />
622 </a>
623 </>
624 ) : (
625 <>
626 <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>
627 <p className="mt-2 flex flex-wrap gap-x-4 gap-y-1 text-sm">
628 {PUBLISH_GUIDES.map((one) => (
629 <a key={one.label} href={one.guide} className="inline-flex items-center gap-1 text-accent hover:underline">
630 {one.label} <ArrowUpRight size={13} />
631 </a>
632 ))}
633 </p>
634 </>
635 )}
636 </>
637 )}
638 </div>
639 );
640}
641
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API642function Stat({ label, value, to }: { label: string; value: ReactNode; to?: string }) {
643 const body = (
644 <>
645 <span className="block text-[0.6875rem] text-faint">{label}</span>
646 <span className="mt-0.5 block truncate text-[0.8125rem] font-medium">{value}</span>
647 </>
648 );
649 return to ? (
650 <Link to={to} className="min-w-0 px-5 py-3 transition-colors hover:bg-raised/50">
651 {body}
652 </Link>
653 ) : (
654 <div className="min-w-0 px-5 py-3">{body}</div>
655 );
656}
657
Projects: what a workspace builds and runs, first on every page658export default function ProjectOverview({ loaderData, actionData, params }: Route.ComponentProps) {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily659 return (
660 <Suspense fallback={<OverviewSkeleton />}>
661 <Await resolve={loaderData.overview} errorElement={<OverviewFailed />}>
Merge branch 'worktree-agent-aca4790ea223aa235'662 {(overview) => (
663 // Faded in over its outline, not switched for it.
664 <div className="animate-fade-in">
665 <Overview loaderData={overview} actionData={actionData} params={params} />
666 </div>
667 )}
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily668 </Await>
669 </Suspense>
670 );
671}
672
673/** The overview's sections as they will sit, while they stream in. */
674function OverviewSkeleton() {
675 return (
Merge branch 'worktree-agent-aca4790ea223aa235'676 <Loading className="space-y-8" aria-label="Loading the overview">
677 {/* Production: its screenshot beside where it is live, then the facts under them. */}
678 <section aria-hidden="true" className="overflow-hidden rounded-2xl border border-line bg-surface">
679 <div className="flex flex-col gap-5 p-5 sm:flex-row sm:items-start sm:p-6">
680 <Skeleton className="aspect-[16/10] w-full shrink-0 rounded-lg sm:w-72" />
681 <div className="min-w-0 grow">
682 <Skeleton className="h-3 w-24" />
683 <Skeleton className="mt-3 h-5 w-72 max-w-full" />
684 <Skeleton className="mt-3 h-3 w-48 max-w-full" />
685 </div>
686 </div>
687 <div className="grid grid-cols-2 gap-px border-t border-line bg-line sm:grid-cols-4">
688 {Array.from({ length: 4 }, (_, index) => (
689 <div key={index} className="bg-surface px-5 py-4">
690 <Skeleton className="h-3 w-16" />
691 <Skeleton className="mt-2.5 h-4 w-28" />
692 </div>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily693 ))}
694 </div>
695 </section>
Merge branch 'worktree-agent-aca4790ea223aa235'696 {/* Right now: the work, column by column. */}
697 <div aria-hidden="true" className="grid grid-cols-2 gap-2 sm:grid-cols-5">
698 {Array.from({ length: 5 }, (_, index) => (
699 <div key={index} className="rounded-xl border border-line bg-surface p-3">
700 <Skeleton className="h-3 w-20" />
701 <Skeleton className="mt-3 h-14 rounded-lg" />
702 </div>
703 ))}
704 </div>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily705 <div className="grid gap-6 lg:grid-cols-2">
706 <section className="rounded-2xl border border-line bg-surface">
707 <SkeletonRows rows={4} />
708 </section>
709 <section className="rounded-2xl border border-line bg-surface">
710 <SkeletonRows rows={4} />
711 </section>
712 </div>
Merge branch 'worktree-agent-aca4790ea223aa235'713 </Loading>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily714 );
715}
716
717function OverviewFailed() {
718 return (
719 <p className="rounded-2xl border border-line bg-surface p-6 text-sm text-muted">
720 The overview could not be loaded just now. Reload the page to try again.
721 </p>
722 );
723}
724
725function Overview({
726 loaderData,
727 actionData,
728 params,
729}: {
730 loaderData: Loaded;
731 actionData: Route.ComponentProps["actionData"];
732 params: Route.ComponentProps["params"];
733}) {
A project is an app or a library: libraries show their package and how to ship a release, not production734 const { member, project, settings, builds, live, commit, open, dependencies, agentsLive, columns, needs, landed, groups, health, knows, checklist, branches, library } =
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look735 loaderData;
Projects: what a workspace builds and runs, first on every page736 const base = `/${params.owner}/${params.repo}`;
737 const production = live.find((app) => app.kind === "production") ?? null;
Agents and memory, checks and conflicts, profiles, slug renames, custom domains738 // The project's own domain, once it is active, is where production is visited.
739 const productionUrl = production ? (settings?.primaryDomain ? `https://${settings.primaryDomain}` : production.url) : null;
Projects: what a workspace builds and runs, first on every page740 const previews = live.filter((app) => app.kind === "preview");
741 const latestProduction = builds.find((build) => build.kind === "production") ?? null;
Merge branch 'projects-kind-and-links'742 const plan = project ? checklistPlan(project).plan : "production";
743 const canChange = member && loaderData.can.manage_settings;
744 // Its homepage and docs beside what it is, unless the card below already links there.
745 const headUrl =
746 plan === "production"
747 ? (productionUrl ?? settings?.productionUrl)
748 : plan === "elsewhere"
749 ? (loaderData.deployment?.url ?? project?.productionUrl)
750 : plan === "docs"
751 ? (project?.links.docs ?? project?.productionUrl ?? project?.links.homepage)
752 : plan === "other"
753 ? (project?.links.homepage ?? project?.links.docs)
754 : null;
755 const stripLinks = project ? linksToShow({ ...project.links, custom: [] }, [headUrl]) : [];
Merge branch 'worktree-agent-aaf03bdceac799c89'756 const addresses = useAddresses();
Projects: what a workspace builds and runs, first on every page757 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 API758 const moving = agentsLive.length > 0 || columns.working.length + columns.checking.length > 0;
759 useLiveRefresh(moving);
760 const ages = health.ages;
761 const oldest = ages ? Math.max(1, ...ages.map((a) => a.count)) : 1;
Projects: what a workspace builds and runs, first on every page762
763 return (
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API764 <div className="space-y-8">
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look765 {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 API766 {/* What the project is, running, and where its code is. */}
767 <section className="overflow-hidden rounded-2xl border border-line bg-surface">
Merge branch 'projects-kind-and-links'768 {project && (
769 <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">
770 <KindMenu project={project} label={kindLabel(project)} canChange={canChange} />
771 {stripLinks.map((link) => (
772 <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">
773 {link.type === "docs" ? <BookOpen size={12} className="shrink-0 text-faint" /> : <Globe size={12} className="shrink-0 text-faint" />}
774 <span className="truncate">{link.type === "docs" ? `Docs · ${bare(link.url)}` : link.label}</span>
775 </a>
776 ))}
777 </div>
778 )}
779 {plan === "elsewhere" && project ? (
780 <ElsewhereHead
781 project={project}
782 base={base}
783 canChange={canChange}
784 canDeploy={member && loaderData.can.manage_integrations}
785 deployment={loaderData.deployment}
786 commit={commit}
787 />
788 ) : plan === "unknown" && project ? (
789 <WhereItRuns project={project} base={base} canChange={canChange} canDeploy={member && loaderData.can.manage_integrations} />
790 ) : plan === "docs" && project ? (
791 <DocsHead project={project} base={base} canChange={canChange} canDeploy={member && loaderData.can.manage_integrations} />
792 ) : plan === "other" && project ? (
793 <OtherHead project={project} base={base} canChange={canChange} />
794 ) : library ? (
795 <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 production796 ) : (
797 <div className="flex flex-col gap-5 p-5 sm:flex-row sm:items-start sm:p-6">
798 {member && (production || settings?.enabled) && (
799 <ProductionShot
800 src={production ? `${base}/production.jpg?v=${shotVersion(production)}` : null}
801 href={production ? (productionUrl ?? production.url) : null}
802 label={host(production ? (productionUrl ?? production.url) : (settings?.productionUrl ?? ""))}
803 className="w-full shrink-0 sm:w-60 lg:w-72"
804 />
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API805 )}
A project is an app or a library: libraries show their package and how to ship a release, not production806 <div className="flex min-w-0 grow flex-wrap items-start justify-between gap-4">
807 <div className="min-w-0">
808 <p className="flex items-center gap-2 text-xs font-medium tracking-wide text-muted uppercase">
809 <Rocket size={13} className="text-accent" />
810 Production
811 </p>
812 {production ? (
813 <>
814 <a
815 href={productionUrl ?? production.url}
816 className="mt-2 flex items-center gap-1.5 truncate font-mono text-lg font-medium hover:text-accent"
817 >
818 {host(productionUrl ?? production.url)}
819 <ArrowUpRight size={16} className="shrink-0 text-faint" />
820 </a>
821 <p className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted">
822 {latestProduction && <StatusDot status={latestProduction.status} label={latestProduction.status === "ready" ? "Live" : undefined} />}
823 <span className="inline-flex items-center gap-1 font-mono">
824 <GitCommitHorizontal size={13} className="text-faint" />
825 {production.commit.slice(0, 7)}
826 </span>
827 <span>
828 deployed <TimeAgo at={production.deployedAt} />
829 </span>
Merge branch 'projects-kind-and-links'830 {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 production831 </>
832 ) : settings?.enabled ? (
833 <>
834 <p className="mt-2 font-mono text-lg text-muted">{host(settings.productionUrl)}</p>
835 <p className="mt-1.5 text-xs text-muted">
836 {latestProduction ? (
837 <StatusDot status={latestProduction.status} label={latestProduction.status === "failed" ? "The last build failed" : undefined} />
838 ) : (
839 "Not deployed yet. Push to the default branch, or deploy it now."
840 )}
841 </p>
842 </>
843 ) : (
844 <>
845 <p className="mt-2 text-lg font-medium">Deployments are off</p>
846 <p className="mt-1 max-w-lg text-sm text-muted">
847 Nothing builds or runs until you turn them on. Then {project?.name ?? params.repo} goes up on g1t.page:
848 production from the default branch, and a live preview for every pull request. It runs only while
849 someone visits.
850 </p>
851 </>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API852 )}
Projects: what a workspace builds and runs, first on every page853 </div>
A project is an app or a library: libraries show their package and how to ship a release, not production854 {member && (
855 <div className="flex shrink-0 items-center gap-2">
856 {production && (
857 <ButtonLink to={productionUrl ?? production.url} variant="accent" reloadDocument>
858 Visit
859 <ArrowUpRight size={14} />
860 </ButtonLink>
861 )}
862 {settings?.enabled ? (
863 loaderData.can.run && <Form method="post">
Merge branch 'worktree-agent-ae1299e92e4462012'864 <Hint label="Build production again from the default branch">
865 <SubmitButton variant="quiet" name="intent" value="redeploy" pending="Redeploying…">
866 <RotateCw size={14} />
867 Redeploy
868 </SubmitButton>
869 </Hint>
A project is an app or a library: libraries show their package and how to ship a release, not production870 </Form>
871 ) : loaderData.can.manage_integrations && (
872 <ButtonLink to={`${base}/settings/deployments`} variant="accent">
873 <Rocket size={14} />
874 Turn on deployments
875 </ButtonLink>
876 )}
877 </div>
878 )}
879 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look880 </div>
A project is an app or a library: libraries show their package and how to ship a release, not production881 )}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API882 {actionData && (
Merge branch 'worktree-agent-ad7c6d88d93adc817'883 <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 API884 {"error" in actionData ? actionData.error : actionData.notice}
885 </p>
886 )}
887 <div className="grid grid-cols-2 divide-line border-t border-line text-sm sm:grid-cols-4 sm:divide-x">
888 <Stat
889 label="Source"
890 to={`${base}/code`}
891 value={
892 source ? (
893 <span className="inline-flex items-center gap-1.5 font-mono">
894 {project?.private ? <Lock size={12} className="text-faint" /> : <Code2 size={12} className="text-faint" />}
895 {source.repo.name}
896 {source.rootDir ? `/${source.rootDir}` : ""}
897 </span>
898 ) : (
899 "—"
900 )
901 }
902 />
903 <Stat
904 label="Default branch"
905 to={`${base}/commits`}
906 value={
907 <span className="inline-flex items-center gap-1.5 font-mono">
908 <GitBranch size={12} className="text-faint" />
909 {source?.defaultBranch ?? "main"}
910 </span>
911 }
912 />
913 <Stat
914 label="Latest commit"
915 to={commit ? `${base}/commit/${commit.hash}` : undefined}
916 value={
917 commit ? (
Merge branch 'worktree-agent-ae1299e92e4462012'918 <Hint label={commit.message.split("\n")[0]}>
919 <span>
920 <span className="font-mono">{commit.hash.slice(0, 7)}</span>{" "}
921 <span className="font-normal text-muted">
922 <TimeAgo at={commit.authoredAt} />
923 </span>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API924 </span>
Merge branch 'worktree-agent-ae1299e92e4462012'925 </Hint>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API926 ) : (
927 "No commits yet"
928 )
929 }
930 />
931 <Stat
A project is an app or a library: libraries show their package and how to ship a release, not production932 label={member && settings?.enabled && !library ? "Last deploy" : "Open"}
933 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 API934 value={
A project is an app or a library: libraries show their package and how to ship a release, not production935 member && settings?.enabled && !library ? (
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API936 latestProduction ? (
937 <span className="inline-flex items-center gap-2">
938 <StatusDot status={latestProduction.status} />
939 <span className="font-normal text-muted">
940 <TimeAgo at={latestProduction.createdAt} />
Projects: what a workspace builds and runs, first on every page941 </span>
942 </span>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API943 ) : (
944 "None yet"
945 )
946 ) : (
947 `${open.pulls} pull requests · ${open.issues} issues`
948 )
949 }
950 />
951 </div>
952 {commit && (
953 <p className="truncate border-t border-line px-5 py-2.5 text-xs text-muted sm:px-6">
954 <GitCommitHorizontal size={12} className="mr-1.5 inline text-faint" />
955 {commit.message.split("\n")[0]} <span className="text-faint">· {commit.author.name}</span>
956 </p>
957 )}
958 </section>
959
Merge branch 'projects-kind-and-links'960 {checklist && <ProductionChecklist base={base} items={checklist} title={loaderData.checklistTitle} />}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look961
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API962 <Panel
963 title="Right now"
Merge branch 'worktree-agent-ad7c6d88d93adc817'964 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 API965 count={agentsLive.length}
966 all={{ to: `${base}/agents`, label: "Agents" }}
967 >
968 {agentsLive.length > 0 && (
969 <ul className="mb-3 grid grid-cols-1 gap-2 sm:grid-cols-2 xl:grid-cols-3">
970 {agentsLive.map((run) => (
Merge branch 'worktree-agent-ad7c6d88d93adc817'971 <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 API972 <div className="flex items-center gap-2 text-sm">
973 <Avatar name={run.agent} size={18} />
974 <span className="font-medium">{run.agent}</span>
975 <span className="text-xs text-muted">{RUN_KIND_LABEL[run.kind].toLowerCase()}</span>
976 <span className="grow" />
977 <span className="text-xs text-muted">
978 <Elapsed from={run.startedAt ?? run.createdAt} />
Projects: what a workspace builds and runs, first on every page979 </span>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API980 </div>
981 <Link
982 to={run.number != null ? `${base}/pull/${run.number}` : `${base}/agents/runs/${run.id}`}
983 className="mt-1 block truncate text-[0.8125rem] hover:text-accent"
984 >
985 {run.title ?? "A run"} {run.number != null && <span className="text-faint">#{run.number}</span>}
986 </Link>
987 {run.step && (
Merge branch 'worktree-agent-ae1299e92e4462012'988 <Hint label={run.step}>
989 <p className="mt-1 truncate font-mono text-[0.6875rem] text-fg/70">
Merge branch 'worktree-agent-ad7c6d88d93adc817'990 <span className="mr-1.5 inline-block size-1.5 animate-pulse rounded-full bg-accent align-middle" />
Merge branch 'worktree-agent-ae1299e92e4462012'991 {run.step}
992 </p>
993 </Hint>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API994 )}
995 <p className="mt-1.5 flex items-center gap-3 text-[0.6875rem] text-faint">
996 {formatCost(run.costUsd) && <span>{formatCost(run.costUsd)} so far</span>}
997 <Link to={`${base}/agents/runs/${run.id}`} className="hover:text-fg">
998 {run.stepCount} steps
999 </Link>
1000 {run.number != null && (
1001 <Link to={`${base}/sessions/${run.number}`} className="hover:text-fg">
1002 Session
1003 </Link>
1004 )}
1005 </p>
1006 </li>
1007 ))}
1008 </ul>
1009 )}
1010 {!loaderData.pullsLoaded ? (
1011 <Unavailable what="Pull requests" />
1012 ) : Object.values(columns).every((c) => c.length === 0) ? (
1013 <Quiet
1014 action={
1015 member ? (
1016 <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">
1017 <Bot size={12} /> Open an issue for an agent
1018 </Link>
1019 ) : null
1020 }
1021 >
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1022 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 API1023 </Quiet>
1024 ) : (
1025 <Pipeline columns={columns} base={base} />
Projects: what a workspace builds and runs, first on every page1026 )}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1027 </Panel>
Projects: what a workspace builds and runs, first on every page1028
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1029 <div className="grid gap-x-8 gap-y-8 lg:grid-cols-[minmax(0,1fr)_20rem]">
1030 <div className="min-w-0 space-y-8">
1031 {needs.length > 0 && (
1032 <Panel title="Needs you" icon={<Hand size={14} className="text-warn" />} count={needs.length}>
1033 <NeedsList needs={needs} limit={5} />
1034 </Panel>
1035 )}
1036
Fast pages, required checks on the branch, self-hosted runners, honest incidents1037 <Suspense
1038 fallback={
1039 <Panel title="Active branches" icon={<GitBranch size={14} />}>
1040 <div aria-busy="true">
1041 <SkeletonRows rows={3} rowClassName="h-12" />
1042 </div>
1043 </Panel>
1044 }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1045 >
Merge branch 'projects-kind-and-links'1046 <Await
1047 resolve={branches}
1048 errorElement={
1049 <Panel title="Active branches" icon={<GitBranch size={14} />}>
1050 <Unavailable what="Branches" />
1051 </Panel>
1052 }
1053 >
Fast pages, required checks on the branch, self-hosted runners, honest incidents1054 {(branches) => (
Merge branch 'projects-kind-and-links'1055 <Panel
1056 title="Active branches"
1057 icon={<GitBranch size={14} />}
1058 count={branches && branches !== "slow" ? branches.total || null : null}
1059 all={{ to: `${base}/branches`, label: "All branches" }}
1060 >
1061 {branches === "slow" ? (
1062 <Quiet>
1063 Its branches are taking a while to compare.{" "}
1064 <Link to={`${base}/branches`} className="text-accent hover:underline">
1065 See them on Branches
1066 </Link>
1067 .
1068 </Quiet>
1069 ) : !branches ? (
Fast pages, required checks on the branch, self-hosted runners, honest incidents1070 <Unavailable what="Branches" />
1071 ) : branches.shown.length === 0 ? (
1072 <Quiet>
1073 Only {branches.main} so far. Branches pushed here show with how far each has moved from {branches.main}, and
1074 its pull request and preview.
1075 </Quiet>
1076 ) : (
1077 <>
1078 <ActiveBranches branches={branches.shown} base={base} main={branches.main} />
1079 {branches.total > branches.shown.length && (
1080 <p className="mt-2 px-1 text-xs text-faint">
1081 The {branches.shown.length} most recently changed of {branches.total} branches.
1082 </p>
1083 )}
1084 </>
1085 )}
1086 </Panel>
1087 )}
1088 </Await>
1089 </Suspense>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1090
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1091 <Panel title="Recent changes" icon={<GitMerge size={14} />} all={{ to: `${base}/pulls?state=closed`, label: "All landed" }}>
1092 {landed.length === 0 ? (
1093 <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 page1094 ) : (
1095 <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 API1096 {landed.map((pull) => {
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1097 const byAgent = pull.runtime === "hosted" || madeByG1t(pull);
Projects: what a workspace builds and runs, first on every page1098 return (
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1099 <li key={pull.id} className="flex items-start gap-3 px-4 py-3">
1100 <GitMerge size={15} className="mt-0.5 shrink-0 text-merged" />
1101 <span className="min-w-0 grow">
1102 <Link to={`${base}/pull/${pull.number}`} prefetch="intent" className="block truncate text-sm font-medium hover:text-accent">
1103 {pull.title}
1104 </Link>
1105 <span className="mt-0.5 flex flex-wrap items-center gap-x-1.5 text-xs text-muted">
1106 <span className="font-mono text-faint">#{pull.number}</span>
1107 <span>·</span>
1108 <span className="inline-flex items-center gap-1">
1109 <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 rights1110 {pull.requestedBy
1111 ? `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-agent1112 : byAgent
1113 ? `made by ${pull.agent}`
1114 : `by ${pull.author.username}`}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1115 </span>
1116 {pull.issue != null && (
1117 <>
1118 <span>· for</span>
1119 <Link to={`${base}/issues/${pull.issue}`} className="hover:text-fg">
1120 #{pull.issue}
1121 </Link>
1122 </>
1123 )}
1124 {pull.mergedBy && <span>· landed by {pull.mergedBy}</span>}
1125 <span>
1126 · <TimeAgo at={pull.mergedAt ?? pull.updatedAt} />
Projects: what a workspace builds and runs, first on every page1127 </span>
1128 </span>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1129 </span>
1130 <span className="hidden shrink-0 text-xs text-faint sm:block">
1131 <ChangeSize files={pull.files} />
1132 </span>
1133 {byAgent && (
1134 <Link to={`${base}/sessions/${pull.number}`} className="shrink-0 text-xs text-muted hover:text-fg">
1135 Session
1136 </Link>
1137 )}
Projects: what a workspace builds and runs, first on every page1138 </li>
1139 );
1140 })}
1141 </ul>
1142 )}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1143 </Panel>
Projects: what a workspace builds and runs, first on every page1144
Merge branch 'projects-kind-and-links'1145 {loaderData.commits.length > 0 && (
1146 <Panel
1147 title={`Latest on ${source?.defaultBranch ?? "main"}`}
1148 icon={<GitCommitHorizontal size={14} />}
1149 all={{ to: `${base}/commits`, label: "All commits" }}
1150 >
1151 <ul className="divide-y divide-line overflow-hidden rounded-xl border border-line bg-surface">
1152 {loaderData.commits.map((one) => (
1153 <li key={one.hash} className="flex items-center gap-3 px-4 py-2.5 text-sm">
1154 <Avatar name={one.author} size={18} />
1155 <span className="min-w-0 grow">
1156 <Link to={`${base}/commit/${one.hash}`} className="block truncate hover:text-accent">
1157 {one.message}
1158 </Link>
1159 <span className="text-xs text-muted">{one.author}</span>
1160 </span>
1161 <Link to={`${base}/commit/${one.hash}`} className="hidden shrink-0 font-mono text-xs text-faint hover:text-fg sm:block">
1162 {one.hash.slice(0, 7)}
1163 </Link>
1164 <span className="w-16 shrink-0 text-right text-xs text-faint">
1165 <TimeAgo at={one.at} />
1166 </span>
1167 </li>
1168 ))}
1169 </ul>
1170 </Panel>
1171 )}
1172
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1173 <Panel title="Activity" icon={<ActivityIcon size={14} />}>
1174 {loaderData.eventsLoaded ? (
1175 <ActivityFeed groups={groups} showRepo={false} limit={8} empty="Nothing has happened here yet." />
1176 ) : (
1177 <Unavailable what="Activity" />
1178 )}
1179 </Panel>
1180
1181 {previews.length > 0 && (
1182 <Panel title="Previews" icon={<GitBranch size={14} />} count={previews.length} all={{ to: `${base}/deployments`, label: "Deployments" }}>
1183 <ul className="divide-y divide-line overflow-hidden rounded-xl border border-line bg-surface">
1184 {previews.map((app) => (
1185 <li key={app.url} className="flex items-center gap-3 px-4 py-2.5 text-sm">
1186 <span className="min-w-0 grow">
1187 <a href={app.url} className="block truncate font-mono text-[0.8125rem] hover:text-accent">
1188 {host(app.url)}
1189 </a>
1190 <span className="text-xs text-muted">
1191 {app.branch}
1192 {app.number != null && (
1193 <>
1194 {" · "}
1195 <Link to={`${base}/pull/${app.number}`} className="hover:underline">
1196 #{app.number}
1197 </Link>
1198 </>
1199 )}
1200 </span>
Projects: what a workspace builds and runs, first on every page1201 </span>
1202 <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 API1203 <TimeAgo at={app.deployedAt} />
Projects: what a workspace builds and runs, first on every page1204 </span>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1205 </li>
1206 ))}
1207 </ul>
1208 </Panel>
1209 )}
1210 </div>
Projects: what a workspace builds and runs, first on every page1211
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1212 <aside className="space-y-4 text-sm">
Merge branch 'projects-kind-and-links'1213 {project && (
1214 <section className="rounded-xl border border-line bg-surface p-5">
1215 <div className="flex items-center justify-between">
1216 <h2 className="text-sm font-semibold">About</h2>
1217 {canChange && <AboutEditor project={project} />}
1218 </div>
1219 <p className={`mt-2.5 text-sm ${project.description ? "text-fg-soft" : "text-faint"}`}>
1220 {project.description ?? "No description."}
1221 </p>
1222 <LinkList links={project.links} className="mt-3" />
1223 <ul className="mt-4 space-y-2 text-xs text-muted">
1224 <li className="flex items-center gap-2">
1225 {project.kind === "library" || project.kind === "tool" ? (
1226 <PackageGlyph size={13} className="shrink-0 text-faint" />
1227 ) : project.kind === "docs" ? (
1228 <BookOpen size={13} className="shrink-0 text-faint" />
1229 ) : (
1230 <Rocket size={13} className="shrink-0 text-faint" />
1231 )}
1232 <Hint label={project.kindReason.detail}>
1233 <span>{kindLabel(project)}</span>
1234 </Hint>
1235 </li>
1236 {loaderData.release && (
1237 <li className="flex items-center gap-2">
1238 <Tag size={13} className="shrink-0 text-faint" />
1239 <Link to={`${base}/tags`} className="hover:text-fg">
1240 <span className="font-mono text-fg-soft">{loaderData.release.name}</span>
1241 {loaderData.release.at && (
1242 <span className="text-faint">
1243 {" · "}
1244 <TimeAgo at={loaderData.release.at} />
1245 </span>
1246 )}
1247 </Link>
1248 </li>
1249 )}
1250 {loaderData.languages && loaderData.languages.length > 0 && (
1251 <li className="flex items-center gap-2">
1252 <Code2 size={13} className="shrink-0 text-faint" />
1253 <span>
1254 {loaderData.languages
1255 .slice(0, 3)
1256 .map((language) => `${language.name} ${Math.round(language.share * 100)}%`)
1257 .join(" · ")}
1258 </span>
1259 </li>
1260 )}
1261 </ul>
1262 {loaderData.contributors && loaderData.contributors.length > 0 && (
1263 <div className="mt-4 flex flex-wrap gap-1">
1264 {loaderData.contributors.slice(0, 12).map((person) => (
1265 <Hint key={person.username} label={person.username}>
1266 <Link to={`/${person.username}`}>
1267 <Avatar name={person.username} size={22} />
1268 </Link>
1269 </Hint>
1270 ))}
1271 </div>
1272 )}
1273 </section>
1274 )}
1275
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1276 {member && (
1277 <section className="rounded-xl border border-line bg-surface p-5">
1278 <div className="flex items-center justify-between">
1279 <h2 className="flex items-center gap-1.5 text-sm font-semibold">
Merge branch 'worktree-agent-ad7c6d88d93adc817'1280 <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 API1281 What agents know here
1282 </h2>
1283 <Link to={`${base}/memory`} className="text-xs text-muted hover:text-fg">
1284 Memory
1285 </Link>
Projects: what a workspace builds and runs, first on every page1286 </div>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1287 {!loaderData.memoriesLoaded ? (
1288 <p className="mt-3 text-xs text-muted">Memory could not be loaded just now.</p>
1289 ) : knows.length === 0 ? (
1290 <p className="mt-3 text-xs leading-5 text-muted">
1291 Nothing yet. Agents note conventions, decisions and traps as they work, and every agent starting here reads them.
1292 </p>
1293 ) : (
1294 <ul className="mt-3 space-y-2.5">
1295 {knows.map((memory) => (
1296 <li key={memory.id} className="text-xs leading-5">
1297 <p className="line-clamp-3 text-fg-soft">
1298 {memory.pinned && <Pin size={11} className="mr-1 inline text-accent" />}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1299 <InlineMarkdown text={memory.text} />
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1300 </p>
1301 <p className="text-faint">
1302 {memory.kind} · {memory.createdBy} · <TimeAgo at={memory.createdAt} />
1303 </p>
1304 </li>
1305 ))}
1306 {loaderData.memoryCount > knows.length && (
1307 <li>
1308 <Link to={`${base}/memory`} className="text-xs text-muted hover:text-fg">
1309 {loaderData.memoryCount - knows.length} more
1310 </Link>
1311 </li>
1312 )}
1313 </ul>
1314 )}
1315 </section>
Projects: what a workspace builds and runs, first on every page1316 )}
1317
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1318 <section className="rounded-xl border border-line bg-surface p-5">
1319 <h2 className="flex items-center gap-1.5 text-sm font-semibold">
1320 <HeartPulse size={14} className="text-faint" />
1321 Health
Project dependencies: addresses, preview stacks, Affects, and agents who know1322 </h2>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1323 <div className="mt-4 space-y-4 text-xs">
1324 <div>
1325 <div className="flex items-baseline justify-between">
1326 <span className="text-muted">Checks passing</span>
1327 <span className="font-medium tabular-nums">{percent(health.passRate)}</span>
1328 </div>
1329 <div className="mt-1.5">
Merge branch 'worktree-agent-ad7c6d88d93adc817'1330 <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 API1331 </div>
1332 <p className="mt-1 text-faint">
1333 {health.checkRuns
1334 ? `${health.checkRuns} recent runs · ${percent(health.firstPass.rate)} pass on the first try`
1335 : "No checks have run recently."}
1336 </p>
1337 </div>
Merge branch 'projects-kind-and-links'1338 {member && (plan === "production" || builds.length > 0) && (
Project dependencies: addresses, preview stacks, Affects, and agents who know1339 <div>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1340 <div className="flex items-baseline justify-between">
1341 <span className="text-muted">Deploys</span>
1342 <Link to={`${base}/deployments`} className="text-faint hover:text-fg">
1343 {builds.length ? `last ${Math.min(builds.length, 20)}` : "none yet"}
1344 </Link>
1345 </div>
1346 <div className="mt-1.5">
1347 <DeployStrip builds={builds} base={base} />
1348 </div>
Project dependencies: addresses, preview stacks, Affects, and agents who know1349 </div>
1350 )}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1351 <div>
1352 <div className="flex items-baseline justify-between">
1353 <span className="text-muted">Open issues by age</span>
1354 <Link to={`${base}/issues`} className="font-medium tabular-nums hover:text-accent">
1355 {open.issues}
1356 </Link>
1357 </div>
1358 {ages ? (
1359 <ul className="mt-2 space-y-1.5">
1360 {ages.map((bucket) => (
1361 <li key={bucket.label} className="grid grid-cols-[6.5rem_1fr_1.5rem] items-center gap-2">
1362 <span className="text-faint">{bucket.label}</span>
1363 <span className="block h-1.5 overflow-hidden rounded-full bg-line">
1364 <span
1365 className={`block h-full rounded-full ${bucket.label === "Older" ? "bg-warn" : "bg-fg-soft/60"}`}
1366 style={{ width: `${(bucket.count / oldest) * 100}%` }}
1367 />
1368 </span>
1369 <span className="text-right tabular-nums text-muted">{bucket.count}</span>
Project dependencies: addresses, preview stacks, Affects, and agents who know1370 </li>
1371 ))}
1372 </ul>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1373 ) : (
1374 <p className="mt-1 text-faint">Issues could not be loaded just now.</p>
1375 )}
1376 </div>
Project dependencies: addresses, preview stacks, Affects, and agents who know1377 </div>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1378 </section>
Project dependencies: addresses, preview stacks, Affects, and agents who know1379
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1380 <section className="rounded-xl border border-line bg-surface p-5">
1381 <div className="flex items-center justify-between">
1382 <h2 className="flex items-center gap-1.5 text-sm font-semibold">
1383 <Network size={14} className="text-faint" />
1384 Dependencies
1385 </h2>
1386 {member && (
1387 <Link to={`${base}/settings/dependencies`} className="text-xs text-muted hover:text-fg">
1388 Manage
1389 </Link>
1390 )}
1391 </div>
1392 {!dependencies ? (
1393 <p className="mt-3 text-xs text-muted">Dependencies could not be loaded just now.</p>
1394 ) : dependencies.dependsOn.length === 0 && dependencies.usedBy.length === 0 ? (
1395 <p className="mt-3 text-xs leading-5 text-muted">
1396 Uses no other project, and none uses it. Declare one, and builds get its address and agents know what depends on
1397 what.
1398 </p>
1399 ) : (
1400 <div className="mt-3 space-y-3 text-xs">
1401 {dependencies.dependsOn.length > 0 && (
1402 <div>
1403 <p className="flex items-center gap-1 text-muted">
1404 <ArrowUpRight size={12} /> Uses
1405 </p>
1406 <ul className="mt-1.5 space-y-1">
1407 {dependencies.dependsOn.map((d) => (
1408 <li key={d.slug} className="flex items-center justify-between gap-2">
1409 <Link to={`/${params.owner}/${d.slug}`} className="font-medium hover:underline">
1410 {d.name}
1411 </Link>
1412 {d.as && <code className="font-mono text-faint">{d.as}</code>}
1413 </li>
1414 ))}
1415 </ul>
1416 </div>
1417 )}
1418 {dependencies.usedBy.length > 0 && (
1419 <div>
1420 <p className="flex items-center gap-1 text-muted">
1421 <ArrowDownLeft size={12} /> Used by
1422 </p>
1423 <ul className="mt-1.5 space-y-1">
1424 {dependencies.usedBy.map((d) => (
1425 <li key={d.slug}>
1426 <Link to={`/${params.owner}/${d.slug}`} className="font-medium hover:underline">
1427 {d.name}
1428 </Link>
1429 </li>
1430 ))}
1431 </ul>
1432 </div>
1433 )}
1434 </div>
1435 )}
Projects: what a workspace builds and runs, first on every page1436 </section>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1437
1438 {source && (
1439 <section className="rounded-xl border border-line bg-surface p-5">
1440 <h2 className="text-sm font-semibold">Clone</h2>
1441 <div className="mt-3">
Merge branch 'worktree-agent-aaf03bdceac799c89'1442 <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 API1443 </div>
1444 </section>
1445 )}
1446 </aside>
1447 </div>
Projects: what a workspace builds and runs, first on every page1448 </div>
1449 );
1450}

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