Boards and Roadmap are gone from the workspace sidebar, with their Soon pages: g1t is not building project management now, so it no longer promises a board, table or timeline view of issues and pull requests. Issues, pull requests, outcomes and milestones are unchanged. An old /<workspace>/-/soon/board or /soon/roadmap link now answers 404, and the social cards and tests that used Board as their example Soon page use Environments instead.
6 files+9−530/6 viewed
| 1 | − | import { Activity, BarChart3, Building2, MessagesSquare, Bell, BookMarked, BookOpen, Bookmark, Blocks, Bot, Box, Check, ChevronDown, ChevronLeft, ChevronRight, CircleDot, GripVertical, CircleUserRound, Code2, Coins, Compass, CreditCard, Fingerprint, GanttChart, Gauge, GitBranch, GitPullRequest, Globe, History, KanbanSquare, Keyboard, KeyRound, Layers, LayoutDashboard, LayoutGrid, LifeBuoy, ListTree, Lock, LogOut, Mail, MonitorCog, MonitorDown, Network, Package, PlayCircle, Plug, Plus, Rocket, Search, ServerCog, Settings, Shapes, Shield, ShieldCheck, Scale, Smile, Sparkles, SlidersHorizontal, House, Ticket, TrendingUp, UserRoundKey, Users, UsersRound, Webhook, X, ArrowLeftRight } from "lucide-react"; | |
| 1 | + | import { Activity, BarChart3, Building2, MessagesSquare, Bell, BookMarked, BookOpen, Bookmark, Blocks, Bot, Box, Check, ChevronDown, ChevronLeft, ChevronRight, CircleDot, GripVertical, CircleUserRound, Code2, Coins, Compass, CreditCard, Fingerprint, Gauge, GitBranch, GitPullRequest, Globe, History, Keyboard, KeyRound, Layers, LayoutDashboard, LayoutGrid, LifeBuoy, ListTree, Lock, LogOut, Mail, MonitorCog, MonitorDown, Network, Package, PlayCircle, Plug, Plus, Rocket, Search, ServerCog, Settings, Shapes, Shield, ShieldCheck, Scale, Smile, Sparkles, SlidersHorizontal, House, Ticket, TrendingUp, UserRoundKey, Users, UsersRound, Webhook, X, ArrowLeftRight } from "lucide-react"; | |
| 2 | 2 | import { Fragment, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react"; | |
| 3 | 3 | import { Link, useFetcher, useLocation, useNavigation, useRouteLoaderData, useSubmit } from "react-router"; | |
| 4 | 4 | ||
| ⋯ | |||
| 263 | 263 | </SidebarMenuItem> | |
| 264 | 264 | ); | |
| 265 | 265 | } | |
| 266 | − | ||
| 267 | − | /** Icons for what spans projects. */ | |
| 268 | − | const WORKSPACE_ICONS: Record<string, ReactNode> = { | |
| 269 | − | board: <KanbanSquare size={15} />, | |
| 270 | − | roadmap: <GanttChart size={15} />, | |
| 271 | − | teams: <UsersRound size={15} />, | |
| 272 | − | packages: <Package size={15} />, | |
| 273 | − | fleet: <Bot size={15} />, | |
| 274 | − | }; | |
| 275 | 266 | ||
| 276 | 267 | /** The Soon pages of a section, as paths, so its link is current on them. */ | |
| 277 | 268 | function soonPaths(base: string, section: RoadmapItem["section"]): string[] { | |
| ⋯ | |||
| 1212 | 1203 | {INSIGHTS.title} | |
| 1213 | 1204 | </SidebarSoonLink> | |
| 1214 | 1205 | )} | |
| 1215 | − | {roadmapIn("Workspace") | |
| 1216 | − | .filter((item) => item.key !== "insights" && item.key !== "teams" && item.key !== "fleet") | |
| 1217 | − | .map((item) => ( | |
| 1218 | − | <SidebarSoonLink key={item.key} to={`/${slug}/-/soon/${item.key}`} icon={WORKSPACE_ICONS[item.key] ?? <Sparkles size={15} />} about={item.summary}> | |
| 1219 | − | {item.title === "Board" ? "Boards" : item.title} | |
| 1220 | − | </SidebarSoonLink> | |
| 1221 | − | ))} | |
| 1222 | 1206 | </NavRows> | |
| 1223 | 1207 | {shared.length > 0 && ( | |
| 1224 | 1208 | <> | |
| 129 | 129 | assert.equal(projectPageAt("settings/branches"), "settings"); | |
| 130 | 130 | assert.equal(projectPageAt("soon/uptime", { uptime: "observability" }), "observability"); | |
| 131 | 131 | assert.equal(projectPageAt("observability/logs"), "observability"); | |
| 132 | − | assert.equal(projectPageAt("soon/boards", { boards: "issues" }), "issues"); | |
| 132 | + | assert.equal(projectPageAt("soon/playbooks", { playbooks: "agents" }), "agents"); | |
| 133 | 133 | assert.equal(projectPageAt("pulse"), null); | |
| 134 | 134 | }); |
| 13 | 13 | title: string; | |
| 14 | 14 | /** | |
| 15 | 15 | * Where it lives: a project page whose tabs it joins, or the workspace, | |
| 16 | − | * for what spans projects (boards, the roadmap, packages, the fleet). | |
| 16 | + | * for what spans projects (Insights). | |
| 17 | 17 | */ | |
| 18 | 18 | section: "Code" | "Issues" | "Agents" | "Deployments" | "Observability" | "Security" | "Insights" | "Workspace"; | |
| 19 | 19 | /** One line, for the menu's tooltip and the page's lead. */ | |
| ⋯ | |||
| 27 | 27 | }; | |
| 28 | 28 | ||
| 29 | 29 | export const ROADMAP: RoadmapItem[] = [ | |
| 30 | − | // --- Work --------------------------------------------------------------- | |
| 31 | − | { | |
| 32 | − | key: "board", | |
| 33 | − | title: "Board", | |
| 34 | − | section: "Workspace", | |
| 35 | − | summary: "Issues and pull requests as a board, a table or a roadmap, with fields of your own.", | |
| 36 | − | why: "One view of everything in flight, arranged the way your team thinks about it. Agents move the cards as they work, so the board is never out of date.", | |
| 37 | − | plans: [ | |
| 38 | − | "Board, table and roadmap views of the same items, saved and shared", | |
| 39 | − | "Custom fields: status, priority, size, iteration, dates, anything", | |
| 40 | − | "Group and filter by owner, outcome, label, agent or state", | |
| 41 | − | "Cards move by themselves as agents open, revise and land changes", | |
| 42 | − | "Across projects: one board can hold work from any of the workspace's projects", | |
| 43 | − | "Attach a board to a project, so it shows on that project too", | |
| 44 | − | ], | |
| 45 | − | }, | |
| 46 | − | { | |
| 47 | − | key: "roadmap", | |
| 48 | − | title: "Roadmap", | |
| 49 | − | section: "Workspace", | |
| 50 | − | summary: "Outcomes on a timeline, with how much of each has landed.", | |
| 51 | − | why: "An outcome is what should be true when the work is done. On a roadmap you see when each is expected and how much has landed.", | |
| 52 | − | plans: [ | |
| 53 | − | "Outcomes as bars on a timeline, from start to target", | |
| 54 | − | "Progress from merged work, not from guesses", | |
| 55 | − | "Slip warnings when the work left outgrows the time left", | |
| 56 | − | ], | |
| 57 | − | today: { label: "Outcomes", path: "plans" }, | |
| 58 | − | }, | |
| 30 | + | // --- Workspace ---------------------------------------------------------- | |
| 59 | 31 | { | |
| 60 | 32 | key: "insights", | |
| 61 | 33 | title: "Insights", | |
| 38 | 38 | assert.equal(workspaceRedirect("/acme/-/marketplace/extensions"), "/acme/-/customize/plugins"); | |
| 39 | 39 | assert.equal(workspaceRedirect("/acme/-/marketplace/extensions/mail/more"), null); | |
| 40 | 40 | assert.equal(workspaceRedirect("/acme/-/soon/insights", "?x=1"), "/acme/-/insights?x=1"); | |
| 41 | − | assert.equal(workspaceRedirect("/acme/-/soon/board"), null); | |
| 41 | + | assert.equal(workspaceRedirect("/acme/-/soon/uptime"), null); | |
| 42 | 42 | assert.equal(workspaceRedirect("/acme/-/people"), null); | |
| 43 | 43 | assert.equal(workspaceRedirect("/acme/web"), null); | |
| 44 | 44 | }); |
| 32 | 32 | return { item, inProjects }; | |
| 33 | 33 | } | |
| 34 | 34 | ||
| 35 | − | /** What a workspace will have across its projects: boards, the roadmap, packages, the fleet. */ | |
| 35 | + | /** What a workspace will have across its projects. */ | |
| 36 | 36 | export default function WorkspaceSoon({ loaderData, params }: Route.ComponentProps) { | |
| 37 | 37 | return ( | |
| 38 | 38 | <div className="mx-auto max-w-5xl px-4 py-10 sm:px-8"> |
| 168 | 168 | // g1t made it, for ada: the card says both. | |
| 169 | 169 | assert.equal(pull.kind === "pull" && pull.author, "g1t"); | |
| 170 | 170 | assert.equal(pull.kind === "pull" && pull.requestedBy, "ada"); | |
| 171 | − | const soon = await resolve("/acme/web/soon/board", s); | |
| 171 | + | const soon = await resolve("/acme/web/soon/environments", s); | |
| 172 | 172 | assert.equal(soon.kind, "soon"); | |
| 173 | − | assert.equal(soon.kind === "soon" && soon.title, "Board"); | |
| 173 | + | assert.equal(soon.kind === "soon" && soon.title, "Environments"); | |
| 174 | 174 | // One that does not exist falls back to the project. | |
| 175 | 175 | assert.equal((await resolve("/acme/web/issues/99", s)).kind, "project"); | |
| 176 | 176 | assert.equal((await resolve("/acme/web/soon/nothing", s)).kind, "project"); | |
| ⋯ | |||
| 179 | 179 | ||
| 180 | 180 | test("nothing private ever reaches a card", async () => { | |
| 181 | 181 | const { sources: s } = sources(); | |
| 182 | − | for (const path of ["/acme/vault", "/acme/vault/issues/7", "/acme/vault/pull/8", "/acme/vault/soon/board", "/acme/vault/settings"]) { | |
| 182 | + | for (const path of ["/acme/vault", "/acme/vault/issues/7", "/acme/vault/pull/8", "/acme/vault/soon/environments", "/acme/vault/settings"]) { | |
| 183 | 183 | assert.deepEqual(await resolve(path, s), { kind: "brand" }, path); | |
| 184 | 184 | } | |
| 185 | 185 | // A missing repository says nothing either. | |