Problem / Goal
A committed ingredient shows its source as a bare cat-scrap-<uuid> monospace string (client/src/pages/CatalogIngredient.jsx:1438), and there is no way to ask "what else came out of this piece?". The catalog already has two grouping seams — refs (universe / series / work) and sources (scrap) — so the fix is to promote the scrap that already groups these rows, not to invent a third parallel taxonomy (an autobiography record type is explicitly the wrong answer: a third taxonomy is what makes the catalog feel arbitrary).
Approach
GET /api/catalog/ingredients/:id/details (server/routes/catalog.js:239-264) returns source rows as { scrapId, span, extractedAt } with no title. Join catalog_scraps.title onto them, and add the sibling extractions for each source scrap via listSourcesForScrap (server/services/catalogDB/refs.js:38) so the detail page needs no second round-trip.
client/src/pages/CatalogIngredient.jsx:1438 renders the scrap title as a link, plus a "From the same source" list of sibling ingredients.
- Add
scrapId to catalogIngredientQuerySchema (server/lib/catalogValidation.js), filter on it through catalog_ingredient_sources in listIngredients (server/services/catalogDB/ingredients.js), and wire ?scrap=<id> into client/src/pages/Catalog.jsx — selection lives in the URL per client/src/AGENTS.md, so "everything extracted from this piece" is one click and one shareable link.
Acceptance criteria
Out of scope
- Per-chunk source provenance:
commitScrap links to the parent scrap only and the extractor never emits span. Pre-existing, unchanged here.
Problem / Goal
A committed ingredient shows its source as a bare
cat-scrap-<uuid>monospace string (client/src/pages/CatalogIngredient.jsx:1438), and there is no way to ask "what else came out of this piece?". The catalog already has two grouping seams — refs (universe / series / work) and sources (scrap) — so the fix is to promote the scrap that already groups these rows, not to invent a third parallel taxonomy (anautobiographyrecord type is explicitly the wrong answer: a third taxonomy is what makes the catalog feel arbitrary).Approach
GET /api/catalog/ingredients/:id/details(server/routes/catalog.js:239-264) returns source rows as{ scrapId, span, extractedAt }with no title. Joincatalog_scraps.titleonto them, and add the sibling extractions for each source scrap vialistSourcesForScrap(server/services/catalogDB/refs.js:38) so the detail page needs no second round-trip.client/src/pages/CatalogIngredient.jsx:1438renders the scrap title as a link, plus a "From the same source" list of sibling ingredients.scrapIdtocatalogIngredientQuerySchema(server/lib/catalogValidation.js), filter on it throughcatalog_ingredient_sourcesinlistIngredients(server/services/catalogDB/ingredients.js), and wire?scrap=<id>intoclient/src/pages/Catalog.jsx— selection lives in the URL perclient/src/AGENTS.md, so "everything extracted from this piece" is one click and one shareable link.Acceptance criteria
/detailsresponse./catalog?scrap=<id>filters the catalog to that scrap's extractions and survives a reload / share.scrapIdfilter and the joined title; the client test covers the link and the sibling list.Out of scope
commitScraplinks to the parent scrap only and the extractor never emitsspan. Pre-existing, unchanged here.