From bbeba961737c8440a64e0331588464f11204a822 Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 9 Jul 2026 09:58:41 +0200 Subject: [PATCH] EANCH-WP-0004: HtmlViewer scroll parity and viewer-shell workplan Export HtmlViewerAdapter from package root, add scrollRequestKey parity, register finished workplan, update SCOPE. --- SCOPE.md | 5 +- src/dom/html-viewer-adapter.tsx | 11 ++- src/index.ts | 14 +++ .../EANCH-WP-0004-viewer-shell-dom-wiring.md | 94 +++++++++++++++++++ 4 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 workplans/EANCH-WP-0004-viewer-shell-dom-wiring.md diff --git a/SCOPE.md b/SCOPE.md index d375194..a1e1efa 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -51,10 +51,9 @@ Selector *behavior* lives here; selector *type interfaces* live in During the umbrella-first MVP these belong to the appropriate citation-evidence subsystem. This repo may depend on **`citation-engine` only** (DependencyMap §4). -### Still deferred (beyond EANCH-WP-0002 / EANCH-WP-0003) +### Still deferred (beyond EANCH-WP-0004) - `FragmentSelector` (export-only deep links) -- wiring `HtmlViewerAdapter` into `citation-work` / the umbrella review shell - human-confirmation UI for ambiguous/fuzzy matches --- @@ -81,7 +80,7 @@ subsystem. This repo may depend on **`citation-engine` only** (DependencyMap §4 - Status: active — standalone package consumed by `citation-evidence` - Implementation: PDF + HTML/Markdown selector paths, stale/orphan/fuzzy resolution, `PdfViewerAdapter`, and `HtmlViewerAdapter` (`evidence-anchor/dom`) -- Stability: evolving — shared contracts stable; viewer wiring in the umbrella is next +- Stability: evolving — shared contracts stable; HTML/MD wired in `citation-work` ViewerShell - Usage: internal (consumed by `citation-evidence` during MVP) --- diff --git a/src/dom/html-viewer-adapter.tsx b/src/dom/html-viewer-adapter.tsx index d6f43a2..609c823 100644 --- a/src/dom/html-viewer-adapter.tsx +++ b/src/dom/html-viewer-adapter.tsx @@ -32,6 +32,11 @@ export interface HtmlViewerAdapterProps { readonly representation: DocumentRepresentation; readonly storedAnnotations?: readonly StoredHtmlAnnotation[]; readonly scrollToAnnotationId?: string; + /** + * Bumps when the same annotation should be re-scrolled (e.g. repeat click). + * Format is opaque — typically `${annotationId}:${version}`. + */ + readonly scrollRequestKey?: string; readonly activeAnnotationId?: string | null; readonly debug?: HtmlViewerDebugOptions; onSelectionCaptured?(capture: DomSelectionCapture, selectors: Selector[]): void; @@ -50,6 +55,7 @@ export function HtmlViewerAdapter(props: HtmlViewerAdapterProps): ReactNode { representation, storedAnnotations = [], scrollToAnnotationId, + scrollRequestKey, activeAnnotationId, debug, onSelectionCaptured, @@ -91,12 +97,13 @@ export function HtmlViewerAdapter(props: HtmlViewerAdapterProps): ReactNode { }, [html, storedAnnotations, representation, activeAnnotationId]); useEffect(() => { - if (!scrollToAnnotationId || !rootRef.current) return; + const requestKey = scrollRequestKey ?? scrollToAnnotationId ?? null; + if (!requestKey || !scrollToAnnotationId || !rootRef.current) return; const mark = rootRef.current.querySelector( `[data-highlight-id="${CSS.escape(scrollToAnnotationId)}"]`, ); mark?.scrollIntoView({ block: "center", behavior: "smooth" }); - }, [scrollToAnnotationId, renderedHtml]); + }, [scrollToAnnotationId, scrollRequestKey, renderedHtml]); return (