§22 S2: Catalog + propose scoped to the active collection
Catalog reads the /c/:collectionId/ segment via useCollectionId and fetches the collection-scoped catalog, building entry/proposal links with the active collection; the propose modal threads the active collection so a propose from a named collection targets it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -66,6 +66,10 @@ export default function App() {
|
||||
// right project. Falls back to the deployment default off a project route.
|
||||
const _projMatch = location.pathname.match(/^\/p\/([^/]+)/)
|
||||
const currentProjectId = (_projMatch && _projMatch[1]) || deployment.defaultProjectId
|
||||
// §22 S2 — the collection the viewer is currently in (from the /c/<cid>/ URL
|
||||
// segment), so a propose targets that collection. Falls back to the default.
|
||||
const _colMatch = location.pathname.match(/^\/p\/[^/]+\/c\/([^/]+)/)
|
||||
const currentCollectionId = (_colMatch && _colMatch[1]) || DEFAULT_COLLECTION
|
||||
// #28 Parts 2–3: the LinkedText create/contribute affordances route via
|
||||
// query params so they need no prop-threading from deep in a comment
|
||||
// list. `?propose=<term>` opens the propose modal pre-filled;
|
||||
@@ -389,12 +393,13 @@ export default function App() {
|
||||
viewer={viewer}
|
||||
initialTitle={proposeParam || ''}
|
||||
projectId={currentProjectId}
|
||||
collectionId={currentCollectionId}
|
||||
onClose={() => { setProposeOpen(false); clearParams('propose') }}
|
||||
onSubmitted={({ pr_number }) => {
|
||||
setProposeOpen(false)
|
||||
clearParams('propose')
|
||||
setCatalogVersion(v => v + 1)
|
||||
navigate(proposalPath(currentProjectId, pr_number))
|
||||
navigate(proposalPath(currentProjectId, pr_number, currentCollectionId))
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useParams, Link } from 'react-router-dom'
|
||||
import { listRFCs, listProposals } from '../api'
|
||||
import { entryPath, proposalPath, useProjectId } from '../lib/entryPaths'
|
||||
import { entryPath, proposalPath, useProjectId, useCollectionId } from '../lib/entryPaths'
|
||||
|
||||
const STATE_CHIPS = [
|
||||
{ id: 'super-draft', label: 'Super-draft' },
|
||||
@@ -32,11 +32,14 @@ export default function Catalog({ viewer, onProposeRFC, version }) {
|
||||
const [pendingOpen, setPendingOpen] = useState(true)
|
||||
const { slug, prNumber } = useParams()
|
||||
const pid = useProjectId()
|
||||
// §22 S2: the catalog is scoped to the active collection (the `/c/:cid/`
|
||||
// route segment, else the project's default collection).
|
||||
const cid = useCollectionId()
|
||||
|
||||
useEffect(() => {
|
||||
listRFCs(pid).then(d => setRfcs(d.items)).catch(() => setRfcs([]))
|
||||
listRFCs(pid, cid).then(d => setRfcs(d.items)).catch(() => setRfcs([]))
|
||||
listProposals(pid).then(d => setProposals(d.items)).catch(() => setProposals([]))
|
||||
}, [version, pid])
|
||||
}, [version, pid, cid])
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const needle = search.trim().toLowerCase()
|
||||
@@ -105,7 +108,7 @@ export default function Catalog({ viewer, onProposeRFC, version }) {
|
||||
return (
|
||||
<Link
|
||||
key={r.slug}
|
||||
to={entryPath(pid, r.slug)}
|
||||
to={entryPath(pid, r.slug, cid)}
|
||||
className={`catalog-row ${isActive ? 'active' : ''} ${isSuper ? 'is-super' : ''}`}
|
||||
>
|
||||
<div className="row-top">
|
||||
@@ -133,7 +136,7 @@ export default function Catalog({ viewer, onProposeRFC, version }) {
|
||||
{proposals.map(p => (
|
||||
<Link
|
||||
key={p.pr_number}
|
||||
to={proposalPath(pid, p.pr_number)}
|
||||
to={proposalPath(pid, p.pr_number, cid)}
|
||||
className={`pending-row ${String(prNumber) === String(p.pr_number) ? 'active' : ''}`}
|
||||
>
|
||||
<div>{p.title.replace(/^Propose:\s*/, '')}</div>
|
||||
|
||||
@@ -28,7 +28,7 @@ function slugify(title) {
|
||||
.replace(/^-+|-+$/g, '')
|
||||
}
|
||||
|
||||
export default function ProposeModal({ viewer, onClose, onSubmitted, initialTitle = '', projectId }) {
|
||||
export default function ProposeModal({ viewer, onClose, onSubmitted, initialTitle = '', projectId, collectionId }) {
|
||||
// #28 Part 2: a "create RFC for '<term>'" affordance pre-fills the title
|
||||
// (App passes the `?propose=<term>` value here); the slug derives from it
|
||||
// via the same effect that drives manual typing.
|
||||
@@ -98,6 +98,7 @@ export default function ProposeModal({ viewer, onClose, onSubmitted, initialTitl
|
||||
pitch: pitch.trim(),
|
||||
tags,
|
||||
proposedUseCase: useCase.trim() || null,
|
||||
collectionId,
|
||||
})
|
||||
// v0.15.0 — analytics: fire on the §9.1 propose-RFC submit.
|
||||
// Slug is a stable, low-cardinality identifier (kebab-case
|
||||
|
||||
Reference in New Issue
Block a user