* 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.