mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-04 17:18:35 +02:00
Squashed history of PR #4892 (pr-4784-fix). Makes spaces the primary navigation and working surface, on the "associations bind at creation time only" model. Model - Issue <-> space is the only enforced ownership (per-space numbering). Parent/child and project<->space associations only seed defaults at creation; cross-space/child and project-association validations are removed. - Moving an issue renumbers it and records the old identifier in issue_identifier_alias; API/CLI lookups and GitHub branch/PR auto-linking fall back to the alias, so old identifiers resolve forever. - Membership drives only the sidebar and personal defaults — never access. Anyone can configure any space's member set wholesale (PUT /api/spaces/{id}/members); saving an empty set archives the space behind a confirm. - Per-user space order (workspace_space_member.sort_order, fractional): drag-sorted sidebar, "my first space" is the personal issue-creation default; the workspace default space backs headless creation (agents/CLI/Slack) and system placement. Surfaces - Sidebar: joined-spaces section (drag reorder, row -> space page, per-group persisted collapse), Workspace group with a More menu, Settings demoted to a footer icon. - /space/:key/{issues,projects,autopilots,settings} — space surfaces reuse shared page components; a routed /space/new create page (replacing the earlier create-space modal), reserved key "NEW" so it can never collide with a real space's /space/:key detail page. - Issue detail moves to /issue/:id (identifier-first, Linear-style; old /issues/:id redirects); create dialogs lead with a required space pill. - Agent runtime brief now carries Space context (id/key/name) through the daemon claim -> TaskContextForEnv -> prompt pipeline, with a "## Space Context" section and --space on issue create/update in both brief renderers, matching Project's existing treatment. - zh-Hans: Space translated to 空间 across locales and conventions.zh.mdx. Fixes along the way - Cache membership judgment gains the space dimension + space_changed WS flag. - Silent skip on default-space lookup during invite acceptance is now a hard failure (no space-less members). - Backfilled space_id into ~28 raw-SQL Go test fixtures across internal/handler and cmd/server that predated migration 132's NOT NULL cutover. - Fixed a resolve-loop bug in the IssueDetail identifier wrapper (mount/ unmount cycle on resolution failure) and gave it its own loading skeleton instead of a blank screen while resolving. - reserved-slugs generator's stale hardcoded doc-comment example synced back to /create-space. Verification - go build ./..., go vet ./..., go test ./... all clean. - pnpm typecheck (core/views/web/desktop) clean. - packages/views: 162 files / 1656 tests passing. - pnpm generate:reserved-slugs produces no diff. Follow-ups tracked in docs/follow-ups/space-rollout.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
151 lines
4.7 KiB
JSON
151 lines
4.7 KiB
JSON
{
|
|
"$comment": "Source of truth for reserved workspace slugs. Edit this file only. The Go side embeds this JSON directly; the TS side (packages/core/paths/reserved-slugs.ts) is regenerated from this file by `pnpm generate:reserved-slugs`. CI re-runs the generator and fails on any diff, so the two sides cannot drift. Convention for new global routes: single word (`/login`, `/inbox`) or `/{noun}/{verb}` (`/workspaces/new`). Never add hyphenated root-level word groups (`/new-workspace`, `/create-space`) — they collide with common user workspace names.",
|
|
"groups": [
|
|
{
|
|
"label": "Auth flow",
|
|
"description": "`onboarding` is historical, kept reserved post-removal of the route.",
|
|
"slugs": [
|
|
"login",
|
|
"logout",
|
|
"signin",
|
|
"signout",
|
|
"signup",
|
|
"auth",
|
|
"oauth",
|
|
"callback",
|
|
"invite",
|
|
"invitations",
|
|
"verify",
|
|
"reset",
|
|
"password",
|
|
"onboarding"
|
|
]
|
|
},
|
|
{
|
|
"label": "Platform / marketing routes (current + likely-future)",
|
|
"description": "`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.",
|
|
"slugs": [
|
|
"api",
|
|
"admin",
|
|
"multica",
|
|
"www",
|
|
"new",
|
|
"home",
|
|
"homepage",
|
|
"dashboard",
|
|
"help",
|
|
"about",
|
|
"pricing",
|
|
"changelog",
|
|
"docs",
|
|
"support",
|
|
"status",
|
|
"legal",
|
|
"privacy",
|
|
"terms",
|
|
"security",
|
|
"contact",
|
|
"contact-sales",
|
|
"blog",
|
|
"careers",
|
|
"press",
|
|
"download",
|
|
"usecases"
|
|
]
|
|
},
|
|
{
|
|
"label": "Account / billing (likely-future global routes in the avatar menu)",
|
|
"slugs": [
|
|
"profile",
|
|
"account",
|
|
"billing",
|
|
"notifications",
|
|
"search",
|
|
"members"
|
|
]
|
|
},
|
|
{
|
|
"label": "Workspace route segments",
|
|
"description": "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; `spaces` is reserved for future space management.",
|
|
"slugs": [
|
|
"issues",
|
|
"projects",
|
|
"autopilots",
|
|
"agents",
|
|
"squads",
|
|
"inbox",
|
|
"my-issues",
|
|
"usage",
|
|
"runtimes",
|
|
"skills",
|
|
"settings",
|
|
"workspaces",
|
|
"spaces",
|
|
"space"
|
|
]
|
|
},
|
|
{
|
|
"label": "API / integration prefixes",
|
|
"description": "`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.",
|
|
"slugs": [
|
|
"v1",
|
|
"v2",
|
|
"graphql",
|
|
"webhooks",
|
|
"sdk",
|
|
"tokens",
|
|
"cli"
|
|
]
|
|
},
|
|
{
|
|
"label": "Backend ops / observability",
|
|
"description": "`/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.",
|
|
"slugs": [
|
|
"health",
|
|
"readyz",
|
|
"healthz",
|
|
"ws",
|
|
"metrics",
|
|
"ping"
|
|
]
|
|
},
|
|
{
|
|
"label": "RFC 2142 — privileged email mailboxes",
|
|
"description": "Allowing user workspaces with these slugs would let attackers spoof system messaging.",
|
|
"slugs": [
|
|
"postmaster",
|
|
"abuse",
|
|
"noreply",
|
|
"webmaster",
|
|
"hostmaster"
|
|
]
|
|
},
|
|
{
|
|
"label": "Hostname / subdomain confusables",
|
|
"description": "Even on path-based routing these names attract phishing and subdomain-takeover attempts.",
|
|
"slugs": [
|
|
"mail",
|
|
"ftp",
|
|
"static",
|
|
"cdn",
|
|
"assets",
|
|
"public",
|
|
"files",
|
|
"uploads"
|
|
]
|
|
},
|
|
{
|
|
"label": "Next.js / web standards",
|
|
"description": "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.",
|
|
"slugs": [
|
|
"_next",
|
|
"favicon.ico",
|
|
"robots.txt",
|
|
"sitemap.xml",
|
|
"manifest.json",
|
|
".well-known"
|
|
]
|
|
}
|
|
]
|
|
}
|