ID-135 {135.3} TECH — OKF human search/browse + source_document provenance UI
ID-135 {135.3} TECH — OKF human search/browse + source_document provenance UI
Section titled “ID-135 {135.3} TECH — OKF human search/browse + source_document provenance UI”Status: TECH artefact — not self-ratified. Authored 28/06/2026 by a fresh Planner instance (Q-PLANNER-2 — NOT the {135.1} RESEARCH author, NOT the {135.2} PRODUCT author). British English throughout; dates DD/MM/YYYY.
Predecessors (read in full):
{135.2}PRODUCT.md (gated PASS_WITH_NOTES) — 32 numbered behaviour invariants BI-1 … BI-32 plus the RD-1…4 / AAT-1…4 / BND-1…2 / OOS-1 decision flags;{135.1}RESEARCH.md (gated PASS) — the per-surface reuse-or-rebuild verdict matrix (§2) and read-contract dependency split (§3). This TECH maps one proposed change (or an explicit “no change”) per invariant and is the implementation spine the Checker verifies against.Invariant-tag convention (inherited): a bare BI-N is THIS spec’s invariant; any other Task’s invariant is Task-qualified (e.g. id-111 B-28, id-117 INV-19, id-131 BI-23, {131.11} AC4).
1. Context
Section titled “1. Context”1.1 Code-intelligence orientation (cited verbatim, not paraphrased)
Section titled “1.1 Code-intelligence orientation (cited verbatim, not paraphrased)”GitNexus runs against the indexed repo at absolute path
/Users/liamj/Documents/development/canonical (the bare name canonical is ambiguous — two
registered repos resolve to it: …/canonical and …/procurement — so the absolute path is
passed to disambiguate). On-disk reads re-verified in the id-135 worktree at HEAD 3a222eac.
S438 {135.19} reconcile (DR-013): the “EXTEND verdict” / “REUSE-AS-IS” / “HARD RULE” framing in this section is superseded. Per DR-013 the id-135 surfaces are net-new-by-design: Surface A reuses id-111 only at the leaf/pattern level (its single-grain, non-paginated
useReferenceDataforces the polymorphic abstraction), and Surface B reuses id-117’s version-chain data leg only (RPC +/versionsroute + diff link-out) — NOTUnifiedDiffContainer. Read the code-intel below as reuse evidence, not an extend-in-place mandate.
Surface-A reuse base (id-111 — EXTEND verdict):
gitnexus_context({name: 'ReferenceContent'})→Function:app/reference/reference-content.tsx:ReferenceContent(44–165). incoming.calls: exactly ONE —ReferencePage(app/reference/page.tsx). outgoing.calls:ReferenceSearchBox,ReferenceFilterControls(both file-local, not exported, same file),ReferenceCard(components/reference/reference-card.tsx),ReferenceLoadingSkeleton/ReferenceEmptyState/ReferenceErrorState(components/reference/reference-states.tsx, exported),Button,useReferenceData. processes: []. The complete Surface-A reuse base; the search box + filter controls are file-private, so Surface A copy-and-generalises them, while the states trio is import-reusable.gitnexus_context({name: 'useReferenceData'})→Function:hooks/reference/use-reference-data.ts:useReferenceData(146–369). incoming.calls: exactly ONE —ReferenceContent. processes: []. Single-caller, so copy-and-generalise to a polymorphic multi-grain hook without mutating/reference. On-disk: list mode =useInfiniteQueryover thereference_listRPC (offset cursor,PAGE_SIZE = 48); search mode =useQueryPOSTing/api/search/reference(NOT/api/search— reference search is reference-scoped, B-23). URL-driven state viauseSearchParams/router.push(?q,?domain,?subtopic,?source,?from,?to).
Surface-B reuse base (id-117 REUSE-AS-IS + id-111 EXTEND):
gitnexus_context({name: 'UnifiedDiffContainer'})→Function:components/diff/unified-diff-container.tsx:UnifiedDiffContainer(74–105). incoming.calls: exactly ONE —DocumentDiffPage(app/documents/[id]/diff/page.tsx). outgoing.calls:BinaryDiffPane,toRevisionBlob,RevisionDiffView,deriveRenderMode. The diff/version-compare ENGINE Surface B links to is shipped; never rebuilt (HARD RULE).gitnexus_context({name: 'sourceDocumentRevisionToUnified'})→Function:lib/diff/adapters/source-document-revision.ts:sourceDocumentRevisionToUnified(53–83). incoming.calls: exactly ONE —DocumentDiffPage. The SD→UnifiedDiffadapter is shipped; reused via the link-out, not re-implemented.VersionHistory/RevisionHistoryPanelreuse check (HARD-RULE due diligence): readcomponents/item-detail/version-history.tsx— it is coupled tocontent_historyviafetchItemHistoryList/ItemHistoryEntryand takesitemId/currentContentprops. The SD version chain is theget_document_version_chainRPC shape (version/parent_id/created_at/filename), structurally distinct. So the BI-25 inline version list is a thin presentational map of the shipped RPC rows — not a net-new diff/compare/version engine (that engine,UnifiedDiffContainer, IS reused for BI-26). This is the deliberate “state why it cannot be reused” the HARD RULE demands.
On-disk reads (load-bearing):
-
app/api/source-documents/[id]/route.ts(GET) — auths viagetAuthenticatedClient()→authFailureResponse, UUID-gates, then reads withcreateServiceClient():.from('source_documents').select('*').eq('id', id).single(), then joinscontent_items(.from('content_items').select('id, title, content_type, primary_domain, primary_subtopic, freshness, created_at').eq('source_document_id', id).is('archived_at', null)) and returns{ ...doc, content_items: items ?? [] }.content_itemsis eliminated by id-131 BI-9 — this read breaks (BND-1 / BI-29). -
app/api/source-documents/[id]/versions/route.ts— auths, UUID-gates, callsget_document_version_chainRPC, returns{ document_id, total_versions, versions }. Shipped, reused as-is (BI-25). -
app/documents/[id]/diff/page.tsx(DocumentDiffPage) — sibling of the net-new detail page; resolves theparent_idversion pair undercreateClient()(RLS, id-117 INV-19), projects viasourceDocumentRevisionToUnified, rendersUnifiedDiffContainer. Confirmsapp/documents/[id]/holds onlydiff/(BND-2:page.tsxis net-new). -
app/api/search/route.ts(POST) — auths, rate-limits, embeds, callshybrid_searchRPC; response schema is{ results: z.array(z.unknown()), count }— results are opaque today (content_items-shaped viahooks/use-search.ts’sSearchResult=Omit<ContentListItem,…>,types/content.ts). {131.11} AC4 must hand this path a typed polymorphic shape — Surface A consumes that, never the legacySearchResult(AAT-2). -
app/reference/[id]/page.tsx+reference-detail-client.tsx— the id-111 read-only detail shell pattern Surface B reuses: server component, UUID gate →notFound(), primary read, secondarytryQueryenrichment that degrades (never 404s) on failure, client presenter. The B-28 provenance field set is verbatim here:original_filename, filename, mime_type, file_size, extraction_method, source_url, created_at, withextractionMethodLabel()plain-language rendering (docling*→“Extracted via Docling”, never the raw enum). -
ccc searchfallback disclosure: the cocoindex-code daemon errors in this environment (DaemonStartError— the same failure {135.1}/{135.2} recorded), so thecccorientation lane is unavailable. The findings above rest on the definitive GitNexus + on-disk evidence, conclusive for these two surfaces. Not a greenfield surface — the reuse base is fully identified.
1.2 Per-surface spec tier (RESEARCH §6.2)
Section titled “1.2 Per-surface spec tier (RESEARCH §6.2)”- Surface A — heavier. Polymorphic multi-grain result model, in-flight {131.11} dependencies (AAT-1/2/3), the kind-narrow + filter UX, URL-driven state, incremental load. New domain subdirs + a polymorphic result card + a generalised data hook + a discriminated-union type.
- Surface B — lighter (composition). Largely composition of shipped id-111 detail-shell pattern + id-117 diff/version leg + id-111 B-28 provenance field set, plus one net-new page, the BND-1 route re-point branch, and the citations / derived-pairs / version-list sections.
2. Architecture & seams
Section titled “2. Architecture & seams”S438 {135.19} reconcile (DR-013): “copy-and-generalisation of the id-111 reference stack (RESEARCH §2 EXTEND)” reads as net-new-by-design — the parallel
app/search/+components/corpus-search/tree is a fresh build that reuses id-111 only at leaf/pattern level; Surface B composes id-117’s version-chain data leg + diff link-out only, neverUnifiedDiffContainer.
Surface A lives under net-new app/search/, components/corpus-search/,
hooks/corpus-search/, types/corpus-search.ts — a parallel copy-and-generalisation of the
id-111 reference stack (RESEARCH §2 EXTEND), leaving /reference untouched (useReferenceData
is single-caller; mutating it in place would disturb the shipped reference surface).
Surface B adds app/documents/[id]/page.tsx (sibling to the shipped diff/) plus
components/source-document-detail/ and hooks/source-document-detail/, composing the id-111
detail-shell pattern and id-117 diff/version leg.
Both surfaces are authenticated, read-only (BI-1): server reads use getAuthenticatedClient()
/ createClient() (RLS) → check auth.success → authFailureResponse(auth); neither is added
to proxy.ts publicRoutes (the publicRoutes allowlist is for NEW public routes — these are
not public; omitting them is what makes an unauthenticated visitor redirect to /login).
3. Proposed changes — 1:1 mapping against PRODUCT BI-1 … BI-32
Section titled “3. Proposed changes — 1:1 mapping against PRODUCT BI-1 … BI-32”Each invariant maps to one or more concrete proposed changes (route / component / hook / query-key / type), or an explicit “no change”. Artefact names are catalogued in §4.
Cross-cutting (BI-1 … BI-6)
Section titled “Cross-cutting (BI-1 … BI-6)”| BI | Proposed change |
|---|---|
| BI-1 (authenticated, read-only) | Surface A app/search/page.tsx + Surface B app/documents/[id]/page.tsx are server components reading via createClient() (RLS) / the auth’d API routes; all API routes keep getAuthenticatedClient() → authFailureResponse(auth). Neither route added to proxy.ts publicRoutes (explicit non-change — these are authenticated, not public). No create/update/delete affordance rendered on either surface. |
| BI-2 (role-uniform) | No role gating in any new component; no requiresEdit on the Surface-A nav entry (§ BI-21). Inherits id-111 B-21. No proposed change introduces a role branch. |
| BI-3 (AI-invisible) | CorpusResultCard and useCorpusSearch never render/return similarity/score/model/profile fields; the polymorphic result type (§5) deliberately omits score fields from the display shape. SourceDocumentProvenance renders the {131.9} classification family as plain metadata with no “AI classified”/confidence chrome (drops classification_confidence/classification_reasoning from display). Per ai-visibility-policy.md. |
| BI-4 (Warm Meridian tokens, AA) | All new components use semantic tokens only (text-foreground, bg-muted, border-border, text-primary, Badge variants) — no raw Tailwind colours; new tokens (if any) defined per components/CLAUDE.md. Every kind label / status / state carries text+icon, never colour-only. |
| BI-5 (UK English, DD/MM/YYYY) | All copy UK English; dates via the shipped formatDateUK() (lib/format) reused by both surfaces. |
| BI-6 (corpus visibility scope) | Surface A inherits the hybrid_search scoping (application_type from workspace_id, §9) — no widening/narrowing here; Surface B inherits the source_documents RLS row read (createClient()). Exact Surface-A scope reconciles with the shipped {131.11} contract (AAT-1). No new scoping code. |
Surface A — corpus search/browse (BI-7 … BI-21)
Section titled “Surface A — corpus search/browse (BI-7 … BI-21)”| BI | Proposed change |
|---|---|
| BI-7 (distinct corpus-search entry) | New route /search (verified free; recommended path — semantically search-first; /browse is freed by id-131 BI-12 as an alternative, but route naming is co-decided with id-118 nav, BI-21). Server shell app/search/page.tsx renders client CorpusSearchContent (app/search/search-content.tsx). Distinct surface, links INTO /library + /documents/[id] + /reference/[id] (RD-1) — not a /library mode. |
| BI-8 (no-query default state) | CorpusSearchContent renders a guidance/empty state when no ?q and no filters — reuse ReferenceEmptyState (its hasActiveQueryOrFilters boolean already distinguishes guidance vs no-results) or a corpus-worded CorpusSearchStates.NoQuery if copy must diverge. Distinct from BI-18/BI-19. |
| BI-9 (query submission + URL state) | useCorpusSearch mirrors useReferenceData’s URL-driven pattern: ?q + filter params via useSearchParams/router.push (shareable, restorable, back/forward-safe). Search box keyed on ?q (key={query}) — clean remount, no setState-in-effect (components/CLAUDE.md). Re-submitting an unchanged set hits the TanStack cache (no duplicate fetch). |
| BI-10 (default scope = ALL grains) | useCorpusSearch POSTs /api/search with no type/scope narrow by default → the §9 search-ALL contract (q_a_pair + content_chunk→SD collapse + reference_item, per-grain normalise+merge). Consumes {131.11}‘s polymorphic shape (AAT-1). |
| BI-11 (answer-first implicit boost) | No client-side ranking — ordering comes from the one shipped application_type profile in hybrid_search (~×1.1 answer-first boost, server-side). The card renders results in returned order; never exposes the boost (BI-3). (AAT-3 — workspace_id→application_type threading is {131.11} AC4.) |
| BI-12 (three result kinds, labelled) | CorpusResultCard switches on the result’s discriminated kind (answer/document/reference) and renders a text+icon kind label. content_chunk hits never appear — they arrive already collapsed to source_document by the §9 contract (de-dup by provenance, server-side); the type union has no content_chunk member. |
| BI-13 (per-kind card content + link target) | CorpusResultCard branches: answer → question title + answer snippet + scope/domain badges → links /library; document → classified title/filename + summary + primary_domain/primary_subtopic ({131.9} cols) → links /documents/[id] (Surface B); reference → reference title + source → links /reference/[id] (RD-3), reusing the id-111 reference card visual pattern. |
| BI-14 (polymorphic id resolution) | The link target derives from the discriminated-union kind field (carried by the id-131 BI-27 content_type = owner_kind, AAT-2). The type makes mis-routing unrepresentable (each variant carries its own id + destination builder). |
| BI-15 (kind-narrow control narrows only) | CorpusKindNarrow control (answers/documents/references) sets a ?kind= param that useCorpusSearch passes as the §9 type/scope NARROW (narrows only, never widens, RD-2). Updates URL (BI-9); clearing returns to ALL grains. |
| BI-16 (metadata filters) | CorpusFilterControls (copy-and-generalise of id-111 ReferenceFilterControls) — domain / subtopic / date, pushed into the search params. q_a_pair results restricted to publication_status = published (id-131 BI-20, shipped) — enforced server-side by hybrid_search; Surface A asserts it, adds no client filter. |
| BI-17 (loading state) | Reuse ReferenceLoadingSkeleton (direct import @/components/reference/reference-states). In-flight supersession via the TanStack query key changing on new ?q (AbortController signal like the reference search path) — no stale render over newer. |
| BI-18 (empty/no-results) | ReferenceEmptyState with hasActiveQueryOrFilters = true (or CorpusSearchStates.NoResults) — guidance to broaden/clear, no zero-count or technical wording. Distinct from BI-8/BI-19. |
| BI-19 (error state) | ReferenceErrorState (or CorpusSearchStates.Error) — non-technical message + retry re-issuing the same query+filter set. No raw error/stack/model/score. |
| BI-20 (pagination / incremental load) | useCorpusSearch uses useInfiniteQuery with an offset cursor (mirroring useReferenceData’s PAGE_SIZE list path) once {131.11}‘s /api/search exposes offset/limit paging; stable ordering + no cross-page dupes + explicit end-of-results indicator. AAT-1 — if {131.11} ships ranked top-N only, fall back to a limit-raising “load more”; the BI-20 contract (stable order, no dupes, end indicator) holds either way. |
| BI-21 (nav placement, co-decided w/ id-118) | Extend components/shell/site-header.tsx NAV_LINKS with the corpus search/browse entry ({ href: '/search', label, icon, requiresEdit: false }), reusing the existing active-state machinery. Placed under the id-118-ratified grouping (pending) — do NOT presume option-B “Knowledge Base”; ship consistently with whatever id-118 ratifies, never a divergent slot. PLAN flags this as id-118-coupled. |
Surface B — source_document detail/provenance (BI-22 … BI-31)
Section titled “Surface B — source_document detail/provenance (BI-22 … BI-31)”| BI | Proposed change |
|---|---|
| BI-22 (net-new detail page, not in nav) | New app/documents/[id]/page.tsx (server), sibling of diff/ (BND-2). Reuses the id-111 detail-shell pattern (server page → notFound() gate → primary read → tryQuery sections → client presenter SourceDocumentDetailClient). No top-nav slot. |
| BI-23 (not-found / invalid-id) | UUID-format gate (the UUID_RE pattern reused from app/reference/[id]/page.tsx) → notFound() for non-UUID; a well-formed id with no source_documents row → notFound(). Never a 500/blank/partial shell. |
| BI-24 (provenance panel) | SourceDocumentProvenance renders the id-111 B-28 field set verbatim (original_filename/filename, mime_type, extraction_method via reused extractionMethodLabel() plain language, source_url, landed created_at via formatDateUK) plus the {131.9} classification family (primary_domain, primary_subtopic, secondary_domain, secondary_subtopic, summary, ai_keywords) as plain metadata — no AI/confidence chrome (BI-3). Null fields omitted or “not recorded”, never an error. |
| BI-25 (version chain, inline list) | DocumentVersionList — thin presentational map of get_document_version_chain rows from the shipped /api/source-documents/[id]/versions route, fetched via useDocumentVersions (TanStack). Ordered along version/parent_id; current version marked; single-version → single-entry list, not empty/error (RD-4). |
| BI-26 (diff link — reuse, never rebuild) | Each non-current version row links to /documents/[id]/diff (shipped id-117 surface reusing UnifiedDiffContainer + sourceDocumentRevisionToUnified). No net-new version/diff component (HARD RULE). Per DR-013, link-out is the only form — Surface B never composes UnifiedDiffContainer inline (RD-4). |
| BI-27 (citations panel) | DocumentCitationsPanel reads the id-131 BI-23 CITE-EXT cited_target_kind ({q_a_pair, reference_item, source_document, concept}) via a citations read (new app/api/source-documents/[id]/citations/route.ts or a server read in page.tsx). citations is 0 rows today → clear empty state, not error. Design against the extended contract (AAT-4). |
| BI-28 (derived q_a_pairs list) | DerivedPairsList lists q_a_pairs where source_document_id = id AND publication_status = 'published'; each links to /library. No published answers → clear empty state. Data from the re-pointed [id] route (BI-29 / BND-1). |
| BI-29 (derived-records data requirement) | app/api/source-documents/[id]/route.ts GET STOPS joining content_items and reads q_a_pairs by source_document_id (published only), returning a derived_pairs field (replacing content_items). See the BND-1 dual-ownership branch (§6) — id-135 designs both paths; the page reads derived_pairs regardless of who lands the route change. |
| BI-30 (loading + partial-failure) | page.tsx renders a skeleton while loading; the three sections (version chain, citations, derived pairs) are independent TanStack queries — each renders its own localised non-technical error + retry on failure without failing the page; a core SD-read failure (not not-found) → single SourceDocumentDetailError (modelled on ReferenceDetailError) with retry. |
| BI-31 (read-only) | No edit/delete/version-mutation affordance in any Surface-B component; re-ingest/send-to-review (id-117) not surfaced. Explicit non-change. |
Out of scope (BI-32)
Section titled “Out of scope (BI-32)”| BI | Proposed change |
|---|---|
| BI-32 (no related-records, no keyword facet — OOS-1) | No change — explicitly out of scope. No find_related_items/filter_by_keywords affordance in either surface; recorded as the §9-dropped co-design backlog seam. No artefact proposed. |
4. Concrete artefact inventory
Section titled “4. Concrete artefact inventory”Routes / pages (net-new):
app/search/page.tsx— Surface A server shell (BI-7). NOT inproxy.tspublicRoutes.app/search/search-content.tsx—CorpusSearchContentclient (BI-7…20).app/documents/[id]/page.tsx— Surface B server detail page (BI-22), sibling ofdiff/.
Routes / API:
- Consumed (id-135 does NOT own):
app/api/searchPOST — {131.11} AC4 delivers the typed polymorphic shape + threadsapplication_type(AAT-1/2/3). - Reused as-is:
app/api/source-documents/[id]/versions(BI-25, shipped). - Re-pointed (DR-012 Path β — id-131 owns):
app/api/source-documents/[id]/route.ts—content_items→q_a_pairspublished, returnsderived_pairs(BI-29). id-135 consumesderived_pairsonly (Task-level dep on id-131;{135.11}dropped). - New or server-read (BI-27):
app/api/source-documents/[id]/citations/route.ts(citations read) — or an in-page.tsxserver read; design against id-131 BI-23 CITE-EXT.
Components (net-new — all in domain subdirs, never repo root):
components/corpus-search/corpus-result-card.tsx→CorpusResultCard(the one genuinely net-new component — polymorphic per-owner_kind, EXTEND of the id-111ReferenceCardpattern).components/corpus-search/corpus-search-controls.tsx→CorpusSearchBox,CorpusFilterControls,CorpusKindNarrow(copy-and-generalise of the file-private id-111ReferenceSearchBox/ReferenceFilterControls).components/corpus-search/corpus-search-states.tsx(optional) → corpus-worded NoQuery/NoResults/Error if id-111reference-statescopy must diverge; otherwise reuseReferenceLoadingSkeleton/ReferenceEmptyState/ReferenceErrorStatedirectly.components/source-document-detail/source-document-detail-client.tsx→SourceDocumentDetailClientSourceDocumentDetailError(modelled onReferenceDetailClient/ReferenceDetailError).
components/source-document-detail/source-document-provenance.tsx→SourceDocumentProvenance(BI-24).components/source-document-detail/document-version-list.tsx→DocumentVersionList(BI-25).components/source-document-detail/document-citations-panel.tsx→DocumentCitationsPanel(BI-27).components/source-document-detail/derived-pairs-list.tsx→DerivedPairsList(BI-28).
Components reused as-is (direct file imports, no barrels): UnifiedDiffContainer +
sourceDocumentRevisionToUnified (via /documents/[id]/diff link-out, BI-26); ReferenceCard
visual pattern; ReferenceLoadingSkeleton/ReferenceEmptyState/ReferenceErrorState; Badge,
Button, formatDateUK/formatFileSize, ContentRenderer.
Hooks (net-new):
hooks/corpus-search/use-corpus-search.ts→useCorpusSearch(generaliseduseReferenceData; polymorphic multi-grain; URL-driven;useInfiniteQuerycursor).hooks/source-document-detail/use-source-document-detail.ts→useDocumentVersions,useDocumentCitations,useDerivedPairs(per-section independent TanStack queries for BI-30).
TanStack query keys (lib/query/query-keys.ts):
- New namespace
corpusSearch:all: ['corpus-search'],search(query, kind, filters). - EXTEND existing
sourceDocuments(add members only, never rewrite):detail(id),versions(id),citations(id),derivedPairs(id).
Types (net-new): types/corpus-search.ts → CorpusSearchResult (discriminated union —
answer | document | reference), CorpusSearchFilters, CorpusKind. See §5.
5. The polymorphic search contract Surface A expects (AAT-1/2/3)
Section titled “5. The polymorphic search contract Surface A expects (AAT-1/2/3)”S438 {135.19} reconcile: the reconcile target is no longer unknown. {131.11} G-SEARCH is implemented-to-commit on
cmux-worker-subo-search-13111(migration20260702120000authored, not yet applied): the shipped emit isPolymorphicSearchResultoverrecord_embeddings(4-arm UNION), preserving the 8-field value-path (id, title, suggested_title, content_type, primary_domain, primary_subtopic, summary, similarity) in a 21-col return. ReconcileCorpusSearchResult(below) againstPolymorphicSearchResultat the read boundary; the discriminated union stays the stable internal contract.
Surface A’s whole result model is designed against the §9 / {131.11} target, not shipped.
The TS interface Surface A expects (and which MUST reconcile with {131.11}‘s actual /api/search
emit before implementation — AAT-2):
// types/corpus-search.ts — the shape Surface A consumes; reconcile vs {131.11} AC4 emit.export type CorpusKind = 'answer' | 'document' | 'reference';
interface CorpusResultBase { id: string; kind: CorpusKind; title: string; }export type CorpusSearchResult = | (CorpusResultBase & { kind: 'answer'; answerSnippet: string; scopeTags: string[]; primaryDomain: string | null; primarySubtopic: string | null }) // → /library | (CorpusResultBase & { kind: 'document'; summary: string | null; primaryDomain: string | null; primarySubtopic: string | null }) // → /documents/[id] | (CorpusResultBase & { kind: 'reference'; sourceUrl: string | null }); // → /reference/[id]kind is derived from the id-131 BI-27 content_type = owner_kind field (§9 §7.3 owner-approved).
No score/similarity/model field is included in the display shape (BI-3). If {131.11} emits
field names differing from the above, the mapping layer in useCorpusSearch reconciles them at
the read boundary (mirroring toReferenceListItem) — the discriminated union is the stable
internal contract.
6. BND branches (carry forward — parent decides, do not resolve unilaterally)
Section titled “6. BND branches (carry forward — parent decides, do not resolve unilaterally)”- BND-1 (OQ-5) — WHO re-points
app/api/source-documents/[id]/route.ts. Confirmed on disk: the GET joins eliminatedcontent_items(route.ts:47–54).S438 {135.19} reconcile (DR-012): this branch is resolved — Path β decided. id-131 owns the route re-point (its content_items-elimination sweep); id-135’s Surface-B subtask is consume-only with a Task-level dependency on id-131 (
{135.11}dropped). Path α is superseded; the α/β wording below is retained only as historical record.- Path α (id-135 owns it) — SUPERSEDED (DR-012): a Surface-B subtask re-points the route — drop the
content_itemsselect, addq_a_pairsbysource_document_id(publication_status = 'published'), returnderived_pairs. Self-contained in id-135. - Path β (id-131 ships it): id-131’s content_items-elimination sweep re-points the route; id-135’s subtask becomes consume-only and gains a Task-level dependency on id-131 (not a sibling Subtask dep — sibling-only constraint).
- Either way the page reads a
derived_pairsfield of publishedq_a_pairs. The Orchestrator rules ownership before the Surface-B wave; PLAN carries both as labelled branches.
- Path α (id-135 owns it) — SUPERSEDED (DR-012): a Surface-B subtask re-points the route — drop the
- BND-2 (OQ-10) — shared route tree. id-135 adds
app/documents/[id]/page.tsxinto id-117’s existing/documents/[id]tree (recommended — same surface family). Note the shared-route-tree seam with id-117; no behavioural coupling (sibling pages), but PLAN flags the co-located tree.
7. Conventions compliance (TECH gate)
Section titled “7. Conventions compliance (TECH gate)”- Semantic Warm Meridian tokens only — no raw Tailwind colours in any new component;
text+icon for every kind/state (WCAG AA, BI-4). New tokens (if any) per
components/CLAUDE.md. - TanStack Query exclusively —
useCorpusSearch+ the three Surface-B section hooks; keys inlib/query/query-keys.ts, fetchers via the establishedfetchJson/tryQuerypaths. No SWR / raw fetch in hooks. - No barrel re-exports — direct file imports (
@/components/reference/reference-states,@/lib/diff/adapters/source-document-revision, etc.). - Auth —
getAuthenticatedClient()/createClient()→ checkauth.success→authFailureResponse(auth)on every API route; RLS client for page reads. - proxy.ts —
/searchand/documents/[id]are AUTHENTICATED → MUST NOT be added topublicRoutes(publicRoutes is for new public routes; these are not public — omission is what enforces the/loginredirect for unauthenticated visitors, BI-1). - Supabase safety —
sb()/tryQuery()for all reads (matches the id-111 detail shell + id-117 diff page); never bare.from().select()without the safe wrapper. - Tests —
bun run test(neverbun test); behaviour-first pertest-philosophy.md(assert rendered states/link targets/empty-vs-error distinctions, not implementation).
8. Migration plan / DDL
Section titled “8. Migration plan / DDL”No DDL in id-135. Every column read is shipped: source_documents classification family
({131.9}, on this HEAD), q_a_pairs.publication_status/source_document_id (id-131 BI-20,
schema shipped), get_document_version_chain RPC (id-117). The in-flight reads (§9 polymorphic
hybrid_search, the typed /api/search shape, the CITE-EXT citations contract) are owned by
{131.11}/id-131 — id-135 consumes them and does not migrate them. The only data-layer change
id-135 may own is the BND-1 route re-point (a .from() swap, no DDL).
9. Empirical verification (OQ-3 / Q-EX2 scope)
Section titled “9. Empirical verification (OQ-3 / Q-EX2 scope)”This TECH cites no external-library symbols. Every symbol is internal Canonical
(ReferenceContent, useReferenceData, UnifiedDiffContainer, sourceDocumentRevisionToUnified,
formatDateUK, the reference/version-chain RPCs, hybrid_search), Postgres/Supabase RPC surface
(get_document_version_chain, .from().select(), .rpc()), or Next.js/TanStack framework
(notFound, useQuery, useInfiniteQuery, useSearchParams) — all out of the import-and-call
verification scope (external-library symbols only, per shared-discipline §Empirical verification).
No verification block required. The two runtime contracts that will drift — the §9 polymorphic
search shape (AAT-1/2/3) and the BND-1 content_items→q_a_pairs route join — are carried as
explicit assumptions-against-target / boundary branches gated on {131.11} and the Orchestrator,
never as unverified present facts.
End of {135.3} TECH. One proposed change (or explicit non-change) per BI-1 … BI-32; concrete
named routes/components/hooks/query-keys/types catalogued (§4); reference_item rendered as a
first-class third result kind; the diff/version-compare engine reused never rebuilt (HARD RULE
honoured with the VersionHistory due-diligence note); BND-1/BND-2 carried as labelled branches
for PLAN; quality bars (semantic tokens, TanStack-only, no barrels, auth helper, proxy.ts
non-addition, sb()/tryQuery(), bun run test) baked in. No DDL, no ledger writes, no commits.