Skip to content

Commit

The site says who it is for, how to switch, and what it costs a team; search engines get a map

From the site audit: the hero names engineering teams running AI coding agents; coordination leads with what works today (forks, conflicts on push, the merge queue) and keeps claims and handoffs as coming soon; a Switching section covers importing, mirroring or moving from GitHub, what keeps working, what holds if g1t goes away, and who it is not a fit for yet. Pricing gets a title that names the model, a Recommended badge, a line on what the plan is for, a per-team anchor and a switching question. robots.txt is served from the request's origin and names /sitemap.xml, which lists the public pages and Explore's public projects. The home page carries Organization and SoftwareApplication JSON-LD; pricing carries the application with its price and FAQPage for its questions. Testimonials and a founder block are wired but empty: each shows once real content is filled in (TESTIMONIALS in landing.tsx, FOUNDER in lib/legal.ts).

syntaqxcommitted Parentf462963Browse files
10 files+308−270/10 viewed
+23−1
1010 import type { User } from "@g1t/contracts";
1111
1212 import { Mark } from "./logo";
13−import { COMPANY, MAKER_PRODUCTS, copyright, listed } from "../lib/legal";
13+import { COMPANY, FOUNDER, MAKER_PRODUCTS, copyright, listed } from "../lib/legal";
1414 import { type OverallState, STATUS_JSON_URL, STATUS_URL, STATUS_WORDS, type StatusReport, dotClass } from "../lib/status";
1515
1616 /** How long one fetched report is reused across the pages of one visit. */
156156 );
157157 }
158158
159+/** Who builds g1t, by name, with their photo and their own words. Nothing until FOUNDER is filled in. */
160+function FounderBlock() {
161+ if (!FOUNDER) return null;
162+ return (
163+ <figure className="mx-auto mb-6 flex max-w-xl flex-col items-center gap-4 text-center sm:flex-row sm:items-start sm:text-left">
164+ <img src={FOUNDER.photo} alt="" width={56} height={56} className="size-14 shrink-0 rounded-full ring-1 ring-line" />
165+ <div>
166+ <blockquote className="text-sm leading-6 text-fg-soft">{FOUNDER.why}</blockquote>
167+ <figcaption className="mt-2 text-sm text-muted">
168+ <span className="font-medium text-fg">{FOUNDER.name}</span>, {FOUNDER.role}
169+ {FOUNDER.links.map((link) => (
170+ <a key={link.url} href={link.url} rel="me noopener" className="ml-3 hover:text-fg hover:underline hover:underline-offset-4">
171+ {link.label}
172+ </a>
173+ ))}
174+ </figcaption>
175+ </div>
176+ </figure>
177+ );
178+}
179+
159180 /** "g1t is designed, built, and backed by Flagon, Inc., …" */
160181 export function MakerLine() {
161182 const products = MAKER_PRODUCTS.map((product) => product.name);
254275 </nav>
255276 <div className="border-t border-line">
256277 <div className="mx-auto max-w-3xl space-y-4 px-4 py-10 text-center">
278+ <FounderBlock />
257279 <MakerLine />
258280 <LegalRow user={user} />
259281 </div>
+92−13
300300 },
301301 ];
302302
303+/**
304+ * What people using g1t say, each with their name, role, workspace and a
305+ * specific outcome. None yet: the section shows once real, permitted
306+ * quotes are here. Never write one on someone's behalf.
307+ */
308+const TESTIMONIALS = [] as { quote: string; name: string; role: string; workspace: string; avatar: string }[];
309+
303310 /** A workspace's org chart, read like a company's: g1t knows everyone; each department has its colleague. */
304311 const ORG: { team: string; who: string }[] = [
305312 { team: "Engineering", who: "Otto" },
353360 to={`${DOCS}/guides/chat/`}
354361 className="inline-flex animate-fade-up items-center gap-2 rounded-full bg-bg/50 px-3 py-1 text-xs text-fg-soft ring-1 ring-white/10 backdrop-blur transition-colors hover:bg-bg/70"
355362 >
356− <span className="size-1.5 rounded-full bg-accent" />
357− Chat, agents, docs and code in one workspace
358− <ArrowRight size={12} />
363+ <span className="size-1.5 shrink-0 rounded-full bg-accent" />
364+ For engineering teams running AI coding agents: chat, agents, docs and code in one workspace
365+ <ArrowRight size={12} className="shrink-0" />
359366 </Link>
360367 <h1 className="mx-auto mt-7 max-w-4xl animate-fade-up text-[2.75rem] leading-[1.04] font-semibold tracking-tight text-balance sm:text-7xl">
361368 Your team and its agents,{" "}
612619 </div>
613620 </section>
614621
615− {/* Coming next: coordination and Docs. */}
622+ {/* Switching: what moving in costs, and what holds if g1t goes away. */}
623+ <section id="switching" className="mx-auto max-w-6xl scroll-mt-20 px-4 pb-24">
624+ <div className="grid gap-10 rounded-3xl bg-surface p-8 ring-1 ring-line sm:p-10 lg:grid-cols-[1fr_1.1fr] lg:gap-16">
625+ <div>
626+ <Eyebrow>Switching</Eyebrow>
627+ <h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance">
628+ Moving in is an import. Leaving is a git clone.
629+ </h2>
630+ <p className="mt-4 max-w-xl leading-7 text-muted">
631+ Bring repositories across from GitHub in a few clicks: every branch and tag with full history, and their
632+ issues if you want them. Import a copy, keep a mirror that follows GitHub while you try g1t, or move and
633+ let g1t push back to GitHub so people still working there see every change.
634+ </p>
635+ <p className="mt-4 max-w-xl leading-7 text-muted">
636+ If g1t went away tomorrow, your code is plain git, the source is MIT licensed, and the core forge runs on
637+ your own machine with Docker Compose.
638+ </p>
639+ <More to={`${DOCS}/guides/github/#import-mirror-or-move-a-repository`}>Import from GitHub</More>
640+ </div>
641+ <div className="space-y-6">
642+ <div>
643+ <p className="text-sm font-medium">What keeps working</p>
644+ <Points
645+ columns={false}
646+ points={[
647+ "git over HTTPS, with your history as it is",
648+ "GitHub Actions workflows: rename .github to .g1t and push",
649+ "The coding agents you use today, through MCP",
650+ "Leaving: git clone, and the REST API for the rest",
651+ ]}
652+ />
653+ </div>
654+ <div className="rounded-xl border border-dashed border-line-strong px-4 py-3.5 text-sm leading-6 text-muted">
655+ <p className="font-medium text-fg-soft">Not a fit yet if you need</p>
656+ <p className="mt-1.5">
657+ Single sign-on, git over SSH, or agents, chat and deployments on your own machines: those run only on
658+ g1t.sh today. Open pull requests, issue comments, releases and wikis stay behind on import.{" "}
659+ <Link to={`${DOCS}/about/limitations/`} className="text-fg-soft underline-offset-4 hover:text-accent hover:underline">
660+ What g1t can&apos;t do yet
661+ </Link>
662+ </p>
663+ </div>
664+ </div>
665+ </div>
666+ </section>
667+
668+ {/* Coordination, as it works today; and Docs, coming next. */}
616669 <section className="mx-auto grid max-w-6xl gap-4 px-4 pb-24 lg:grid-cols-2">
617670 <div className="rounded-3xl bg-surface p-8 ring-1 ring-line">
618− <div className="flex items-center gap-3">
619− <Eyebrow>Coordination</Eyebrow>
620− <Soon />
621− </div>
671+ <Eyebrow>Coordination</Eyebrow>
622672 <h3 className="mt-3 text-2xl font-semibold tracking-tight text-balance">Many agents, no collisions</h3>
623673 <p className="mt-3 text-sm leading-6 text-muted">
624− Before an agent touches an issue, a branch, an environment or a set of paths, it claims it. When two
625− agents&apos; work would overlap, they agree who goes first in a thread you can read. Chains of agents stop
626− and ask a person after a few hops, and work done for another agent is charged to the task that asked.
674+ Every agent already sees what the others are changing and can ask them, through the forge. Each change is a
675+ pull request in the agent&apos;s own fork, conflicts are found on every push, and the merge queue tests
676+ changes together with what lands ahead of them. Five agents can open pull requests the same afternoon and
677+ main still only moves to what passed.
627678 </p>
628− <p className="mt-3 text-sm leading-6 text-muted">
629− Today, every agent already sees what the others are changing and can ask them, through the forge.
679+ <p className="mt-4 flex flex-wrap items-center gap-x-2.5 gap-y-1.5 text-sm leading-6 text-muted">
680+ <Soon />
681+ <span>
682+ Claims and handoffs: an agent claims an issue, branch or set of paths before it starts, and overlapping
683+ agents agree who goes first in a thread you can read.
684+ </span>
630685 </p>
686+ <More to={`${DOCS}/guides/merge-queue/`}>The merge queue</More>
631687 </div>
632688 <div className="rounded-3xl bg-surface p-8 ring-1 ring-line">
633689 <div className="flex items-center gap-3">
644700 </div>
645701 </section>
646702
703+ {/* What people using it say. Nothing until there are real quotes. */}
704+ {TESTIMONIALS.length > 0 && (
705+ <section className="mx-auto max-w-6xl px-4 pb-24">
706+ <Eyebrow>From the teams using it</Eyebrow>
707+ <ul className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
708+ {TESTIMONIALS.map((item) => (
709+ <li key={item.name} className="flex flex-col rounded-2xl bg-surface p-6 ring-1 ring-line">
710+ <blockquote className="grow text-sm leading-6 text-fg-soft">&ldquo;{item.quote}&rdquo;</blockquote>
711+ <p className="mt-5 flex items-center gap-3 text-sm">
712+ <img src={item.avatar} alt="" width={32} height={32} className="size-8 rounded-full ring-1 ring-line" />
713+ <span className="min-w-0 leading-tight">
714+ <span className="block font-medium text-fg">{item.name}</span>
715+ <span className="block truncate text-xs text-muted">
716+ {item.role}, {item.workspace}
717+ </span>
718+ </span>
719+ </p>
720+ </li>
721+ ))}
722+ </ul>
723+ </section>
724+ )}
725+
647726 {/* How it flows */}
648727 <section id="how" className="scroll-mt-20 border-y border-line bg-surface/40">
649728 <div className="mx-auto max-w-6xl px-4 py-24">
+15−0
1313 };
1414
1515 /**
16+ * The person behind g1t, for the footer's founder block. None yet: the
17+ * block shows once a real name, photo, links and their own words are here.
18+ */
19+export type Founder = {
20+ name: string;
21+ role: string;
22+ /** A square photo, served from g1t's own assets or usercontent. */
23+ photo: string;
24+ /** One paragraph, in their words: why they built g1t. */
25+ why: string;
26+ links: { label: string; url: string }[];
27+};
28+export const FOUNDER = null as Founder | null;
29+
30+/**
1631 * Flagon's other launched products, which the footer's maker line names
1732 * ("…, the people behind X and Y"). None yet: add each here when it ships.
1833 */
+74−0
1+/**
2+ * Schema.org JSON-LD for the pages that say what g1t is and what it costs,
3+ * so search engines and AI assistants read it without parsing the prose.
4+ * Each is a `script:ld+json` meta descriptor, added after `page(…)`.
5+ */
6+import type { MetaDescriptor } from "react-router";
7+
8+import { COMPANY } from "./legal";
9+import { DESCRIPTION, SITE } from "./meta";
10+
11+export function organization(): MetaDescriptor {
12+ return {
13+ "script:ld+json": {
14+ "@context": "https://schema.org",
15+ "@type": "Organization",
16+ name: COMPANY.name,
17+ url: COMPANY.url,
18+ brand: { "@type": "Brand", name: "g1t", url: SITE },
19+ },
20+ };
21+}
22+
23+/** g1t as an application, with the plan's monthly price per workspace. */
24+export function application(monthlyDollars: number): MetaDescriptor {
25+ return {
26+ "script:ld+json": {
27+ "@context": "https://schema.org",
28+ "@type": "SoftwareApplication",
29+ name: "g1t",
30+ url: SITE,
31+ applicationCategory: "DeveloperApplication",
32+ operatingSystem: "Web",
33+ description: DESCRIPTION,
34+ publisher: { "@type": "Organization", name: COMPANY.name, url: COMPANY.url },
35+ license: "https://opensource.org/licenses/MIT",
36+ offers: [
37+ {
38+ "@type": "Offer",
39+ name: "Free",
40+ price: "0",
41+ priceCurrency: "USD",
42+ description: "Chat and the forge, with no card.",
43+ },
44+ {
45+ "@type": "Offer",
46+ name: "The g1t plan",
47+ price: String(monthlyDollars),
48+ priceCurrency: "USD",
49+ description: "A month per workspace, never per seat, with $10 of usage included.",
50+ priceSpecification: {
51+ "@type": "UnitPriceSpecification",
52+ price: String(monthlyDollars),
53+ priceCurrency: "USD",
54+ unitText: "month per workspace",
55+ },
56+ },
57+ ],
58+ },
59+ };
60+}
61+
62+export function faqPage(questions: { q: string; a: string }[]): MetaDescriptor {
63+ return {
64+ "script:ld+json": {
65+ "@context": "https://schema.org",
66+ "@type": "FAQPage",
67+ mainEntity: questions.map((item) => ({
68+ "@type": "Question",
69+ name: item.q,
70+ acceptedAnswer: { "@type": "Answer", text: item.a },
71+ })),
72+ },
73+ };
74+}
+3−0
7575 route("status", "routes/status.tsx"),
7676 route("status.json", "routes/status-json.ts"),
7777 route(".well-known/security.txt", "routes/security-txt.ts"),
78+ // For search engines: what may be crawled, and a map of the public pages.
79+ route("robots.txt", "routes/robots-txt.ts"),
80+ route("sitemap.xml", "routes/sitemap-xml.ts"),
7881 // Releases of the self-hosted runner and the g1t CLI, from R2.
7982 route("downloads/:tool/*", "routes/downloads-runner.ts"),
8083 // A workspace's own pages sit under `-`, which no repository can be named.
+10−5
33
44 import type { Route } from "./+types/home";
55 import { page } from "../lib/meta";
6+import { application, organization } from "../lib/structured-data";
67 import { WORKSPACE_COOKIE, chosenWorkspace } from "../lib/workspace-choice";
78 import { readCookie } from "../lib/mission";
89 import { type NotStarted, chosenRepo, delegateForm, issuePath, notStarted } from "../lib/delegate";
1213 import { homePath } from "../lib/workspace-nav";
1314
1415 export function meta(args: Route.MetaArgs) {
15− return page(args, {
16− title: "g1t · Your team and its agents, working in one place",
17− description:
18− "Chat with your team and your agents in channels and DMs. Agents are teammates with a role, a personality and a budget, and their code lands through pull requests, checks and a merge queue. Chat is free on every plan, never per seat.",
19− });
16+ return [
17+ ...page(args, {
18+ title: "g1t · Your team and its agents, working in one place",
19+ description:
20+ "Chat with your team and your agents in channels and DMs. Agents are teammates with a role, a personality and a budget, and their code lands through pull requests, checks and a merge queue. Chat is free on every plan, never per seat.",
21+ }),
22+ organization(),
23+ application(20),
24+ ];
2025 }
2126
2227 /**
+26−6
55 import type { Route } from "./+types/pricing";
66 import { wholeDollars } from "../lib/billing";
77 import { page } from "../lib/meta";
8+import { application, faqPage } from "../lib/structured-data";
89 import { TimeAgo } from "../components/ui";
910 import { billing } from "../lib/services.server";
1011
1112 export function meta(args: Route.MetaArgs) {
12− return page(args, {
13− title: "Pricing · g1t",
14− description:
15− "People chat free on every plan, and the forge is free. One plan, $20 a month per workspace with $10 of usage included. Agents pay the model's price plus a flat agent rate, from each agent's budget. No seats, ever.",
16− });
13+ const plan = args.loaderData?.book?.plans?.find((p) => p.feature === "plan") ?? DEFAULT_PLAN;
14+ return [
15+ ...page(args, {
16+ title: `g1t pricing - $${plan.monthlyCents / 100} per workspace, never per seat, cost-plus AI`,
17+ description:
18+ "People chat free on every plan, and the forge is free. One plan, $20 a month per workspace with $10 of usage included. Agents pay the model's price plus a flat agent rate, from each agent's budget. No seats, ever.",
19+ }),
20+ application(plan.monthlyCents / 100),
21+ faqPage(FAQ),
22+ ];
1723 }
1824
1925 export async function loader() {
148154 a: "No. There are no seats. People who only chat, and ask agents questions, cost nothing until their questions use an agent; then the agent's reply is charged like any other.",
149155 },
150156 {
157+ q: "What does moving from GitHub cost, and can we leave again?",
158+ a: "Moving in is an import: bring repositories across from GitHub with every branch, tag and their issues, or keep a mirror that follows GitHub while you try g1t. Your code is plain git and g1t is MIT licensed, so leaving is a git clone, and the core forge runs on your own machine with Docker Compose.",
159+ },
160+ {
151161 q: "Is Docs included?",
152162 a: "Docs is coming soon. When it ships, pages, editing and history are included on every plan, like chat. Files are on the storage meter.",
153163 },
312322
313323 <section className="mt-10 rounded-xl border border-accent/40 bg-surface p-6">
314324 <div className="flex flex-wrap items-baseline justify-between gap-3">
315− <h2 className="text-xl font-semibold tracking-tight">{plan.title}</h2>
325+ <h2 className="flex flex-wrap items-center gap-2 text-xl font-semibold tracking-tight">
326+ {plan.title}
327+ <span className="rounded-full bg-accent/15 px-2 py-0.5 text-xs font-medium tracking-normal text-accent">Recommended</span>
328+ </h2>
316329 <p>
317330 <span className="text-3xl font-semibold tabular-nums">${price}</span>{" "}
318331 <span className="text-sm text-muted">a month per workspace</span>
321334 </span>
322335 </p>
323336 </div>
337+ <p className="mt-2 text-sm text-muted">
338+ The plan for a team whose agents do real work: unlimited members, agents, checks, the merge queue and
339+ deployments.
340+ </p>
341+ <p className="mt-3 text-sm text-fg-soft">
342+ One price for the whole team: a 10-person team pays ${price} a month, not ${price} a person.
343+ </p>
324344 <ul className="mt-4 space-y-1.5 text-sm text-muted">
325345 {plan.includes.map((line) => (
326346 <li key={line} className="flex gap-2">
+20−0
1+/**
2+ * /robots.txt: everything may be crawled, and where the site's maps are.
3+ * Served from the request's own origin, so a self-hosted g1t names itself.
4+ */
5+import type { Route } from "./+types/robots-txt";
6+
7+/** g1t.sh's docs keep their own map; a self-hosted g1t has no docs site. */
8+const DOCS_SITEMAP = "https://docs.g1t.sh/sitemap-index.xml";
9+
10+export function loader({ request }: Route.LoaderArgs) {
11+ const site = new URL(request.url).origin;
12+ const lines = ["User-agent: *", "Allow: /", "", `Sitemap: ${site}/sitemap.xml`];
13+ if (site === "https://g1t.sh") lines.push(`Sitemap: ${DOCS_SITEMAP}`);
14+ return new Response(`${lines.join("\n")}\n`, {
15+ headers: {
16+ "content-type": "text/plain; charset=utf-8",
17+ "cache-control": "public, max-age=86400",
18+ },
19+ });
20+}
+45−0
1+/**
2+ * /sitemap.xml: the public pages, then the public projects Explore lists,
3+ * newest first. Explore being unreachable leaves just the pages.
4+ */
5+import type { Route } from "./+types/sitemap-xml";
6+import { search } from "../lib/services.server";
7+
8+/** Pages anyone can read, signed in or not. */
9+const PAGES = ["/", "/pricing", "/explore", "/support", "/security", "/policies", "/policies/privacy"];
10+
11+/** How many of Explore's pages to list; each holds one page of projects. */
12+const EXPLORE_PAGES = 10;
13+
14+function escape(text: string): string {
15+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
16+}
17+
18+export async function loader({ request }: Route.LoaderArgs) {
19+ const site = new URL(request.url).origin;
20+ const entries: { loc: string; lastmod?: string }[] = PAGES.map((path) => ({ loc: `${site}${path}` }));
21+ for (let pageNumber = 1; pageNumber <= EXPLORE_PAGES; pageNumber++) {
22+ const explore = await search.explore(null, { sort: "new", page: pageNumber }).catch(() => null);
23+ if (!explore) break;
24+ for (const repo of explore.repos) {
25+ const lastmod = (repo.pushedAt ?? repo.createdAt)?.slice(0, 10);
26+ entries.push({ loc: `${site}/${repo.namespace}/${repo.name}`, lastmod });
27+ }
28+ if (!explore.more) break;
29+ }
30+ const body = [
31+ '<?xml version="1.0" encoding="UTF-8"?>',
32+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
33+ ...entries.map(
34+ (entry) => ` <url><loc>${escape(entry.loc)}</loc>${entry.lastmod ? `<lastmod>${entry.lastmod}</lastmod>` : ""}</url>`,
35+ ),
36+ "</urlset>",
37+ "",
38+ ].join("\n");
39+ return new Response(body, {
40+ headers: {
41+ "content-type": "application/xml; charset=utf-8",
42+ "cache-control": "public, max-age=3600",
43+ },
44+ });
45+}
+0−2
1−User-agent: *
2−Allow: /