evidence-anchor/README.md
tegwick 5b77dbd314 EANCH-WP-0001 T04: extract PDF adapter + scroll/highlight helpers
Move pdf-viewer-adapter-spike.tsx, scroll-job.ts (+test), highlight-styles.css
and debug-textlayer.css into src/pdf/ (the adapter boundary). Rewrite the
spike's @shared import to @citation-evidence/engine/shared and its ./types to
../types. Add src/css.d.ts so tsc accepts side-effect .css imports.

Decisions:
- adapter stays the explicitly-named PdfSpikeViewer spike; promotion to a
  production PDFViewerAdapter is registered as T06 follow-on work
- viewer libs (pdfjs-dist, react, react-pdf-highlighter-plus) confined to
  src/pdf/ and exposed only via the evidence-anchor/pdf subpath; the root
  entrypoint stays pure. Boundary enforced by eslint no-restricted-imports.
- ported unit suites (pdf-selector-math round-trip + scroll-job retry) serve
  as the inspectable harness for capture->selectors->resolve->scroll.

Verified: pnpm test (30 passed), typecheck, lint all green. README layout updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 20:44:52 +02:00

4.1 KiB

evidence-anchor

Selector creation, resolution, and the DocumentViewerAdapter contract that every document viewer in the citation-evidence workspace implements. This repo turns annotations from static marks into durable, reopenable source references.

  • Owns: selector behaviorcreateSelectors, resolveSelectors, PDF selector math, the viewer-adapter contract, and highlight/scroll helpers.
  • Does not own: selector type interfaces — those live in citation-engine (shared/selector). See ADR-0006 and SharedContracts.md §8.
  • May depend on: citation-engine only (DependencyMap §4). Nothing from binder/, source/, or work/ may flow back into it.

See SCOPE.md for the boundary and INTENT.md for the long-range intent.

Status: extracting from the umbrella

The concrete anchor slice currently lives upstream in ../citation-evidence/src/anchor/. Workplan EANCH-WP-0001 moves it here as a standalone TypeScript package, wires the umbrella to consume this package, and verifies the round-trip. Shared-contract changes still happen in the umbrella (citation-evidence/wiki/), not here.

Install model

Sibling-checkout, linked-package model — this repo is checked out next to its consumers and consumed via a local link (e.g. link:../evidence-anchor), not published to a registry during MVP. Its only shared-type dependency is citation-engine, imported through the engine's public shared entrypoint (@citation-evidence/engine/shared) rather than umbrella-only @shared/* aliases.

Package layout (initial extracted version)

src/
  index.ts                      public entrypoint — pure surface only
  types.ts                      adapter-side types: SelectionCapture,
                                ResolvedAnchorTarget, AnchorResolution,
                                HighlightRenderOptions, DocumentViewerAdapter
  css.d.ts                      ambient decl for side-effect .css imports
  selectors/                    pure core — no viewer/UI deps
    index.ts                    createSelectors, resolveSelectors, DEFAULT_CONTEXT_CHARS
    create.ts                   selector creation from a captured selection
    resolve.ts                  resolution + the exact-match confidence ladder
    create.test.ts
    resolve.test.ts
  pdf/                          adapter boundary — the only place viewer libs live
    index.ts                    subpath entry `evidence-anchor/pdf`
    pdf-selector-math.ts        pure page + normalized-rect math (capture↔selectors)
    pdf-selector-math.test.ts
    pdf-viewer-adapter-spike.tsx  concrete PDF adapter (PdfSpikeViewer)
    scroll-job.ts               retryable scroll-to-highlight helper
    scroll-job.test.ts
    highlight-styles.css        highlight rendering styles
    debug-textlayer.css         optional text-layer debugging styles

Boundary rules for the layout (enforced by eslint.config.js):

  • viewer-library imports (pdfjs-dist, react, react-pdf-highlighter-plus) are confined to src/pdf/ — the pure zone (src/selectors/**, src/types.ts) may not import them;
  • src/selectors/ is pure and depends only on citation-engine shared types;
  • the root entrypoint (evidence-anchor) exposes only the pure surface — createSelectors, resolveSelectors, the selector/resolution types, and the DocumentViewerAdapter contract;
  • the concrete PDF adapter is reached through the evidence-anchor/pdf subpath, so pure consumers never pull PDF.js or React into their bundle. That adapter is still the explicitly-named PdfSpikeViewer spike; promoting it to a production PDFViewerAdapter is registered follow-on work (T06).

Public API (target surface)

import {
  createSelectors,
  resolveSelectors,
  type DocumentViewerAdapter,
  type AnchorResolution,
} from "evidence-anchor";

Resolution is explicit about uncertainty — AnchorResolution.status is one of resolved / ambiguous / unresolved / stale with a 0..1 confidence, so a caller can highlight, ask the user to confirm, or mark a citation stale rather than silently highlight the wrong passage.