Commit Graph

5 Commits

Author SHA1 Message Date
430c0e124c Refine evidence UX: sidebar capture form, inline edit, click highlight
Significant UX iteration:

Visual palette
- Debug text-layer overlay flips from yellow to light grey so it no
  longer collides with the evidence highlight colour.
- New highlight-styles.css matches the sidebar's #fff8d6/#e0c050
  palette so a passage marked in the document and its sidebar card
  speak the same visual language.
- Active (focused) evidence: same fill, thick #b78b1c outline on both
  the highlight and the sidebar card. Library's red --scrolledTo
  box-shadow is suppressed.

Activation model
- Click an evidence card in the sidebar → activates that item +
  scrolls the viewer to the passage + thickens the borders (existing
  behaviour, now visually clearer).
- Click a highlight in the document → activates the evidence that
  owns that annotation. New `findByAnnotationId()` on EvidenceService
  is the reverse lookup. Wired through a new `onHighlightClicked`
  prop on PdfSpikeViewer + `activeAnnotationId` prop that drives the
  data-ce-active attribute on the highlight wrapper.

Inline edit
- Each evidence card has a ✎ button that flips the card into an
  inline form with the citation (quote) and commentary fields.
- Saving calls a new `AnnotationService.updateQuote()` +
  existing `EvidenceService.updateCommentary()`. The selectors are
  untouched, so the marked passage in the document stays put — the
  inline hint says so explicitly.
- New `AnnotationUpdated` event added to the engine event vocabulary
  (SharedContracts.md §4 updated).

Capture form placement
- The yellow "New annotation" toolbar that lived above the viewer is
  gone. A new InlineCaptureForm component is now slotted into the
  sidebar between the cards that bracket the new selection in
  document flow (sorted by page + y of the first PdfRectSelector).
  If the new selection is before all existing evidence it appears at
  the top; if after all of them, at the bottom.
- The legacy AnnotationToolbar.tsx is removed; the public surface
  re-exports `InlineCaptureForm` instead.

Test updates
- tests/integration/citation-card-export-e2e.dom.test.tsx: switched
  to the seed-session helper (matches the other E2Es) since the
  fixture-button click path is gone.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 22:57:48 +02:00
f0af8887d1 Strengthen text-layer debug + log highlight render path
The first cut of "Debug text layer" only painted direct `<span>`
children of `.textLayer`. PDF.js 4.x wraps marked content in nested
spans/divs, so the entire selectable area wasn't visible — making it
hard to tell whether a region is "no text layer at all" vs. "text
layer present but small/dense".

Changes:
- CSS now targets every descendant of `.textLayer`, dims the canvas
  underneath, and outlines the `.textLayer` container itself so its
  full extent is obvious.
- TextHighlight rectangles flip to green in debug mode so saved
  highlights don't get washed out by the debug yellow.
- The viewer now logs:
    [ce] viewer highlights        — which annotations rendered, which
                                    were skipped, with rects + page
    [ce] scrollToAnnotation       — whether the target was found in
                                    the highlights array when an
                                    activation arrived

This is the diagnostic loop for the "viewport scrolls but the
highlight doesn't appear" report — if highlight count is > 0 in the
first log but the green rectangle is off-screen, the saved rects
inherited the same text-layer misalignment that caused the partial
selection captures in the first place.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 22:05:13 +02:00
0638c441c4 Add Debug text layer toggle for diagnosing PDF selection issues
PDF text selection misbehaviour (some glyphs unselectable, selections
jumping to other positions) is almost always caused by misalignment
between the visible canvas-rendered glyphs and the invisible text
layer that PDF.js overlays for selection. There's no way to see this
without devtools — which makes it hard for end users to tell whether
a specific PDF is OCR-noisy, has bad font fallbacks, or has no text
layer at all.

This adds a developer-facing toggle in the SessionMenu ("Debug text
layer") that:

- paints every text-layer span yellow with a blue outline so it's
  obvious where text is selectable and where it isn't, and
- logs every onSelection event to the browser console with the
  captured text, page, normalized rects, and the selectors the
  pipeline derived from it.

Preference persists in localStorage under
`citation-evidence:debug:textLayer`. Surfaced via a new
`useDebugFlag()` hook in @work so the SessionMenu (app layer) and the
ViewerShell (work layer) can both subscribe without breaching the
boundary plugin.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 21:43:15 +02:00
8607c252c4 Implement CE-WP-0003 T01-T08: form binding + visual guide overlay
T01 EvidenceLink/EvidenceSet types
  - src/shared/evidence-link.ts: status (§2.4), relation (§2.5), target
  - src/shared/evidence-set.ts: ordered group + activeEvidenceItemId
  - enum-conformance test parses SharedContracts.md and asserts the
    runtime lists match exactly

T02 Binding service + in-memory link repo + active-state machine
  - src/binder/repos/in-memory-links.ts: Map-backed EvidenceLinkRepository
  - src/binder/services/bindings.ts: link/unlink/list/update/setActive
    emitting §4 EvidenceLinkCreated / EvidenceLinkUpdated /
    EvidenceItemActivated
  - src/binder/state/active.ts: (target, evidence, annotation) reducer
    + ActiveStateProvider + useActiveState hook
  - extended engine/events/types.ts with EvidenceLinkCreated,
    EvidenceLinkUpdated, FormFieldActivated payloads

T03 Rect registry (SharedContracts §7 — contract FROZEN)
  - src/binder/visual-guide/rect-registry.ts: register/getRect/subscribe
    + invalidate + getVersion for useSyncExternalStore
  - events.ts: scroll/resize/focus pumps via window + ResizeObserver +
    IntersectionObserver, rAF-throttled
  - react-hooks.ts: RectRegistryProvider, useRegisterRect(kind,id,ref),
    useRectRegistryVersion

T04 Form schema + renderer
  - src/app/forms/demo-schema.ts: text/textarea/date minimal schema
  - src/binder/FormRenderer.tsx: renders schema, each field registers
    as rect kind="field"; active field gets aria-current="true"
  - placed in binder/ (not work/) because work cannot import binder per
    DependencyMap.md §2 and the renderer needs the rect-registry hook;
    workplan T04 was amended in-place to document this

T05 Side-by-side Forms layout + click-to-link
  - src/app/forms/FormsApp.tsx + src/app/App.tsx top-bar router with
    hash route #/forms/demo
  - BinderProvider mounted at app root so links survive tab switching
  - stage-evidence-then-click-field linking interaction with banner
    + per-field link-count chip

T06 Active-evidence cycling
  - src/app/forms/ActiveEvidenceChips.tsx: chips per active target,
    Tab cycles natively, first chip auto-activates on field focus,
    each chip registers as rect kind="evidence-card"
  - ScrollBridge in FormsApp wires activeAnnotationId to viewer scroll
  - EvidenceSidebar + EvidenceStrip highlight the active item via the
    new useLastActivatedEvidence hook in work/EngineContext

T07 SVG visual-guide overlay
  - src/binder/visual-guide/Overlay.tsx: single fixed-positioned SVG,
    draws field→card and card→highlight bezier curves for the active
    triple, rAF-throttled via the registry
  - src/anchor exposes getHighlightClientRects(annotationId); the
    spike viewer wraps highlights in [data-highlight-id] so the helper
    can locate them
  - src/app/forms/HighlightRectBridge.tsx: registers the active
    annotation's rect via that helper

T08 End-to-end test (PRD scenario steps 5-9)
  - tests/integration/forms-overlay-e2e.dom.test.tsx: full path from
    Review-mode capture through Forms-mode link to active triple +
    aria-current assertions + 2 SVG paths in the overlay
  - additional integration coverage: forms-link-flow + forms-active-cycling

Gates: typecheck ✓ · lint ✓ · build ✓ · 152/152 tests across 21 files.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 15:53:17 +02:00
2a7b05c190 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>
2026-05-25 02:21:31 +02:00