- Align agent files with on-disk workplan prefixes (infer from workplan ids) - Set workplan domain to registered domain_slug; add topic_slug where applicable - Repair frontmatter delimiter formatting; migrate legacy task status literals - Regenerate AGENTS.md, CLAUDE.md, and .claude/rules from State Hub templates
9.4 KiB
react-pdf-highlighter-plus integration can store and reload selectors
without leaking viewer types into engine code. If that breaks, the rest of
the workplan stops and a new ADR is required for ADR-0004 (PDF viewer choice).
## Dependency Order
T01 (engine types: Document, Representation, Annotation, Selector, EvidenceItem) └─ T02 (PDF viewer adapter spike — store + reload selectors as JSON) └─ T03 (evidence-source: PDF ingest, fingerprint, canonical text) └─ T04 (evidence-anchor: TextQuote + TextPosition resolution against representation) └─ T05 (in-memory repositories + engine services) └─ T06 (citation-work UI: collection list + viewer shell + sidebar) └─ T07 (annotation create flow) └─ T08 (click-to-reopen flow) └─ T09 (end-to-end test of PRD scenario steps 1-4)
T01 — Engine types in src/shared/
id: CE-WP-0002-T01
state_hub_task_id: b015c082-4272-407d-b6e4-9e1bd97f0193
priority: critical
status: done
Translate the type definitions in wiki/SharedContracts.md §1 and §3 into
TypeScript under src/shared/:
src/shared/document.ts—Document,DocumentRepresentation,PageMap,OffsetMapsrc/shared/selector.ts—Selectordiscriminated union with at minimumTextQuoteSelector,TextPositionSelector,PdfRectSelector,PdfPageTextSelector. Other selector kinds defined asnever-typed stubs for now.src/shared/annotation.ts—Annotationwithselectors,quote,note,normalizeVersionsrc/shared/evidence.ts—EvidenceItem,EvidenceItem.statusenum per §2.2src/shared/ids.ts— branded ID types and anewId(prefix)helper
No services, no behavior. Pure data shapes + the ID helper.
Add JSDoc on each type pointing at the §-reference in
wiki/SharedContracts.md it implements.
T02 — PDF viewer adapter spike
id: CE-WP-0002-T02
state_hub_task_id: 59846d9e-7ac1-4306-b02e-0980a52f44c8
priority: critical
status: done
depends_on: [T01]
This is the architectural spike. Build a throwaway
src/anchor/pdf-viewer-adapter-spike.tsx that:
- Loads
fixtures/pdfs/simple.pdfusingreact-pdf-highlighter-plus(assumed; if a better library appears, document it in ADR-0004 before committing). - Lets the user select text and produces selectors per
T01shapes. - Serializes the selectors to a JSON blob in
localStorage. - On reload, reads the blob, asks the adapter to resolve, scrolls to the passage, and renders a highlight.
Success criteria:
- Reload-and-resolve works for all fixture PDFs.
- No PDF.js or
react-pdf-highlighter-plustypes appear in any file undersrc/shared/orsrc/engine/. - The adapter's public surface matches the contract in
wiki/SharedContracts.md§5.
If success criteria fail: stop. Write a short note in
docs/decisions/ADR-0004-pdf-viewer-library.md describing the failure mode
and proposed alternative. Do not proceed with T03+.
T03 — src/source/: PDF ingest, fingerprint, canonical text
id: CE-WP-0002-T03
state_hub_task_id: 01dad096-3521-42b9-aed9-ce0b2f5d3450
priority: high
status: done
depends_on: [T02]
Implement under src/source/pdf/:
ingest.ts—ingestPdf(file: File | Buffer): Promise<{ document: Document; representation: DocumentRepresentation }>fingerprint.ts— stable SHA-256 of bytesextract.ts— uses PDF.js to extract page text; runsnormalize()from T04 of WP-0001 over the canonical text; builds thePageMapandOffsetMapperDocument.DocumentRepresentation
Tests use the fixture corpus from CE-WP-0001-T05. For each fixture,
extracted canonical text must contain the manifest's known-good quote.
T04 — src/anchor/: TextQuote and TextPosition resolution
id: CE-WP-0002-T04
state_hub_task_id: 62e4839a-8026-4e15-b4cc-6685e56b3584
priority: high
status: done
depends_on: [T01, T03]
Implement under src/anchor/:
selectors/create.ts— given aSelectionCapturefrom the adapter, build the maximal set of available selectors (alwaysTextQuoteSelectorwith prefix/suffix;TextPositionSelectorwhen the representation provides offsets; PDF rect/text selectors when on PDF)selectors/resolve.ts— implements the resolution strategy fromwiki/ArchitectureOverview.md§7 (try position, verify quote, fall back through quote+prefix/suffix, returnAnchorResolution)selectors/types.ts—AnchorResolution,SelectionCapture,ResolvedAnchorTarget
Fuzzy matching is out of scope here — return unresolved if exact+prefix/suffix
fails. Fuzzy is a later workplan.
Unit tests using fixtures: for each fixture+known-quote pair, create selectors then immediately resolve them; resolution must succeed with confidence ≥ 0.9.
T05 — In-memory repositories + engine services
id: CE-WP-0002-T05
state_hub_task_id: b339a73a-6b58-471c-a01d-e769ea414ee7
priority: high
status: done
depends_on: [T01]
Under src/engine/:
repos/in-memory.ts—Map-backed implementations ofDocumentRepository,AnnotationRepository,EvidenceItemRepositoryservices/documents.ts,services/annotations.ts,services/evidence.ts— thin orchestration layer that creates IDs, calls repos, and emits the events fromwiki/SharedContracts.md§4events/bus.ts— minimal pub/sub. Synchronous for MVP.
No persistence to disk yet. ADR-0005 (persistence) is still pending.
T06 — src/work/: collection list + viewer shell + sidebar
id: CE-WP-0002-T06
state_hub_task_id: f400e133-6ec6-4d5a-98a0-a6408ca4125e
priority: high
status: done
depends_on: [T02, T05]
Under src/work/ and src/app/:
src/app/App.tsx— three-pane layout per Architecture §12.1: collection list (left), viewer (centre), evidence sidebar (right)src/work/CollectionList.tsx— listsfixtures/pdfs/manifest.jsonentries; click to loadsrc/work/ViewerShell.tsx— hosts the viewer adapter from T02 wrapped cleanly; viewer adapter API is the only surfacework/usessrc/work/EvidenceSidebar.tsx— lists evidence items for the current document, shows quote + commentary + status
No styling beyond minimum legibility. CSS in Tailwind or vanilla — pick one, note in ADR-0001 if it wasn't already.
T07 — Annotation create flow
id: CE-WP-0002-T07
state_hub_task_id: 26346a07-bf98-4d43-8b30-de2038ab72f8
priority: high
status: done
depends_on: [T04, T05, T06]
Wire selection → annotation → evidence item:
- User selects text in the viewer.
- A small toolbar appears with a comment input + Save button.
- On Save: adapter produces
SelectionCapture→ anchor createsSelector[]→ engine createsAnnotation→ engine createsEvidenceItemwith the commentary → sidebar updates.
Active state lives in a single React context for now; no Redux/Zustand.
T08 — Click-to-reopen flow
id: CE-WP-0002-T08
state_hub_task_id: 469e3fb4-1b42-49a7-88dc-29a6d5055ef5
priority: critical
status: done
depends_on: [T04, T06, T07]
Implement the round trip:
- User clicks an evidence item in the sidebar.
- Engine loads the annotation → anchor resolves selectors against the current representation → adapter scrolls to and highlights the target.
Critically, this must also work after a page reload. Persistence to
localStorage is acceptable for MVP (decide explicitly in
ADR-0005-persistence.md that we are deferring real persistence).
T09 — End-to-end test of PRD scenario steps 1-4
id: CE-WP-0002-T09
state_hub_task_id: 77423e57-f2c5-42e1-9e6c-c9b6fa35dfcf
priority: high
status: done
depends_on: [T07, T08]
Write a Playwright (or similar) E2E test that:
- Opens the app.
- Picks
simple.pdf. - Programmatically selects the known-good quote from the manifest.
- Saves an evidence item with a comment.
- Verifies the item appears in the sidebar.
- Reloads the page.
- Clicks the evidence item.
- Verifies the highlight is rendered on the expected page.
This is the contract for "MVP slice 1 works". If it passes, CE-WP-0003 may begin.