Files
multica/packages/core/paths/reserved-slugs.ts
Bohan Jiang 451c46c43f refactor(usage): rename Dashboard → Usage + dynamic per-agent leaderboard (#2511)
The page added in #2462 lived at `/{slug}/dashboard` and was titled
"Dashboard", which collides with the conventional meaning ("personal
landing surface") and doesn't tell new users what the page is for. Its
actual contents — token spend, cost, run time, task counts — map cleanly
onto the OpenAI / Anthropic / Vercel "Usage" surface, so rename to that.

Renames (user-visible)
- Route: `/{slug}/dashboard` → `/{slug}/usage` (web App Router + desktop
  memory router)
- Sidebar entry: label "Dashboard" / "看板" → "Usage" / "用量", icon
  LayoutDashboard → BarChart3 (page header icon swapped in sync)
- Page title in en/zh-Hans
- Reserved-slugs: add `usage` to workspace route segments group;
  `dashboard` stays reserved in the marketing group (back-compat against
  workspace slug collisions + keeps the name free for a future Home page)
- i18n namespace `dashboard` → `usage` across resources-types.ts,
  locales/index.ts, and the moved JSON files
- WORKSPACE_ROUTE_SEGMENTS in editor link-handler
- paths.workspace(slug).dashboard() → .usage(), with matching test
  expectation updates

Per-agent leaderboard polish (`packages/views/dashboard/components/
dashboard-page.tsx`)
- Card title "Cost & run time by agent" → "Leaderboard" with a 4-way
  Segmented control: Tokens / Cost / Time / Tasks
- Active metric drives row order, progress-bar width, and the
  emphasised column header / cell — keeping ranking, visual quantity,
  and column emphasis in lockstep so users always see what's being
  measured
- Default sort = Tokens (most universally meaningful; Cost still one
  click away)
- Project filter dropdown:
  - Show ProjectIcon next to the selected project + each list item;
    FolderKanban as the "All projects" fallback (matches ProjectPicker
    language)
  - alignItemWithTrigger={false} so "All projects" doesn't get pushed
    above the trigger and clipped when the header sits at the top of
    the viewport (was the root cause of "can't re-select All projects"
    once a project was selected)
  - max-h-72 to cap the dropdown when workspaces accrue many projects;
    matches the runtime-detail Select precedent
- Folder name `packages/views/dashboard/*` and `DashboardPage`
  component name intentionally left in place — user-visible rename
  only, no broad code refactor.

Old `/dashboard` routes are not redirected because the page only landed
in #2462 (a few days ago); no real users, external links, or
desktop-tab persistence have settled on it yet.
2026-05-13 14:07:53 +08:00

152 lines
3.9 KiB
TypeScript

// AUTO-GENERATED by scripts/generate-reserved-slugs.mjs.
// Do not edit by hand — edit server/internal/handler/reserved_slugs.json
// and run `pnpm generate:reserved-slugs`.
/**
* Slugs reserved because they collide with frontend top-level routes,
* platform features, or web standards.
*
* Single source of truth: `server/internal/handler/reserved_slugs.json`.
* The Go backend embeds that JSON; this file is regenerated from it.
*
* Convention for new global routes (CLAUDE.md): use a single word
* (`/login`, `/inbox`) or `/{noun}/{verb}` (`/workspaces/new`). Hyphenated
* root-level word groups (`/new-workspace`, `/create-team`) collide with
* common user workspace names — see PR for full discussion.
*/
export const RESERVED_SLUGS: ReadonlySet<string> = new Set([
// Auth flow
// `onboarding` is historical, kept reserved post-removal of the route.
"login",
"logout",
"signin",
"signout",
"signup",
"auth",
"oauth",
"callback",
"invite",
"invitations",
"verify",
"reset",
"password",
"onboarding",
// Platform / marketing routes (current + likely-future)
// `multica` is reserved as the brand name to block impersonation workspaces.
// `www`, `new`, `home`, `homepage`, `dashboard` are confusables or
// likely-future global landing/entry routes; `homepage` matches the existing
// `/homepage` landing variant in apps/web.
"api",
"admin",
"multica",
"www",
"new",
"home",
"homepage",
"dashboard",
"help",
"about",
"pricing",
"changelog",
"docs",
"support",
"status",
"legal",
"privacy",
"terms",
"security",
"contact",
"blog",
"careers",
"press",
"download",
// Account / billing (likely-future global routes in the avatar menu)
"profile",
"account",
"billing",
"notifications",
"search",
"members",
// Workspace route segments
// Reserving each segment name prevents `/{slug}/{view}` from being visually
// ambiguous (e.g. a workspace named `issues` would make `/issues/abc` mean two
// things). `workspaces` covers the global `/workspaces/new` workspace-creation
// page; `teams` is reserved for future team management.
"issues",
"projects",
"autopilots",
"agents",
"inbox",
"my-issues",
"usage",
"runtimes",
"skills",
"settings",
"workspaces",
"teams",
// API / integration prefixes
// `api` above already covers `/api/*`; these guard against future top-level
// API alias routes (e.g. `/v1`, `/graphql`) and against accidental workspace
// slugs that read like API identifiers.
"v1",
"v2",
"graphql",
"webhooks",
"sdk",
"tokens",
"cli",
// Backend ops / observability
// `/health`, `/readyz`, `/healthz`, and `/ws` exist on the backend host;
// reserving them on the workspace slug space prevents naming confusion if/when
// these paths are ever proxied through the web origin.
"health",
"readyz",
"healthz",
"ws",
"metrics",
"ping",
// RFC 2142 — privileged email mailboxes
// Allowing user workspaces with these slugs would let attackers spoof system
// messaging.
"postmaster",
"abuse",
"noreply",
"webmaster",
"hostmaster",
// Hostname / subdomain confusables
// Even on path-based routing these names attract phishing and
// subdomain-takeover attempts.
"mail",
"ftp",
"static",
"cdn",
"assets",
"public",
"files",
"uploads",
// Next.js / web standards
// These entries contain characters (dots, underscores) that today's slug regex
// `^[a-z0-9]+(?:-[a-z0-9]+)*$` already rejects at the format-validation step —
// so `isReservedSlug` never actually matches them. They are kept as
// defense-in-depth so that if the slug regex is ever relaxed (e.g. to support
// dotted corporate slugs like `acme.io`), these system paths stay protected.
"_next",
"favicon.ico",
"robots.txt",
"sitemap.xml",
"manifest.json",
".well-known",
]);
export function isReservedSlug(slug: string): boolean {
return RESERVED_SLUGS.has(slug);
}