Skip to content

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.

  • bl-317 is real and unblocked-by-design. ID-110 (closed) deliberately landed manual-URL ingests as reference_items with no read surface — the success card shows only a copyable referenceId. 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) and reference_get_verbatim (single-row detail). Both are GRANT 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 the intelligence_workspaces satellite 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, no platform, no layer-suggestion, no QA, no thumbnails, no governance/review lifecycle, no entity-mentions consumer wired yet). A full clone of the content_items browse 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] over reference_get_verbatim, wire the success card’s “view reference” link. ~0.5–1 day.
    • Phase B (medium): a reference browse/list + search page /reference over reference_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).

(a) Content-item UI inventory (the reference baseline to compare against)

Section titled “(a) Content-item UI inventory (the reference baseline to compare against)”
RouteFileRole
Browse list/gridapp/browse/page.tsx (Suspense shell) → app/browse/browse-content.tsx (client)The browse/list/filter view.
Item detailapp/item/[id]/page.tsx (server) → app/item/[id]/item-detail-client.tsxDetail view. Server-fetches the row + related items RPC + feed-article provenance.
Library (Q&A subset)app/library/page.tsxapp/library/library-content.tsxParallel browse scoped to Q&A pairs.
Createapp/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.

  • Types: types/content.ts
    • ContentListItem (lines 6–73) — display subset of content_items Row (~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 a supabase.from('content_items').select(...) query, applies every BrowseFilters dimension as .in()/.eq()/.gte()/.or() clauses + cursor /offset pagination + 6 sort modes.
    • useInfiniteQuery (cursor/offset paginated, PAGE_SIZE 48) for browse mode; useQueryPOST /api/search (semantic) for search mode; plus quality-flag + freshness-count side queries. Pre-filter resolvers hit content_item_workspaces, entity_mentions, filter_by_keywords RPC, get_items_with_quality_flags RPC.
    • Filters come from hooks/browse/use-browse-filters.ts (URL ↔ state).
  • Query keys: lib/query/query-keys.tsqueryKeys.contentItems.{all,browse(filters),search(q)} (lines ~19+). (There is an intelligence.articles key family at 203–207 — that’s feed_articles, not references.)
  • Detail fetch: app/item/[id]/page.tsx does a direct server-side from('content_items').select(CONTENT_DETAIL_COLUMNS).eq('id',id).single() (with PGRST116 read-after-write retry) + find_related_items RPC + a feed_articles → feed_sources provenance 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.tsx composes DomainFilter, SubtopicFilter, ContentTypeFilter, PlatformFilter, AuthorFilter, freshness, layer (via useLayerVocabulary), + 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 DEFAULT
title text NOT NULL
body text NOT NULL -- PullMD/Docling markdown = canonical body of record
summary text NULL
source_url text NOT NULL -- canonical normalised URL; UNIQUE (one reference per URL)
published_at timestamptz NULL -- original pub time, never ingest time
primary_domain text NULL
primary_subtopic text NULL
layer text NULL -- v1 constant 'research'; validate_layer_key() trigger
embedding 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 NULL
created_at/updated_at timestamptz
  • RLS: reference_items_select policy FOR 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 the reference_ingest SECURITY DEFINER RPC for manual URLs).
  • reference_ingest RPC (20260614010200_id110_reference_ingest_rpc.sql, ID-110): owner-gated, atomically lands the source_documents + reference_items evidence pair, server-side uuid5 PKs, ON CONFLICT idempotency. This is the WRITE path for manual URLs.
  • reference_search + reference_get_verbatim RPCs (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)”
Dimensioncontent_itemsreference_items
Workspace scopingcontent_item_workspaces junction (ID-69)NONE — global, workspace-less, RATIFIED-DO-NOT-BUILD a workspace FK/junction (ID-75 BI-7)
Classificationcontent_type, platform, author, source_domain, thumbnails, ai_keywords, freshness, priorityonly primary_domain, primary_subtopic, layer(=‘research’), published_at — no content_type/platform/author/freshness/thumbnail
Body / chunkscontent + content_chunks + transcripts/QAsingle body markdown, no chunk table, whole-record embedding
Lifecyclegovernance_review_status, publication_status, verified_at, review cadence, starred, user_tagsnone of these
Entity mentionsentity_mentions.content_item_id consumer wiredentity-mentions re-point named as ID-71 follow-on (NOT wired)
Identityvariousuuid5(‘ri:‘+URL), source_url UNIQUE (idempotent re-ingest UPSERTs in place)
Write pathapp routes + pipelinepipeline (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.py step 6 (≈line 2732) UPSERTs the reference_items row (ingestion_source carried from the item; 'rss_feed' for feeds, 'url_import' for manual URLs). Step 7 (_backlink_feed_articles, ≈2521) sets feed_articles.reference_item_id.
  • feed_articles still also carry content_item_id and the intelligence workspace articles list (/api/intelligence/workspaces/[id]/articlesfrom('feed_articles')) reads feed_articles, not references. So the RSS triage/relevance surface (intelligence workspace) is unchanged; what moved to reference_items is the canonical external body of record, and that is what has no read UI.
  • bl-316 (summary_data disposition 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 (only notifications/preferences matches as substring).
  • Grep across app/ lib/ components/ hooks/ for reference_search/reference_get_verbatim0 callers (only a code comment in app/api/ingest/url/route.ts:142).
  • Grep for reference_items in 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_workspaces satellite 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_verbatim RPCs 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.


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. No proxy.ts change needed (authenticated surface).

(i) Reference detail view/reference/[id]

  • Route: app/reference/[id]/page.tsx (server) — call reference_get_verbatim via tryQuery, notFound() on miss. Render body markdown (reuse components/item-detail/content-renderer.tsx or 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 / ReferenceListItem to types/content.ts (or a new types/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.tsx ReferenceSuccessCard 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, currently describe.skip pending 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 client reference-content.tsx.
  • Query: TanStack hook (hooks/reference/use-reference-data.ts) + queryKeys.references.* in lib/query/query-keys.ts. Card click → /reference/[id].
  • Gap — list vs search: reference_search requires a query embedding (it filters WHERE embedding IS NOT NULL and 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 by published_at DESC (mirror the existing idx_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.
  • Reuse: a lightweight card (clone content-card.tsx down to the reference shape, OR a new components/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.
  • Implementation: client-side post-filter on the list result (small corpus v1, mirrors how applyPostFilters already works for search results in use-browse-data.ts), OR push domain/source predicates into the direct reference_items select. Reuse DomainFilter/SubtopicFilter if the vocabulary matches; a dedicated ingestion_source toggle 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: 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 over reference_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): /reference browse/list (direct reference_items SELECT for the list default + reference_search for 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 buildBrowseQuery at reference_items, add a ReferenceListItem branch), but it’s a poor fit + larger: ~half the BrowseFilters dimensions 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 over reference_get_verbatim”); (2) list path = direct SELECT vs new reference_list RPC; (3) the exact filter set; (4) whether search reuses reference_search or the existing /api/search (which is content_items-scoped — references need their own search endpoint calling reference_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 → surface already_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.ts publicRoutes change required.
  • 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)