Commit Graph

6 Commits

Author SHA1 Message Date
2fd085b65e CE-WP-0006/0007: Capture view polish, workplans, and UX refinements
- Blob URL stability, scroll centre, strip-only visual guide
- Focus-gated linking, unlink clears overlay, field badge tooltips
- Capture layout (viewer centre), grey guide lines, Add field button
- Workplans CE-WP-0006 (done) and CE-WP-0007 (T01-T09 done, T10-T12 todo)
- Integration tests and viewer-url helpers
2026-06-08 00:37:34 +02:00
bef2725fdd Unify capture/edit form, thicker active document border, layer-hide toggles
Three UX iterations rolled into one:

1. Unified evidence form
   - New EvidenceFormBody is the single source for "citation +
     commentary" editing. Both InlineCaptureForm (creating fresh
     evidence from a selection) and the EvidenceCard edit mode render
     this body with their own save/cancel labels + badge/helper text.
   - The capture form now exposes the citation as an editable
     textarea — pre-filled with the selection text — so the user can
     refine a partial capture before saving without re-selecting.
   - Old testid prefixes are unchanged for the inline-capture flow
     (`inline-capture-quote/commentary/save/cancel`); edit-mode
     testids are now `evidence-edit-<id>-{quote,commentary,save,cancel}`.

2. Active document card
   - The blue background alone was the only "this is open" cue. Added
     a 3px #0050b3 border (matching the evidence-card thick-border
     pattern, but in the documents-are-blue palette) plus a
     `data-active` attribute.

3. PDF layer-hide diagnostics
   - New debug flags `hideCanvas`, `hideTextLayer`, `hideAnnotationLayer`,
     `hideXfaLayer` — applied as `.ce-hide-<layer>` classes on the viewer
     wrapper, each `display: none`-ing the matching PDF.js layer.
   - SessionMenu groups the toggles under a "PDF diagnostics" header
     with a new shared DebugCheckbox helper. The existing "Debug text
     layer" highlight toggle now lives in the same group.
   - Lets the user isolate stacking issues by elimination — e.g.
     "hide text layer, can I now see the canvas content underneath?".

Tests
   - citation-card-export-e2e + session-export-reimport switched from
     placeholder/role-name lookups to the inline-capture testids so
     they survive form-copy changes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 23:27:08 +02:00
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
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
67bcc2423c Add per-row session delete + Reset all data; fix viewer URL fallback
UX gaps that surfaced while running the demo:

- ViewerShell hardcoded `/fixtures/pdfs/<title>` for the PDF URL,
  ignoring the `document.uri` blob URL that uploaded PDFs carry. The
  viewer either 404'd or — worse — silently served a fixture whose
  filename happened to collide. Prefer document.uri when present.

- SessionMenu only let you delete the *active* session. Added a small
  per-row "✕" button next to every session in the Switch-to list so a
  user can drop a session's data without first switching to it. Same
  click-to-confirm pattern as the existing Delete action.

- Added a "Reset all data…" affordance in both the SessionMenu and the
  empty-state landing. Calls a new `clearAllSessionData()` helper that
  wipes every `citation-evidence:*` key from localStorage, then forces
  a reload so all in-memory caches start fresh.

- `attachSessionPersister.writeOnDelete` was leaking the per-session
  `active-document-id:v1` key on every session delete. Now removed
  alongside the engine snapshot key.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 20:49:37 +02:00
d54daf2e61 Implement CE-WP-0002 T03-T09: ingest, anchor resolution, engine, UI, persistence, e2e
Completes the PDF review slice end-to-end. After this commit a user can
open a fixture, select text, save an evidence item with commentary, see
it in the sidebar, reload the page, click the item, and the viewer
scrolls to the passage.

- T03 src/source/pdf/{fingerprint,extract,ingest}.ts + 39 fixture tests
  - SHA-256 fingerprint over a fresh ArrayBuffer (TS BufferSource-safe)
  - PDF.js text extract; per-page normalize then join with "\n\n"
  - PageMap + OffsetMap (gap-free coverage); pageLength = end - start
  - Updated manifest's Betriebskosten quote to one PDF.js extracts cleanly
- T04 src/anchor/selectors/{create,resolve}.ts + 25 unit + 7 fixture tests
  - createSelectors emits the maximal redundant set (TextQuote +
    TextPosition + PdfRect + PdfPageText when available)
  - resolveSelectors implements the SharedContracts §7 ladder; confidence
    1.0 (pos+quote) → 0.7 (rect-only) → 0 (unresolved)
  - Cross-module integration test moved to tests/integration/ to honor
    the anchor↛source boundary lint rule
- T05 engine: sync event bus over the closed §4 vocabulary, Map-backed
  repos, services, createEngine() composition root, 12 tests
- T06 work + app: three-pane shell (CollectionList | ViewerShell |
  EvidenceSidebar) wired through EngineProvider; EngineContext lives in
  src/work/ to respect the work↛app boundary; SpikeApp deleted
- T07 AnnotationToolbar: pendingSelection in context; Save runs
  createSelectors → engine.annotations.create → engine.evidence.create
- T08 click-to-reopen + localStorage persistence
  - scrollToAnnotation state in context with a version counter so a
    second click on the same item re-fires the viewer scroll
  - captureSnapshot/restoreSnapshot/attachPersister/restoreFromStorage;
    restore bypasses services to avoid event-loops
  - active-document id persisted alongside the snapshot so reload lands
    on the same fixture; ADR-0005 written
  - 9 persistence tests
- T09 tests/integration/app-prd-scenario.dom.test.tsx
  - end-to-end happy-dom test of PRD scenario steps 1-8 through the real
    React tree; viewer + ingest mocked per ADR-0004's headless-Chromium
    limitation. Fixed memo-deps bug in EvidenceSidebar/ViewerShell where
    useEngineEventTick values were not included in the useMemo deps,
    leaving stale memoization across event-driven re-renders
- vitest.config.ts: happy-dom for *.dom.test.{ts,tsx} files
- noEmit added to tsconfig so tsc -b doesn't litter src/ with .js outputs

Gates: typecheck ✓ lint ✓ test 109/109 across 11 files ✓ build ✓

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 10:58:11 +02:00