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 | /** |
| 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 sidebar | 7 | */ |
| 8 | ||
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 9 | /** The workspace's pages a person moves between, and which the sidebar lists. */ |
| 10 | export type WorkspacePageKey = "overview" | "projects" | "packages" | "teams" | "people" | "insights"; | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 11 | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 12 | /** Those pages under `-/`: the overview is the workspace's own address. */ |
| 13 | export const WORKSPACE_PAGES = ["projects", "packages", "teams", "people", "insights"] as const; | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 14 | |
| 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 | */ | |
| 20 | export 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 sidebar | 27 | |
| 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 sidebar | 31 | */ |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 32 | export 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. */ | |
| 50 | export const SETTINGS_PAGES = [ | |
| 51 | "settings", | |
| 52 | "repositories", | |
| 53 | "tokens", | |
| 54 | "guardrails", | |
| 55 | "secrets", | |
| 56 | "runners", | |
| 57 | "integrations", | |
| 58 | "webhooks", | |
| 59 | "billing", | |
| 60 | "audit", | |
| 61 | ] as const; | |
| 62 | ||
| 63 | /** Pages under `-/` whose sidebar row has another name. */ | |
| 64 | const ROW_OF: Record<string, SidebarKey> = { | |
| 65 | projects: "projects", | |
| 66 | agents: "agents", | |
| 67 | context: "context", | |
| 68 | memory: "memory", | |
| 69 | security: "security", | |
| 70 | packages: "packages", | |
| 71 | insights: "insights", | |
| 72 | people: "people", | |
| 73 | teams: "teams", | |
| 74 | usage: "usage", | |
| 75 | }; | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 76 | |
| 77 | /** | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 78 | * The sidebar row that is current on a path: one row at most, so the |
| 79 | * sidebar always says where you are. `slug` is the workspace the sidebar | |
| 80 | * is about; another workspace's pages light nothing. | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 81 | */ |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 82 | export function sidebarCurrent(pathname: string, slug: string | null): SidebarKey | null { |
| 83 | const path = pagePath(pathname); | |
| 84 | if (path === "/") return "mission"; | |
| 85 | if (path === "/inbox" || path.startsWith("/inbox/")) return "inbox"; | |
| 86 | if (path === "/support" || path.startsWith("/support/")) return "support"; | |
| 87 | const parts = path.split("/").filter(Boolean); | |
| 88 | if (!slug || parts[0]?.toLowerCase() !== slug.toLowerCase()) return null; | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 89 | if (parts.length === 1) return "overview"; |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 90 | if (parts[1] !== "-") return null; |
| 91 | const page = parts[2] ?? ""; | |
| 92 | if ((SETTINGS_PAGES as readonly string[]).includes(page)) return "settings"; | |
| 93 | return ROW_OF[page] ?? null; | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 94 | } |
| 95 | ||
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 96 | /** `?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 sidebar | 97 | const TAB_WORDS: Record<string, string> = { |
| 98 | overview: "", | |
| 99 | projects: "-/projects", | |
| 100 | repositories: "-/projects", | |
| 101 | repos: "-/projects", | |
| 102 | packages: "-/packages", | |
| 103 | people: "-/people", | |
| 104 | members: "-/people", | |
| 105 | teams: "-/teams", | |
| 106 | insights: "-/insights", | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 107 | settings: "-/settings", |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 108 | }; |
| 109 | ||
| 110 | /** Workspace pages that moved, by their old name under `-/`. */ | |
| 111 | const MOVED: Record<string, string> = { | |
| 112 | members: "-/people", | |
| 113 | overview: "", | |
| 114 | "soon/teams": "-/teams", | |
| 115 | "soon/insights": "-/insights", | |
| 116 | }; | |
| 117 | ||
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 118 | /** The page a request is for: a click's data request ends in `.data`. */ |
| 119 | export function pagePath(pathname: string): string { | |
| 120 | return pathname.replace(/\.data$/, "").replace(/\/+$/, "") || "/"; | |
| 121 | } | |
| 122 | ||
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 123 | /** |
| Merge branch 'worktree-agent-a8385d293d42c913a' | 124 | * The page at `pathname` under the workspace `slug` instead of its first |
| 125 | * segment, keeping its query: where an old name or an alias leads. A click | |
| 126 | * asks for the page's data at `<path>.data?_routes=…`; the address given is | |
| 127 | * the page's own, which the browser then asks for as usual. | |
| 128 | */ | |
| 129 | export function underWorkspace(pathname: string, search: string, slug: string): string { | |
| 130 | const params = new URLSearchParams(search); | |
| 131 | params.delete("_routes"); | |
| 132 | const query = params.toString(); | |
| 133 | const segments = pagePath(pathname).split("/"); | |
| 134 | // segments[0] is the empty string before the leading slash. | |
| 135 | segments[1] = slug; | |
| 136 | return `${segments.join("/")}${query ? `?${query}` : ""}`; | |
| 137 | } | |
| 138 | ||
| 139 | /** | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 140 | * Where an old address of a workspace's pages is now, keeping its query; |
| 141 | * null when it has not moved. `/<workspace>?tab=projects` and the like | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 142 | * open that page. |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 143 | */ |
| 144 | export function workspaceRedirect(pathname: string, search = ""): string | null { | |
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 145 | // A click asks for the page's data at `<path>.data?_routes=…`; the page |
| 146 | // is the same one a full load asks for. | |
| 147 | const params = new URLSearchParams(search); | |
| 148 | params.delete("_routes"); | |
| 149 | search = params.toString(); | |
| 150 | const parts = pagePath(pathname).split("/").filter(Boolean); | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 151 | const slug = parts[0]; |
| 152 | if (!slug) return null; | |
| 153 | if (parts.length === 1 && params.has("tab")) { | |
| 154 | const to = TAB_WORDS[(params.get("tab") ?? "").toLowerCase()]; | |
| 155 | if (to === undefined) return null; | |
| 156 | params.delete("tab"); | |
| 157 | const rest = params.toString(); | |
| 158 | return `/${slug}${to ? `/${to}` : ""}${rest ? `?${rest}` : ""}`; | |
| 159 | } | |
| 160 | if (parts[1] !== "-") return null; | |
| 161 | const to = MOVED[parts.slice(2).join("/")]; | |
| 162 | if (to === undefined) return null; | |
| 163 | return `/${slug}${to ? `/${to}` : ""}${search && search !== "?" ? (search.startsWith("?") ? search : `?${search}`) : ""}`; | |
| 164 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.