backnotprop / backnotprop/plannotator
Comment on individual Mermaid nodes and edges
- Dominant language
- TypeScript
- Stars
- 8.7k
- Forks
- 649
- Avg merge
- 11h 12m
- Merged PRs (30d)
- 109
Description
I'm happy to work on this one, but wanted to track the proposal first, especially given the related issue.
A rendered Mermaid diagram is currently an all-or-nothing annotation target. Pinpoint skips it entirely (`data-pinpoint-ignore` on the grab container, added in #819 so that releasing a pan-drag stopped creating a whole-block annotation), and text selection is excluded along with it. The only way to comment on a diagram today is to toggle Show source and annotate the whole fenced block as one code block, which points the agent at the entire diagram.
Reviewers want to point at a specific node, edge, edge label, or subgraph and say what is wrong with that one thing.
## Relation to existing issues
#911 tracks diagram *generation and iteration* and does not cover annotating a rendered diagram. Its one comment asks for something adjacent: freehand circling on a Mermaid diagram, with the resulting image handed back to a multimodal agent. That is a different mechanism (raster capture through the Image Annotator) reaching for a similar goal, and the two can coexist. Structured targeting gives the agent the source identifier of the thing being discussed. Freehand gives it a picture.
Nothing else in the tracker covers per-element diagram commenting.
## Feasibility
I rendered the common diagram types through the pinned Mermaid 11 configuration (`securityLevel: 'strict'`, `htmlLabels: true`) in a browser and inspected the emitted SVG and its hit behavior.
**Mermaid emits source-derived identity for flowchart, class, state, and ER diagrams:**
| Surface | DOM | Anchor |
|---|---|---|
| Node | `` | source key `A` |
| Edge | `` | fully semantic |
| Subgraph | `` | source id verbatim |
| Edge label | `` | ordinal plus text |
Re-rendering identical source reproduces those ids exactly. Inserting a node upstream shifts only the trailing counter (`flowchart-A-0` becomes `flowchart-A-1`) while the source key and every edge id survive unchanged. So the durable anchor is the source key plus a text snapshot, resolved back through `g.node[id^="flowchart-A-"]` and verified against the snapshot. That is the same fail-closed shape as the existing `HtmlElementAnchor`.
Sequence, pie, and gantt diagrams carry no source-derived ids. Actors are `#root-0`, pie slices are anonymous `path.pieCircle`. Those degrade to class, ordinal, and text.
**Hit-testing needs one instrumentation pass.** Untouched, clicking an edge misses it: edge paths are 1px `fill:none` strokes, and `foreignObject` label boxes sit on top and absorb the hit. Cloning each edge path as a transparent `stroke-width:16; pointer-events:stroke` sibling, then setting `pointer-events:none` on `foreignObject` and `auto` on the label spans, makes every surface resolve correctly from `elementFromPoint` plus `closest()`. I confirmed this in the browser for nodes, edges, edge labels, and the subgraph frame. `closest()` crosses the `foreignObject` boundary, so a click on a node's HTML label resolves to its ``.
**Painting is cheaper here than on the HTML surface.** A highlight rect derived from `getBBox()` and inserted inside the annotated `` lives in the diagram's own coordinate space. It pans and zooms with the diagram for free and clips naturally when panned out of view, so there is no overlay reprojection to maintain.
## Proposal
Add a diagram target kind to the existing Pinpoint pipeline.
- **Anchor.** A new additive field on `Annotation`, alongside `mathTargets` and `htmlAnchor`: `{ kind: 'node' | 'edge' | 'edgeLabel' | 'cluster', sourceKey, label }`. Resolution matches on the source key and verifies the label snapshot, failing closed when the diagram has changed underneath.
- **Targeting.** `usePinpoint` already branches out of the range path for code blocks. A diagram branch slots in the same way, fed by a resolver over the instrumented SVG.
- **Pointer conflict.** Keep pan and zoom. Discriminate a click from a drag by movement threshold on mouseup, which is the failure #819 fixed by excluding the block outright.
- **Export.** Match the source key back to its line in the fence body so the exported feedback quotes the real Mermaid source (`B -->|no| C[(Database)]`) rather than only a rendered label, and derive `startOffset` from that line so the existing export ordering holds.
## Scope
Comparable to the math annotation feature, not to the raw-HTML pinpoint surface. Server-side changes are not required.
- Flowchart family first, where identity is strong.
- Ordinal-anchored families (sequence, pie, gantt) as a follow-up, or explicitly out of scope for a first version.
## Risks
- `data-pinpoint-ignore` must be narrowed rather than removed. Text selection has to stay excluded, or web-highlighter will attempt to mark inside `foreignObject`.
- The SVG is re-injected through `dangerouslySetInnerHTML` on content change, and the expanded view remounts it into a portal. Annotations must be re-resolved on each, under the same contract `onCodeHighlightSwap` already enforces for code-block marks.
- Share links will not carry diagram anchors. That is the existing documented contract for `htmlAnchor`, which restores by text search only. Either extend the share payload or accept the degradation.
- The text-selection path is not a viable alternative. With `htmlLabels: true` the labels are real HTML and selectable, but offsets would address the fence source, which has no correspondence to the rendered label.
## Open decisions
- Whether the ordinal-anchored diagram families ship at all, given that their anchors break on any reordering.
- Whether freehand circling from the #911 comment is the same feature or a separate one.
Contributor guide
Assessment
This issue has not been assessed yet.