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.
This commit is contained in:
parent
e4582b52fa
commit
a1af0c6a45
22 changed files with 1118 additions and 105 deletions
77
src/selectors/resolve.dom.test.ts
Normal file
77
src/selectors/resolve.dom.test.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import type { DocumentRepresentation } from "@citation-evidence/engine/shared";
|
||||
import type { DocumentId, RepresentationId } from "@citation-evidence/engine/shared";
|
||||
import type { Selector } from "@citation-evidence/engine/shared";
|
||||
import { resolveSelectors } from "./resolve";
|
||||
|
||||
const blockText = "The quick brown fox jumps over the lazy dog.";
|
||||
const text = `## Section\n\n${blockText}`;
|
||||
|
||||
function repr(structureStart = text.indexOf(blockText)): DocumentRepresentation {
|
||||
return {
|
||||
id: "rep_dom" as RepresentationId,
|
||||
documentId: "doc_dom" as DocumentId,
|
||||
representationType: "markdown-rendered",
|
||||
contentHash: "test",
|
||||
canonicalText: text,
|
||||
structureMap: [
|
||||
{
|
||||
globalStart: structureStart,
|
||||
globalEnd: structureStart + blockText.length,
|
||||
path: [{ kind: "section", index: 0, label: "Section" }],
|
||||
containerPath: [0, 1, 0],
|
||||
},
|
||||
],
|
||||
generatedAt: "2026-07-09T00:00:00.000Z",
|
||||
};
|
||||
}
|
||||
|
||||
describe("resolveSelectors (dom)", () => {
|
||||
it("resolves DomRangeSelector via structureMap", () => {
|
||||
const foxStart = text.indexOf("brown fox");
|
||||
const selectors: Selector[] = [
|
||||
{
|
||||
type: "DomRangeSelector",
|
||||
startPath: [0, 1, 0],
|
||||
startOffset: foxStart - text.indexOf(blockText),
|
||||
endPath: [0, 1, 0],
|
||||
endOffset: foxStart - text.indexOf(blockText) + "brown fox".length,
|
||||
},
|
||||
];
|
||||
const r = resolveSelectors(selectors, repr());
|
||||
expect(r.status).toBe("resolved");
|
||||
expect(r.confidence).toBe(0.75);
|
||||
expect(r.candidates[0]?.textPosition?.start).toBe(foxStart);
|
||||
});
|
||||
|
||||
it("resolves StructuralSelector when offsets shift within the block", () => {
|
||||
const shifted = "The quick brown fox jumps over a sleepy dog.";
|
||||
const shiftedDoc = `## Section\n\n${shifted}`;
|
||||
const blockStart = shiftedDoc.indexOf(shifted);
|
||||
const selectors: Selector[] = [
|
||||
{
|
||||
type: "StructuralSelector",
|
||||
path: [{ kind: "section", index: 0, label: "Section" }],
|
||||
startOffset: blockText.indexOf("brown fox"),
|
||||
endOffset: blockText.indexOf("brown fox") + "brown fox".length,
|
||||
},
|
||||
];
|
||||
const representation: DocumentRepresentation = {
|
||||
...repr(blockStart),
|
||||
canonicalText: shiftedDoc,
|
||||
structureMap: [
|
||||
{
|
||||
globalStart: blockStart,
|
||||
globalEnd: blockStart + shifted.length,
|
||||
path: [{ kind: "section", index: 0, label: "Section" }],
|
||||
},
|
||||
],
|
||||
};
|
||||
const r = resolveSelectors(selectors, representation);
|
||||
expect(r.status).toBe("resolved");
|
||||
expect(r.confidence).toBe(0.65);
|
||||
expect(r.candidates[0]?.textPosition?.start).toBe(
|
||||
representation.canonicalText!.indexOf("brown fox"),
|
||||
);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue