ID-117 {117.4} PLAN — Unified document/revision diff surface (decomposition of the ratified PRODUCT+TECH into implementation Subtasks across clusters A–E)
ID-117 {117.4} PLAN — Unified document/revision diff surface
Section titled “ID-117 {117.4} PLAN — Unified document/revision diff surface”Inputs (read in full): ratified
{117.2}PRODUCT.md (20 Behavior invariants, clusters A–F), ratified{117.3}TECH.md (1:1 Proposed-change-per-invariant, clusters A–E + F, RETIRE migration plan), and{117.1}RESEARCH.md (CORRECTED S384). This PLAN decomposes all of clusters A–E (cluster F wiring folds into cluster A’s container slice) into 9 implementation Subtasks for the Executor wave. Authored by a FRESH Planner (Q-PLANNER-2) runningplanning-and-task-breakdown.REV S391 (this revision) — 5 staff-review edits + 2 Liam decisions, applied WITHOUT touching the APPROVED spine (
UnifiedRevision/UnifiedDifftype+adapter, option-C binary leg, gatedsource_document_diffsDROP, api-view regen + task-view re-vendor). Summary of what changed vs the 8-Subtask draft (full rationale in §8 Revision log):
- item 3 absorbs jscpd CMP-2 — additionally extracts
lib/diff/line-diff.ts(the sharedcomputeLineDiff/buildLcsTable/OP_CLASS/OP_PREFIX) and re-pointsPromptDiffView. ID-117 is the single owner oflib/diff/. jscpd Wave-3 S2 (CMP-2) is struck in favour of this (parent records that roadmap edit on main).- item 7 WIDENED — retirement reach now enumerates the full
diff_available→reupload-bannerfeature chain (a SECOND dangling affordance INV-15 did not name) and is sequenced AFTER a new gated investigation Subtask.- NEW item 8 (investigation) — Liam decision: investigate whether the re-ingest notification path (
sendSourceDocumentUpdateNotifications, independent of the diff display) is live/used, BEFORE the DROP. Retire-vs-rehome of that path is DEFERRED to this finding; the DROP must not proceed past it unresolved.- item 9 (was item 8) deletion list WIDENED — adds
e2e/tests/document-diff.spec.ts+ the unit tests referencingsource_document_diffsso item 9’s own acceptance grep passes.- ID-121 sequencing note added (§7) — CodeQL must defer/dismiss
source-document/*diff-file findings as “retired by ID-117”.UnifiedDifftype nit noted in item 1 — deriverenderModefromviewDepth, don’t store both free (avoids illegal combos).Resolved gates carried into this PLAN (no re-litigation):
- OQ-117-1 → RETIRE (Liam Decision C / S388):
source_document_diffsis 0 rows in the CLIENT DB too; the re-ingest review workflow is RETIRED, not re-homed. The drop gate is CLEARED — cluster E proceeds; no RE-HOME review table, no apply/dismiss affordance. The RE-HOME addendum in TECH §6 is moot. NOTE (REV S391): OQ-117-1 retired the diff review workflow; the re-ingest notification path is a SEPARATE behaviour the new item 8 investigation resolves (see §8).- OQ-117-2′ → option C (visual-compare-only + alongside text summary for v1; change-overlay → v1.1). Honoured in the binary-pane slice; not re-opened.
- OQ-117-3 →
content_items.contentviacontent_historycanonical for the markdown depth;extracted_textlegacy (binary-leg text fallback only). Resolved in TECH §4.- OQ-117-4 → synthesised
changeType:'reingest'/'initial_ingest',editIntent:null. TECH §4.- OQ-117-5 → TECH default (binary at the rebuilt
/documents/[id]/diff; markdown/user-edit inline) — adopted as this PLAN’s IA, folded into the container slice (item 6).- OQ-117-6 → reference page OUT of scope (no
reference_historyto diff).
1. Context + code-intelligence orientation (cited verbatim)
Section titled “1. Context + code-intelligence orientation (cited verbatim)”ID-117 builds ONE unified diff/revision surface over a common revision abstraction —
UnifiedRevision / UnifiedDiff (a TYPE + adapter layer generalising the landed RevisionBlob,
NOT a DB schema change) — at three view-depths (binary / canonical-markdown / user-edit), and
retires the 0-row source_document_diffs engine. The architectural spine: establish the
type+adapter spine first (items 1–2), extend the shared RevisionDiffView engine with render modes
and extract the shared LCS primitive into lib/diff/line-diff.ts (item 3, REV S391: absorbs
CMP-2), install + compose the binary leg (items 4–5), wire the container + entry points (item 6),
investigate the re-ingest notification path’s fate (item 7, gated), then retire
source_document_diffs code couplings (item 8) and drop the table + re-vendor (item 9).
Orientation tooling note: gitnexus MCP tools were not exposed in this Planner’s tool surface, so
per the binding code-intel fallback (gitnexus → ccc → grep) the citations below were INDEPENDENTLY
re-verified by grep against the worktree (source_document_diffs is a DB table + several couplings
are in .ts/.sql outside the TS graph). The PRODUCT/TECH gitnexus citations (S384/S385) are
reproduced and confirmed; the REV S391 widen citations were freshly grep-verified:
RevisionDiffView(gitnexus_context, TECH §1):components/item-detail/revision-diff-view.tsx—export function RevisionDiffView(confirmed @229 (grep);export interface RevisionBlobconfirmed @31 (grep). Incoming calls: exactly TWO —CompareVersionsPanel(components/item-detail/version-history.tsx,function CompareVersionsPanelconfirmed @92) andQARevisionHistory(components/qa/qa-revision-history.tsx). grep forRevisionDiffViewacrosscomponents/returns exactly these two callers + the definition file.gitnexus_impact(TECH §1): risk LOW, direct 2, processes 0, modules 1. The two-caller seam is the central extension point.- Shared LCS primitive (REV S391 — CMP-2, grep-verified):
computeLineDiff/buildLcsTable/OP_CLASS/OP_PREFIXare duplicated verbatim in exactly two files —components/item-detail/revision-diff-view.tsx(buildLcsTable@70,computeLineDiff@94,OP_CLASS@139,OP_PREFIX@146) andcomponents/intelligence/prompt-refinement/prompt-diff-view.tsx(buildLcsTable@40,computeLineDiff@65,OP_CLASS@117,OP_PREFIX@123). grep forcomputeLineDiff/buildLcsTableacrosscomponents/+lib/returns ONLY these two files → ID-117 is the correct single owner of the extractedlib/diff/line-diff.ts. CompareVersionsPanel— confirmedcomponents/item-detail/version-history.tsx:92(definition), rendered @387. Readscontent_historyonly (header comment :88–90 confirmed).source_document_diffs(DB table — grep sweep, TS+SQL+Py): couplings confirmed atlib/source-documents/source-document-impact.ts(@69 read, @141 write),lib/mcp/tools/content.ts(@1994/@2118,get_document_diffdef @1962),app/api/source-documents/[id]/diff/route.ts(@75/125/183/412/490/552/586/607),app/documents/[id]/diff/page.tsx(@56–266),scripts/export-user-data.ts(type @496, read @647, assign @691),scripts/generate-api-views.ts:**118**(the mirrored-table array — confirmed),components/source-document/source-document-history.tsx:**256**(compare link →/documents/{id}/diff),hooks/use-diff-review.ts(review hook). SQL:supabase/migrations/20260617130000_squash_baseline.sql.- ⚠ FULL upload→banner feature chain (REV S391 — widened item-8 retire scope; freshly grep-verified):
The upload re-ingest block
app/api/upload/route.ts:903–969dynamically importscomputeDocumentDiff(@906) +analyseDocumentImpact(@908), INSERTs intosource_document_diffs@943, setsdiffAvailable=true@945, runsanalyseDocumentImpact@948, conditionally sends notifications viasendSourceDocumentUpdateNotifications(dynamic import @955, call @957), and the response setsdiff_available: true@1057. That flag flows:route.ts:1057→hooks/use-file-upload-pipeline.ts:62(diffAvailable?) /:354(diffAvailable: data.diff_available ?? false) →components/create-content/upload-tab-content.tsx:677–683(<ReuploadBanner … diffAvailable … diffDocumentId={…newDocumentId} />) →components/source-document/reupload-banner.tsx:20/45(props):93–101renders a live<Link href={/documents/${diffDocumentId}/diff}>“Review Q&A changes”. This is a SECOND dangling affordance to the deleted page that INV-15 does NOT name (INV-15 names onlysource-document-history.tsx:256). Item 8 (RETIRE) must clean up this whole chain so neither entry point 404-gaps. This is a LIVE WRITE path: dropping the table while the INSERT @943 + thedocument-diff.tsimport remain breaks the upload flow at runtime AND breaksbun run build.
- ⚠ Notification path (REV S391 — gated, item 7): the SAME block drives
analyseDocumentImpactsendSourceDocumentUpdateNotifications— a re-ingest notification feature (lib/source-documents/ source-document-notifications.ts,export … sendSourceDocumentUpdateNotifications@25) that is INDEPENDENT of the diff display. Its only non-test caller is the upload route. The DROP would silently kill this notification behaviour, which no invariant acknowledges. Liam decision: investigate live usage FIRST (item 7) — retire-vs-rehome of the notification path is DEFERRED to that finding.
- ⚠ FULL upload→banner feature chain (REV S391 — widened item-8 retire scope; freshly grep-verified):
The upload re-ingest block
generate-api-views.ts—'source_document_diffs'confirmed in the mirrored-table array atscripts/generate-api-views.ts:**118**(betweensignup_policyandsource_documents). Dropping the table → this entry is removed → theapi.view-set regenerates →task-view-vendor-drift/schema-parityfire → re-vendor REQUIRED (item 9).RevisionBlob—interface RevisionBlob@31; becomes a structural subset ofUnifiedRevision(TECH §1.1); the widen is non-breaking (additive optionalsbinary/recordKind).
Substrate facts (grep-confirmed): storage bucket = documents (app/api/upload/route.ts:378
.from('documents'), storage_path @430); signed-URL template = createSignedUrl(path, 300)
(app/api/procurement/[id]/tender/download/route.ts:67-68); PDF client viewer present
(components/reader/pdf-viewer.tsx:4 import { Document, Page } from 'react-pdf'); diffWords
primitive live (components/item-detail/version-diff.tsx:18); components.json has no registries
block (only style/aliases — extend-ui install must add it).
2. Architecture decisions
Section titled “2. Architecture decisions”- Spine =
UnifiedRevisionTYPE + adapter layer (NOT a DB change). One type generalisesRevisionBlob; three adapters (content-item, qa-pair, source-document) project each substrate into it; the three view-depths all project through this one revision abstraction. The ONLY DB change in the whole Task is thesource_document_diffsDROP (cluster E). UnifiedDifftype-design nit (REV S391, type-design note in item 1).viewDepthandrenderModeonUnifiedDiffare partly redundant and admit illegal combos (e.g.viewDepth:'binary'+renderMode:'word-inline'). DeriverenderMode’s default FROMviewDepth(binary→binary-split, canonical-markdown/user-edit→unified-line, overridable toside-by-side/word-inlineonly for the content-item depths) rather than carrying both as independent free fields — or constrain via a discriminated union. This is a type-shape refinement on the spine, not a spine change.lib/diff/has ONE owner: ID-117 (REV S391). ID-117 hostsunified-revision.ts(item 1),adapters/(items 1–2), andline-diff.ts(item 3, extracted from the shared LCS primitive jscpd CMP-2 named). jscpd Wave-3 S2 is struck.revision-diff-view.tsxANDprompt-diff-view.tsxboth consume the extracted primitive — the duplication CMP-2 flagged is killed inside ID-117’s churn window instead of by a separate Wave-3 PR re-touching a file ID-117 just rewrote.- Extend, don’t replace,
RevisionDiffView. NewrenderModeprop (default derived as above → byte-identical user-edit output, INV-12). Side-by-side + word-inline are render-mode additions, not a new engine. The binary leg is a NEW composition alongside the engine, with the text line-diff shown alongside (option C). - Cluster E is THREE slices (notification investigation, code-deletion, then DB-drop) in strict order
to respect the type-regen ordering trap (TECH §6) AND the new notification gate: item 7 resolves the
notification path’s fate (gate); item 8 removes all code couplings INCLUDING the full upload→banner
chain +
export-user-data.ts:496’s type ref; item 9 then drops the table, regens types, regens api-views, and re-vendors. Reversing the order breaksbun run buildon regen; bypassing item 7 silently kills a notification feature. - Unify legs (A–D) are INDEPENDENT of cluster E (TECH §6) — they share no file with the retire
slices except the rebuilt
/documents/[id]/diffroute (item 6 ↔ item 8 coordinate the rebuild-vs-delete boundary). The dep graph sequences accordingly so the drop lands after the new surface preserves the leg-1 use-case.
3. Dependency graph (sibling ordinals)
Section titled “3. Dependency graph (sibling ordinals)”item 1 UnifiedRevision type (+renderMode-from-viewDepth) + content-item & qa-pair adapters (cluster A) deps: none │ ├── item 2 source-document adapter + binary-url signed route (cluster A→B) deps: 1 │ │ ├── item 3 RevisionDiffView renderMode + extract lib/diff/line-diff.ts (cluster C+D, CMP-2) deps: 1 │ │ + re-point PromptDiffView │ │item 4 extend-ui-kit registry install (DOCX/XLSX; PDF=react-pdf) (cluster B) deps: none (parallel) │ │ └──────┴── item 5 binary-diff-pane compose + INV-6 fallback (cluster B) deps: 2,3,4 │ item 6 UnifiedDiffContainer + entry-point wiring (cluster F+A) deps: 3,5 │ item 7 INVESTIGATE re-ingest notification path (GATE) (cluster E) deps: 6 │ → retire-vs-rehome DECISION (Liam, deferred to finding) │ item 8 RETIRE legacy code/UI/MCP + full upload→banner chain (cluster E) deps: 7 │ item 9 drop migration + type/api-view regen + re-vendor (cluster E) deps: 8Item 4 (install) is parallelisable with items 1–3. Cluster E (7→8→9) is strictly last and strictly ordered; item 8 (the DROP code-deletion) must NOT proceed past item 7 without resolving the notification path’s fate (Liam-gated). 9 Subtasks — well under the 25-Subtask soft ceiling. All dependencies are sibling-only (ID-117 internal); no cross-Task dependency surfaced — no escalation required. (The ID-121 CodeQL overlap in §7 is a sequencing note, not a Subtask dependency.)
4. Subtask list (cluster → invariants → verification)
Section titled “4. Subtask list (cluster → invariants → verification)”| # | Title | Cluster | Spec slices | Deps | Acceptance (testStrategy) |
|---|---|---|---|---|---|
| 1 | UnifiedRevision type (renderMode-from-viewDepth) + content-item/qa-pair adapters | A | INV-1/2/3, TECH §1.1 | — | New adapters yield well-formed UnifiedRevision from fixtures; depth→recordKind correct; renderMode default derived from viewDepth (no illegal combo); no >2-rev path; no diff write; bun run test+build green |
| 2 | source-document adapter + signed-URL binary-url route | A→B | INV-4/8, OQ-117-4 | 1 | Route: unauth→authFailureResponse, cross-workspace row→no URL, success→300s URL on documents; adapter synthesises reingest/initial_ingest provenance |
| 3 | RevisionDiffView renderMode + extract lib/diff/line-diff.ts + re-point PromptDiffView (CMP-2) | C+D | INV-10/11/12/13, jscpd CMP-2 | 1 | INV-12 regression: existing suite passes UNCHANGED, callers byte-identical; line-diff extracted to lib/diff/line-diff.ts, BOTH RevisionDiffView+PromptDiffView consume it; new modes render diffWords + semantic tokens |
| 4 | extend-ui-kit registry install (DOCX/XLSX viewers) | B | INV-5, Empirical | — | components.json registry added; DOCX/XLSX viewers import clean vs React 19.2.6/Next 16.2.6; dated re-verification line recorded; build green |
| 5 | binary-diff-pane compose + INV-6 fallback + text summary | B | INV-5/6/7 (OQ-117-2′=C) | 2,3,4 | PDF/DOCX/XLSX→correct viewer; unsupported/signed-URL-fail→fallback notice (not blank); text summary alongside; NO binary inline-marking |
| 6 | UnifiedDiffContainer + entry-point wiring | F+A | INV-1/17/18/19/20 | 3,5 | Binary at /documents/[id]/diff, md/user-edit inline; read-only, NO apply/dismiss (RETIRE); workspace-scoped; no AI labelling; INV-12 preserved |
| 7 | INVESTIGATE re-ingest notification path (live-usage finding + retire-vs-rehome decision) — GATE | E | INV-15 (gate), A1 | 6 | Finding documents whether sendSourceDocumentUpdateNotifications is live/used (callers, telemetry, Liam input); retire-vs-rehome decided + recorded; DROP (item 8) blocked until resolved |
| 8 | RETIRE legacy code/UI/MCP + full diff_available→reupload-banner chain + GDPR | E | INV-14/15/16 (RETIRE), A1/A2 | 7 | grep source_document_diffs → zero non-migration/non-comment TS hits; reupload-banner diff-link + diff_available plumbing removed; both entry points 404-gap-free; notification path handled per item-7 decision; build green |
| 9 | drop migration + type/api-view regen + task-view re-vendor (+ test/e2e deletion) | E | INV-14/16 (RETIRE), A2 | 8 | View-before-table drop; e2e/document-diff.spec.ts+4 unit tests removed; types regen clean; api-view excludes view; task-view-vendor-drift/schema-parity GREEN after re-vendor; build+test green |
Full load-bearing details per Subtask (file lists, line anchors, deletion lists, gitnexus discipline
reminders, the full upload→banner chain, the notification-gate decision record) are carried in the
dispatch briefs the Orchestrator forms from this PLAN — not duplicated here.
4.1 Widened item-8 deletion set (REV S391 — full upload→banner feature chain, A1)
Section titled “4.1 Widened item-8 deletion set (REV S391 — full upload→banner feature chain, A1)”Item 8 must delete/clean the WHOLE chain, not just the upload/route.ts:943 INSERT the prior draft caught:
app/api/upload/route.ts:903–969— remove the re-ingest diff block (thecomputeDocumentDiff+source_document_diffsINSERT @943); item 7’s decision governs whether theanalyseDocumentImpact/sendSourceDocumentUpdateNotificationslines survive (rehome) or go (retire). Remove thediff_available: trueresponse field @1057 ifdiffAvailableis no longer set.hooks/use-file-upload-pipeline.ts:62(diffAvailable?field) +:354(diffAvailable: data.diff_available ?? false) — remove the plumbing.components/create-content/upload-tab-content.tsx:682(diffAvailable={…}prop) +:683(diffDocumentId={…newDocumentId}) — remove the props passed to<ReuploadBanner>.components/source-document/reupload-banner.tsx:20/45(diffAvailable/diffDocumentIdprops) +:93–101(the live<Link href={/documents/${diffDocumentId}/diff}>“Review Q&A changes”) — remove the dangling affordance (the SECOND entry point INV-15 did not name).- The INV-15-named compare link
components/source-document/source-document-history.tsx:256(already in scope).
4.2 Widened item-9 deletion set (REV S391 — test/e2e surface, A2)
Section titled “4.2 Widened item-9 deletion set (REV S391 — test/e2e surface, A2)”Item 9’s own acceptance grep (“zero non-migration TS hits for source_document_diffs”) is tripped by
the test/e2e files unless they are deleted in the same slice. Add to item 9’s deletion set (named in
RESEARCH §6, absent from the prior per-subtask acceptance):
e2e/tests/document-diff.spec.ts— seeds the table, so it hard-fails post-drop (not merely a dangling reference). Delete.- Unit tests referencing
source_document_diffs/ the retired engine:__tests__/mcp/document-diff-tool.test.ts,__tests__/lib/source-documents/document-diff.test.ts,__tests__/api/source-documents-diff.test.ts,__tests__/components/source-document-diff-review.test.tsx, plus theuse-diff-review/reupload-banner/source-document-historytest assertions that reference the deleted couplings. (Executor runs the full grep at item-9 start to enumerate the exact set; the acceptance gate is zero non-migration hits.)
5. Checkpoints
Section titled “5. Checkpoints”- After items 1–3 (spine + engine + CMP-2 extraction):
bun run testgreen incl. the INV-12 regression suite UNCHANGED;bun run buildclean; UnifiedRevision adapters + render modes unit-tested;lib/diff/line-diff.tsextracted, BOTHRevisionDiffViewandPromptDiffViewconsume it (CMP-2 resolved — jscpd Wave-3 S2 confirmed struck). Review before the binary leg. - After items 4–6 (binary leg + container): binary depth renders at
/documents/[id]/diff; INV-6 fallback verified (no blank panel); all three depths back onto the one engine; read-only roles enforced. The new surface PRESERVES the leg-1 use-case — prerequisite for the drop. - After item 7 (notification GATE): the re-ingest notification path’s live-usage finding is recorded and a retire-vs-rehome decision is made (Liam) and documented. Item 8 MUST NOT start until this gate resolves. This is the explicit forcing function the A1 review demands.
- After items 8–9 (retirement): zero
source_document_diffsreferences survive (grep/ast sweep incl. the full upload→banner chain + test/e2e files); the notification path is handled per the item-7 decision; table+view dropped; types/api-views regenerated; re-vendor leaves the side-workflows GREEN; GDPR export runs clean. Task close.
6. Risks and mitigations
Section titled “6. Risks and mitigations”| Risk | Impact | Mitigation |
|---|---|---|
Notification path silently killed by the DROP (REV S391, A1) — sendSourceDocumentUpdateNotifications is independent of the diff display; the prior draft’s item-7 deletion would have removed it with no decision | High | NEW item 7 gate: investigate live usage FIRST; retire-vs-rehome decided + recorded BEFORE item 8; item 8 cannot proceed past it unresolved. |
Second dangling affordance (REV S391, A1) — reupload-banner.tsx:93 “Review Q&A changes” link to the deleted /documents/{id}/diff page, NOT named by INV-15 | High | Item 8 deletion set §4.1 enumerates the full diff_available→banner chain (route→hook→tab→banner); both entry points must be 404-gap-free. |
item-9 acceptance grep tripped by test/e2e files (REV S391, A2) — e2e/document-diff.spec.ts SEEDS the table → hard-fail post-drop | Med | Item 9 deletion set §4.2 adds the e2e spec + the 4 unit tests so the acceptance grep passes. |
Upload-route write coupling (upload/route.ts:903-969) missed → drop breaks re-ingest at runtime + build | High | Surfaced + line-anchored in §1 / §4.1; folded into item 8’s explicit deletion list. |
Type-regen ordering trap (drop before removing export-user-data.ts:496) → build break on regen | High | Strict dep 8→9; item 8 removes the export type ref BEFORE item 9’s regen (TECH §6). |
lib/diff/ two-owner collision with jscpd Wave-3 S2 (REV S391, A3/C) | Med | ID-117 absorbs CMP-2 (item 3 extracts line-diff.ts); jscpd Wave-3 S2 struck (parent records the roadmap edit on main); lib/diff/ has one owner. |
RevisionBlob→UnifiedRevision widen regresses the 2 callers | Med | INV-12 regression gate (existing suite passes unchanged) + structural-subset design + LOW gitnexus_impact (2 callers) — run impact before the widen (item 1/3). |
| extend-ui DOCX/XLSX viewer fails to install vs React 19.2.6/Next 16.2.6 | Med | Item 4 re-fetches the registry + records a dated empirical line; on fail, escalate; INV-6 text fallback covers a missing viewer gracefully (never blank); PDF uses the working react-pdf. |
Re-vendor step skipped → task-view-vendor-drift/schema-parity red in CI | Med | Item 9 makes re-vendor an explicit, verified step (INV-16); checkpoint gates on the side-workflows green. |
binary-url route added to proxy.ts publicRoutes by mistake | Low | Item 2 details explicitly: auth-gated route stays OFF the allowlist (TECH §6 inverse-mistake note). |
7. ID-121 (CodeQL baseline) sequencing note (REV S391, edit B — non-blocking)
Section titled “7. ID-121 (CodeQL baseline) sequencing note (REV S391, edit B — non-blocking)”ID-121’s CodeQL resolution waves must defer/dismiss the source-document/* diff-file findings as
“retired by ID-117” — do NOT fix code ID-117 deletes. Concretely:
js/implicit-returnatcomponents/source-document/source-document-diff-review.tsx:84(getDiffTypeBadgeClasses) — this file is DELETED by ID-117 item 8. A CodeQL fix here is wasted work on a file about to be removed; ID-121 should dismiss/defer it as “retired by ID-117”.js/mixed-returnsatcomponents/source-document/source-document-history.tsx:69— ID-117 item 8 modifies this file (removes the compare link @256). Don’t both touch: ID-121 defers, or sequences after ID-117 cluster E.js/mixed-returnsatcomponents/reader/pdf-viewer.tsx:171— ID-117’s binary leg may reuse this (OQ-117-7 PDF path); low-risk, but coordinate so neither stream churns it twice.
ID-121’s genuine-fix core (lib/validation/index.ts:111-120, lib/attention.ts:368,
lib/reorient.ts:485, error-boundary.tsx:24) is disjoint from ID-117 — the two tasks are ~95%
independent; only the 3 diff/source-document files above need a “don’t both touch” note. This is a
SEQUENCING note for the Orchestrator/ID-121, NOT an ID-117 Subtask dependency.
8. Revision log (REV S391) + open questions carried to implementation (non-blocking)
Section titled “8. Revision log (REV S391) + open questions carried to implementation (non-blocking)”REV S391 — what changed vs the 8-Subtask draft (5 staff-review edits + 2 Liam decisions):
- A1 [HIGH] — item 8 (RETIRE) widened + NEW item 7 (notification gate). The prior draft caught
only the
upload/route.ts:943INSERT. The full reach (§1, §4.1) is thediff_available→use-file-upload-pipeline.ts→upload-tab-content.tsx→reupload-banner.tsx:93“Review Q&A changes” link — a SECOND dangling affordance to the deleted page that INV-15 does not name. The same block drivesanalyseDocumentImpact+sendSourceDocumentUpdateNotifications— a re-ingest notification feature independent of the diff display that the DROP would silently kill. Liam decision: investigate live usage FIRST — NEW item 7 (its own Subtask, sequenced before the DROP). Retire-vs-rehome of the notification path is DEFERRED to that finding; item 8 must not proceed past item 7 unresolved (gated). - A2 [MED] — item 9 deletion list completed (§4.2). Added
e2e/tests/document-diff.spec.ts(which SEEDS the table → hard-fails post-drop) + the unit tests referencingsource_document_diffs(named in RESEARCH §6) so item 9’s own acceptance grep passes. - Nit —
UnifiedDifftype fixed (item 1, §2).viewDepth+renderModewere partly redundant and admitted illegal combos; deriverenderMode’s default FROMviewDepth(or discriminated union) rather than store both free. - B — ID-121 sequencing note added (§7). ID-121 should defer/dismiss the
source-document/*diff-file findings (e.g.js/implicit-return@source-document-diff-review.tsx:84) as “retired by ID-117”; don’t fix code ID-117 deletes. - C — jscpd CMP-2 folded into item 3 (Liam-approved). Item 3 additionally extracts
lib/diff/line-diff.ts(the sharedcomputeLineDiff/buildLcsTable/OP_CLASS/OP_PREFIXused by BOTHrevision-diff-view.tsxANDprompt-diff-view.tsx) and re-pointsPromptDiffView. ID-117 is the single owner oflib/diff/. jscpd Wave-3 S2 (CMP-2) is struck in favour of this (the parent records that roadmap edit on main). CMP-1 (PDF clone) is adjacent — coordinate the OQ-117-7 PDF path so ID-117 doesn’t add a third PDF consumer while Wave-3/S1 extractspdf-document.tsx.
The APPROVED spine is UNCHANGED: UnifiedRevision/UnifiedDiff type+adapter, option-C binary leg
(visual-compare v1 + alongside text, overlay v1.1), gated source_document_diffs DROP, api-view regen
- task-view re-vendor. None of the 5 edits touch the spine — they tighten the blast radius the spine already implies.
Open questions carried to implementation (non-blocking):
- OQ-117-7 (PDF viewer choice) — item 4 default: reuse
react-pdffor PDF, extend-ui only for DOCX/XLSX. Confirm at install time. (Coordinate with jscpd CMP-1 per edit 5.) - Item 6 ↔ item 8 boundary — whether item 8 deletes
app/documents/[id]/diff/page.tsxand item 6 rebuilds it, or item 6 owns the full replace. Either is valid; the route must never 404-gap. Executor picks the lower-churn boundary; flagged in both Subtasks’ details. - OQ-NOTIF (item 7 output) — the retire-vs-rehome decision for the re-ingest notification path is
deferred to item 7’s finding; item 8’s handling of
analyseDocumentImpact/sendSourceDocumentUpdateNotificationsfollows that decision.
End of PLAN (REV S391). Decomposes the ratified PRODUCT+TECH into 9 sibling-ordered Subtasks
across clusters A–E (cluster F folded into the container slice). OQ-117-1 RETIRE / OQ-117-2′ option-C /
OQ-117-3/-4/-5/-6 dispositions honoured (not re-litigated). 5 staff-review edits + 2 Liam decisions
applied without touching the APPROVED spine: item 3 absorbs jscpd CMP-2; item 8 (RETIRE) widened to the
full upload→banner chain; NEW item 7 gates the re-ingest notification path; item 9 deletion list widened
to the test/e2e surface; ID-121 sequencing note added; UnifiedDiff type nit noted. Code-intel
orientation re-verified by grep (gitnexus fallback). No cross-Task dependency — no escalation. No
implementation, no DDL, no ledger writes, no commits performed.