ID-117 {117.1} RESEARCH — Unified document/revision diff surface (binary + canonical-markdown + user-edit) over extend-ui viewers
ID-117 {117.1} RESEARCH — Unified diff/revision surface (3 view-depths over a common revision abstraction)
Section titled “ID-117 {117.1} RESEARCH — Unified diff/revision surface (3 view-depths over a common revision abstraction)”Supersedes the S383 draft; grounded on the S380 decision (transcript
dc46727f) + the ID-117 ledger framing. The S383 draft drifted: it treated extend-ui-kit’s not-yet-installed status as a blocker and recommended “markdown side-by-side v1 + binary render v1.1” (the OQ-117-2 fork). That contradicts S380 — where Liam locked extend-ui-kit as the binary-leg approach and made the single coherent surface (with the binary leg) the v1 target. This rewrite preserves the S383 file:line findings (re-verified against currentmain, S384), fixes the extend-ui framing, and kills the deferral fork. It also adds the reference-page overlap (S383 omitted it) and an empirically confirmed 0-row count for the retirement target (S383 could not run it).Status: RESEARCH complete — recommendations PENDING Liam (§10 decision queue). RESEARCH-only — no implementation, no DDL, no code edits, no ledger writes.
Task: ID-117 (Track C, S380,
spec_needed, priority high, 0 deps) — “Unified document/revision diff surface (binary + canonical-markdown + in-platform user-edit) over extend-ui viewers”. Promoted from bl-267 (S243-RATIFIED diff-UI retention). NOT an ID-45 gate (render-layer, droppable legacy leaf). Retiressource_document_diffsas part of this Task.Companion artefacts (inputs, not replacements): ID-59’s
PRODUCT.md({59.2}) andTECH.md({59.3}) — the user-edit Diff-UI (INV-14..INV-17) that ID-117 builds on top of. ID-59’sRevisionDiffViewIS the source-agnostic engine ID-117 extends (§3.3).
0. What this rewrite corrects (the S383 → S380 reconciliation)
Section titled “0. What this rewrite corrects (the S383 → S380 reconciliation)”| The S383 draft said | The S380 decision (authoritative) | This rewrite |
|---|---|---|
| extend-ui-kit is “ABSENT” → a blocker → recommend markdown-side-by-side v1, binary render deferred to v1.1 (§5(C), §7.4, OQ-117-2) | extend-ui-kit is the LOCKED approach for the binary leg (Liam, S380): “Build the surface with extend-ui-kit Document Split Viewer. Single coherent diff surface.” Binary is v1. | extend-ui-kit absence is a build prerequisite (a shadcn-registry install), not grounds to defer. The binary leg ships in v1 on extend-ui viewers (§5). |
| Binary leg is “largely greenfield UI — either adopt extend-ui-kit or build on installed primitives” — framed as an open fork | The fork is already resolved: extend-ui-kit, because “most of our client’s content will likely be binary, before our pipeline converts it” (S380). | The installed primitives (react-pdf etc.) are a partial fallback (PDF only); extend-ui-kit is the chosen substrate (§5). |
| Leaned on the S243 “markdown-first / markdown-sidecar” lineage | S380 supersedes the markdown-first framing with “unify on extend-ui viewers” | The S243 markdown-sidecar framing is cited only as superseded (§1). |
| (omitted) | — | NEW: extend-ui’s “Document Split Viewer” is a packet-splitter (“review large document packets as separate files”), not a two-version comparator — so the binary version-diff is composed from extend-ui file-viewers, not delegated to one component (§5, OQ-117-2′). A PRODUCT clarification, not a deferral. |
source_document_diffs 0-rows claim “inherited, not verified” | — | NEW: empirically CONFIRMED 0 rows on the dev/platform DB (zjqbrdctesqvouboziae), this RESEARCH (§6, §8). |
| (omitted) | — | NEW: the reference detail page (app/reference/[id]) assessed — not a current consumer; future seam only (§7). |
The S383 evidence base (the three-surface map, the UnifiedRevision abstraction, the retire-list, the
installed-primitive inventory) was sound and is preserved — re-verified against current main (HEAD
includes the ID-111 reference fix 913effe8), with drifted line numbers corrected.
1. The grounding decision (S380, transcript dc46727f)
Section titled “1. The grounding decision (S380, transcript dc46727f)”S380 (2026-06-20, 532 events; ID-117 session_refs: ["ca-s380"]) is the canonical decision source — no
ca-s380 continuation prompt was written (the session hit the S381 API-error interrupt), so the
decision survives only in the raw transcript. Verbatim Liam turns:
-
“is it feasible to create a single coherent diff surface which includes all three concepts (the table is empty as this is a dev DB — and even in the client DB, the diff engine was newly built prior to the pivot, so will also be empty — app hasn’t launched yet).”
-
“extend-ui simplifies this to some degree, with its binary viewers — keeping in mind that most of our client’s content will likely be binary, before our pipeline converts it.”
-
“Drop the 0-row
source_document_diffstable +document-diff.tsengine … Build the surface with extend-ui-kit Document Split Viewer. Single coherent diff surface.” - Locked: document UI (diff / Q&A / file-tree) uses extend-ui-kit open-source components; substantial unspecced work → research-first. Action taken: promote bl-267 → Task 117 (Track C diff surface).
The framing (matches the ID-117 ledger description — authoritative): ONE unified diff/revision
surface; three view-depths over a common revision abstraction:
- Binary source diff — docx/pdf/xlsx (what the client actually edits; most content is binary pre-pipeline) via extend-ui-kit DOCX/PDF/XLSX viewers (the “Document Split Viewer” family).
- Canonical-markdown revision diff — the post-Docling markdown projection.
- In-platform user-edit diff — over
content_history/q_a_pair_history(the live{59.12}RevisionDiffView).
Aligns with files-are-truth “one record, many views” (#1) + “observe and intervene” (#3). Retire
source_document_diffs (0 rows dev — §6, §8). No new DB table.
Superseded predecessor framing (cite, do not follow): bl-267 / S243 ratified “Diff-UI RETAINED for
v1, re-pointed OFF source_document_diffs ONTO a markdown sidecar” (id-31-canonical-pipeline- implementation-plan/PLAN.md:21, :575 “Diff-UI retire scope RATIFIED-S243”). The historical
markdown-first / V1.1-deferral notes (archive decision-graph.md, project-plan.md §8,
deferral-register.md; ID-59-era “markdown-first”) pre-date the S380 unify-on-extend-ui call and are
SUPERSEDED by it. They are not current intent and must not drive PRODUCT.
2. Method + code-intelligence orientation
Section titled “2. Method + code-intelligence orientation”- gitnexus (
repo:'canonical'):query('document revision diff comparison version history')surfaced the legacy binary-diff flow (app/api/source-documents/[id]/diff/route.ts→document-diff.ts), the Q&A history GET, and the standalone diff/revision components.context({name:'RevisionDiffView'})returned exactly two incoming callers (CompareVersionsPanel,QARevisionHistory) — the central extension seam. - Three parallel survey agents (S384): extend-ui-kit live state · the three surfaces (re-verify
file:line vs current
main) · the reference-page overlap. All file:line below corrected to current tree. - Direct DB verification (Supabase MCP, dev/platform
zjqbrdctesqvouboziae): row counts for the retirement target + sibling history tables (§8).
This Task is render-layer with a small DB-retirement (coupling class C5/C2 in the ID-93/ID-59
vocabulary): render-layer over existing revision stores, plus a C2 drop of an empty table. It does
NOT touch the cocoindex source-file layout and is NOT an ID-45 gate (Task status_note).
3. The three view-depths — current-state map (re-verified S384)
Section titled “3. The three view-depths — current-state map (re-verified S384)”ID-117’s thesis: ONE surface, three view-depths over a common revision abstraction. Today these are three disjoint, partially-built surfaces:
| Leg | What it compares | Current surface (file:line) | State |
|---|---|---|---|
| (1) BINARY source diff | two source_documents versions (parent_id chain) — Q&A-pair or full-text diff of extracted_text | app/documents/[id]/diff/page.tsx → components/source-document/source-document-diff-review.tsx → source_document_diffs store; computed by lib/source-documents/document-diff.ts; entry source-document-history.tsx:256 (GitCompareArrows → /documents/{id}/diff) | EXISTS, text-only. Diffs extracted_text strings — no binary rendering (no DOCX/PDF/XLSX render). This is the leg ID-117 replaces with real binary viewers. |
| (2) canonical-markdown revision diff | two revisions of the post-Docling markdown | No distinct surface. cocoindex markdown → content_items.content (flow.py:2107), versioned via content_history (= leg 3’s substrate). Upload-path markdown → source_documents.extracted_text (upload/route.ts:626), versioned via source_documents.parent_id (= leg 1). | ABSENT / ambiguous source (§3.2). |
| (3) in-platform user-edit diff | two content_history OR q_a_pair_history revisions | components/item-detail/revision-diff-view.tsx:229 (RevisionDiffView engine) ← version-history.tsx:92 (CompareVersionsPanel, content) + qa-revision-history.tsx:64 (QARevisionHistory, Q&A) | LANDED-FULL ({59.12}+{59.16}). Both legs wired live. Unified line-diff (bespoke LCS), not side-by-side; richer view DEFERRED (ID-59 INV-15). |
3.1 Leg 1 — the source_document_diffs engine (the retirement target)
Section titled “3.1 Leg 1 — the source_document_diffs engine (the retirement target)”Schema (supabase/migrations/20260617130000_squash_baseline.sql:7376): old/new_document_id
(FK→source_documents, CASCADE), diff_type {added,removed,modified,unchanged}, diff_mode {qa,full_text}, old/new_content, old/new_question, similarity_score, affected_content_item_id
(FK→content_items, SET NULL), status {pending_review,applied,dismissed}, reviewed_at/by,
reviewer_note, section_header. Table COMMENT (:7406): “Stores Q&A pair-level diffs between source
document versions. Each row represents one matched or unmatched pair.” Plus an
api.source_document_diffs security-invoker view (:7434; GRANTs :13488–13490).
It is the re-ingest / AI-suggest REVIEW store, never the user-edit store. Compute+persist: POST /api/source-documents/[id]/diff calls computeDocumentDiff(oldId,newId,old.extracted_text, new.extracted_text) (document-diff.ts:507; computeFullTextDiff:333) and inserts. Read: GET + the
diff page. Review: PATCH drives the status workflow. The full retire-list is in §6.
3.2 Leg 2 — the canonical-markdown projection (the ambiguity)
Section titled “3.2 Leg 2 — the canonical-markdown projection (the ambiguity)”Post-Docling markdown has two homes by ingest path:
- cocoindex path:
convert_binary_to_markdown(file)→content_items.content(scripts/cocoindex_pipeline/flow.py:2107), keyed bycontent_item_id = uuid5(rel_path). Versioned viacontent_history(auto-version triggers, migration:1203/:1488/:1684) — = leg 3. - upload-API path:
source_documents.extracted_text(app/api/upload/route.ts:626), versioned via thesource_documents.parent_idchain (= leg 1). cocoindex never writesextracted_text(0 hits).
So “diff two revisions of the canonical markdown” is not a third store — it is (a) two
content_history versions of content_items.content (already leg 3), OR (b) two
source_documents.extracted_text versions (already leg 1). Leg 2 is a framing of legs 1+3 at
markdown granularity. → the “three stores” collapse to two physical revision substrates viewed at
three depths.
3.3 Leg 3 — the landed user-edit diff (the engine to extend)
Section titled “3.3 Leg 3 — the landed user-edit diff (the engine to extend)”RevisionDiffView (revision-diff-view.tsx:229–282) takes two RevisionBlobs and computes a
bespoke LCS line-diff (buildLcsTable:70, computeLineDiff:94 — does NOT use the diff package),
rendering a unified (not side-by-side) view with [+]/[-] gutters + [Added]/[Removed] labels
(WCAG: colour never sole signal), semantic tokens (bg-status-success/10, bg-status-error/10), UK
dates, and a “no changes” empty state. It is deliberately source-agnostic — header comment (:12):
“deliberately source-agnostic: callers pass two
RevisionBlobs derived from either content_history or q_a_pair_history rows, so the same component serves both substrates (PC-14/15/16/17, INV-14..INV-17).”
RevisionBlob shape (:31–46):
RevisionBlob { version: number text: string changeType: string changeSummary: string | null createdAt: string createdByLabel: string editIntent: string | null}A separate word-level VersionDiff (version-diff.tsx:18, diffWords from diff) is live —
rendered inline per history row in version-history.tsx:480/490 (NOT an orphan; lower-fidelity companion
to RevisionDiffView). It is a candidate primitive for the v1.1 inline-word-highlight (ID-59 INV-15).
4. The COMMON revision abstraction (the central research question)
Section titled “4. The COMMON revision abstraction (the central research question)”All three legs reduce to “two snapshots of one logical record, rendered at a chosen depth, optionally
with a binary original to render alongside.” RevisionBlob (§3.3) is already ~80% of this. The proposed
generalisation:
UnifiedRevision { recordKind: 'content_item' | 'qa_pair' | 'source_document' // which substrate recordId: string version: number text: string // diffable projection: content | answer_standard | extracted_text changeType: string // change_type | (synthesised 'reingest' for source-doc) changeSummary: string | null createdAt: string createdByLabel: string editIntent: string | null binary?: { storagePath: string; mimeType: string; signedUrlFetcher: () => Promise<string> } // binary leg only}
UnifiedDiff { older: UnifiedRevision newer: UnifiedRevision viewDepth: 'binary' | 'canonical-markdown' | 'user-edit' renderMode: 'unified-line' | 'side-by-side' | 'word-inline' | 'binary-split'}Mapping each leg (the proof it unifies):
| Leg | recordKind | text source | binary? | Default renderMode |
|---|---|---|---|---|
| (1) binary source diff | source_document | extracted_text | YES (storage_path + mime_type) | binary-split (two extend-ui viewers side-by-side) + side-by-side markdown fallback |
| (2) canonical-markdown | content_item | content (cocoindex) OR extracted_text (upload) | no | side-by-side / word-inline (the ID-59 INV-15 deferral) |
| (3) user-edit | content_item / qa_pair | content / answer_standard | no | unified-line (today’s RevisionDiffView) |
Consequences for PRODUCT/TECH:
- Two physical revision substrates, not three — the
source_documentsparent_id chain and the*_historytables. Leg 2 is leg 1 or leg 3 at markdown granularity. No new diff-storage table (consistent with ID-59 INV-14 — diff computed on demand). - The abstraction is a TYPE + a fetcher/adapter layer, not a DB change. The only DB change is the
source_document_diffsDROP (§6). All else islib/query/fetchers.ts+query-keys.ts+ aUnifiedRevisionadapter perrecordKind+ the render container. - The binary leg adds a viewer dimension absent from
RevisionDiffView— rendering the actual DOCX/PDF/XLSX side-by-side. This is the only leg needing new viewer components → extend-ui-kit (§5). Legs 2+3 are pure render-mode additions to the existing engine. source_documentsrevisions need a synthesisededitIntent/changeType— they have noedit_intentcolumn (onlycontent_history/q_a_pair_historydo). PRODUCT decides the provenance panel for a re-ingest revision (e.g.changeType:'reingest',editIntent:null) — OQ-117-4.
5. The binary-viewer leg — extend-ui-kit (LOCKED) + the component-mapping clarification
Section titled “5. The binary-viewer leg — extend-ui-kit (LOCKED) + the component-mapping clarification”extend-ui-kit is the locked substrate (S380). Live state (S384):
-
Install state: ABSENT —
package.jsonhas noextend*dep;node_moduleshas only unrelatedextend/extend-shallow;components.jsonregisters onlyui.shadcn.com(notextend-hq/ui); nomcp-apps/orcomponents/references. This is a build prerequisite, not a blocker — extend-ui-kit is a shadcn-style registry (extend-hq/ui, MIT, React; install vianpx shadcn@latest add @extend/<component>, which copies source into the repo — so there is no compiled peer-dep version lock). No hard blocker found against React 19.2.6 / Next 16.2.6; it consumes radix primitives the repo already has (radix-ui ^1.4.3). -
Component inventory (extend.ai /
ui.extend.ai/docs, verified S384). The relevant subset for ID-117:extend-ui component Documented purpose PDF Viewer ”Viewers and editors for PDF files” DOCX Viewer / DOCX Editor (exp.) ”Viewers and editors for DOCX files” Excel (XLSX) Viewer / Editor (exp.) ”Viewers and editors for XLSX files” CSV Viewer ”Viewers and editors for CSV and TSV files” Document Splits ”Document split viewer for reviewing large document packets as separate files” Bounding Box Citations ”review extracted values against the source PDF … field-level citations … JSON diffing … page overlays” File System ”file system view for browsing packet files, folders, and document previews” -
⚠ Component-mapping clarification (a PRODUCT precision, NOT a contradiction of S380): the “Document Split Viewer” named in the S380 decision is, in the live inventory, a packet-splitter (surfaces individual files within a multi-file packet) — it is not a two-version comparator, and no extend-ui component documents a doc-to-doc diff/compare/side-by-side prop. The only diff-adjacent feature is Bounding Box Citations’ “JSON diffing” (field-extraction review vs source, not version-comparison). Implication: the binary version-diff is composed — two extend-ui file-viewer instances (PDF/DOCX/XLSX) rendered side-by-side + a bespoke change/compare affordance — rather than dropped onto one magic component. This is fully consistent with S380 (“build the surface with extend-ui … binary viewers”): extend-ui supplies the hard part (client DOCX/XLSX/PDF rendering the repo entirely lacks); ID-117 builds the compare composition on top. It is a v1 deliverable, not a v1.1 deferral. Carried as OQ-117-2′ (binary-diff UX precision).
-
Installed primitives (partial fallback, PDF-only). Versions + usage (S384):
Package Version Use today Client/server react-pdf^10.4.1components/reader/pdf-viewer.tsx(own PDF viewer)client diff^9.0.0diff-highlighted-text.tsx,version-diff.tsx,document-diff.tsboth mammoth^1.12.0upload/extract routes (DOCX→HTML) server-only exceljs4.4.0procurement-export-xlsx.ts(export)server-only @tiptap/markdown3.23.4content/response editors client The repo can render PDF client-side already, but has no client DOCX or XLSX viewer — exactly the gap extend-ui-kit fills. So “build on installed primitives” is NOT a substitute for the binary leg (it covers only 1 of 3 formats); extend-ui-kit is the right substrate, as S380 decided.
Net: the binary leg is v1, on extend-ui-kit. The only open question is UX-shape precision (OQ-117-2′), not whether to build it.
6. The retirement — source_document_diffs (C2 drop; 0 rows CONFIRMED)
Section titled “6. The retirement — source_document_diffs (C2 drop; 0 rows CONFIRMED)”Empirically confirmed 0 rows on the dev/platform DB (zjqbrdctesqvouboziae, this RESEARCH — §8). The
client DB is unreachable from here (still an OQ — §9 OQ-117-1), but S380 states it is also empty
(“the diff engine was newly built prior to the pivot … will also be empty — app hasn’t launched yet”).
Exhaustive retire-list (every reader/writer of source_document_diffs, current file:line, S384):
lib/source-documents/document-diff.ts—computeFullTextDiff:333,computeDocumentDiff:507(compute engine)app/api/source-documents/[id]/diff/route.ts—.from('source_document_diffs')@ 75, 125, 183, 412, 490, 552, 586, 607 (GET/POST/PATCH)app/documents/[id]/diff/page.tsx—.from(...)@ 56, 65, 97, 106, 173, 182, 215, 224, 266components/source-document/source-document-diff-review.tsx+diff-highlighted-text.tsx+hooks/use-diff-review.ts— the review UI (couples indirectly via the API route; no direct table string — correcting the S383 claim that these reference the table directly)components/source-document/source-document-history.tsx:256— compare entry link (GitCompareArrows→/documents/{id}/diff)lib/source-documents/source-document-impact.ts—analyseDocumentImpact,.from(...)@ 69, writeaffected_content_item_id@ 141/142lib/mcp/tools/content.ts—get_document_diffdef @ 2033,.from(...)@ 2065/2189scripts/generate-api-views.ts:118— theapi.view-set entry (re-vendor trigger — §10)scripts/export-user-data.ts— type @ 496,.from(...)@ 647, assign @ 691 (GDPR export)- Tests:
__tests__/mcp/document-diff-tool.test.ts,__tests__/lib/source-documents/document-diff.test.ts,__tests__/api/source-documents-diff.test.ts,e2e/tests/document-diff.spec.ts - Migration:
…squash_baseline.sqlCREATE:7376, COMMENT:7406, api view:7434, GRANTs:13488–13490
Two workstreams hide here — PRODUCT must keep them separate:
- RETIRE the storage + review workflow (the
status/reviewed_by/reviewer_note/AI-suggest-apply path). C2-cheap drop IF the use-case it served (compare two binary source-doc versions, with affected-item impact) is preserved on the unified surface. The review-workflow columns are the part that genuinely disappears — confirm with Liam the re-ingest review workflow is no longer wanted vs. re-homed (OQ-117-1). - UNIFY the three view-depths (§4) — the substantial render-layer build (extend
RevisionDiffView/ a new container with the binary + markdown legs). They can be sibling-ordered within ID-117; no cross-Task dep.
7. Reference detail page overlap (app/reference/[id]) — assessed, OUT of v1 scope
Section titled “7. Reference detail page overlap (app/reference/[id]) — assessed, OUT of v1 scope”S383 omitted scope item #4. Findings (S384):
app/reference/[id]/page.tsxis an RSC that callsrpc('reference_get_verbatim', {p_reference_id})overreference_items+ enriches fromsource_documents; renders viareference-detail-client.tsx→ContentRenderer(components/item-detail/content-renderer.tsx:89,reference-detail-client.tsx:155,<ContentRenderer content={reference.body} />). The recent ID-111 B-5 fix (913effe8) added aUUID_REgate (page.tsx:33/64) →notFound()on a malformed id.reference_itemsis a flat, workspace-less, write-once global evidence table — NOreference_history, no revision versioning, no in-platform user-edit path (schema comment: “Global, workspace-less external reference/evidence layer … Never auto-promotes into content_items”;types/reference.ts:8).ContentRendererrenders exactly one revision.- Verdict: the reference page is NOT a current consumer of the unified diff surface. Its substrate has
no two-revision pair to diff. It is not a thinner twin of item-detail (which has
content_history→VersionHistory→RevisionDiffView); it is a structurally different read-only surface. - Future seam (documented, not in scope):
RevisionDiffViewis source-agnostic by design, so ifreference_itemsever gains areference_historytable + a/api/references/[id]/historyfetch analog, the reference page could become a 4th consumer (<RevisionDiffView older newer />). That requires the DB substrate first → explicitly excluded from ID-117 (OQ-117-6 confirms the exclusion).
8. Empirical verification (pre-ratification)
Section titled “8. Empirical verification (pre-ratification)”| Fact | Method | Result |
|---|---|---|
source_document_diffs row count (retire-safety) | Supabase MCP execute_sql on zjqbrdctesqvouboziae (dev/platform) | 0 rows ✅ (S383 could not run this). Client DB unreachable here → OQ-117-1. |
content_history rows (user-edit substrate is live) | same | 11 rows — leg 3 has real data. |
q_a_pair_history rows | same | 0 rows (Q&A-edit leg wired but unexercised in dev). |
source_documents / reference_items rows | same | 1 / 1. |
extend-ui-kit / DocumentSplitViewer / DocxViewer / XlsxViewer | package.json + node_modules + corpus grep + registry WebFetch | ABSENT in repo; registry extend-hq/ui live, MIT, shadcn-install. No doc-to-doc diff prop on any component (Document Splits = packet-splitter). |
react-pdf ^10.4.1 / diff ^9.0.0 / mammoth ^1.12.0 / exceljs 4.4.0 / @tiptap/markdown 3.23.4 | package.json + usage grep | PRESENT; DOCX/XLSX server-only (no client viewer) → extend-ui fills the gap. |
RevisionDiffView / RevisionBlob (engine to extend) | grep + gitnexus_context | PRESENT revision-diff-view.tsx:229/31; 2 callers (CompareVersionsPanel, QARevisionHistory). |
source_document_diffs table + api. view (retire target) | migration grep | PRESENT squash_baseline.sql:7376/7406/7434. |
content_history.edit_intent / q_a_pair_history.edit_intent | migration | PRESENT (ID-59 substrate) — the provenance the unified surface surfaces. |
No ABSENT/SIGNATURE_DRIFT on a library the v1 surface relies on. extend-ui-kit (the one ABSENT
dependency) is a build prerequisite installed via the registry — it does not block PRODUCT.
9. Open questions for {117.2} PRODUCT
Section titled “9. Open questions for {117.2} PRODUCT”- OQ-117-1 (retirement scope): dev 0-rows CONFIRMED (§8). Remaining: (a) confirm
source_document_diffsis also 0 rows in the client DB (unreachable from this RESEARCH context), and (b) confirm the re-ingest review workflow (status/reviewed_by/reviewer_note/AI-suggest-apply viaaffected_content_item_id) is genuinely retired, vs. re-homed onto the unified surface. The storage drops cleanly only if the workflow is retired, not just the table. - OQ-117-2′ (binary-diff UX precision — REPLACES the old defer-fork): the binary leg is extend-ui-kit
(LOCKED, v1). Since extend-ui’s Document Split Viewer is a packet-splitter (not a version-comparator),
PRODUCT must define the binary version-diff UX: (A) two file-viewer instances side-by-side, visual
compare only (no inline change marking) for v1; (B) add a change-detection overlay (e.g. diff the
extracted_text/markdown and annotate the rendered originals). Plus: which extend-ui components to install (minimum PDF + DOCX + XLSX viewers; Document Splits + File System optional for multi-file packet navigation). This is a behaviour question, not a v1.1 deferral. - OQ-117-3 (canonical-markdown source): for the markdown leg, which projection is canonical — cocoindex
content_items.content(content_history-versioned) or uploadsource_documents.extracted_text(parent_id-versioned)? Post-ID-45 (full-corpus re-ingest), cocoindex is the dominant path — recommend PRODUCT pincontent_items.content+content_historyas canonical,extracted_textlegacy. - OQ-117-4 (binary-revision provenance):
source_documentsrevisions have noedit_intent/change_type/change_summary— what does the provenance panel show for a re-ingest revision (synthesisedchangeType:'reingest',editIntent:null)? - OQ-117-5 (entry-point / IA): leg 1 lives at
/documents/[id]/diff; leg 3 inline inversion-history.tsx/qa-revision-history.tsx. Does the unified surface get ONE route, or stay inline-per-record with a shared engine? Affects {117.4} decomposition. - OQ-117-6 (reference-page scope exclusion): confirm
reference_items(flat, un-versioned; noreference_history) is explicitly OUT of ID-117 scope — future seam only (§7).
10. Recommendations (Liam / Orchestrator decide — this doc writes no ledger)
Section titled “10. Recommendations (Liam / Orchestrator decide — this doc writes no ledger)”- Binary leg ships in v1 on extend-ui-kit (S380 lock). Install the PDF/DOCX/XLSX viewers from the
extend-hq/uiregistry as a build prerequisite; compose the version-compare layer on top (two viewers side-by-side + change affordance) — extend-ui supplies the client DOCX/XLSX rendering the repo lacks. Resolve the binary-diff UX precision via OQ-117-2′. Do not defer binary to v1.1. - Common abstraction =
UnifiedRevision/UnifiedDiff(§4) — a TYPE + fetcher/adapter layer over two physical substrates (thesource_documentschain; the*_historytables). No new diff-storage table. Build by extending the landedRevisionDiffViewwith newrenderModes (side-by-side,word-inline,binary-split). - ID-117 builds ON TOP of the landed {59.12}/{59.16} user-edit diff — it does not consolidate an unbuilt plan. The side-by-side markdown + inline word-highlight that ID-59 INV-15 deferred is the heart of legs 2+3 here; reference ID-59 PRODUCT INV-14..17 as the inherited user-edit contract.
- Retire
source_document_diffs(§6) — 0 rows confirmed (dev); C2 drop once the review-workflow retire-vs-rehome is settled (OQ-117-1). Keep RETIRE and UNIFY as separate PRODUCT invariant clusters. - Right-sizing: Full chain confirmed. Author {117.2} PRODUCT (per-leg behaviour + retirement +
binary-diff UX), {117.3} TECH (the
UnifiedRevisiontype + fetcher/adapter + thesource_document_diffsdrop migration + the render container + the extend-ui install), then {117.4} PLAN (likely: retire-leg-1 slice;UnifiedRevisionadapter+fetchers; side-by-side render-mode; binary-viewer install+compose slice — well under the 25-subtask ceiling). - Re-vendor reminder: dropping
source_document_diffschanges theapi.view-set (generate-api-views.ts:118) →task-view-vendor-drift/schema-paritywill fire; TECH must include the re-vendor step.
11. Sources cited
Section titled “11. Sources cited”| Source | Used for |
|---|---|
S380 transcript dc46727f (verbatim Liam turns; in-session extend-ui WebFetch) | §0, §1, §5 — the grounding decision + extend-ui inventory |
ID-117 ledger record (get task 117 — description/status_note/session_refs) | §0, §1 — authoritative framing |
components/item-detail/revision-diff-view.tsx :12 header comment, :31–46 RevisionBlob, :70/:94 LCS, :229–282 RevisionDiffView | §3.3, §4 — the source-agnostic engine to extend |
components/item-detail/version-history.tsx :92 CompareVersionsPanel, :176 VersionHistory, :283 handleToggleCompare, :480/:490 VersionDiff | §3, §3.3 — content user-edit leg |
components/qa/qa-revision-history.tsx :45 toRevisionBlob, :64 QARevisionHistory, :149 render | §3, §3.3 — Q&A user-edit leg |
components/item-detail/version-diff.tsx :4/:18 (diffWords) | §3.3 — live word-level primitive (not orphan) |
lib/source-documents/document-diff.ts :333 computeFullTextDiff, :507 computeDocumentDiff | §3.1, §6 — legacy compute engine |
app/api/source-documents/[id]/diff/route.ts (GET/POST/PATCH; .from @75–607) | §3.1, §6 — legacy diff API + retire-list |
app/documents/[id]/diff/page.tsx (.from @56–266) | §3.1, §6 — legacy diff page |
components/source-document/{source-document-diff-review,diff-highlighted-text}.tsx + hooks/use-diff-review.ts | §6 — legacy review UI (indirect coupling) |
components/source-document/source-document-history.tsx :256 compare link | §3.1, §6 — version-chain UI + diff entry |
lib/source-documents/source-document-impact.ts :69/:141/:142 analyseDocumentImpact | §6 — impact-matching write |
lib/mcp/tools/content.ts :2033/:2065/:2189 get_document_diff | §6 — MCP retire-list |
scripts/generate-api-views.ts :118; scripts/export-user-data.ts :496/:647/:691 | §6, §10 — api. view + GDPR export; re-vendor reminder |
scripts/cocoindex_pipeline/flow.py :2107 "content": content_text | §3.2, §4 — cocoindex markdown projection |
app/api/upload/route.ts :626 extracted_text write; :324 initial_ingest | §3.2 — upload-path markdown + version chain |
supabase/migrations/20260617130000_squash_baseline.sql :7376/:7406/:7434/:13488–90 (source_document_diffs); :1203/:1488/:1684 (content_history triggers) | §3.1, §3.2, §6 — DB shapes |
app/reference/[id]/page.tsx :33/:64 (UUID gate, ID-111 913effe8), :71 RPC; reference-detail-client.tsx :155; components/item-detail/content-renderer.tsx :89; types/reference.ts :8 | §7 — reference-page overlap |
package.json (react-pdf ^10.4.1, diff ^9.0.0, mammoth ^1.12.0, exceljs 4.4.0, @tiptap/markdown 3.23.4, radix-ui ^1.4.3; extend*→[]); components.json | §5, §8 — installed-viewer empirical + extend-ui absence |
extend.ai /resources/extend-ui-kit + ui.extend.ai/docs (registry extend-hq/ui, MIT) | §5 — current component inventory + diff-prop check |
id-31-canonical-pipeline-implementation-plan/PLAN.md :21/:575 (bl-267 / S243 markdown-sidecar) | §1 — superseded predecessor framing |
ID-59 PRODUCT.md INV-14..17 (:276 INV-15 v1.1 deferral) | §3.3, §6, §10 — inherited user-edit contract |
gitnexus query/context (repo:'canonical'); 3 S384 survey agents; Supabase MCP row counts | §2, §8 — orientation + verification |
End of RESEARCH. Supersedes the S383 draft; grounded on S380 (dc46727f) + the ID-117 ledger.
Recommendations PENDING Liam (§9/§10). No implementation, no DDL, no ledger edits performed.