Commit

Workspace things at the bottom of the sidebar, and on mission control

Usage and Settings move to the foot of the sidebar, above the usage meter and the account, as Vercel keeps them. Overview leaves the nav: mission control, still the viewer's own across every workspace, gains a card for the workspace being looked at, with its repositories, open pull requests and what needs you, and the way to its members, settings and page. Workflows of one name show their files.

syntaqxcommitted Parent7fef3f2Browse files
3 files+83−200/3 viewed
+15−18
392392 </SidebarLink>
393393 </div>
394394
395− {ws && (
396− <SidebarGroup title="Workspace">
397− <SidebarLink to={`/${ws.slug}`} end icon={<LayoutDashboard size={15} />}>
398− Overview
399− </SidebarLink>
400− <SidebarLink to={`/${ws.slug}/-/usage`} icon={<BarChart3 size={15} />}>
401− Usage
402− </SidebarLink>
403− {/* Everything else about the workspace lives in its settings, and only there. */}
404− <SidebarLink
405− to={ws.role === "owner" ? `/${ws.slug}/-/settings` : `/${ws.slug}/-/people`}
406− icon={<Settings size={15} />}
407− >
408− Settings
409− </SidebarLink>
410− </SidebarGroup>
411− )}
412−
413395 <SidebarGroup
414396 title="Repositories"
415397 action={
506488 </div>
507489 </div>
508490
491+ {/* The workspace's own things sit at the bottom, by its usage and the account. */}
509492 <div className="space-y-2 p-2">
493+ {ws && (
494+ <div className="space-y-px">
495+ <SidebarLink to={`/${ws.slug}/-/usage`} icon={<BarChart3 size={15} />}>
496+ Usage
497+ </SidebarLink>
498+ {/* Everything else about the workspace lives in its settings, and only there. */}
499+ <SidebarLink
500+ to={ws.role === "owner" ? `/${ws.slug}/-/settings` : `/${ws.slug}/-/people`}
501+ icon={<Settings size={15} />}
502+ >
503+ Settings
504+ </SidebarLink>
505+ </div>
506+ )}
510507 {ws && <UsageCard slug={ws.slug} shell={shell} />}
511508 <AccountMenu user={user} />
512509 </div>
+61−1
1−import { ArrowRight, Hand, Plus } from "lucide-react";
1+import { ArrowRight, Hand, Plus, Settings, Users } from "lucide-react";
22 import { type ReactNode, useEffect } from "react";
33 import { Link, data, useRevalidator, useRouteLoaderData } from "react-router";
44
141141 type Step = { done: boolean; title: string; about: string; to: string | null; action: string };
142142
143143 /**
144+ * The workspace being looked at, from mission control: what it holds and
145+ * what is moving in it, and the way to its people and settings. The rest of
146+ * the page stays the viewer's own, across every workspace.
147+ */
148+function WorkspaceCard({ slug, role, repos, active }: { slug: string; role: string; repos: Repo[]; active: Active[] }) {
149+ const mine = active.filter((item) => item.repo.namespace.toLowerCase() === slug);
150+ const stats = [
151+ { value: repos.filter((repo) => repo.namespace.toLowerCase() === slug).length, label: "repositories" },
152+ { value: mine.length, label: "pull requests open" },
153+ { value: mine.filter((item) => item.lifecycle?.stage === "needs_you").length, label: "need you" },
154+ ];
155+ return (
156+ <div className="rounded-xl border border-line bg-surface p-5">
157+ <div className="flex items-center gap-3">
158+ <Avatar name={slug} size={32} square />
159+ <div className="min-w-0">
160+ <Link to={`/${slug}`} className="block truncate font-medium hover:underline">
161+ {slug}
162+ </Link>
163+ <p className="text-xs text-muted">Workspace · {role}</p>
164+ </div>
165+ </div>
166+ <dl className="mt-4 grid grid-cols-3 gap-2">
167+ {stats.map((stat) => (
168+ <div key={stat.label} className="rounded-lg bg-bg/60 px-2.5 py-2 ring-1 ring-line">
169+ <dt className="sr-only">{stat.label}</dt>
170+ <dd className="text-lg font-semibold tabular-nums tracking-tight">{stat.value}</dd>
171+ <dd className="text-[0.6875rem] leading-tight text-muted">{stat.label}</dd>
172+ </div>
173+ ))}
174+ </dl>
175+ <div className="mt-4 flex flex-wrap gap-x-4 gap-y-1 text-sm">
176+ <Link to={`/${slug}/-/people`} className="inline-flex items-center gap-1.5 text-muted hover:text-fg">
177+ <Users size={13} />
178+ Members
179+ </Link>
180+ <Link
181+ to={role === "owner" ? `/${slug}/-/settings` : `/${slug}/-/people`}
182+ className="inline-flex items-center gap-1.5 text-muted hover:text-fg"
183+ >
184+ <Settings size={13} />
185+ Settings
186+ </Link>
187+ <Link to={`/${slug}`} className="inline-flex items-center gap-1 text-accent hover:underline">
188+ Its page <ArrowRight size={13} />
189+ </Link>
190+ </div>
191+ </div>
192+ );
193+}
194+
195+/**
144196 * The first things to do, ticked off as they are done, until an outcome has
145197 * been handed to agents. Shown above everything else on mission control.
146198 */
343395 </div>
344396
345397 <aside className="space-y-4">
398+ {shell?.workspace && (
399+ <WorkspaceCard
400+ slug={shell.workspace.slug.toLowerCase()}
401+ role={shell.workspace.role}
402+ repos={repos}
403+ active={active}
404+ />
405+ )}
346406 <div className="rounded-xl border border-line bg-surface p-5">
347407 <h2 className="font-medium">Connect an agent</h2>
348408 <p className="mt-1.5 text-sm text-muted">
+7−1
300300 w.id === workflow?.id ? "bg-raised text-fg" : "text-muted hover:bg-raised/60 hover:text-fg"
301301 }`}
302302 >
303− <span className="min-w-0 truncate">{w.name}</span>
303+ <span className="min-w-0 truncate">
304+ {w.name}
305+ {/* Two workflows of one name are told apart by their files. */}
306+ {workflows.some((other) => other.id !== w.id && other.name === w.name) && (
307+ <span className="block truncate font-mono text-[0.6875rem] text-faint">{w.path}</span>
308+ )}
309+ </span>
304310 {w.error && <AlertTriangle size={12} className="shrink-0 text-danger" aria-label="Its file has a problem" />}
305311 {w.state === "disabled" && <span className="ml-auto shrink-0 text-xs text-faint">off</span>}
306312 </Link>