Implement CE-WP-0002 T01-T02: engine types + PDF viewer adapter spike

T01: shared engine types (Document, Selector union, Annotation, EvidenceItem,
branded IDs with newId factory) per wiki/SharedContracts.md §1-§3.

T02: react-pdf-highlighter-plus v1.1.4 spike behind the §5
DocumentViewerAdapter contract in src/anchor/. Pure round-trip math
extracted to pdf-selector-math.ts with 11 unit tests proving lossless
capture → selectors → JSON → restored-rects. ADR-0004 accepted; full
user-flow Playwright verification deferred to T09.

Adds Vite app shell (index.html, src/app/SpikeApp.tsx) so the spike is
exercisable via pnpm dev. tsconfig --noEmit prevents tsc -b from
littering src/ with stray .js outputs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 02:21:31 +02:00
parent 2f25f99cae
commit 2a7b05c190
22 changed files with 2538 additions and 13 deletions

55
src/shared/ids.ts Normal file
View File

@@ -0,0 +1,55 @@
/**
* Branded ID types and the `newId(kind)` factory.
*
* Implements the identifier portion of `wiki/SharedContracts.md` §1 and
* `wiki/ArchitectureOverview.md` §3.2. Each branded type is structurally a
* `string` but nominally distinct, so passing an `AnnotationId` where a
* `DocumentId` is required is a compile-time error.
*/
declare const __brand: unique symbol;
type Brand<K, T extends string> = K & { readonly [__brand]: T };
export type DocumentId = Brand<string, "DocumentId">;
export type RepresentationId = Brand<string, "RepresentationId">;
export type AnnotationId = Brand<string, "AnnotationId">;
export type EvidenceItemId = Brand<string, "EvidenceItemId">;
export type EvidenceSetId = Brand<string, "EvidenceSetId">;
export type EvidenceLinkId = Brand<string, "EvidenceLinkId">;
export type CitationCardId = Brand<string, "CitationCardId">;
export type CitationRecoveryAttemptId = Brand<string, "CitationRecoveryAttemptId">;
export type IdKindMap = {
document: DocumentId;
representation: RepresentationId;
annotation: AnnotationId;
evidence: EvidenceItemId;
"evidence-set": EvidenceSetId;
"evidence-link": EvidenceLinkId;
"citation-card": CitationCardId;
"citation-recovery": CitationRecoveryAttemptId;
};
export type IdKind = keyof IdKindMap;
const PREFIXES: Record<IdKind, string> = {
document: "doc",
representation: "rep",
annotation: "ann",
evidence: "ev",
"evidence-set": "evset",
"evidence-link": "evlink",
"citation-card": "card",
"citation-recovery": "crec",
};
/**
* Mint a new branded identifier of the requested kind.
*
* IDs use the shape `<prefix>_<uuid>` so they are human-recognizable when
* they show up in logs, URLs, or stored JSON.
*/
export function newId<K extends IdKind>(kind: K): IdKindMap[K] {
return `${PREFIXES[kind]}_${crypto.randomUUID()}` as IdKindMap[K];
}