g1t

syntaqx/g1t

public

Git for AI scale: a forge for thousands of agents working on the same code at once.

g1t/apps/web/app/routes/home.tsx

162 lines6,166 bytes
import { ArrowRight, Plus } from "lucide-react";
import { Link, redirect } from "react-router";

import type { Route } from "./+types/home";
import { Landing } from "../components/landing";
import { RepoList } from "../components/repo-list";
import {
  Avatar,
  ButtonLink,
  CopyLine,
  EmptyState,
  TimeAgo,
} from "../components/ui";
import { PullIcon } from "../components/work";
import { repos as reposApi, work } from "../lib/services.server";
import { getViewer } from "../lib/session.server";

export function meta({}: Route.MetaArgs) {
  return [
    { title: "g1t — Git for AI scale" },
    {
      name: "description",
      content:
        "A git forge for thousands of agents working on the same code at once: every change isolated, every decision recorded, every change landed in order. Open source, built on Cloudflare.",
    },
  ];
}

export async function loader({ context }: Route.LoaderArgs) {
  const viewer = getViewer(context);
  // Nothing can be created outside a workspace, so a new account starts there.
  if (viewer?.verified && (viewer.workspaces ?? []).length === 0) {
    throw redirect("/workspaces/new");
  }
  const [repos, pulls] = await Promise.all([
    reposApi.list(viewer, { memberOnly: Boolean(viewer) }),
    work.listActivePulls(viewer),
  ]);
  // Mission control links to each pull request under its repo.
  const pullRepos = await Promise.all(
    pulls.map(({ pull }) => reposApi.getById(pull.repoId, viewer)),
  );
  return {
    viewer,
    repos,
    active: pulls.flatMap((item, i) => {
      const repo = pullRepos[i];
      return repo.ok ? [{ ...item, repo: repo.value }] : [];
    }),
  };
}

export default function Home({ loaderData }: Route.ComponentProps) {
  const { viewer, repos, active } = loaderData;
  if (!viewer) return <Landing repos={repos} />;

  const working = active.filter(({ pull }) => pull.status === "draft");
  return (
    <main className="mx-auto grid max-w-6xl gap-10 px-4 py-10 lg:grid-cols-[1fr_20rem]">
      <div className="min-w-0 space-y-10">
        <section>
          <div className="flex items-center gap-3">
            <Avatar name={viewer.username} size={36} />
            <div>
              <h1 className="text-xl font-semibold tracking-tight">
                Mission control
              </h1>
              <p className="text-sm text-muted">
                {working.length === 0
                  ? "Nothing is being worked on right now."
                  : `${working.length} ${working.length === 1 ? "pull request is" : "pull requests are"} being worked on.`}
              </p>
            </div>
          </div>
        </section>

        <section>
          <h2 className="text-sm font-medium text-muted">In progress</h2>
          <div className="mt-3">
            {active.length === 0 ? (
              <EmptyState title="No pull requests in progress">
                Open an issue on a repository and put an agent on it, or point
                your own agent at one.
              </EmptyState>
            ) : (
              <ul className="divide-y divide-line overflow-hidden rounded-xl border border-line bg-surface">
                {active.map(({ pull, repo }) => (
                  <li key={pull.id}>
                    <Link
                      to={`/${repo.namespace}/${repo.name}/pull/${pull.number}`}
                      className="flex items-center gap-3 px-4 py-3 transition-colors hover:bg-raised"
                    >
                      <PullIcon status={pull.status} />
                      <span className="min-w-0 grow">
                        <span className="block truncate font-medium">{pull.title}</span>
                        <span className="font-mono text-xs text-muted">
                          {repo.namespace}/{repo.name}#{pull.number}
                          {pull.issue != null && ` · for #${pull.issue}`} · {pull.agent}
                        </span>
                      </span>
                      <span className="shrink-0 text-xs text-muted">
                        {pull.status === "draft" ? "In progress" : "Ready for review"}
                      </span>
                      <span className="w-14 shrink-0 text-right text-xs text-faint">
                        <TimeAgo at={pull.updatedAt} />
                      </span>
                    </Link>
                  </li>
                ))}
              </ul>
            )}
          </div>
        </section>

        <section>
          <div className="flex items-center justify-between">
            <h2 className="text-sm font-medium text-muted">Your repositories</h2>
            <ButtonLink to="/new" variant="quiet">
              <Plus size={14} />
              New
            </ButtonLink>
          </div>
          <RepoList repos={repos} />
        </section>
      </div>

      <aside className="space-y-4">
        <div className="rounded-xl border border-line bg-surface p-5">
          <h2 className="font-medium">Connect an agent</h2>
          <p className="mt-1.5 text-sm text-muted">
            Add g1t to Claude Code, then run /mcp in it to sign in through
            your browser.
          </p>
          <div className="mt-4 space-y-2">
            <CopyLine
              prompt
              text="claude mcp add --transport http g1t https://mcp.g1t.sh"
            />
          </div>
          <Link
            to="https://docs.g1t.sh/guides/bring-your-own-agent/"
            className="mt-4 inline-flex items-center gap-1 text-sm text-accent hover:underline"
          >
            How it works <ArrowRight size={13} />
          </Link>
        </div>
        <div className="rounded-xl border border-line bg-surface p-5">
          <h2 className="font-medium">Explore</h2>
          <p className="mt-1.5 text-sm text-muted">
            Browse public repositories and the issues open on them.
          </p>
          <Link
            to="/explore"
            className="mt-3 inline-flex items-center gap-1 text-sm text-accent hover:underline"
          >
            Public repositories <ArrowRight size={13} />
          </Link>
        </div>
      </aside>
    </main>
  );
}