evidence-anchor/src/types.ts
tegwick a1af0c6a45
All checks were successful
CI Smoke / host-smoke (push) Successful in 4s
CI Smoke / container-smoke (push) Successful in 31s
Implement EANCH-WP-0002 and EANCH-WP-0003 anchor hardening and DOM selectors
WP-0002: stale vs unresolved resolution, orphaned flag/helper, bounded
fuzzy quote recovery, and PdfViewerAdapter promotion with resolveSelectors
integration. WP-0003: DomSelectionCapture, DOM create/resolve ladder,
HtmlViewerAdapter under evidence-anchor/dom, and finished workplans.

Verification: 42 anchor tests, citation-evidence typecheck + 51 tests green.
2026-07-09 09:18:42 +02:00

115 lines
4.3 KiB
TypeScript

/**
* Adapter-side types owned by `evidence-anchor`.
*
* Implements the contract surface from `wiki/SharedContracts.md` §5 and the
* resolution result shape from `wiki/ArchitectureOverview.md` §3.3 / §7.
*
* Anything that mentions a concrete viewer library (pdfjs, react-pdf-highlighter-plus)
* lives *behind* this surface, never on it. `src/shared/` and `src/engine/`
* must never import this file.
*/
import type { Document, DocumentRepresentation } from "@citation-evidence/engine/shared";
import type { Selector } from "@citation-evidence/engine/shared";
import type { AnnotationResolutionStatus } from "@citation-evidence/engine/shared";
import type {
DomNodePath,
NormalizedRect,
StructuralPathSegment,
} from "@citation-evidence/engine/shared";
/**
* The raw selection captured from a viewer adapter — an opaque payload that
* the adapter understands. The shape is intentionally permissive: each
* concrete adapter narrows the `kind` discriminator and adds its own
* payload. The shared layer never inspects the payload directly.
*/
export type SelectionCapture =
| PdfSelectionCapture
| DomSelectionCapture;
export interface PdfSelectionCapture {
readonly kind: "pdf";
/** Verbatim selected text, before canonical normalisation. */
readonly text: string;
/** 1-indexed physical page number the selection started on. */
readonly page: number;
/** Page-relative normalized rectangles covering the selection (0..1). */
readonly rects: readonly NormalizedRect[];
/** Optional bounding rectangle (page-relative, normalized). */
readonly boundingRect?: NormalizedRect;
}
/** Selection captured from a rendered HTML/Markdown viewer. */
export interface DomSelectionCapture {
readonly kind: "dom";
/** Verbatim selected text, before canonical normalisation. */
readonly text: string;
readonly startPath: DomNodePath;
readonly startOffset: number;
readonly endPath: DomNodePath;
readonly endOffset: number;
readonly structuralPath?: readonly StructuralPathSegment[];
}
/**
* A passage located inside a representation, ready to be scrolled to and
* highlighted.
*/
export interface ResolvedAnchorTarget {
readonly representationId: string;
/** 1-indexed page (PDF) or undefined for HTML/Markdown. */
readonly page?: number;
/** Page-relative normalized rectangles to highlight. */
readonly rects?: readonly NormalizedRect[];
/** Canonical-text offsets, when known. */
readonly textPosition?: { readonly start: number; readonly end: number };
}
/**
* The outcome of asking the adapter to resolve a `Selector[]`.
* Matches `wiki/ArchitectureOverview.md` §3.3.
*/
export interface AnchorResolution {
readonly status: AnnotationResolutionStatus;
/** 0..1 confidence in the best candidate. */
readonly confidence: number;
readonly candidates: readonly ResolvedAnchorTarget[];
/** Names of the selector kinds that produced a usable candidate. */
readonly usedSelectorTypes: readonly string[];
readonly warnings?: readonly string[];
/**
* True when no selector could place the anchor at all. Distinct from `stale`,
* which signals representation drift and may still return fuzzy candidates.
*/
readonly orphaned?: boolean;
}
export interface HighlightRenderOptions {
readonly color?: string;
readonly opacity?: number;
}
/**
* The format-neutral viewer adapter contract from `wiki/SharedContracts.md` §5.
*
* Concrete implementations live alongside the viewer they wrap (e.g. the
* PDF spike in `src/anchor/pdf-viewer-adapter-spike.tsx`). The shared/engine
* layers depend only on this interface.
*/
export interface DocumentViewerAdapter {
readonly mediaTypes: readonly string[];
load(document: Document, representation?: DocumentRepresentation): Promise<void>;
getCurrentSelection(): Promise<SelectionCapture | null>;
createSelectorsFromSelection(selection: SelectionCapture): Promise<Selector[]>;
resolveSelectors(selectors: readonly Selector[]): Promise<AnchorResolution>;
scrollToResolvedTarget(
target: ResolvedAnchorTarget,
opts?: { readonly center?: boolean; readonly behavior?: "auto" | "smooth" },
): Promise<void>;
renderHighlight(
target: ResolvedAnchorTarget,
opts?: HighlightRenderOptions,
): Promise<void>;
getHighlightClientRects(annotationId: string): Promise<readonly DOMRect[]>;
}