bl-317 — Reference-item read/browse/detail UI: scope + recommendation
bl-317 — Reference-item read/browse/detail UI: scope + recommendation
Section titled “bl-317 — Reference-item read/browse/detail UI: scope + recommendation”Agent 5, S355. READ-ONLY research. Branch canonical-pipeline-setup.
TL;DR / Recommendation
Section titled “TL;DR / Recommendation”- bl-317 is real and unblocked-by-design. ID-110 (closed) deliberately landed manual-URL
ingests as
reference_itemswith no read surface — the success card shows only a copyablereferenceId. ID-110’s own spec (PRODUCT OQ-N, TECH “Follow-ups”) files the missing reference-detail view as a follow-on — that follow-on is bl-317. - The data-access substrate already exists. ID-75 shipped two SECURITY DEFINER read RPCs —
reference_search(ranked list/preview = browse + search) andreference_get_verbatim(single-row detail). Both areGRANT authenticated, service_role. No TypeScript/web code calls either one today — they were built for the AI/MCP consumer (ID-71). bl-317 is the human web UI over that substrate. - The intelligence-workspace specs do NOT define a reference-item UI and do NOT supersede a
like-for-like treatment. They are a schema-migration spec (Shape B: promote 3 JSONB keys —
company_profile_id,guide_id,relevance_threshold— to typed columns on theintelligence_workspacessatellite table). Zero browse/list/detail/filter UI for reference items is specified there. So “like-for-like vs intelligence-workspace direction” is a false dichotomy — there is no competing UI design to build toward. - The genuine open product question (the user’s “may NOT be like-for-like” instinct) is real
but for a different reason: reference items are deliberately global + workspace-less, with
a reduced shape (no
content_type, noplatform, no layer-suggestion, no QA, no thumbnails, no governance/review lifecycle, no entity-mentions consumer wired yet). A full clone of thecontent_itemsbrowse stack would over-build. ID-75 framed references as “AI-consumer-first” with reads “available to all authenticated.” - RECOMMENDED: Build a purpose-fit reference surface over the existing RPCs, NOT a clone of
the content_items browse machine, and NOT folded into the intelligence workspace.
- Phase A (small, unblocks bl-119 round-trip + OQ-N): a reference-detail page
/reference/[id]overreference_get_verbatim, wire the success card’s “view reference” link. ~0.5–1 day. - Phase B (medium): a reference browse/list + search page
/referenceoverreference_search, with lightweight filtering (domain/subtopic/ingestion_source/date) done client-side or via a small RPC extension. ~2–3 days. - Reusing the content_items browse stack wholesale (Option “like-for-like”) is possible but larger and a poorer fit (~4–6 days + ongoing coupling tax) — see §(d).
- Phase A (small, unblocks bl-119 round-trip + OQ-N): a reference-detail page
(a) Content-item UI inventory (the reference baseline to compare against)
Section titled “(a) Content-item UI inventory (the reference baseline to compare against)”Routes (app/)
Section titled “Routes (app/)”| Route | File | Role |
|---|---|---|
| Browse list/grid | app/browse/page.tsx (Suspense shell) → app/browse/browse-content.tsx (client) | The browse/list/filter view. |
| Item detail | app/item/[id]/page.tsx (server) → app/item/[id]/item-detail-client.tsx | Detail view. Server-fetches the row + related items RPC + feed-article provenance. |
| Library (Q&A subset) | app/library/page.tsx → app/library/library-content.tsx | Parallel browse scoped to Q&A pairs. |
| Create | app/item/new/* (new-item-tabs.tsx, write/url/upload/batch) | Ingest entry incl. the URL tab that now lands references. |
/browse, /item, /library, /intelligence are authenticated (NOT in proxy.ts
publicRoutes). A reference surface would be authenticated too — the RPCs already gate to
authenticated.
Data layer
Section titled “Data layer”- Types:
types/content.tsContentListItem(lines 6–73) — display subset ofcontent_itemsRow (~40 fields: title, summary, primary_domain/subtopic, content_type, platform, author_name, source_domain, thumbnail_url, captured_date, ai_keywords, classification_confidence, priority, freshness, user_tags, governance_review_status, layer, starred, publication_status, quality_score, …).BrowseFilters(lines 158–190) — URL-driven filter state. ~25 filter dimensions: domain[], subtopic, content_type[], platform[], author[], date_from/to, keywords[], starred, priority[], workspace, user_tags[], freshness[], layer, entity, entity_type, quality_issues, include_drafts, include_qa, owner, review_status, source, sort, order.CONTENT_LIST_COLUMNS/CONTENT_DETAIL_COLUMNS(lines 193–216) — the.select()projections.
- Fetch hook:
hooks/browse/use-browse-data.ts— the engine.buildBrowseQuery()(lines 268–456): builds asupabase.from('content_items').select(...)query, applies everyBrowseFiltersdimension as.in()/.eq()/.gte()/.or()clauses + cursor /offset pagination + 6 sort modes.useInfiniteQuery(cursor/offset paginated, PAGE_SIZE 48) for browse mode;useQuery→POST /api/search(semantic) for search mode; plus quality-flag + freshness-count side queries. Pre-filter resolvers hitcontent_item_workspaces,entity_mentions,filter_by_keywordsRPC,get_items_with_quality_flagsRPC.- Filters come from
hooks/browse/use-browse-filters.ts(URL ↔ state).
- Query keys:
lib/query/query-keys.ts—queryKeys.contentItems.{all,browse(filters),search(q)}(lines ~19+). (There is anintelligence.articleskey family at 203–207 — that’s feed_articles, not references.) - Detail fetch:
app/item/[id]/page.tsxdoes a direct server-sidefrom('content_items').select(CONTENT_DETAIL_COLUMNS).eq('id',id).single()(with PGRST116 read-after-write retry) +find_related_itemsRPC + afeed_articles → feed_sourcesprovenance join. NOT via a TanStack hook.
Components (the surface area a clone would touch)
Section titled “Components (the surface area a clone would touch)”- Browse client:
app/browse/browse-content.tsx(673 lines) — orchestrates grid/list, search bar, filter panel, preset bar, filter badges, bulk actions, infinite scroll, keyboard nav, quick-assign, read-marks. Card click →router.push('/item/${id}'). - Cards/lists:
components/content/content-grid.tsx,content-list.tsx,content-card.tsx. - Filtering:
components/browse/—filter-panel.tsxcomposesDomainFilter,SubtopicFilter,ContentTypeFilter,PlatformFilter,AuthorFilter, freshness, layer (viauseLayerVocabulary), +filter-bar.tsx,filter-badges.tsx,preset-bar.tsx,search-bar.tsx,bulk-actions.tsx,manage-presets-dialog.tsx,save-preset-dialog.tsx(~20 files). - Detail:
components/item-detail/—content-tabs.tsx,reader-view.tsx,content-body.tsx,content-renderer.tsx,metadata-sidebar.tsx,entity-badges.tsx,related-content-section.tsx,verification-history.tsx, editor components, etc. (~35 files; many are content_items-specific — editing, governance, QA provenance, transcripts).
Net: the content_items browse/detail stack is a large, mature, deeply content_items-coupled machine (workspaces, entity_mentions, governance lifecycle, QA, presets, read-marks, bulk review).
(b) Reference-item storage delta vs content items
Section titled “(b) Reference-item storage delta vs content items”reference_items table (created supabase/migrations/20260606121451_id75_reference_items_layer.sql)
Section titled “reference_items table (created supabase/migrations/20260606121451_id75_reference_items_layer.sql)”id uuid PK -- PIPELINE-MINTED uuid5('ri:'+normalised URL); NO DEFAULTtitle text NOT NULLbody text NOT NULL -- PullMD/Docling markdown = canonical body of recordsummary text NULLsource_url text NOT NULL -- canonical normalised URL; UNIQUE (one reference per URL)published_at timestamptz NULL -- original pub time, never ingest timeprimary_domain text NULLprimary_subtopic text NULLlayer text NULL -- v1 constant 'research'; validate_layer_key() triggerembedding vector(1024) NULL-- whole-record embedding (no chunk table)source_document_id uuid NOT NULL -- FK source_documents ON DELETE RESTRICT (provenance)ingestion_source text NOT NULL -- CHECK IN ('rss_feed','url_import')op_id uuid NULLcreated_at/updated_at timestamptz- RLS:
reference_items_selectpolicyFOR SELECT TO authenticated USING (true)— corpus-level read for all authenticated; no app-side INSERT/UPDATE/DELETE policies (writes are pipeline-only via the asyncpg owner connection, OR via thereference_ingestSECURITY DEFINER RPC for manual URLs). reference_ingestRPC (20260614010200_id110_reference_ingest_rpc.sql, ID-110): owner-gated, atomically lands thesource_documents+reference_itemsevidence pair, server-side uuid5 PKs, ON CONFLICT idempotency. This is the WRITE path for manual URLs.reference_search+reference_get_verbatimRPCs (20260606130224_id75_reference_search_rpcs.sql, ID-75) — the READ substrate. See §(d). GRANT authenticated, service_role; REVOKE anon+PUBLIC.
Deltas vs content_items (why a clone over-builds)
Section titled “Deltas vs content_items (why a clone over-builds)”| Dimension | content_items | reference_items |
|---|---|---|
| Workspace scoping | content_item_workspaces junction (ID-69) | NONE — global, workspace-less, RATIFIED-DO-NOT-BUILD a workspace FK/junction (ID-75 BI-7) |
| Classification | content_type, platform, author, source_domain, thumbnails, ai_keywords, freshness, priority | only primary_domain, primary_subtopic, layer(=‘research’), published_at — no content_type/platform/author/freshness/thumbnail |
| Body / chunks | content + content_chunks + transcripts/QA | single body markdown, no chunk table, whole-record embedding |
| Lifecycle | governance_review_status, publication_status, verified_at, review cadence, starred, user_tags | none of these |
| Entity mentions | entity_mentions.content_item_id consumer wired | entity-mentions re-point named as ID-71 follow-on (NOT wired) |
| Identity | various | uuid5(‘ri:‘+URL), source_url UNIQUE (idempotent re-ingest UPSERTs in place) |
| Write path | app routes + pipeline | pipeline (asyncpg) OR reference_ingest RPC only |
How RSS now lands vs old content_items path (bl-316 context)
Section titled “How RSS now lands vs old content_items path (bl-316 context)”scripts/cocoindex_pipeline/flow.pystep 6 (≈line 2732) UPSERTs thereference_itemsrow (ingestion_source carried from the item;'rss_feed'for feeds,'url_import'for manual URLs). Step 7 (_backlink_feed_articles, ≈2521) setsfeed_articles.reference_item_id.feed_articlesstill also carrycontent_item_idand the intelligence workspace articles list (/api/intelligence/workspaces/[id]/articles→from('feed_articles')) reads feed_articles, not references. So the RSS triage/relevance surface (intelligence workspace) is unchanged; what moved toreference_itemsis the canonical external body of record, and that is what has no read UI.- bl-316 (
summary_datadisposition at cutover) is parked KEEP-DEFER — orthogonal to bl-317.
Confirmation that NO reference read surface exists
Section titled “Confirmation that NO reference read surface exists”find app -path '*reference*'→ nothing (onlynotifications/preferencesmatches as substring).- Grep across
app/ lib/ components/ hooks/forreference_search/reference_get_verbatim→ 0 callers (only a code comment inapp/api/ingest/url/route.ts:142). - Grep for
reference_itemsin TS → only the WRITE path (app/api/ingest/url/route.ts,lib/ai/classify.ts,lib/intelligence/pipeline.ts) + the success card. No read/browse/detail.
(c) What the intelligence-workspace specs actually say (decisive for the recommendation)
Section titled “(c) What the intelligence-workspace specs actually say (decisive for the recommendation)”Files: ${KH_PRIVATE_DOCS_DIR}/src/content/docs/specs/intelligence-workspaces/{PRODUCT.md, TECH.md, p0-web-si-web-feed-handler-spec.md}.
They are NOT a reference-item UI spec. They are a Shape-B schema-migration spec for the
intelligence_workspaces SATELLITE TABLE. Verbatim:
- PRODUCT.md §“How to use this doc”: “This file holds user-perspective invariants for the
intelligence_workspacessatellite table … with three typed columns (company_profile_id,guide_id,relevance_threshold) promoted from JSONB per the Shape B pattern.” - PRODUCT.md S-1: the three typed columns on the satellite (FK to company_profiles, FK to guides, CHECK-constrained relevance_threshold). S-2/S-3: backfill 4 prod workspaces from JSONB then strip the JSONB keys. S-4/S-5: RLS + grants. S-6: don’t break the existing intelligence surface across the migration. S-7: FK target validity. S-8: future columns via ALTER.
- PRODUCT.md “Out of scope (v1)”: “Net-new intelligence features. Anything beyond promoting the three existing JSONB-buried fields to typed columns.”
- TECH.md is entirely DDL/migration mechanics (ALTER TABLE, backfill INSERT…SELECT with a 4/3/2/0 assertion, JSONB strip UPDATE, helper-first hybrid read-path sweep, type-interface drift fix). The word “reference_items” does not appear; there is no browse/list/detail/filter design.
Therefore: the intelligence-workspace specs neither define a reference-item UI nor supersede a
like-for-like treatment. There is no approved competing UI design to build toward. The
“intelligence workspace” is an existing, separate feature (app/intelligence/[workspaceId]/*:
overview, sources, articles, filter-rules, metrics, settings) over feed_articles/workspaces/RSS —
it is the RSS relevance-triage surface, not a reference corpus reader.
Where the intended reference UI direction IS actually written — ID-110 + ID-75 specs:
- ID-110 PRODUCT.md OQ-N (lines 245–252): “what is the canonical ‘view this reference’ destination …
— a reference-detail view, the reference-search surface pre-filtered to it, or (interim) a copyable
reference id with no dedicated page? ID-75 shipped
reference_search/reference_get_verbatimRPCs but the RESEARCH does not cite a user-facing reference-detail page. Recommended default: if no reference-detail page exists yet, the success card surfaces the landed title/summary + a copyable id and omits a dead ‘view item’ link … promote a proper destination as a follow-on.” - ID-110 TECH.md “Follow-ups” (lines 538–540): “Reference-detail destination (OQ-N). Build a
reference-detail view (page or modal over
reference_get_verbatim) so manual references are navigable; then wire the success card’s ‘view reference’ link. File as backlog.” → this is bl-317. - ID-110 TECH.md OQ-N resolution (line 223): “No reference-detail page exists; the success card surfaces the landed title + summary + a copyable reference id and OMITS the ‘view item’ link.”
- ID-75 PRODUCT.md BI-16 (lines 425–440): two retrieval surfaces; reference reads are “available to all authenticated” (human OR MCP-driven AI), “AI-consumer-first payload discipline.”
- ID-75 PRODUCT.md line 144: “ID-71 owns the tool surface; this spec fixes the data contract only.” Line 337: re-pointing reference UI/MCP consumers is a “named, ID-71-inherited follow-on.”
So: the data contract + read RPCs are done; the AI/MCP tool surface is ID-71’s; the human web read UI is unbuilt and is bl-317. The shape the specs gesture at is a reference-detail view + (eventually) the reference-search surface, explicitly built over the two existing RPCs — i.e. a purpose-fit reference reader, not a content_items clone and not an intelligence-workspace tab.
(d) Scoped requirements breakdown for bl-317 + recommended approach
Section titled “(d) Scoped requirements breakdown for bl-317 + recommended approach”Read substrate already in place (no schema/RPC work needed for the core)
Section titled “Read substrate already in place (no schema/RPC work needed for the core)”reference_search(p_query text, p_query_embedding vector(1024), p_limit int)→ ranked preview list: reference_id, title, summary_preview, body_preview, embedding_score, fulltext_score, source_url, published_at, primary_domain, primary_subtopic, layer, ingestion_source, source_document_id. = browse/list + semantic search in one. (Note: needs an embedding for the query, like/api/search; for a plain “list all” browse you’d want a non-embedding list path — see gap below.)reference_get_verbatim(p_reference_id uuid)→ full row minus embedding. = detail view.- Both
GRANT authenticated. Noproxy.tschange needed (authenticated surface).
Gaps to close, by deliverable
Section titled “Gaps to close, by deliverable”(i) Reference detail view — /reference/[id]
- Route:
app/reference/[id]/page.tsx(server) — callreference_get_verbatimviatryQuery,notFound()on miss. Renderbodymarkdown (reusecomponents/item-detail/content-renderer.tsxor the markdown renderer), title/summary, domain/subtopic/layer badges, source_url link, published_at, a “provenance” line to source_documents. NO editor/governance/QA chrome. - Type: add
ReferenceDetail/ReferenceListItemtotypes/content.ts(or a newtypes/reference.ts) — small, ~13 fields each, mirroring the RPC return shapes. - Wire the success card’s “view reference” link (
components/create-content/ingestion-success-card.tsxReferenceSuccessCard variant) from copyable-id-only →/reference/[id]. Resolves OQ-N; unblocks the bl-119 create→read round-trip E2E (e2e/tests/content-ingestion-url.spec.ts, currentlydescribe.skippending this surface). - Size: ~0.5–1 day. Lowest-risk, highest-leverage (unblocks bl-119 + closes the ID-110 follow-on).
(ii) Reference browse/list — /reference
- Route:
app/reference/page.tsx+ a clientreference-content.tsx. - Query: TanStack hook (
hooks/reference/use-reference-data.ts) +queryKeys.references.*inlib/query/query-keys.ts. Card click →/reference/[id]. - Gap — list vs search:
reference_searchrequires a query embedding (it filtersWHERE embedding IS NOT NULLand orders by blend). For an unfiltered “browse all references” default (no search term) you need either:- (a) a small new RPC
reference_list(p_limit, p_offset, filters…)ordered bypublished_at DESC(mirror the existingidx_reference_items_published_at), or - (b) a direct
from('reference_items').select(<list cols>)query (RLS already allows authenticated SELECT) — simplest; no new migration. Recommend (b) for v1; promote (a) only if pagination/filter pushdown is needed at scale.
- (a) a small new RPC
- Reuse: a lightweight card (clone
content-card.tsxdown to the reference shape, OR a newcomponents/reference/reference-card.tsx), grid/list toggle optional. - Size: ~1.5–2.5 days (most of it is the list path + card + empty/loading states).
(iii) Filtering
- Reference items support far fewer dimensions than
BrowseFilters. Realistic set: primary_domain, primary_subtopic, ingestion_source (rss_feed|url_import), date (published_at),- free-text/semantic search via
reference_search.
- free-text/semantic search via
- Implementation: client-side post-filter on the list result (small corpus v1, mirrors how
applyPostFiltersalready works for search results inuse-browse-data.ts), OR push domain/source predicates into the directreference_itemsselect. ReuseDomainFilter/SubtopicFilterif the vocabulary matches; a dedicatedingestion_sourcetoggle is net-new but trivial. - Do NOT bring over workspace/entity/quality/governance/owner/freshness/content_type/platform filters — references don’t carry those columns.
- Size: ~0.5–1 day on top of (ii).
Recommended approach (and why)
Section titled “Recommended approach (and why)”RECOMMENDED: purpose-fit reference reader over the existing RPCs, phased. NOT a content_items clone; NOT an intelligence-workspace tab.
- Phase A (do first, ~0.5–1 day):
/reference/[id]detail overreference_get_verbatim+ wire the success card link. Unblocks bl-119 round-trip and discharges the explicit ID-110 OQ-N follow-on. - Phase B (~2–3 days):
/referencebrowse/list (directreference_itemsSELECT for the list default +reference_searchfor the search box) with lightweight domain/subtopic/source/date filtering and a reference card. - Total ~3–4 days for a coherent browse+detail+filter surface.
Option compared — “like-for-like” reuse of the content_items browse stack:
- Mechanically possible (point
buildBrowseQueryatreference_items, add aReferenceListItembranch), but it’s a poor fit + larger: ~half theBrowseFiltersdimensions are dead for references (workspace, entity, content_type, platform, author, freshness, quality, owner, governance, presets, bulk-review, read-marks), and the detail stack (item-detail/*) is content_items-shaped (editor, governance, QA, transcripts). You’d spend effort removing coupling. - Size: ~4–6 days + ongoing coupling tax, for a worse UX (controls that don’t apply). Not recommended beyond visually echoing the browse card/list look-and-feel.
Why NOT build toward / into the intelligence-workspace design: there is no such reference-UI design
(those specs are a satellite-table migration). References are global/workspace-less by ratified
decision (ID-75 BI-7), so they must NOT live under /intelligence/[workspaceId]. The intelligence
articles surface stays on feed_articles (RSS triage); the reference corpus is a separate global read.
Cross-cutting notes for whoever specs bl-317
Section titled “Cross-cutting notes for whoever specs bl-317”- bl-317 status is
spec_needed— a fresh PRODUCT/TECH should ratify: (1) detail page vs modal (spec leans “page or modal overreference_get_verbatim”); (2) list path = direct SELECT vs newreference_listRPC; (3) the exact filter set; (4) whether search reusesreference_searchor the existing/api/search(which is content_items-scoped — references need their own search endpoint callingreference_search). - Coordinate with ID-71 (owns the MCP/AI tool surface over the same RPCs) to avoid divergent contracts — the RPCs are the shared seam.
- Adjacent nits already filed: bl-314 (success-card
dedup_status:'clean'misleading → surfacealready_existed), bl-315 (classifyText latency log). Both are ingest-route, not UI — out of bl-317 scope but same ID-110 lineage. - A reference surface is authenticated; no
proxy.tspublicRoutes change required.
Key file references (absolute)
Section titled “Key file references (absolute)”- Browse:
/Users/liamj/Documents/development/knowledge-hub/app/browse/page.tsx,/Users/liamj/Documents/development/knowledge-hub/app/browse/browse-content.tsx,/Users/liamj/Documents/development/knowledge-hub/hooks/browse/use-browse-data.ts,/Users/liamj/Documents/development/knowledge-hub/hooks/browse/use-browse-filters.ts - Detail:
/Users/liamj/Documents/development/knowledge-hub/app/item/[id]/page.tsx,/Users/liamj/Documents/development/knowledge-hub/components/item-detail/(dir) - Types/keys:
/Users/liamj/Documents/development/knowledge-hub/types/content.ts,/Users/liamj/Documents/development/knowledge-hub/lib/query/query-keys.ts - Reference schema + RPCs:
/Users/liamj/Documents/development/knowledge-hub/supabase/migrations/20260606121451_id75_reference_items_layer.sql,/Users/liamj/Documents/development/knowledge-hub/supabase/migrations/20260606130224_id75_reference_search_rpcs.sql,/Users/liamj/Documents/development/knowledge-hub/supabase/migrations/20260614010200_id110_reference_ingest_rpc.sql - RSS landing:
/Users/liamj/Documents/development/knowledge-hub/scripts/cocoindex_pipeline/flow.py(step 6 ≈2732, backlink ≈2521) - Success card to wire:
/Users/liamj/Documents/development/knowledge-hub/components/create-content/ingestion-success-card.tsx - Specs:
${KH_PRIVATE_DOCS_DIR}/src/content/docs/specs/intelligence-workspaces/{PRODUCT,TECH}.md(NOT a reference UI),${KH_PRIVATE_DOCS_DIR}/src/content/docs/specs/id-110-url-import-reference-items/{PRODUCT,TECH}.md(OQ-N follow-on = bl-317),${KH_PRIVATE_DOCS_DIR}/src/content/docs/specs/id-75-pullmd-cocoindex/PRODUCT.md(BI-16 two-surface, reads authenticated)