Skip to content
168 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.

Navigation: the workspace's own page with tabs, pins and recent in the sidebar1/**
Merge branch 'worktree-agent-ad7c6d88d93adc817'2 * A workspace's pages and where the sidebar finds them. `g1t.sh/<workspace>`
3 * is the workspace's own page, its header and overview; Projects, Packages,
4 * Teams, People, Insights and Settings are pages of their own under `-/`,
5 * each reached from the sidebar and each with its own heading. The sidebar
6 * is always the current workspace's.
Navigation: the workspace's own page with tabs, pins and recent in the sidebar7 */
8
Merge branch 'worktree-agent-ad7c6d88d93adc817'9/** The workspace's pages a person moves between, and which the sidebar lists. */
10export type WorkspacePageKey = "overview" | "projects" | "packages" | "teams" | "people" | "insights";
Navigation: the workspace's own page with tabs, pins and recent in the sidebar11
Merge branch 'worktree-agent-ad7c6d88d93adc817'12/** Those pages under `-/`: the overview is the workspace's own address. */
13export const WORKSPACE_PAGES = ["projects", "packages", "teams", "people", "insights"] as const;
Navigation: the workspace's own page with tabs, pins and recent in the sidebar14
Merge branch 'worktree-agent-ad7c6d88d93adc817'15/**
16 * Which of those pages a path is, under `/<workspace>`: null for the
17 * workspace's other pages (settings, Agent fleet, Usage and the rest), for
18 * one package's or one team's page, and for anything else.
19 */
20export function workspacePage(pathname: string, slug: string): WorkspacePageKey | null {
21 const parts = pagePath(pathname).split("/").filter(Boolean);
22 if (parts[0]?.toLowerCase() !== slug.toLowerCase()) return null;
23 if (parts.length === 1) return "overview";
24 if (parts[1] !== "-" || parts.length !== 3) return null;
25 return (WORKSPACE_PAGES as readonly string[]).includes(parts[2]!) ? (parts[2] as WorkspacePageKey) : null;
26}
Navigation: the workspace's own page with tabs, pins and recent in the sidebar27
28/**
Merge branch 'worktree-agent-ad7c6d88d93adc817'29 * The sidebar's rows. The first two are the person's own, whichever
30 * workspace they are in; the rest are the workspace's.
Navigation: the workspace's own page with tabs, pins and recent in the sidebar31 */
Merge branch 'worktree-agent-ad7c6d88d93adc817'32export type SidebarKey =
33 | "mission"
34 | "inbox"
35 | "overview"
36 | "projects"
37 | "agents"
38 | "context"
39 | "memory"
40 | "security"
41 | "packages"
42 | "insights"
43 | "people"
44 | "teams"
45 | "usage"
46 | "support"
47 | "settings";
48
49/** The workspace's pages that its Settings row drills into. */
50export const SETTINGS_PAGES = [
51 "settings",
52 "repositories",
53 "tokens",
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge54 "rules",
Merge branch 'worktree-agent-ad7c6d88d93adc817'55 "guardrails",
56 "secrets",
Merge branch 'worktree-agent-a3abfcce648e87dca'57 "actions",
Merge branch 'worktree-agent-ad7c6d88d93adc817'58 "runners",
59 "integrations",
60 "webhooks",
61 "billing",
62 "audit",
63] as const;
64
65/** Pages under `-/` whose sidebar row has another name. */
66const ROW_OF: Record<string, SidebarKey> = {
67 projects: "projects",
68 agents: "agents",
69 context: "context",
70 memory: "memory",
71 security: "security",
72 packages: "packages",
73 insights: "insights",
74 people: "people",
75 teams: "teams",
76 usage: "usage",
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens77 // The AI Gateway's requests are usage.
78 gateway: "usage",
Merge branch 'worktree-agent-ad7c6d88d93adc817'79};
Navigation: the workspace's own page with tabs, pins and recent in the sidebar80
81/**
Merge branch 'worktree-agent-ad7c6d88d93adc817'82 * The sidebar row that is current on a path: one row at most, so the
83 * sidebar always says where you are. `slug` is the workspace the sidebar
84 * is about; another workspace's pages light nothing.
Navigation: the workspace's own page with tabs, pins and recent in the sidebar85 */
Merge branch 'worktree-agent-ad7c6d88d93adc817'86export function sidebarCurrent(pathname: string, slug: string | null): SidebarKey | null {
87 const path = pagePath(pathname);
88 if (path === "/") return "mission";
89 if (path === "/inbox" || path.startsWith("/inbox/")) return "inbox";
90 if (path === "/support" || path.startsWith("/support/")) return "support";
91 const parts = path.split("/").filter(Boolean);
92 if (!slug || parts[0]?.toLowerCase() !== slug.toLowerCase()) return null;
Navigation: the workspace's own page with tabs, pins and recent in the sidebar93 if (parts.length === 1) return "overview";
Merge branch 'worktree-agent-ad7c6d88d93adc817'94 if (parts[1] !== "-") return null;
95 const page = parts[2] ?? "";
96 if ((SETTINGS_PAGES as readonly string[]).includes(page)) return "settings";
97 return ROW_OF[page] ?? null;
Navigation: the workspace's own page with tabs, pins and recent in the sidebar98}
99
Merge branch 'worktree-agent-ad7c6d88d93adc817'100/** `?tab=` as people write it, from the tabs the workspace page once had: the page it means. */
Navigation: the workspace's own page with tabs, pins and recent in the sidebar101const TAB_WORDS: Record<string, string> = {
102 overview: "",
103 projects: "-/projects",
104 repositories: "-/projects",
105 repos: "-/projects",
106 packages: "-/packages",
107 people: "-/people",
108 members: "-/people",
109 teams: "-/teams",
110 insights: "-/insights",
Merge branch 'worktree-agent-ad7c6d88d93adc817'111 settings: "-/settings",
Navigation: the workspace's own page with tabs, pins and recent in the sidebar112};
113
114/** Workspace pages that moved, by their old name under `-/`. */
115const MOVED: Record<string, string> = {
116 members: "-/people",
117 overview: "",
118 "soon/teams": "-/teams",
119 "soon/insights": "-/insights",
120};
121
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging122/** The page a request is for: a click's data request ends in `.data`. */
123export function pagePath(pathname: string): string {
124 return pathname.replace(/\.data$/, "").replace(/\/+$/, "") || "/";
125}
126
Navigation: the workspace's own page with tabs, pins and recent in the sidebar127/**
Merge branch 'worktree-agent-a8385d293d42c913a'128 * The page at `pathname` under the workspace `slug` instead of its first
129 * segment, keeping its query: where an old name or an alias leads. A click
130 * asks for the page's data at `<path>.data?_routes=…`; the address given is
131 * the page's own, which the browser then asks for as usual.
132 */
133export function underWorkspace(pathname: string, search: string, slug: string): string {
134 const params = new URLSearchParams(search);
135 params.delete("_routes");
136 const query = params.toString();
137 const segments = pagePath(pathname).split("/");
138 // segments[0] is the empty string before the leading slash.
139 segments[1] = slug;
140 return `${segments.join("/")}${query ? `?${query}` : ""}`;
141}
142
143/**
Navigation: the workspace's own page with tabs, pins and recent in the sidebar144 * Where an old address of a workspace's pages is now, keeping its query;
145 * null when it has not moved. `/<workspace>?tab=projects` and the like
Merge branch 'worktree-agent-ad7c6d88d93adc817'146 * open that page.
Navigation: the workspace's own page with tabs, pins and recent in the sidebar147 */
148export function workspaceRedirect(pathname: string, search = ""): string | null {
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging149 // A click asks for the page's data at `<path>.data?_routes=…`; the page
150 // is the same one a full load asks for.
151 const params = new URLSearchParams(search);
152 params.delete("_routes");
153 search = params.toString();
154 const parts = pagePath(pathname).split("/").filter(Boolean);
Navigation: the workspace's own page with tabs, pins and recent in the sidebar155 const slug = parts[0];
156 if (!slug) return null;
157 if (parts.length === 1 && params.has("tab")) {
158 const to = TAB_WORDS[(params.get("tab") ?? "").toLowerCase()];
159 if (to === undefined) return null;
160 params.delete("tab");
161 const rest = params.toString();
162 return `/${slug}${to ? `/${to}` : ""}${rest ? `?${rest}` : ""}`;
163 }
164 if (parts[1] !== "-") return null;
165 const to = MOVED[parts.slice(2).join("/")];
166 if (to === undefined) return null;
167 return `/${slug}${to ? `/${to}` : ""}${search && search !== "?" ? (search.startsWith("?") ? search : `?${search}`) : ""}`;
168}

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