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
326 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.

The docs' Copy page menu keeps its own spacing, with an icon per item1/**
Merge branch 'main' of github.com:flagon-io/g1t2 * The site's footer: the logo, what g1t is and its accounts elsewhere as
3 * icons beside a few links worth having, then who makes g1t, and a row of
The docs' Copy page menu keeps its own spacing, with an icon per item4 * the trust pages with the live status dot, the account, and the legal
5 * line. Marketing and signed-out pages show it; the app's account menu
6 * has its own rows (components/shell.tsx).
7 */
Merge branch 'main' of github.com:flagon-io/g1t8import { GitBranch } from "lucide-react";
The docs' Copy page menu keeps its own spacing, with an icon per item9import { useEffect, useState } from "react";
10import { Link } from "react-router";
11
12import type { User } from "@g1t/contracts";
13
Merge site-audit: SEO routes, analytics with consent first in Europe, security page, founder and X account, docs header; the tour opens on the pull request and leads with Code, on the real shell, with Agents and Docs working today14import { useAsksFirst } from "./analytics-consent";
Merge branch 'main' of github.com:flagon-io/g1t15import { Logo, Mark } from "./logo";
16import { Button } from "./ui/button";
17import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
Merge site-audit: SEO routes, analytics with consent first in Europe, security page, founder and X account, docs header; the tour opens on the pull request and leads with Code, on the real shell, with Agents and Docs working today18import { choose } from "../lib/analytics-consent";
19import { COMPANY, MAKER_PRODUCTS, SOCIAL, copyright, listed } from "../lib/legal";
The docs' Copy page menu keeps its own spacing, with an icon per item20import { type OverallState, STATUS_JSON_URL, STATUS_URL, STATUS_WORDS, type StatusReport, dotClass } from "../lib/status";
21
22/** How long one fetched report is reused across the pages of one visit. */
23const STATUS_REUSE_MS = 60_000;
24let lastStatus: { report: StatusReport; at: number } | null = null;
25let pendingStatus: Promise<StatusReport | null> | null = null;
26
27function fetchStatus(): Promise<StatusReport | null> {
28 if (lastStatus && Date.now() - lastStatus.at < STATUS_REUSE_MS) return Promise.resolve(lastStatus.report);
29 pendingStatus ??= fetch(STATUS_JSON_URL, { headers: { accept: "application/json" } })
30 .then((response) => (response.ok ? (response.json() as Promise<StatusReport>) : null))
31 .then((report) => {
32 if (report?.overall) lastStatus = { report, at: Date.now() };
33 return report?.overall ? report : null;
34 })
35 // The status page being unreachable only means no dot.
36 .catch(() => null)
37 .finally(() => {
38 pendingStatus = null;
39 });
40 return pendingStatus;
41}
42
43/**
44 * The live status, from status.g1t.sh once the page is up (or `when` turns
45 * true), so no page waits on it. Kept a minute across pages. Null until it
46 * arrives, or if it cannot.
47 */
48export function useSiteStatus(when = true): StatusReport | null {
49 const [report, setReport] = useState<StatusReport | null>(() => lastStatus?.report ?? null);
50 useEffect(() => {
51 if (!when) return;
52 let live = true;
53 fetchStatus().then((fresh) => {
54 if (live && fresh) setReport(fresh);
55 });
56 return () => {
57 live = false;
58 };
59 }, [when]);
60 return report;
61}
62
63/** The coloured dot, with words for screen readers. */
64export function StatusDot({ state, className = "" }: { state: OverallState | null; className?: string }) {
65 return (
66 <span className={`relative inline-flex size-2 shrink-0 ${className}`}>
67 {state && state !== "up" && state !== "unknown" && (
68 <span aria-hidden className={`absolute inset-0 animate-ping rounded-full opacity-60 ${dotClass(state)}`} />
69 )}
70 <span aria-hidden className={`relative inline-flex size-2 rounded-full ${dotClass(state)}`} />
71 <span className="sr-only">{state ? STATUS_WORDS[state] : "Checking status"}</span>
72 </span>
73 );
74}
75
76/** Where the footer's grid links go. Plain links: some are files or other hosts. */
77const FOOTER_LINKS: { title: string; links: [string, string][] }[] = [
78 {
79 title: "Product",
80 links: [
81 ["Explore", "/explore"],
82 ["Pricing", "/pricing"],
g1t.sh/download is a marketing page: it is drawn with the site's header and footer whoever opens it, never inside the app's rail and sidebar, in the shape of Pricing, with Download in the public header, its phone menu and the footer. The desktop app's check in CI runs on the larger machine the Rust tests use; on the small one the linker ran out of room and died with a bus error on every push since the app arrived.83 ["Download", "/download"],
Chat and workspace agents: channels, DMs and named agents you talk to84 ["Chat", "https://docs.g1t.sh/guides/chat/"],
The docs' Copy page menu keeps its own spacing, with an icon per item85 ["Bring your own agent", "https://docs.g1t.sh/guides/bring-your-own-agent/"],
86 ["Integrations", "https://docs.g1t.sh/guides/integrations/"],
87 ],
88 },
89 {
90 title: "Developers",
91 links: [
92 ["Documentation", "https://docs.g1t.sh/"],
93 ["API reference", "https://docs.g1t.sh/reference/api/"],
94 ["MCP tools", "https://docs.g1t.sh/reference/mcp/"],
95 ["llms.txt", "/llms.txt"],
96 ],
97 },
98 {
99 title: "Company",
100 links: [
101 ["Flagon, Inc.", COMPANY.url],
102 ["Support", "/support"],
103 ["Security", "/security"],
104 ["Status", STATUS_URL],
105 ],
106 },
107 {
108 title: "Open source",
109 links: [
110 ["Source on g1t", "/flagon-io/g1t"],
111 ["MIT license", "/flagon-io/g1t/blob/main/LICENSE"],
112 ["Run g1t yourself", "https://docs.g1t.sh/guides/self-hosting/"],
113 ],
114 },
115];
116
Merge branch 'main' of github.com:flagon-io/g1t117/** What the footer says under the logo. */
The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404.118const TAGLINE = "Where your team and its coding agents ship code.";
Merge branch 'main' of github.com:flagon-io/g1t119
120/** The icons under it: g1t's accounts elsewhere (lib/legal), then its source. */
121const ICON_LINKS: { label: string; url: string; icon: React.ReactNode }[] = [
122 ...SOCIAL.map((account) => ({
123 label: `g1t on ${account.label}`,
124 url: account.url,
125 icon: (
126 <svg viewBox="0 0 24 24" aria-hidden="true" fill="currentColor" className="size-4">
127 <path d={account.icon.path} />
128 </svg>
129 ),
130 })),
131 { label: "Source on g1t", url: "/flagon-io/g1t", icon: <GitBranch aria-hidden className="size-4" /> },
132];
133
134/** The footer's first column: the logo, the tagline and the icons. */
135function BrandColumn() {
136 return (
137 <div className="col-span-2 sm:col-span-4 lg:col-span-2 lg:pr-8">
138 <Link to="/" aria-label="g1t home" className="inline-flex rounded-sm">
139 <Logo />
140 </Link>
141 <p className="mt-3 max-w-xs text-sm leading-6 text-muted text-balance">{TAGLINE}</p>
142 <ul className="mt-4 -ml-2 flex items-center gap-1">
143 {ICON_LINKS.map((link) => (
144 <li key={link.url}>
145 <Tooltip>
146 <TooltipTrigger asChild>
147 <Button asChild variant="ghost" size="icon-sm">
148 <a href={link.url} aria-label={link.label}>
149 {link.icon}
150 </a>
151 </Button>
152 </TooltipTrigger>
153 <TooltipContent>{link.label}</TooltipContent>
154 </Tooltip>
155 </li>
156 ))}
157 </ul>
158 </div>
159 );
160}
161
The docs' Copy page menu keeps its own spacing, with an icon per item162/** The row's own links, after Status. */
163const ROW_LINKS: [string, string][] = [
164 ["Support", "/support"],
165 ["Policies", "/policies"],
166 ["Privacy", "/policies/privacy"],
167 ["Security", "/security"],
168];
169
170/**
171 * Flagon's mark, single-ink, as in Flagon's brand files
172 * (flagon-mark-mono.svg): a flagon, its brew a tone of the text colour.
173 */
174function FlagonMark({ className = "" }: { className?: string }) {
175 return (
176 <svg viewBox="11.3 12.3 41 41" fill="none" aria-hidden="true" className={className}>
177 <defs>
178 <clipPath id="g1t-footer-flagon-body">
179 <path d="M19.5 23 L34.5 23 L36.5 46 Q36.7 48 34.3 48 L19.7 48 Q17.3 48 17.5 46 Z" />
180 </clipPath>
181 </defs>
182 <g clipPath="url(#g1t-footer-flagon-body)">
183 <path
184 d="M13 27 q3.75 -0.7 7.5 0 t7.5 0 t7.5 0 t7.5 0 t7.5 0 L51 52 L13 52 Z"
185 fill="currentColor"
186 fillOpacity="0.22"
187 />
188 </g>
189 <path
190 d="M37 27 L45.5 27 L48.5 30 L48.5 36.5 L45.5 39.5 L38 39.5"
191 stroke="currentColor"
192 strokeWidth="3.1"
193 strokeLinecap="round"
194 strokeLinejoin="round"
195 />
196 <path
197 d="M17.5 21 L36.5 21 L38.6 47 Q39 50 36 50 L18 50 Q15 50 15.4 47 Z"
198 stroke="currentColor"
199 strokeWidth="3.1"
200 strokeLinejoin="round"
201 />
202 <path d="M17 21 Q16.3 16.3 20 15.6 L33 15.6 Q37.2 16.2 37 21 Z" stroke="currentColor" strokeWidth="3" strokeLinejoin="round" />
203 <path d="M36.7 17.1 L39.4 16.1" stroke="currentColor" strokeWidth="2.4" strokeLinejoin="round" />
204 <circle cx="40.3" cy="15.8" r="1.4" fill="currentColor" />
205 </svg>
206 );
207}
208
209/** "g1t is designed, built, and backed by Flagon, Inc., …" */
210export function MakerLine() {
211 const products = MAKER_PRODUCTS.map((product) => product.name);
212 return (
213 <p className="text-sm leading-6 text-muted text-balance">
214 <Mark tight className="mr-1.5 inline-block h-[0.85em] w-auto align-baseline" />
215 <span className="font-semibold text-fg">g1t</span> is designed, built, and backed by{" "}
216 <a href={COMPANY.url} className="whitespace-nowrap font-semibold text-fg hover:underline hover:underline-offset-4">
217 <FlagonMark className="mr-1 inline-block size-[0.95em] -translate-y-px align-middle" />
218 {COMPANY.name}
219 </a>
Merge site-audit: SEO routes, analytics with consent first in Europe, security page, founder and X account, docs header; the tour opens on the pull request and leads with Code, on the real shell, with Agents and Docs working today220 {products.length > 0 ? `, the people behind ${listed(products)}.` : `, ${COMPANY.about} founded by ${COMPANY.founder}.`}
The docs' Copy page menu keeps its own spacing, with an icon per item221 </p>
222 );
223}
224
225const ROW_LINK = "rounded-sm transition-colors hover:text-fg";
226
227/**
228 * Status, the trust pages, the account and the legal line, separated by
229 * dots that wrap away cleanly on a phone.
230 */
231export function LegalRow({ user }: { user: User | null | undefined }) {
232 const status = useSiteStatus();
Merge site-audit: SEO routes, analytics with consent first in Europe, security page, founder and X account, docs header; the tour opens on the pull request and leads with Code, on the real shell, with Agents and Docs working today233 const asksFirst = useAsksFirst();
The docs' Copy page menu keeps its own spacing, with an icon per item234 const links = [
235 <a key="status" href={STATUS_URL} className={`inline-flex items-center gap-1.5 ${ROW_LINK}`}>
236 <StatusDot state={status?.overall.state ?? null} />
237 Status
238 </a>,
239 ...ROW_LINKS.map(([label, to]) => (
240 <Link key={to} to={to} className={ROW_LINK}>
241 {label}
242 </Link>
243 )),
Merge site-audit: SEO routes, analytics with consent first in Europe, security page, founder and X account, docs header; the tour opens on the pull request and leads with Code, on the real shell, with Agents and Docs working today244 // Where the visitor was asked about the analytics cookie: ask again.
245 ...(asksFirst
246 ? [
247 <button key="consent" type="button" onClick={() => choose(null)} className={ROW_LINK}>
248 Cookie choices
249 </button>,
250 ]
251 : []),
The docs' Copy page menu keeps its own spacing, with an icon per item252 ];
253 const account = [
254 user ? (
255 <Link key="me" to={`/u/${user.username}`} className={`font-medium text-fg/90 ${ROW_LINK}`}>
256 {user.username}
257 </Link>
258 ) : (
259 <Link key="login" to="/login" className={ROW_LINK}>
260 Sign in
261 </Link>
262 ),
263 ];
264 const legal = [
265 ...account,
266 <span key="copyright" className="text-faint">
267 {copyright()}
268 </span>,
269 ];
270 const gap = "gap-x-3";
271 const dot = (
272 <span aria-hidden className="text-line-strong">
273 ·
274 </span>
275 );
276 // Two runs, the links and the legal line, so that on a narrow screen the
277 // second starts its own line rather than a line starting with a dot.
278 const run = (items: React.ReactNode[]) =>
279 items.map((item, index) => (
280 <li key={index} className={`flex items-center ${gap}`}>
281 {index > 0 && dot}
282 {item}
283 </li>
284 ));
285 return (
286 <div className={`flex flex-wrap items-center justify-center gap-y-1.5 ${gap} text-[0.8125rem] text-muted`}>
287 <ul className={`flex flex-wrap items-center justify-center gap-y-1.5 ${gap}`}>{run(links)}</ul>
288 {/* Between the two runs only while they share a line. */}
289 <span className="hidden md:inline">{dot}</span>
290 <ul className={`flex flex-wrap items-center justify-center gap-y-1.5 ${gap}`}>{run(legal)}</ul>
291 </div>
292 );
293}
294
295export function SiteFooter({ user }: { user: User | null | undefined }) {
296 return (
297 <footer className="mt-24 border-t border-line">
Merge branch 'main' of github.com:flagon-io/g1t298 <div className="mx-auto grid max-w-6xl grid-cols-2 gap-x-8 gap-y-10 px-4 pt-12 pb-10 sm:grid-cols-4 lg:grid-cols-6">
299 <BrandColumn />
300 {/* The link columns sit in the same grid as the brand column. */}
301 <nav aria-label="Footer" className="contents">
302 {FOOTER_LINKS.map((group) => (
303 <div key={group.title}>
304 <p className="text-sm font-medium">{group.title}</p>
305 <ul className="mt-3 space-y-2 text-sm text-muted">
306 {group.links.map(([label, to]) => (
307 <li key={to}>
308 <a href={to} className="transition-colors hover:text-fg">
309 {label}
310 </a>
311 </li>
312 ))}
313 </ul>
314 </div>
315 ))}
316 </nav>
317 </div>
The docs' Copy page menu keeps its own spacing, with an icon per item318 <div className="border-t border-line">
319 <div className="mx-auto max-w-3xl space-y-4 px-4 py-10 text-center">
320 <MakerLine />
321 <LegalRow user={user} />
322 </div>
323 </div>
324 </footer>
325 );
326}

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