Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
573 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.

Navigation: the workspace's own page with tabs, pins and recent in the sidebar1import { ArrowUpRight, Box, ChevronLeft, ChevronRight, LayoutGrid, List, Lock, Rocket, Search } from "lucide-react";
The top of every page is one surface: the breadcrumb, search, spend, Ask g1t, bell and + share one height, corners and spacing, Ask g1t is a plain tinted button with the g1t mark drawn on whole pixels, a page's own header sits directly under the bar at the same left edge, and every section, conversations and open artifacts included, draws the same rounded panel. Tables written in chat render as tables, and the site's file, checkbox, radio, number and date controls are the design system's. Button, Separator, Breadcrumb, Table and FileDrop join the shared components; the chat guide says how tables work.2import { type KeyboardEvent, useEffect, useRef, useState } from "react";
Navigation: the workspace's own page with tabs, pins and recent in the sidebar3import { Form, Link, useLocation, useNavigation, useSubmit } from "react-router";
4
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.5import type { MirrorState } from "@g1t/contracts";
6
Navigation: the workspace's own page with tabs, pins and recent in the sidebar7import type { Route } from "./+types/projects";
8import { host } from "../../components/deploy";
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.9import { MirrorBadge } from "../../components/mirror";
Navigation: the workspace's own page with tabs, pins and recent in the sidebar10import { PinButton } from "../../components/pin-button";
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.11import { EmptyState, TimeAgo, notACredential } from "../../components/ui";
12import { Button } from "../../components/ui/button";
13import { Badge } from "../../components/ui/badge";
14import { Card } from "../../components/ui/card";
The top of every page is one surface: the breadcrumb, search, spend, Ask g1t, bell and + share one height, corners and spacing, Ask g1t is a plain tinted button with the g1t mark drawn on whole pixels, a page's own header sits directly under the bar at the same left edge, and every section, conversations and open artifacts included, draws the same rounded panel. Tables written in chat render as tables, and the site's file, checkbox, radio, number and date controls are the design system's. Button, Separator, Breadcrumb, Table and FileDrop join the shared components; the chat guide says how tables work.15import { Checkbox } from "../../components/ui/checkbox";
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.16import { Kbd } from "../../components/ui/kbd";
Chat controls, public profiles, shadcn selects, and no Docs tab in a project17import { SelectField } from "../../components/ui/select";
Navigation: the workspace's own page with tabs, pins and recent in the sidebar18import { page as pageMeta } from "../../lib/meta";
19import {
20 LANGUAGE_OF,
21 type Listed,
22 type ProjectQuery,
23 SORTS,
24 facetsOf,
25 filterProjects,
26 isFiltered,
27 lastUpdated,
28 nextRow,
29 pageOf,
30 projectQueryString,
31 readProjectQuery,
32} from "../../lib/project-list";
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9733import { kindLabel, primaryLink } from "../../lib/project-kind";
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.34import { deployments, mirrors, projects } from "../../lib/services.server";
Navigation: the workspace's own page with tabs, pins and recent in the sidebar35import { getViewer, roleIn } from "../../lib/session.server";
36import { workspaceProjects } from "../../lib/workspace-projects.server";
37
38export function meta({ params, ...args }: Route.MetaArgs) {
39 return pageMeta(args, { title: `Projects · ${params.owner} · g1t` });
40}
41
42/**
43 * Every project the viewer can see, filtered, sorted and paged here: one
44 * listing (shared with the sidebar's), with Deployments and the viewer's
45 * pins asked for at the same time.
46 */
47export async function loader({ params, context, request }: Route.LoaderArgs) {
48 const viewer = getViewer(context);
49 const slug = params.owner.toLowerCase();
50 const role = roleIn(viewer, slug);
51 const query = readProjectQuery(new URL(request.url).searchParams);
52 const [listed, deploys, shortcuts] = await Promise.all([
53 workspaceProjects(slug, viewer),
54 role ? deployments.overview(slug, viewer).catch(() => null) : null,
55 role ? projects.shortcuts(slug, viewer).catch(() => null) : null,
56 ]);
57 const live = new Map((deploys?.ok ? deploys.value : []).map((entry) => [entry.slug, entry]));
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9758 const full = new Map((listed.ok ? listed.value : []).map((project) => [project.id, project]));
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.59 // Which repositories mirror a remote, and where each mirror stands, so a
60 // row says it is a mirror and the list can be narrowed to them: one call
61 // for every repository listed. Without an answer, nothing is marked.
62 const repoIds = [...new Set((listed.ok ? listed.value : []).flatMap((project) => (project.source.kind === "hosted" ? [project.source.repoId] : [])))];
63 const briefs = repoIds.length > 0 ? await mirrors.briefs(repoIds).catch(() => []) : [];
64 const leaders = new Map(briefs.filter((brief) => brief.role === "leader").map((brief) => [brief.repoId, brief]));
Navigation: the workspace's own page with tabs, pins and recent in the sidebar65 const all: Listed[] = (listed.ok ? listed.value : []).map((project) => ({
66 id: project.id,
67 slug: project.slug,
68 name: project.name,
69 description: project.description,
70 private: project.private,
71 archived: project.archived,
72 kind: project.kind,
73 ecosystem: project.ecosystem,
74 updatedAt: project.updatedAt,
75 pushedAt: project.pushedAt ?? null,
76 activity: project.activity ?? 0,
77 deploying: live.get(project.slug)?.enabled ?? false,
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.78 mirror: (() => {
79 const leader = project.source.kind === "hosted" ? leaders.get(project.source.repoId) : undefined;
80 return leader ? { state: leader.state as MirrorState, remote: leader.name } : null;
81 })(),
Navigation: the workspace's own page with tabs, pins and recent in the sidebar82 }));
83 const matched = filterProjects(all, query);
84 const shown = pageOf(matched, query.page);
85 return {
86 slug,
87 role,
88 query: { ...query, page: shown.page },
89 total: all.length,
90 matched: matched.length,
91 page: { page: shown.page, pages: shown.pages, from: shown.from, to: shown.to },
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9792 // Production where g1t serves it or elsewhere, else its homepage or docs.
93 items: shown.items.map((project) => {
94 const one = full.get(project.id)!;
95 return { ...project, runs: one.runs, production: primaryLink(one, live.get(project.slug)?.production?.url) };
96 }),
Navigation: the workspace's own page with tabs, pins and recent in the sidebar97 facets: facetsOf(all),
98 pinned: (shortcuts?.pinned ?? []).map((project) => project.id),
99 failed: !listed.ok,
100 };
101}
102
103type Item = Route.ComponentProps["loaderData"]["items"][number];
104
105const CONTROL =
106 "h-9 rounded-md border border-line bg-surface px-2.5 text-sm text-fg outline-none transition-colors hover:border-line-strong focus:border-accent-dim";
Chat controls, public profiles, shadcn selects, and no Docs tab in a project107/** A filter's select, on the same surface as the other controls. */
108const FILTER = "w-auto bg-surface";
Navigation: the workspace's own page with tabs, pins and recent in the sidebar109
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.110/**
111 * Its language, its kind when it is not an app, whether it mirrors a
112 * remote (and where the mirror stands), and its state, as small words.
113 */
Navigation: the workspace's own page with tabs, pins and recent in the sidebar114function Facts({ project }: { project: Item }) {
115 const language = project.ecosystem ? LANGUAGE_OF[project.ecosystem] : null;
116 return (
117 <>
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.118 {project.mirror && (
119 <MirrorBadge mirror={{ state: project.mirror.state, remote: project.mirror.remote, url: "", since: "" }} briefs={[]} />
120 )}
Navigation: the workspace's own page with tabs, pins and recent in the sidebar121 {language && <span>{language}</span>}
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97122 {project.kind !== "app" && <span>{kindLabel(project)}</span>}
123 {project.kind === "app" && project.runs === "elsewhere" && <span>Deployed elsewhere</span>}
Navigation: the workspace's own page with tabs, pins and recent in the sidebar124 {project.deploying && (
Merge branch 'worktree-agent-ad7c6d88d93adc817'125 <span className="inline-flex items-center gap-1 text-success">
Navigation: the workspace's own page with tabs, pins and recent in the sidebar126 <Rocket size={11} />
127 Deploys
128 </span>
129 )}
130 <span className="text-faint">
131 Updated <TimeAgo at={lastUpdated(project)} />
132 </span>
133 </>
134 );
135}
136
137function ProjectRow({ project, slug, pinned, member }: { project: Item; slug: string; pinned: boolean; member: boolean }) {
138 const to = `/${slug}/${project.slug}`;
139 return (
140 <li className="group relative flex items-center gap-3 px-4 py-3 transition-colors focus-within:bg-raised/60 hover:bg-raised/60">
141 <span className="flex size-8 shrink-0 items-center justify-center rounded-md bg-raised text-muted ring-1 ring-line">
142 {project.private ? <Lock size={14} /> : <Box size={14} />}
143 </span>
144 <div className="min-w-0 grow">
145 <div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
146 <Link
147 to={to}
148 prefetch="intent"
149 data-row
150 className="truncate font-medium outline-none after:absolute after:inset-0 hover:underline focus-visible:underline"
151 >
152 {project.name}
153 </Link>
154 {project.name.toLowerCase() !== project.slug && <span className="truncate font-mono text-xs text-faint">{project.slug}</span>}
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.155 {project.private && <Badge size="md">private</Badge>}
156 {project.archived && <Badge size="md">archived</Badge>}
Navigation: the workspace's own page with tabs, pins and recent in the sidebar157 </div>
158 {project.description && <p className="mt-0.5 truncate text-sm text-muted">{project.description}</p>}
159 <p className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs text-muted">
160 <Facts project={project} />
161 </p>
162 </div>
163 {project.production && (
164 <a
165 href={project.production}
166 className="relative z-10 hidden shrink-0 items-center gap-1 font-mono text-xs text-muted hover:text-accent md:flex"
167 >
168 {host(project.production)}
169 <ArrowUpRight size={11} />
170 </a>
171 )}
172 {member && <PinButton workspace={slug} slug={project.slug} name={project.name} pinned={pinned} compact />}
173 </li>
174 );
175}
176
177function ProjectTile({ project, slug, pinned, member }: { project: Item; slug: string; pinned: boolean; member: boolean }) {
178 return (
179 <li className="group relative flex flex-col rounded-2xl border border-line bg-surface p-5 transition-colors focus-within:border-line-strong hover:border-line-strong">
180 <div className="flex items-start gap-3">
181 <span className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-raised text-muted ring-1 ring-line">
182 {project.private ? <Lock size={15} /> : <Box size={15} />}
183 </span>
184 <div className="min-w-0 grow">
185 <Link
186 to={`/${slug}/${project.slug}`}
187 prefetch="intent"
188 data-row
189 className="block truncate font-medium outline-none after:absolute after:inset-0 hover:underline focus-visible:underline"
190 >
191 {project.name}
192 </Link>
193 {project.production ? (
194 <a href={project.production} className="relative z-10 mt-0.5 flex items-center gap-1 truncate font-mono text-xs text-muted hover:text-accent">
195 {host(project.production)}
196 <ArrowUpRight size={11} className="shrink-0" />
197 </a>
198 ) : (
199 <p className="mt-0.5 truncate font-mono text-xs text-faint">{project.slug}</p>
200 )}
201 </div>
202 {member && <PinButton workspace={slug} slug={project.slug} name={project.name} pinned={pinned} compact className="-mt-1 -mr-2" />}
203 </div>
204 <p className="mt-3 line-clamp-2 min-h-10 text-sm text-muted">{project.description ?? ""}</p>
205 <p className="mt-auto flex flex-wrap items-center gap-x-3 gap-y-0.5 border-t border-line pt-3 text-xs text-muted">
206 {project.private && <span>Private</span>}
207 {project.archived && <span>Archived</span>}
208 <Facts project={project} />
209 </p>
210 </li>
211 );
212}
213
214/** The pages around the current one: the first, the last, and two either side. */
215function pageNumbers(page: number, pages: number): (number | null)[] {
216 const wanted = new Set([1, pages, page - 2, page - 1, page, page + 1, page + 2].filter((n) => n >= 1 && n <= pages));
217 const sorted = [...wanted].sort((a, b) => a - b);
218 return sorted.flatMap((n, i) => (i > 0 && n - sorted[i - 1]! > 1 ? [null, n] : [n]));
219}
220
221function Pages({ query, page, pages }: { query: ProjectQuery; page: number; pages: number }) {
222 if (pages <= 1) return null;
223 const link = "flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm transition-colors";
224 return (
225 <nav aria-label="Pages" className="mt-6 flex flex-wrap items-center justify-center gap-1">
226 {page > 1 ? (
227 <Link to={projectQueryString(query, { page: page - 1 })} className={`${link} gap-1 text-muted hover:bg-raised hover:text-fg`}>
228 <ChevronLeft size={14} />
229 Previous
230 </Link>
231 ) : null}
232 {pageNumbers(page, pages).map((n, i) =>
233 n == null ? (
234 <span key={`gap${i}`} className="px-1 text-faint">
235 …
236 </span>
237 ) : (
238 <Link
239 key={n}
240 to={projectQueryString(query, { page: n })}
241 aria-current={n === page ? "page" : undefined}
242 className={`${link} tabular-nums ${n === page ? "bg-raised font-medium text-fg ring-1 ring-line" : "text-muted hover:bg-raised hover:text-fg"}`}
243 >
244 {n}
245 </Link>
246 ),
247 )}
248 {page < pages ? (
249 <Link to={projectQueryString(query, { page: page + 1 })} className={`${link} gap-1 text-muted hover:bg-raised hover:text-fg`}>
250 Next
251 <ChevronRight size={14} />
252 </Link>
253 ) : null}
254 </nav>
255 );
256}
257
258export default function WorkspaceProjects({ loaderData }: Route.ComponentProps) {
259 const { slug, role, query, total, matched, page, items, facets, pinned, failed } = loaderData;
260 const submit = useSubmit();
261 const navigation = useNavigation();
262 const { pathname } = useLocation();
263 const form = useRef<HTMLFormElement>(null);
264 const searchBox = useRef<HTMLInputElement>(null);
265 const typing = useRef<ReturnType<typeof setTimeout> | null>(null);
The top of every page is one surface: the breadcrumb, search, spend, Ask g1t, bell and + share one height, corners and spacing, Ask g1t is a plain tinted button with the g1t mark drawn on whole pixels, a page's own header sits directly under the bar at the same left edge, and every section, conversations and open artifacts included, draws the same rounded panel. Tables written in chat render as tables, and the site's file, checkbox, radio, number and date controls are the design system's. Button, Separator, Breadcrumb, Table and FileDrop join the shared components; the chat guide says how tables work.266 const deploysBox = useRef<HTMLButtonElement>(null);
267 const [deploys, setDeploys] = useState(query.deployments);
Navigation: the workspace's own page with tabs, pins and recent in the sidebar268 const member = role != null;
269 const pins = new Set(pinned);
270 // A filter or page of this list on its way: the list says it is busy.
271 const busy = navigation.state === "loading" && navigation.location?.pathname === pathname;
272
273 const apply = (delay = 0) => {
274 if (typing.current) clearTimeout(typing.current);
275 typing.current = setTimeout(() => {
276 if (form.current) submit(form.current, { replace: true, preventScrollReset: true });
277 }, delay);
278 };
279 useEffect(() => () => void (typing.current && clearTimeout(typing.current)), []);
280 // Back and forward change the address, not the fields: they follow it,
281 // except the one being used.
282 useEffect(() => {
283 const fields = form.current?.elements;
284 if (!fields) return;
285 const values: Record<string, string> = {
286 q: query.q,
287 visibility: query.visibility,
288 kind: query.kind,
289 language: query.language ?? "",
290 archived: query.archived,
291 sort: query.sort,
292 };
293 for (const [name, value] of Object.entries(values)) {
294 const field = fields.namedItem(name) as HTMLInputElement | HTMLSelectElement | null;
295 if (field && field !== document.activeElement && "value" in field) field.value = value;
296 }
The top of every page is one surface: the breadcrumb, search, spend, Ask g1t, bell and + share one height, corners and spacing, Ask g1t is a plain tinted button with the g1t mark drawn on whole pixels, a page's own header sits directly under the bar at the same left edge, and every section, conversations and open artifacts included, draws the same rounded panel. Tables written in chat render as tables, and the site's file, checkbox, radio, number and date controls are the design system's. Button, Separator, Breadcrumb, Table and FileDrop join the shared components; the chat guide says how tables work.297 if (deploysBox.current !== document.activeElement) setDeploys(query.deployments);
Navigation: the workspace's own page with tabs, pins and recent in the sidebar298 }, [query]);
299
300 // `/` goes to the search, from anywhere on the page that is not a field.
301 useEffect(() => {
302 const onKey = (event: globalThis.KeyboardEvent) => {
303 const target = event.target as HTMLElement | null;
304 if (event.key !== "/" || event.metaKey || event.ctrlKey || event.altKey) return;
305 if (target && (target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName))) return;
306 event.preventDefault();
307 searchBox.current?.focus();
308 };
309 window.addEventListener("keydown", onKey);
310 return () => window.removeEventListener("keydown", onKey);
311 }, []);
312
313 // The arrows (and j and k) move between projects; Enter opens one.
314 const list = useRef<HTMLDivElement>(null);
315 const onListKey = (event: KeyboardEvent<HTMLDivElement>) => {
316 const rows = [...(list.current?.querySelectorAll<HTMLAnchorElement>("a[data-row]") ?? [])];
317 const at = rows.indexOf(document.activeElement as HTMLAnchorElement);
318 const key = event.key === "ArrowRight" ? "ArrowDown" : event.key === "ArrowLeft" ? "ArrowUp" : event.key;
319 if (at < 0 && key !== "ArrowDown" && key !== "j") return;
320 const to = nextRow(key, at, rows.length);
321 if (to == null || event.metaKey || event.ctrlKey || event.altKey) return;
322 event.preventDefault();
323 rows[to]?.focus();
324 };
325 // From the search box, down goes to the first project.
326 const onSearchKey = (event: KeyboardEvent<HTMLInputElement>) => {
327 if (event.key === "ArrowDown") {
328 event.preventDefault();
329 list.current?.querySelector<HTMLAnchorElement>("a[data-row]")?.focus();
330 }
331 if (event.key === "Escape" && event.currentTarget.value) {
332 event.currentTarget.value = "";
333 apply();
334 }
335 };
336
337 const filtered = isFiltered(query);
338 const option = (label: string, count: number) => `${label} (${count})`;
339
340 return (
341 <div>
342 <Form ref={form} id="project-filters" method="get" role="search" aria-label="Find projects" className="flex flex-wrap items-center gap-2">
343 <div className="relative w-full sm:w-72">
344 <Search size={14} className="pointer-events-none absolute top-1/2 left-3 -translate-y-1/2 text-faint" />
345 <input
346 ref={searchBox}
347 type="search"
348 name="q"
349 defaultValue={query.q}
350 {...notACredential()}
351 placeholder="Find a project…"
352 aria-label="Find a project"
353 aria-keyshortcuts="/"
354 onChange={() => apply(250)}
355 onKeyDown={onSearchKey}
356 className={`${CONTROL} w-full pr-8 pl-8 placeholder:text-faint`}
357 />
358 <kbd className="pointer-events-none absolute top-1/2 right-2 hidden -translate-y-1/2 rounded bg-raised px-1.5 font-mono text-[0.625rem] text-muted ring-1 ring-line sm:block">
359 /
360 </kbd>
361 </div>
Chat controls, public profiles, shadcn selects, and no Docs tab in a project362 <SelectField
363 key={`visibility-${query.visibility}`}
364 name="visibility"
365 aria-label="Visibility"
366 defaultValue={query.visibility}
367 afterChange={() => apply()}
368 className={FILTER}
369 options={[
370 { value: "all", label: "All visibility" },
371 { value: "public", label: option("Public", facets.visibility.public) },
372 { value: "private", label: option("Private", facets.visibility.private) },
373 ]}
374 />
375 <SelectField
376 key={`kind-${query.kind}`}
377 name="kind"
378 aria-label="Kind"
379 defaultValue={query.kind}
380 afterChange={() => apply()}
381 className={FILTER}
382 options={[
383 { value: "all", label: "All kinds" },
384 { value: "app", label: option("Apps", facets.kind.app) },
385 { value: "library", label: option("Libraries", facets.kind.library) },
386 ...(facets.kind.tool > 0 || query.kind === "tool" ? [{ value: "tool", label: option("Tools", facets.kind.tool) }] : []),
387 ...(facets.kind.docs > 0 || query.kind === "docs" ? [{ value: "docs", label: option("Docs", facets.kind.docs) }] : []),
388 ...(facets.kind.other > 0 || query.kind === "other" ? [{ value: "other", label: option("Other", facets.kind.other) }] : []),
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.389 // Where they are managed, once a mirror is among them.
390 ...(facets.mirrors > 0 || query.kind === "mirror" || query.kind === "g1t"
391 ? [
392 { value: "mirror", label: option("Mirrors", facets.mirrors) },
393 { value: "g1t", label: option("On g1t", total - facets.mirrors) },
394 ]
395 : []),
Chat controls, public profiles, shadcn selects, and no Docs tab in a project396 ]}
397 />
Navigation: the workspace's own page with tabs, pins and recent in the sidebar398 {(facets.languages.length > 0 || query.language) && (
Chat controls, public profiles, shadcn selects, and no Docs tab in a project399 <SelectField
400 key={`language-${query.language ?? ""}`}
401 name="language"
402 aria-label="Language"
403 defaultValue={query.language ?? ""}
404 afterChange={() => apply()}
405 className={FILTER}
406 options={[
407 { value: "", label: "Any language" },
408 ...facets.languages.map((language) => ({ value: language.name, label: option(language.name, language.count) })),
409 ]}
410 />
Navigation: the workspace's own page with tabs, pins and recent in the sidebar411 )}
412 {(facets.archived > 0 || query.archived !== "hide") && (
Chat controls, public profiles, shadcn selects, and no Docs tab in a project413 <SelectField
414 key={`archived-${query.archived}`}
415 name="archived"
416 aria-label="Archived"
417 defaultValue={query.archived}
418 afterChange={() => apply()}
419 className={FILTER}
420 options={[
421 { value: "hide", label: "Without archived" },
422 { value: "include", label: "With archived" },
423 { value: "only", label: option("Only archived", facets.archived) },
424 ]}
425 />
Navigation: the workspace's own page with tabs, pins and recent in the sidebar426 )}
427 {member && (
428 <label className={`${CONTROL} flex cursor-pointer items-center gap-2 select-none`}>
The top of every page is one surface: the breadcrumb, search, spend, Ask g1t, bell and + share one height, corners and spacing, Ask g1t is a plain tinted button with the g1t mark drawn on whole pixels, a page's own header sits directly under the bar at the same left edge, and every section, conversations and open artifacts included, draws the same rounded panel. Tables written in chat render as tables, and the site's file, checkbox, radio, number and date controls are the design system's. Button, Separator, Breadcrumb, Table and FileDrop join the shared components; the chat guide says how tables work.429 <Checkbox
430 ref={deploysBox}
Navigation: the workspace's own page with tabs, pins and recent in the sidebar431 name="deployments"
432 value="on"
The top of every page is one surface: the breadcrumb, search, spend, Ask g1t, bell and + share one height, corners and spacing, Ask g1t is a plain tinted button with the g1t mark drawn on whole pixels, a page's own header sits directly under the bar at the same left edge, and every section, conversations and open artifacts included, draws the same rounded panel. Tables written in chat render as tables, and the site's file, checkbox, radio, number and date controls are the design system's. Button, Separator, Breadcrumb, Table and FileDrop join the shared components; the chat guide says how tables work.433 checked={deploys}
434 onCheckedChange={(checked) => {
435 setDeploys(checked === true);
436 apply();
437 }}
Navigation: the workspace's own page with tabs, pins and recent in the sidebar438 />
439 Deploys
440 <span className="text-xs tabular-nums text-faint">{facets.deploying}</span>
441 </label>
442 )}
443 {query.view !== "list" && <input type="hidden" name="view" value={query.view} />}
444 <noscript>
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.445 <Button type="submit" variant="outline" className="bg-surface px-2.5 font-normal text-fg">
Navigation: the workspace's own page with tabs, pins and recent in the sidebar446 Apply
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.447 </Button>
Navigation: the workspace's own page with tabs, pins and recent in the sidebar448 </noscript>
449 </Form>
450
451 <div className="mt-4 flex flex-wrap items-center gap-3 text-sm">
452 <p className="text-muted" aria-live="polite">
453 {filtered ? (
454 <>
455 <span className="font-medium text-fg tabular-nums">{matched}</span> of {total} {total === 1 ? "project" : "projects"}
456 {query.q && (
457 <>
458 {" "}
459 matching <span className="text-fg">“{query.q}”</span>
460 </>
461 )}
462 </>
463 ) : (
464 <>
465 <span className="font-medium text-fg tabular-nums">{total}</span> {total === 1 ? "project" : "projects"}
466 </>
467 )}
468 {page.pages > 1 && (
469 <span className="text-faint">
470 {" "}
471 · showing {page.from}–{page.to}
472 </span>
473 )}
474 </p>
475 {filtered && (
476 <Link to={projectQueryString({ ...query, q: "", visibility: "all", kind: "all", language: null, deployments: false, archived: "hide" })} className="text-xs text-muted hover:text-fg">
477 Clear filters
478 </Link>
479 )}
480 <div className="ml-auto flex items-center gap-2">
481 {/* Part of the filters' form, beside the view it orders. */}
Chat controls, public profiles, shadcn selects, and no Docs tab in a project482 <SelectField
483 key={`sort-${query.sort}`}
484 form="project-filters"
485 name="sort"
486 aria-label="Sort"
487 defaultValue={query.sort}
488 afterChange={() => apply()}
489 size="sm"
490 className={FILTER}
491 align="end"
492 options={SORTS.map((sort) => ({ value: sort.value, label: sort.label }))}
493 />
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.494 <Card asChild tone="bg" radius="lg" className="flex items-center gap-0.5 p-0.5">
495 <nav aria-label="View">
496 {(
497 [
498 ["list", "List", <List key="list" size={14} />],
499 ["grid", "Grid", <LayoutGrid key="grid" size={14} />],
500 ] as const
501 ).map(([view, label, icon]) => (
502 <Link
503 key={view}
504 to={projectQueryString(query, { view, page: query.page })}
505 replace
506 preventScrollReset
507 aria-label={`${label} view`}
508 aria-current={query.view === view ? "true" : undefined}
509 className={`flex size-7 items-center justify-center rounded-md transition-colors ${
510 query.view === view ? "bg-raised text-fg ring-1 ring-line" : "text-faint hover:text-fg"
511 }`}
512 >
513 {icon}
514 </Link>
515 ))}
516 </nav>
517 </Card>
Navigation: the workspace's own page with tabs, pins and recent in the sidebar518 </div>
519 </div>
520
521 <div ref={list} onKeyDown={onListKey} aria-busy={busy || undefined} className={`mt-4 transition-opacity ${busy ? "opacity-60" : ""}`}>
522 {failed ? (
523 <EmptyState title="Projects cannot be listed right now">Try again in a moment.</EmptyState>
524 ) : total === 0 ? (
525 <EmptyState title="No projects yet">
526 {member ? (
527 <>
528 <Link to={`/new?workspace=${slug}`} className="text-accent hover:underline">
529 Create one
530 </Link>
531 , or push a repository and it becomes one.
532 </>
533 ) : (
534 "This workspace has no public projects."
535 )}
536 </EmptyState>
537 ) : items.length === 0 ? (
538 <EmptyState title="No projects match">
539 Try other words or filters, or{" "}
540 <Link to={projectQueryString({ ...query, q: "", visibility: "all", kind: "all", language: null, deployments: false, archived: "hide" })} className="text-accent hover:underline">
541 clear them
542 </Link>
543 .
544 </EmptyState>
545 ) : query.view === "grid" ? (
546 <ul aria-label="Projects" className="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
547 {items.map((project) => (
548 <ProjectTile key={project.id} project={project} slug={slug} pinned={pins.has(project.id)} member={member} />
549 ))}
550 </ul>
551 ) : (
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.552 <Card asChild divided className="overflow-hidden">
553 <ul aria-label="Projects">
554 {items.map((project) => (
555 <ProjectRow key={project.id} project={project} slug={slug} pinned={pins.has(project.id)} member={member} />
556 ))}
557 </ul>
558 </Card>
Navigation: the workspace's own page with tabs, pins and recent in the sidebar559 )}
560 </div>
561 <Pages query={query} page={page.page} pages={page.pages} />
562 {items.length > 0 && (
563 <p className="mt-4 hidden text-center text-xs text-faint sm:block">
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.564 <Kbd>/</Kbd> to find,{" "}
565 <Kbd>↑</Kbd>{" "}
566 <Kbd>↓</Kbd> to move,{" "}
567 <Kbd>Enter</Kbd> to open
Navigation: the workspace's own page with tabs, pins and recent in the sidebar568 </p>
569 )}
570 </div>
571 );
572}
573

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