From 86d10e7e904495a3f45d3856eaa6f796073f63a5 Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 9 Jul 2026 09:18:42 +0200 Subject: [PATCH] Promote DomRange and Structural selector types for non-PDF anchoring Define real DomRangeSelector, StructuralSelector, StructureMap, and containerPath fields so evidence-anchor can implement HTML/Markdown selector creation and resolution. Update SharedContracts.md with field shapes and DomSelectionCapture payload. --- src/shared/document.ts | 20 +++++++++++--- src/shared/selector.ts | 43 ++++++++++++++++++++++++----- wiki/SharedContracts.md | 60 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 111 insertions(+), 12 deletions(-) diff --git a/src/shared/document.ts b/src/shared/document.ts index 1303eec..f0bbd00 100644 --- a/src/shared/document.ts +++ b/src/shared/document.ts @@ -6,6 +6,7 @@ */ import type { DocumentId, RepresentationId } from "./ids"; +import type { DomNodePath, StructuralPathSegment } from "./selector"; /** * The kind of normalized view derived from a source document. @@ -56,10 +57,23 @@ export interface PageOffsetRange { export type OffsetMap = readonly PageOffsetRange[]; /** - * Reserved for `StructuralSelector` (heading/section/AST path). - * Not implementable in MVP — type is `never` to enforce that at compile time. + * Maps canonical-text offset ranges to structural paths for non-paginated + * representations (`html-dom`, `markdown-rendered`). + * + * Entries are sorted by `globalStart`, are non-overlapping, and together + * cover `[0, canonicalText.length)`. */ -export type StructureMap = never; +export interface StructureRange { + /** Inclusive canonical-text offset where this block begins. */ + readonly globalStart: number; + /** Exclusive canonical-text offset where this block ends. */ + readonly globalEnd: number; + readonly path: readonly StructuralPathSegment[]; + /** DOM path to the block's text container, when captured from a viewer. */ + readonly containerPath?: DomNodePath; +} + +export type StructureMap = readonly StructureRange[]; /** A source document known to the system. */ export interface Document { diff --git a/src/shared/selector.ts b/src/shared/selector.ts index 16784c6..1d14338 100644 --- a/src/shared/selector.ts +++ b/src/shared/selector.ts @@ -7,9 +7,8 @@ * * The MVP implements the four PDF-relevant variants * (`TextQuoteSelector`, `TextPositionSelector`, `PdfRectSelector`, - * `PdfPageTextSelector`). The other three kinds (DOM, structural, fragment) - * are reserved as `never`-typed stubs so adding them later is a localised - * change. + * `PdfPageTextSelector`) plus DOM/structural selectors for HTML/Markdown. + * `FragmentSelector` remains a `never`-typed stub. */ /** Exact quote with optional surrounding context (W3C-aligned). */ @@ -54,11 +53,41 @@ export interface PdfPageTextSelector { readonly end: number; } -/** Reserved for HTML/Markdown viewer adapters. Not implementable in MVP. */ -export type DomRangeSelector = never; +/** + * Child-index path from the document root to a text container node. + * Each entry is the 0-based index among `childNodes` at that level. + */ +export type DomNodePath = readonly number[]; -/** Reserved for heading/section/AST-path locators. Not implementable in MVP. */ -export type StructuralSelector = never; +/** DOM range for rendered HTML/Markdown (W3C Range-aligned). */ +export interface DomRangeSelector { + readonly type: "DomRangeSelector"; + readonly startPath: DomNodePath; + readonly startOffset: number; + readonly endPath: DomNodePath; + readonly endOffset: number; +} + +/** One segment of a structural path (heading, section, or block). */ +export interface StructuralPathSegment { + readonly kind: "heading" | "section" | "block"; + /** 0-based index among siblings of the same kind at this level. */ + readonly index: number; + /** Heading level (1–6) when `kind` is `heading`. */ + readonly level?: number; + /** Snapshot of heading/section label text when known. */ + readonly label?: string; +} + +/** Structural locator for re-render-stable anchoring in HTML/Markdown. */ +export interface StructuralSelector { + readonly type: "StructuralSelector"; + readonly path: readonly StructuralPathSegment[]; + /** Inclusive start offset within the resolved block's canonical text. */ + readonly startOffset: number; + /** Exclusive end offset within the resolved block's canonical text. */ + readonly endOffset: number; +} /** Reserved for exported deep-link fragments. Not implementable in MVP. */ export type FragmentSelector = never; diff --git a/wiki/SharedContracts.md b/wiki/SharedContracts.md index c222ef2..0924d06 100644 --- a/wiki/SharedContracts.md +++ b/wiki/SharedContracts.md @@ -148,6 +148,38 @@ StructuralSelector heading/section/AST path FragmentSelector exported fragment / deep link (export-only) ``` +Field shapes (non-PDF selectors, EANCH-WP-0003): + +```ts +type DomNodePath = readonly number[]; // child-index path from document root + +interface DomRangeSelector { + type: "DomRangeSelector"; + startPath: DomNodePath; + startOffset: number; + endPath: DomNodePath; + endOffset: number; +} + +interface StructuralPathSegment { + kind: "heading" | "section" | "block"; + index: number; + level?: number; // headings only + label?: string; +} + +interface StructuralSelector { + type: "StructuralSelector"; + path: readonly StructuralPathSegment[]; + startOffset: number; + endOffset: number; +} +``` + +`StructureMap` on `DocumentRepresentation` maps canonical-text spans to +structural paths (sorted, non-overlapping). Ingest pipelines may omit it; +viewer adapters populate it at selection time. + **Selector redundancy rule:** when an annotation is created, the system stores *all selector types that are available* for that document representation, not just one. Resolution tries them in order of expected confidence and stops at @@ -218,8 +250,32 @@ interface DocumentViewerAdapter { } ``` -MVP delivers a single `PDFViewerAdapter`. HTML and Markdown adapters are -deferred. +MVP delivers a single `PDFViewerAdapter`. HTML and Markdown adapters ship in +`evidence-anchor/dom`. + +`SelectionCapture` is a discriminated union: + +```ts +interface PdfSelectionCapture { + kind: "pdf"; + text: string; + page: number; + rects: readonly NormalizedRect[]; + boundingRect?: NormalizedRect; +} + +interface DomSelectionCapture { + kind: "dom"; + text: string; + startPath: DomNodePath; + startOffset: number; + endPath: DomNodePath; + endOffset: number; + structuralPath?: readonly StructuralPathSegment[]; +} + +type SelectionCapture = PdfSelectionCapture | DomSelectionCapture; +``` ---