§22 S1: frontend /c/<collection>/ route layer + C3.7 + legacy-URL redirects

- entryPaths builders carry the /c/<collection>/ segment (default collection in S1)
- /p/:projectId/* gains c/:collectionId/ corpus routes; serving stays project-scoped
- DefaultCollectionRedirect (C3.7: project landing -> default collection)
- LegacyCorpusRedirect (v0.35.0 /p/<p>/e/<slug> bookmarks -> /c/default/, query preserved)
- entryPaths unit test; build + vitest green (18 tests)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-05 08:30:18 -07:00
parent 9ca07a3f81
commit 4f72aa31e0
3 changed files with 94 additions and 18 deletions
+19 -12
View File
@@ -1,22 +1,29 @@
// §22.10 — project-scoped path builders. After M3 every entry/proposal link
// lives under `/p/<project>/…`. Until Plan B serves multiple corpora, that
// project id is the deployment's corpus-served default for chrome surfaces, or
// the contextual project when a component renders inside a project subtree
// (ProjectContext). Components build links via these helpers so the later
// per-project-serving slice flips them in one place.
// §22 three-tier — project + collection-scoped path builders. The canonical
// entry route now carries the collection segment: `/p/<project>/c/<collection>/…`.
// In S1 each project has a single (default) collection and serving stays
// project-scoped, so the builders emit the default collection segment; the
// collection-aware link layer (named collections) lands in S2. Components build
// links via these helpers so that flip happens in one place.
import { useProject } from '../components/ProjectLayout.jsx'
import { useDeployment } from '../context/DeploymentProvider'
export function entryPath(pid, slug) {
return `/p/${pid}/e/${slug}`
// The default collection id (migration 029 seeds one per project at this id).
export const DEFAULT_COLLECTION = 'default'
export function entryPath(pid, slug, cid = DEFAULT_COLLECTION) {
return `/p/${pid}/c/${cid}/e/${slug}`
}
export function entryPrPath(pid, slug, prNumber) {
return `/p/${pid}/e/${slug}/pr/${prNumber}`
export function entryPrPath(pid, slug, prNumber, cid = DEFAULT_COLLECTION) {
return `/p/${pid}/c/${cid}/e/${slug}/pr/${prNumber}`
}
export function proposalPath(pid, prNumber) {
return `/p/${pid}/proposals/${prNumber}`
export function proposalPath(pid, prNumber, cid = DEFAULT_COLLECTION) {
return `/p/${pid}/c/${cid}/proposals/${prNumber}`
}
export function collectionHome(pid, cid = DEFAULT_COLLECTION) {
return `/p/${pid}/c/${cid}/`
}
export function projectHome(pid) {