Skip to content
341 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.

Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971/**
2 * The top of a project's overview for what g1t does not deploy: an app
3 * deployed elsewhere, an app nobody has said where it runs, docs, and
4 * anything else. An app on g1t keeps its production card, and a library
5 * or a tool its packages (routes/repo/overview.tsx).
6 */
7import { ArrowUpRight, BookOpen, Box, ChevronDown, GitCommitHorizontal, Globe, Rocket } from "lucide-react";
8import { type ReactNode, useState } from "react";
9import { Link, useFetcher } from "react-router";
10
11import type { DeployStatus, Project } from "@g1t/contracts";
12
Merge branch 'site-fixes' into fast-push13import { DeployLink, host, StatusDot } from "./deploy";
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9714import { ButtonLink, Input, SubmitButton, TimeAgo } from "./ui";
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.15import { Button } from "./ui/button";
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9716import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "./ui/dropdown-menu";
17import { Hint } from "./ui/hint";
18
19/**
20 * A deployment reported to g1t from outside it, such as by a pipeline that
21 * deploys production itself. When there is one for production, the card
22 * shows it.
23 */
24export type ExternalDeployment = {
25 environment: string;
26 url: string | null;
27 /** success, failure, error, pending, queued, in_progress or inactive. */
28 state: string;
29 sha: string;
30 created_at: string;
31};
32
33/** A reported deployment's state, as g1t's own deployments show theirs. */
34export function deploymentStatus(state: string): DeployStatus {
35 switch (state) {
36 case "success":
37 return "ready";
38 case "failure":
39 case "error":
40 return "failed";
41 case "in_progress":
42 return "building";
43 case "inactive":
44 return "replaced";
45 default:
46 return "queued";
47 }
48}
49
50type Head = {
51 project: Project;
52 base: string;
53 /** May change the project's settings: what it is, its links. */
54 canChange: boolean;
55 /** May turn on Deployments. */
56 canDeploy: boolean;
57};
58
59export function HeadLabel({ icon, children }: { icon: ReactNode; children: ReactNode }) {
60 return (
61 <p className="flex items-center gap-2 text-xs font-medium tracking-wide text-muted uppercase [&_svg]:size-3.25 [&_svg]:text-accent">
62 {icon}
63 {children}
64 </p>
65 );
66}
67
68function BigLink({ url }: { url: string }) {
69 return (
Merge branch 'site-fixes' into fast-push70 <DeployLink href={url} className="mt-2 flex min-w-0 items-center gap-1.5 font-mono text-lg font-medium hover:text-accent">
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9771 <span className="truncate">{host(url).replace(/\/$/, "")}</span>
72 <ArrowUpRight size={16} className="shrink-0 text-faint" />
Merge branch 'site-fixes' into fast-push73 </DeployLink>
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9774 );
75}
76
77/** An address, saved for the project in place: production's, or its docs'. */
78function AddressForm({
79 field,
80 placeholder,
81 label,
82 extra,
83}: {
84 field: "productionUrl" | "docsUrl";
85 placeholder: string;
86 label: string;
87 /** Hidden fields sent with it. */
88 extra: Record<string, string>;
89}) {
90 const fetcher = useFetcher<{ error?: string }>();
91 return (
92 <fetcher.Form method="post" className="mt-3 max-w-lg">
93 {Object.entries(extra).map(([name, value]) => (
94 <input key={name} type="hidden" name={name} value={value} />
95 ))}
96 <div className="flex items-center gap-2">
97 <div className="min-w-0 grow">
98 <Input name={field} type="text" inputMode="url" required placeholder={placeholder} aria-label={label} maxLength={255} />
99 </div>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.100 <SubmitButton fetcher={fetcher} variant="outline" pending="Saving…">
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97101 Save
102 </SubmitButton>
103 </div>
104 {fetcher.data?.error && <p className="mt-1.5 text-xs text-danger">{fetcher.data.error}</p>}
105 </fetcher.Form>
106 );
107}
108
109/** Deploying on g1t, offered as an option and never as a step. */
110function DeployQuietly({ base, canDeploy, what }: { base: string; canDeploy: boolean; what: string }) {
111 if (!canDeploy) return null;
112 return (
113 <p className="mt-4 text-xs text-faint">
114 g1t can deploy {what} on g1t.page instead, with a preview for every pull request.{" "}
115 <Link to={`${base}/settings/deployments`} className="underline-offset-4 hover:text-fg hover:underline">
116 Deployment settings
117 </Link>
118 </p>
119 );
120}
121
122/**
123 * An app deployed by its own pipeline: production at the address it was
124 * given, with the latest reported deployment, or else the default branch's
125 * latest commit and its checks.
126 */
127export function ElsewhereHead({
128 project,
129 base,
130 canChange,
131 canDeploy,
132 deployment,
133 commit,
134 checks,
135}: Head & {
136 deployment?: ExternalDeployment | null;
137 commit: { hash: string; authoredAt: string } | null;
138 /** The commit's checks, as a badge; left out until there is one to show. */
139 checks?: ReactNode;
140}) {
141 const url = deployment?.url ?? project.productionUrl;
142 return (
143 <div className="flex flex-col gap-4 p-5 sm:flex-row sm:items-start sm:justify-between sm:p-6">
144 <div className="min-w-0">
145 <HeadLabel icon={<Rocket />}>Production</HeadLabel>
146 {url ? (
147 <>
148 <BigLink url={url} />
149 <p className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted">
150 {deployment ? (
151 <>
152 <StatusDot status={deploymentStatus(deployment.state)} />
153 <span className="inline-flex items-center gap-1 font-mono">
154 <GitCommitHorizontal size={13} className="text-faint" />
155 {deployment.sha.slice(0, 7)}
156 </span>
157 <span>
158 deployed <TimeAgo at={deployment.created_at} />
159 </span>
160 </>
161 ) : (
162 <>
163 <span>Deployed by its own pipeline</span>
164 {commit && (
165 <span className="inline-flex items-center gap-1.5">
166 <span className="text-faint">·</span>
167 <Link to={`${base}/commit/${commit.hash}`} className="inline-flex items-center gap-1 font-mono hover:text-fg">
168 <GitCommitHorizontal size={13} className="text-faint" />
169 {commit.hash.slice(0, 7)}
170 </Link>
171 {checks}
172 <TimeAgo at={commit.authoredAt} />
173 </span>
174 )}
175 </>
176 )}
177 </p>
178 </>
179 ) : canChange ? (
180 <>
181 <p className="mt-2 text-lg font-medium">Where is production?</p>
182 <p className="mt-1 max-w-lg text-sm text-muted">
183 {project.name} is deployed by its own pipeline. Give production's address, and this card links to it.
184 </p>
185 <AddressForm field="productionUrl" placeholder="https://example.com" label="Production's address" extra={{ intent: "kind", choice: "elsewhere" }} />
186 </>
187 ) : (
188 <p className="mt-2 text-sm text-muted">Deployed by its own pipeline.</p>
189 )}
190 <DeployQuietly base={base} canDeploy={canDeploy} what="it" />
191 </div>
192 {url && (
193 <div className="flex shrink-0 items-center gap-2">
Merge branch 'site-fixes' into fast-push194 <ButtonLink to={url} variant="accent" reloadDocument target="_blank" rel="noopener noreferrer">
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97195 Visit
196 <ArrowUpRight size={14} />
197 </ButtonLink>
198 </div>
199 )}
200 </div>
201 );
202}
203
204const NOT_DEPLOYED: { value: string; label: string }[] = [
205 { value: "library", label: "A library or package" },
206 { value: "tool", label: "A tool or CLI" },
207 { value: "docs", label: "Documentation" },
208 { value: "other", label: "Something else" },
209];
210
211/**
212 * An app that nobody has said where it runs, with Deployments off: one
213 * question, answered in place, instead of a push to turn Deployments on.
214 */
215export function WhereItRuns({ project, base, canChange, canDeploy }: Head) {
216 const fetcher = useFetcher<{ error?: string }>();
217 const [elsewhere, setElsewhere] = useState(false);
218 if (!canChange) {
219 return (
220 <div className="p-5 sm:p-6">
221 <HeadLabel icon={<Globe />}>Homepage</HeadLabel>
222 {project.links.homepage ? <BigLink url={project.links.homepage} /> : <p className="mt-2 text-sm text-muted">No homepage given.</p>}
223 </div>
224 );
225 }
226 return (
227 <div className="p-5 sm:p-6">
228 <HeadLabel icon={<Rocket />}>Production</HeadLabel>
229 <p className="mt-2 text-lg font-medium">Where does {project.name} run?</p>
230 <p className="mt-1 max-w-xl text-sm text-muted">
231 Say once, and this page follows: production where it is deployed, or its packages and releases if it isn't
232 deployed at all. <span className="text-faint">{project.detected.reason.detail}</span>
233 </p>
234 <div className="mt-4 flex flex-wrap items-center gap-2">
235 {canDeploy && (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.236 <ButtonLink to={`${base}/settings/deployments`} variant="outline">
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97237 <Rocket size={14} />
238 Deploy on g1t
239 </ButtonLink>
240 )}
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.241 <Button
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97242 type="button"
243 onClick={() => setElsewhere((open) => !open)}
244 aria-expanded={elsewhere}
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.245 variant="outline"
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97246 >
247 <Globe size={14} />
248 It's deployed elsewhere
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.249 </Button>
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97250 <DropdownMenu>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.251 <DropdownMenuTrigger asChild>
252 <Button variant="outline">
253 <Box size={14} />
254 It isn't deployed
255 <ChevronDown size={13} className="text-faint" />
256 </Button>
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97257 </DropdownMenuTrigger>
258 <DropdownMenuContent align="start">
259 {NOT_DEPLOYED.map((option) => (
260 <DropdownMenuItem key={option.value} onSelect={() => fetcher.submit({ intent: "kind", choice: option.value }, { method: "post" })}>
261 {option.label}
262 </DropdownMenuItem>
263 ))}
264 </DropdownMenuContent>
265 </DropdownMenu>
266 </div>
267 {elsewhere && (
268 <AddressForm field="productionUrl" placeholder="Production's address, such as https://example.com" label="Production's address" extra={{ intent: "kind", choice: "elsewhere" }} />
269 )}
270 {fetcher.data?.error && <p className="mt-2 text-xs text-danger">{fetcher.data.error}</p>}
271 </div>
272 );
273}
274
275/** Documentation: where it is read, or a place to say so. */
276export function DocsHead({ project, base, canChange, canDeploy }: Head) {
277 const url = project.links.docs ?? project.productionUrl ?? project.links.homepage;
278 return (
279 <div className="flex flex-col gap-4 p-5 sm:flex-row sm:items-start sm:justify-between sm:p-6">
280 <div className="min-w-0">
281 <HeadLabel icon={<BookOpen />}>Documentation</HeadLabel>
282 {url ? (
283 <BigLink url={url} />
284 ) : canChange ? (
285 <>
286 <p className="mt-2 text-lg font-medium">Where are its docs read?</p>
287 <p className="mt-1 max-w-lg text-sm text-muted">Give the address, and this card and its listing link to it.</p>
288 <AddressForm field="docsUrl" placeholder="https://docs.example.com" label="Where its docs are read" extra={{ intent: "about" }} />
289 </>
290 ) : (
291 <p className="mt-2 text-sm text-muted">Its docs are in its repository.</p>
292 )}
293 <DeployQuietly base={base} canDeploy={canDeploy && project.runs == null} what="its docs as a site" />
294 </div>
295 {url && (
296 <div className="flex shrink-0 items-center gap-2">
Merge branch 'site-fixes' into fast-push297 <ButtonLink to={url} variant="accent" reloadDocument target="_blank" rel="noopener noreferrer">
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97298 Read
299 <ArrowUpRight size={14} />
300 </ButtonLink>
301 </div>
302 )}
303 </div>
304 );
305}
306
307/** Anything else: its homepage, or where to add its links. */
308export function OtherHead({ project, base, canChange }: Omit<Head, "canDeploy">) {
309 const url = project.links.homepage ?? project.links.docs ?? project.links.custom[0]?.url ?? null;
310 return (
311 <div className="p-5 sm:p-6">
312 <HeadLabel icon={<Globe />}>Homepage</HeadLabel>
313 {url ? (
314 <BigLink url={url} />
315 ) : canChange ? (
316 <p className="mt-2 text-sm text-muted">
317 No links yet.{" "}
318 <Link to={`${base}/settings#links`} className="text-accent hover:underline">
319 Add a homepage, docs or others
320 </Link>{" "}
321 and they show here, on its card and in its workspace's Projects.
322 </p>
323 ) : (
324 <p className="mt-2 text-sm text-muted">No homepage given.</p>
325 )}
326 </div>
327 );
328}
329
330/** Production as g1t serves it, with the project's homepage when that is somewhere else. */
331export function AlsoAt({ url }: { url: string | null }) {
332 if (!url) return null;
333 return (
334 <Hint label="Its homepage">
Merge branch 'site-fixes' into fast-push335 <DeployLink href={url} className="inline-flex items-center gap-1 text-faint hover:text-fg">
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97336 <Globe size={12} />
337 {host(url).replace(/\/$/, "")}
Merge branch 'site-fixes' into fast-push338 </DeployLink>
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97339 </Hint>
340 );
341}

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