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 sidebar | 1 | import { ArrowRight, ArrowUpRight, Box, CircleDot, GitBranch, GitPullRequest, KeyRound, Lock, Pin, Plus, Rocket } from "lucide-react"; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 2 | import { Link, redirect } from "react-router"; |
| Commit pages credit co-authors instead of printing their trailers | 3 | |
| A project is an app or a library: libraries show their package and how to ship a release, not production | 4 | import type { PackageSummary, Project, ProjectDeploys, User } from "@g1t/contracts"; |
| Projects: what a workspace builds and runs, first on every page | 5 | |
| Commit pages credit co-authors instead of printing their trailers | 6 | import type { Route } from "./+types/overview"; |
| Merge branch 'site-fixes' into fast-push | 7 | import { DeployLink, host, StatusDot } from "../../components/deploy"; |
| Commit pages credit co-authors instead of printing their trailers | 8 | import { Avatar, ButtonLink, CopyLine, Pill, TimeAgo } from "../../components/ui"; |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 9 | import { UsageCard } from "../../components/usage-card"; |
| 10 | import { PullIcon } from "../../components/work-icons"; | |
| g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent | 11 | import { openedBy } from "../../lib/opened-by"; |
| Merge branch 'worktree-agent-aaf03bdceac799c89' | 12 | import { cloneUrl, useAddresses } from "../../lib/addresses"; |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 13 | import { kindLabel, libraryPackages, packageLine, packagePath, primaryLink } from "../../lib/project-kind"; |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 14 | import { PinButton } from "../../components/pin-button"; |
| 15 | import { sortProjects } from "../../lib/project-list"; | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 16 | import { usageFor } from "../../lib/workspace-usage.server"; |
| 17 | import { deployments, identity, packages, projects as projectsApi, work } from "../../lib/services.server"; | |
| Commit pages credit co-authors instead of printing their trailers | 18 | import { getViewer, roleIn } from "../../lib/session.server"; |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 19 | import { workspaceProjects } from "../../lib/workspace-projects.server"; |
| Commit pages credit co-authors instead of printing their trailers | 20 | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 21 | /** Projects whose open issues and pull requests are counted: the most active. */ |
| Commit pages credit co-authors instead of printing their trailers | 22 | const MAX_COUNTED = 30; |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 23 | /** Projects shown under Recently active, after the pins. */ |
| 24 | const MAX_ACTIVE = 6; | |
| Projects: what a workspace builds and runs, first on every page | 25 | /** Projects whose pull requests are listed under "In progress". */ |
| Commit pages credit co-authors instead of printing their trailers | 26 | const MAX_LISTED = 8; |
| 27 | const MAX_PULLS = 8; | |
| 28 | const MAX_FACES = 8; | |
| 29 | export async function loader({ params, context }: Route.LoaderArgs) { | |
| 30 | const viewer = getViewer(context); | |
| 31 | const slug = params.owner.toLowerCase(); | |
| 32 | const role = roleIn(viewer, slug); | |
| g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them. | 33 | // A member's way in is Today; this page |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 34 | // is the workspace as a visitor sees it. |
| g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them. | 35 | if (role) throw redirect(`/${slug}/-/today`); |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 36 | const [listed, members, deploys, usage, published, shortcuts] = await Promise.all([ |
| 37 | // The listing the sidebar and the Projects tab share. | |
| 38 | workspaceProjects(slug, viewer), | |
| Commit pages credit co-authors instead of printing their trailers | 39 | role ? identity.listMembers(slug, viewer) : null, |
| Projects: what a workspace builds and runs, first on every page | 40 | role ? deployments.overview(slug, viewer) : null, |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 41 | role ? usageFor(slug, viewer) : null, |
| A project is an app or a library: libraries show their package and how to ship a release, not production | 42 | // One listing for every card: a library's card shows its package where an app's shows production. |
| 43 | packages.list(slug, viewer).catch(() => null), | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 44 | // The viewer's pins lead the page, as they lead the sidebar. |
| 45 | role ? projectsApi.shortcuts(slug, viewer).catch(() => null) : null, | |
| Commit pages credit co-authors instead of printing their trailers | 46 | ]); |
| Projects: what a workspace builds and runs, first on every page | 47 | const projects = listed.ok ? listed.value : []; |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 48 | const pinnedIds = new Set((shortcuts?.pinned ?? []).map((project) => project.id)); |
| 49 | const byId = new Map(projects.map((project) => [project.id, project])); | |
| 50 | const pinned = (shortcuts?.pinned ?? []).map((pin) => byId.get(pin.id)).filter((project): project is Project => project != null); | |
| 51 | // However many there are: the most active first, archived ones last. | |
| 52 | const byActivity = sortProjects( | |
| 53 | projects.map((project) => ({ ...project, pushedAt: project.pushedAt ?? null, activity: project.activity ?? 0, deploying: false })), | |
| 54 | "active", | |
| 55 | ) | |
| 56 | .sort((a, b) => Number(a.archived) - Number(b.archived)) | |
| 57 | .map((listedProject) => byId.get(listedProject.id)!); | |
| 58 | const active = byActivity.filter((project) => !pinnedIds.has(project.id)).slice(0, MAX_ACTIVE); | |
| Commit pages credit co-authors instead of printing their trailers | 59 | |
| Projects: what a workspace builds and runs, first on every page | 60 | const pathOf = (project: Project) => |
| 61 | project.source.kind === "hosted" ? project.source.repo : { namespace: project.workspace, name: project.slug }; | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 62 | const counted = [...new Map([...pinned, ...active, ...byActivity].map((project) => [project.id, project])).values()].slice(0, Math.max(MAX_COUNTED, pinned.length + active.length)); |
| Projects: what a workspace builds and runs, first on every page | 63 | const counts = await Promise.all(counted.map((project) => work.counts(pathOf(project), viewer))); |
| Commit pages credit co-authors instead of printing their trailers | 64 | const open: Record<string, { issues: number; pulls: number }> = {}; |
| Projects: what a workspace builds and runs, first on every page | 65 | counted.forEach((project, i) => { |
| Commit pages credit co-authors instead of printing their trailers | 66 | const result = counts[i]; |
| Projects: what a workspace builds and runs, first on every page | 67 | if (result.ok) open[project.id] = result.value; |
| Commit pages credit co-authors instead of printing their trailers | 68 | }); |
| 69 | ||
| Projects: what a workspace builds and runs, first on every page | 70 | const busy = counted.filter((project) => (open[project.id]?.pulls ?? 0) > 0).slice(0, MAX_LISTED); |
| 71 | const lists = await Promise.all(busy.map((project) => work.listPulls(pathOf(project), viewer, "open"))); | |
| Commit pages credit co-authors instead of printing their trailers | 72 | const pulls = lists |
| Projects: what a workspace builds and runs, first on every page | 73 | .flatMap((result, i) => (result.ok ? result.value.map((pull) => ({ pull, project: busy[i] })) : [])) |
| Commit pages credit co-authors instead of printing their trailers | 74 | .sort((a, b) => b.pull.updatedAt.localeCompare(a.pull.updatedAt)) |
| 75 | .slice(0, MAX_PULLS); | |
| 76 | ||
| Projects: what a workspace builds and runs, first on every page | 77 | const bySlug: Record<string, ProjectDeploys> = {}; |
| 78 | for (const entry of deploys?.ok ? deploys.value : []) bySlug[entry.slug] = entry; | |
| 79 | ||
| A project is an app or a library: libraries show their package and how to ship a release, not production | 80 | const byRepo = new Map<string, PackageSummary[]>(); |
| 81 | for (const pkg of published?.ok ? published.value : []) { | |
| 82 | if (pkg.repo) byRepo.set(pkg.repo.id, [...(byRepo.get(pkg.repo.id) ?? []), pkg]); | |
| 83 | } | |
| 84 | const packageOf: Record<string, PackageSummary> = {}; | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 85 | for (const project of [...pinned, ...active]) { |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 86 | if ((project.kind !== "library" && project.kind !== "tool") || project.source.kind !== "hosted") continue; |
| A project is an app or a library: libraries show their package and how to ship a release, not production | 87 | const first = libraryPackages(byRepo.get(project.source.repoId) ?? [])[0]; |
| 88 | if (first) packageOf[project.id] = first; | |
| 89 | } | |
| 90 | ||
| Projects: what a workspace builds and runs, first on every page | 91 | return { |
| 92 | slug, | |
| 93 | role, | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 94 | total: projects.length, |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 95 | elsewhere: projects.filter((project) => project.runs === "elsewhere" && project.productionUrl).length, |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 96 | pinned, |
| 97 | active, | |
| Projects: what a workspace builds and runs, first on every page | 98 | open, |
| 99 | deploys: bySlug, | |
| A project is an app or a library: libraries show their package and how to ship a release, not production | 100 | packageOf, |
| Projects: what a workspace builds and runs, first on every page | 101 | pulls: pulls.map(({ pull, project }) => ({ pull, slug: project.slug, name: project.name })), |
| 102 | members: members?.ok ? members.value : null, | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 103 | usage, |
| Projects: what a workspace builds and runs, first on every page | 104 | }; |
| Commit pages credit co-authors instead of printing their trailers | 105 | } |
| 106 | ||
| 107 | function Stat({ value, label }: { value: number; label: string }) { | |
| 108 | return ( | |
| 109 | <div className="rounded-xl border border-line bg-surface px-4 py-3"> | |
| 110 | <p className="text-xl font-semibold tabular-nums tracking-tight">{value}</p> | |
| 111 | <p className="text-xs text-muted">{label}</p> | |
| 112 | </div> | |
| 113 | ); | |
| 114 | } | |
| 115 | ||
| Projects: what a workspace builds and runs, first on every page | 116 | /** What a member sees in a workspace with no projects yet. */ |
| Commit pages credit co-authors instead of printing their trailers | 117 | function GetStarted({ slug }: { slug: string }) { |
| Merge branch 'worktree-agent-aaf03bdceac799c89' | 118 | const addresses = useAddresses(); |
| Commit pages credit co-authors instead of printing their trailers | 119 | return ( |
| Projects: what a workspace builds and runs, first on every page | 120 | <div className="rounded-2xl border border-line bg-surface p-8"> |
| 121 | <span className="flex size-10 items-center justify-center rounded-xl bg-accent/10 text-accent ring-1 ring-accent/30"> | |
| 122 | <Box size={18} /> | |
| 123 | </span> | |
| 124 | <h2 className="mt-4 text-lg font-semibold tracking-tight">Create your first project</h2> | |
| Commit pages credit co-authors instead of printing their trailers | 125 | <p className="mt-1.5 max-w-xl text-sm text-muted"> |
| Projects: what a workspace builds and runs, first on every page | 126 | A project is what you build and run: its code, the issues that say what should change, the agents and |
| 127 | people who change it, and where it is deployed. | |
| Commit pages credit co-authors instead of printing their trailers | 128 | </p> |
| Projects: what a workspace builds and runs, first on every page | 129 | <div className="mt-6 flex flex-wrap gap-3"> |
| 130 | <ButtonLink to={`/new?workspace=${slug}`} variant="accent"> | |
| 131 | <Plus size={14} /> | |
| 132 | New project | |
| 133 | </ButtonLink> | |
| Commit pages credit co-authors instead of printing their trailers | 134 | </div> |
| Projects: what a workspace builds and runs, first on every page | 135 | <p className="mt-6 text-sm text-muted">Or push code you already have, and it becomes a project:</p> |
| Commit pages credit co-authors instead of printing their trailers | 136 | <div className="mt-2 max-w-xl"> |
| Merge branch 'worktree-agent-aaf03bdceac799c89' | 137 | <CopyLine prompt text={`git push ${cloneUrl(addresses, `${slug}/my-project`)} main`} /> |
| Commit pages credit co-authors instead of printing their trailers | 138 | </div> |
| 139 | </div> | |
| 140 | ); | |
| 141 | } | |
| 142 | ||
| Projects: what a workspace builds and runs, first on every page | 143 | function ProjectCard({ |
| 144 | project, | |
| 145 | open, | |
| 146 | deploys, | |
| A project is an app or a library: libraries show their package and how to ship a release, not production | 147 | pkg, |
| Projects: what a workspace builds and runs, first on every page | 148 | member, |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 149 | pinned, |
| Projects: what a workspace builds and runs, first on every page | 150 | }: { |
| 151 | project: Project; | |
| 152 | open: { issues: number; pulls: number } | undefined; | |
| 153 | deploys: ProjectDeploys | undefined; | |
| A project is an app or a library: libraries show their package and how to ship a release, not production | 154 | /** For a library, the package it publishes. */ |
| 155 | pkg: PackageSummary | undefined; | |
| Projects: what a workspace builds and runs, first on every page | 156 | member: boolean; |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 157 | /** Whether the viewer pinned it; null when they cannot pin. */ |
| 158 | pinned: boolean | null; | |
| Projects: what a workspace builds and runs, first on every page | 159 | }) { |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 160 | // A library or a tool shows the package it publishes. |
| 161 | const library = project.kind === "library" || project.kind === "tool"; | |
| Projects: what a workspace builds and runs, first on every page | 162 | const base = `/${project.workspace}/${project.slug}`; |
| 163 | const production = deploys?.production ?? null; | |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 164 | // Production where g1t serves it or elsewhere, else its homepage or docs. |
| 165 | const link = primaryLink(project, production?.url); | |
| Projects: what a workspace builds and runs, first on every page | 166 | const latest = deploys?.latest ?? null; |
| 167 | const source = project.source.kind === "hosted" ? project.source : null; | |
| 168 | return ( | |
| 169 | <li className="group relative flex flex-col rounded-2xl border border-line bg-surface p-5 transition-colors hover:border-line-strong"> | |
| 170 | <div className="flex items-start gap-3"> | |
| 171 | <span className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-raised text-muted ring-1 ring-line"> | |
| 172 | {project.private ? <Lock size={15} /> : <Box size={15} />} | |
| 173 | </span> | |
| 174 | <div className="min-w-0 grow"> | |
| 175 | <Link to={base} prefetch="intent" className="font-medium after:absolute after:inset-0 hover:underline"> | |
| 176 | {project.name} | |
| 177 | </Link> | |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 178 | {link && !(library && pkg && !production) ? ( |
| Merge branch 'site-fixes' into fast-push | 179 | <DeployLink |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 180 | href={link} |
| Projects: what a workspace builds and runs, first on every page | 181 | className="relative z-10 mt-0.5 flex items-center gap-1 truncate font-mono text-xs text-muted hover:text-accent" |
| 182 | > | |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 183 | {host(link)} |
| Projects: what a workspace builds and runs, first on every page | 184 | <ArrowUpRight size={11} className="shrink-0" /> |
| Merge branch 'site-fixes' into fast-push | 185 | </DeployLink> |
| A project is an app or a library: libraries show their package and how to ship a release, not production | 186 | ) : library && pkg ? ( |
| 187 | <Link | |
| 188 | to={packagePath(pkg)} | |
| 189 | className="relative z-10 mt-0.5 block truncate font-mono text-xs text-muted hover:text-accent" | |
| 190 | > | |
| 191 | {packageLine(pkg)} | |
| 192 | </Link> | |
| Projects: what a workspace builds and runs, first on every page | 193 | ) : ( |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 194 | <p className="mt-0.5 truncate text-xs text-faint"> |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 195 | {project.description ?? |
| 196 | (library | |
| 197 | ? "Not published yet" | |
| 198 | : project.runs === "g1t" | |
| 199 | ? deploys?.enabled | |
| 200 | ? "Not deployed yet" | |
| 201 | : "Deployments are off" | |
| 202 | : kindLabel(project))} | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 203 | </p> |
| Projects: what a workspace builds and runs, first on every page | 204 | )} |
| 205 | </div> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 206 | {(project.private || project.archived) && ( |
| 207 | <span className="flex shrink-0 gap-1.5"> | |
| 208 | {project.private && <Pill>private</Pill>} | |
| 209 | {project.archived && <Pill>archived</Pill>} | |
| 210 | </span> | |
| 211 | )} | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 212 | {pinned != null && ( |
| 213 | <PinButton workspace={project.workspace} slug={project.slug} name={project.name} pinned={pinned} compact className="-mt-1.5 -mr-2" /> | |
| 214 | )} | |
| Projects: what a workspace builds and runs, first on every page | 215 | </div> |
| 216 | ||
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 217 | {project.description && (link || (library && pkg)) && <p className="mt-3 line-clamp-2 text-sm text-muted">{project.description}</p>} |
| Projects: what a workspace builds and runs, first on every page | 218 | |
| 219 | <div className="mt-auto pt-5"> | |
| 220 | {member && latest && ( | |
| 221 | <p className="mb-3 flex items-center gap-2 text-xs text-muted"> | |
| 222 | <StatusDot status={latest.status} /> | |
| 223 | <span className="truncate"> | |
| 224 | {latest.kind === "production" ? "Production" : `Preview of ${latest.branch}`} | |
| 225 | </span> | |
| 226 | <span className="ml-auto shrink-0 text-faint"> | |
| 227 | <TimeAgo at={latest.createdAt} /> | |
| 228 | </span> | |
| 229 | </p> | |
| 230 | )} | |
| 231 | <div className="flex items-center gap-4 border-t border-line pt-3 text-xs text-muted"> | |
| 232 | {source && ( | |
| 233 | <span className="flex min-w-0 items-center gap-1.5 font-mono"> | |
| 234 | <GitBranch size={12} className="shrink-0 text-faint" /> | |
| 235 | <span className="truncate">{source.defaultBranch}</span> | |
| 236 | </span> | |
| 237 | )} | |
| 238 | <span className="flex items-center gap-1"> | |
| 239 | <CircleDot size={12} className="text-faint" /> | |
| 240 | {open?.issues ?? 0} | |
| 241 | </span> | |
| 242 | <span className="flex items-center gap-1"> | |
| 243 | <GitPullRequest size={12} className="text-faint" /> | |
| 244 | {open?.pulls ?? 0} | |
| 245 | </span> | |
| 246 | {member && deploys?.enabled && (deploys.previews ?? 0) > 0 && ( | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 247 | <span className="ml-auto flex items-center gap-1 text-success"> |
| Projects: what a workspace builds and runs, first on every page | 248 | <Rocket size={12} /> |
| 249 | {deploys.previews} {deploys.previews === 1 ? "preview" : "previews"} | |
| 250 | </span> | |
| 251 | )} | |
| 252 | </div> | |
| 253 | </div> | |
| 254 | </li> | |
| 255 | ); | |
| 256 | } | |
| 257 | ||
| Commit pages credit co-authors instead of printing their trailers | 258 | export default function WorkspaceOverview({ loaderData }: Route.ComponentProps) { |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 259 | const { slug, role, total, pinned, active, open, deploys, pulls, members, usage } = loaderData; |
| 260 | const card = (project: Project, isPinned: boolean) => ( | |
| 261 | <ProjectCard | |
| 262 | key={project.id} | |
| 263 | project={project} | |
| 264 | open={open[project.id]} | |
| 265 | deploys={deploys[project.slug]} | |
| 266 | pkg={loaderData.packageOf[project.id]} | |
| 267 | member={role != null} | |
| 268 | pinned={role ? isPinned : null} | |
| 269 | /> | |
| 270 | ); | |
| Commit pages credit co-authors instead of printing their trailers | 271 | const totals = Object.values(open).reduce( |
| Projects: what a workspace builds and runs, first on every page | 272 | (sum, counts) => ({ issues: sum.issues + counts.issues, pulls: sum.pulls + counts.pulls }), |
| Commit pages credit co-authors instead of printing their trailers | 273 | { issues: 0, pulls: 0 }, |
| 274 | ); | |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 275 | // Live on g1t, or deployed elsewhere with an address. |
| 276 | const liveApps = | |
| 277 | Object.values(deploys).filter((entry) => entry.production).length + | |
| 278 | loaderData.elsewhere; | |
| Commit pages credit co-authors instead of printing their trailers | 279 | return ( |
| 280 | <div className="grid gap-10 lg:grid-cols-[1fr_18rem]"> | |
| 281 | <div className="min-w-0 space-y-10"> | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 282 | {total === 0 && role ? ( |
| Commit pages credit co-authors instead of printing their trailers | 283 | <GetStarted slug={slug} /> |
| 284 | ) : ( | |
| 285 | <> | |
| Projects: what a workspace builds and runs, first on every page | 286 | <div className="grid grid-cols-2 gap-3 sm:grid-cols-4"> |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 287 | <Stat value={total} label={total === 1 ? "Project" : "Projects"} /> |
| Projects: what a workspace builds and runs, first on every page | 288 | <Stat value={liveApps} label="In production" /> |
| Commit pages credit co-authors instead of printing their trailers | 289 | <Stat value={totals.issues} label="Open issues" /> |
| 290 | <Stat value={totals.pulls} label="Pull requests in progress" /> | |
| 291 | </div> | |
| 292 | ||
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 293 | {role && ( |
| 294 | <section aria-labelledby="pinned"> | |
| 295 | <h2 id="pinned" className="text-sm font-medium text-muted"> | |
| 296 | Pinned | |
| 297 | </h2> | |
| 298 | {pinned.length > 0 ? ( | |
| 299 | <ul className="mt-3 grid gap-4 sm:grid-cols-2">{pinned.map((project) => card(project, true))}</ul> | |
| 300 | ) : ( | |
| 301 | <p className="mt-3 flex items-center gap-2 rounded-xl border border-dashed border-line px-4 py-3 text-sm text-muted"> | |
| 302 | <Pin size={14} className="shrink-0 text-faint" /> | |
| 303 | Pin the projects you use most, from their page or from Projects, to keep them here and in your sidebar. | |
| 304 | </p> | |
| Projects: what a workspace builds and runs, first on every page | 305 | )} |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 306 | </section> |
| 307 | )} | |
| 308 | ||
| 309 | {active.length > 0 && ( | |
| 310 | <section aria-labelledby="active"> | |
| 311 | <div className="flex items-center justify-between"> | |
| 312 | <h2 id="active" className="text-sm font-medium text-muted"> | |
| 313 | {role ? "Recently active" : "Projects"} | |
| 314 | </h2> | |
| 315 | {role && ( | |
| 316 | <Link to={`/new?workspace=${slug}`} className="inline-flex items-center gap-1 text-xs text-muted hover:text-fg"> | |
| 317 | <Plus size={13} /> | |
| 318 | New project | |
| 319 | </Link> | |
| 320 | )} | |
| 321 | </div> | |
| 322 | <ul className="mt-3 grid gap-4 sm:grid-cols-2">{active.map((project) => card(project, false))}</ul> | |
| 323 | </section> | |
| 324 | )} | |
| 325 | ||
| 326 | <Link | |
| 327 | to={`/${slug}/-/projects`} | |
| 328 | prefetch="intent" | |
| 329 | className="flex items-center justify-between rounded-xl border border-line bg-surface px-4 py-3 text-sm transition-colors hover:border-line-strong" | |
| 330 | > | |
| 331 | <span> | |
| 332 | All projects <span className="tabular-nums text-muted">({total})</span> | |
| 333 | </span> | |
| 334 | <span className="flex items-center gap-1 text-xs text-muted"> | |
| 335 | Search, filter and sort | |
| 336 | <ArrowRight size={13} /> | |
| 337 | </span> | |
| 338 | </Link> | |
| Projects: what a workspace builds and runs, first on every page | 339 | |
| Commit pages credit co-authors instead of printing their trailers | 340 | {pulls.length > 0 && ( |
| 341 | <section> | |
| 342 | <h2 className="text-sm font-medium text-muted">In progress</h2> | |
| 343 | <ul className="mt-3 divide-y divide-line overflow-hidden rounded-xl border border-line bg-surface"> | |
| Projects: what a workspace builds and runs, first on every page | 344 | {pulls.map(({ pull, slug: project, name }) => ( |
| Commit pages credit co-authors instead of printing their trailers | 345 | <li key={pull.id}> |
| 346 | <Link | |
| 347 | prefetch="intent" | |
| Projects: what a workspace builds and runs, first on every page | 348 | to={`/${slug}/${project}/pull/${pull.number}`} |
| Commit pages credit co-authors instead of printing their trailers | 349 | className="flex items-center gap-3 px-4 py-3 transition-colors hover:bg-raised" |
| 350 | > | |
| 351 | <PullIcon status={pull.status} /> | |
| 352 | <span className="min-w-0 grow"> | |
| 353 | <span className="block truncate font-medium">{pull.title}</span> | |
| Projects: what a workspace builds and runs, first on every page | 354 | <span className="text-xs text-muted"> |
| 355 | {name} <span className="font-mono">#{pull.number}</span> | |
| g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent | 356 | {pull.issue != null && ` · for #${pull.issue}`} · {openedBy(pull).name} |
| Commit pages credit co-authors instead of printing their trailers | 357 | </span> |
| 358 | </span> | |
| 359 | <span className="hidden shrink-0 text-xs text-muted sm:block"> | |
| 360 | {pull.status === "draft" ? "Being worked on" : "Ready for review"} | |
| 361 | </span> | |
| 362 | <span className="w-14 shrink-0 text-right text-xs text-faint"> | |
| 363 | <TimeAgo at={pull.updatedAt} /> | |
| 364 | </span> | |
| 365 | </Link> | |
| 366 | </li> | |
| 367 | ))} | |
| 368 | </ul> | |
| 369 | </section> | |
| 370 | )} | |
| 371 | </> | |
| 372 | )} | |
| 373 | </div> | |
| 374 | ||
| 375 | <aside className="space-y-6"> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 376 | {role && usage && <UsageCard slug={slug} glance={usage} owner={role === "owner"} />} |
| 377 | ||
| Commit pages credit co-authors instead of printing their trailers | 378 | {members ? ( |
| 379 | <section> | |
| 380 | <div className="flex items-center justify-between"> | |
| Projects: what a workspace builds and runs, first on every page | 381 | <h2 className="text-sm font-medium">Members</h2> |
| Commit pages credit co-authors instead of printing their trailers | 382 | <Link to={`/${slug}/-/people`} className="text-xs text-muted hover:text-fg"> |
| 383 | {role === "owner" ? "Manage" : "See all"} | |
| 384 | </Link> | |
| 385 | </div> | |
| 386 | <ul className="mt-3 space-y-1.5"> | |
| 387 | {members.slice(0, MAX_FACES).map((member) => ( | |
| 388 | <li key={member.username} className="flex items-center gap-2 text-sm"> | |
| 389 | <Avatar name={member.username} /> | |
| 390 | <span className="grow truncate font-mono">{member.username}</span> | |
| 391 | {member.role === "owner" && <Pill>owner</Pill>} | |
| 392 | </li> | |
| 393 | ))} | |
| 394 | </ul> | |
| Projects: what a workspace builds and runs, first on every page | 395 | {members.length > MAX_FACES && <p className="mt-2 text-xs text-faint">and {members.length - MAX_FACES} more</p>} |
| Commit pages credit co-authors instead of printing their trailers | 396 | </section> |
| 397 | ) : ( | |
| 398 | <p className="text-sm text-muted"> | |
| Projects: what a workspace builds and runs, first on every page | 399 | Members of this workspace build its projects. Anyone can open an issue or a pull request on a public one. |
| Commit pages credit co-authors instead of printing their trailers | 400 | </p> |
| 401 | )} | |
| 402 | ||
| 403 | {role && ( | |
| 404 | <section className="rounded-xl border border-line bg-surface p-5"> | |
| 405 | <h2 className="flex items-center gap-2 font-medium"> | |
| Projects: what a workspace builds and runs, first on every page | 406 | <Rocket size={15} className="text-faint" /> |
| 407 | Deploy on g1t.page | |
| 408 | </h2> | |
| 409 | <p className="mt-1.5 text-sm text-muted"> | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 410 | Off until you turn them on for a project, under its Settings. Then production builds from its default |
| 411 | branch, with a live preview for every pull request. Apps cost nothing while no one visits. | |
| Projects: what a workspace builds and runs, first on every page | 412 | </p> |
| 413 | <Link | |
| 414 | prefetch="intent" | |
| 415 | to={`/${slug}/-/billing`} | |
| 416 | className="mt-3 inline-flex items-center gap-1 text-sm text-accent hover:underline" | |
| 417 | > | |
| 418 | Billing and plans <ArrowRight size={13} /> | |
| 419 | </Link> | |
| 420 | </section> | |
| 421 | )} | |
| 422 | ||
| 423 | {role && ( | |
| 424 | <section className="rounded-xl border border-line bg-surface p-5"> | |
| 425 | <h2 className="flex items-center gap-2 font-medium"> | |
| Commit pages credit co-authors instead of printing their trailers | 426 | <KeyRound size={15} className="text-faint" /> |
| 427 | Automate without a service account | |
| 428 | </h2> | |
| 429 | <p className="mt-1.5 text-sm text-muted"> | |
| Projects: what a workspace builds and runs, first on every page | 430 | A workspace has access tokens of its own for CI, integrations and agents. They act as the workspace, |
| 431 | not as a person. | |
| Commit pages credit co-authors instead of printing their trailers | 432 | </p> |
| 433 | <Link | |
| 434 | prefetch="intent" | |
| 435 | to={`/${slug}/-/tokens`} | |
| 436 | className="mt-3 inline-flex items-center gap-1 text-sm text-accent hover:underline" | |
| 437 | > | |
| 438 | Access tokens <ArrowRight size={13} /> | |
| 439 | </Link> | |
| 440 | </section> | |
| 441 | )} | |
| 442 | </aside> | |
| 443 | </div> | |
| 444 | ); | |
| 445 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.