feat(ui): highlight external SSE annotations inline on plan text#511
Merged
backnotprop merged 3 commits intomainfrom Apr 7, 2026
Merged
feat(ui): highlight external SSE annotations inline on plan text#511backnotprop merged 3 commits intomainfrom
backnotprop merged 3 commits intomainfrom
Conversation
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.
…ad 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.
For provenance purposes, this commit was AI assisted.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Contract
No schema change to `transformPlanInput`. Tools choose inline vs global purely by what they supply:
Inline (highlighted on real plan text):
```json
{ "source": "my-tool", "type": "COMMENT",
"text": "This assumption is wrong because...",
"originalText": "deterministic non-git cold-start MVP" }
```
Global (sidebar-only):
```json
{ "source": "my-tool", "type": "GLOBAL_COMMENT",
"text": "Consider adding a rollback step." }
```
If `originalText` is not found in the rendered DOM the annotation degrades gracefully to sidebar-only (warning logged, no error response). First match wins — tools can disambiguate repeated phrases with more surrounding context.
Test plan