Commit Graph

1 Commits

Author SHA1 Message Date
Michael Ramos 91f57ad7a2 feat(ui): highlight external SSE annotations inline on plan text (#511)
* feat(ui): highlight external SSE annotations inline on plan text

External annotations posted via /api/external-annotations previously
appeared in the sidebar only. They now highlight the matching `originalText`
span in the rendered plan, giving tools (linters, agents) an optional way
to attach feedback to specific phrases — while `GLOBAL_COMMENT` (or any
annotation without `originalText`) still degrades to sidebar-only.

Implementation is a new focused hook that drives the Viewer's existing
imperative `applySharedAnnotations` / `removeHighlight` API, reusing the
same DOM text-search path that share-URL restoration uses. No protocol
change, no schema change to `transformPlanInput`, and App.tsx gains only
a single hook call.

The hook tracks applied ids with a type+originalText fingerprint so SSE
updates correctly remove+reapply, clears its bookkeeping only on plan
markdown change (where blocks re-render), and early-returns (preserving
state) while diff view or a linked doc overlay is active so SSE removals
arriving under those conditions still reconcile when the hook re-enables.

For provenance purposes, this commit was AI assisted.

* fix(ui): repaint external SSE highlights after share import, clean dead code

Addresses three review findings on the external annotation highlight hook:

- Share-import wipe: `importFromShareUrl` merges annotations without
  changing `markdown`, so our `planKey` stayed stable. The share-apply
  effect in App.tsx calls `clearAllHighlights()` to reset the DOM before
  applying imported annotations — which also wiped live external SSE
  highlights. The hook believed they were still painted and never re-drove
  them, leaving sidebar entries with no visible highlight until the next
  SSE event. Fix: hook now exposes a `reset()` that clears its applied-set
  and re-runs the main effect via a counter; App.tsx calls it right after
  `clearAllHighlights()` in the share-apply path.

- Removed a dead `nextIds.has(a.id)` guard inside the apply timer callback.
  `toAdd` is computed as a subset of `eligible`, and `nextIds` was built
  from `eligible.map(.id)`, so the guard was vacuously always true.

- Removed the stable `viewerRef` from the main effect's dep array; React
  ref objects have stable identity for the component's lifetime so it was
  noise. Added a comment noting the intentional omission.

For provenance purposes, this commit was AI assisted.

* docs(ui): note reset() in useExternalAnnotationHighlights header comment

For provenance purposes, this commit was AI assisted.
2026-04-07 11:10:23 -07:00