backnotprop / backnotprop/plannotator
Bug: empty/whitespace selection crashes _handleSelection and creates garbage annotations
- Dominant language
- TypeScript
- Stars
- 8.7k
- Forks
- 649
- Avg merge
- 11h 12m
- Merged PRs (30d)
- 109
Description
## Summary
When a user double-clicks at a block boundary (e.g. end of a heading element) or selects whitespace-only text, two bugs occur in `@plannotator/web-highlighter`'s `_handleSelection`:
### Bug 1 — `parentNode` crash (before CREATE event)
When the browser's selection range has a non-text `endContainer` that is an Element node with `endOffset >= childNodes.length` (pointing past the last child), `formatDomNode()` in `src/model/range/dom.ts` returns `{: undefined, offset: 0}`. This causes `getDomMeta → getOriginParent` to throw:
```
Cannot read properties of undefined (reading 'parentNode')
```
**Stack trace (approximate):**
`getOriginParent` ← `getDomMeta` ← `HighlightRange.serialize` ← `_highlightFromHRange` ← `_handleSelection`
**Root cause:** `formatDomNode` does not bounds-check the offset before accessing `childNodes[offset]`:
```ts
// src/model/range/dom.ts
return {
: n.$node.childNodes[n.offset], // undefined when offset >= childNodes.length
offset: 0,
};
```
**Minimal fix in `formatDomNode`:**
```ts
const child = n.$node.childNodes[n.offset];
if (!child) return n; // safe fallback
return { $node: child, offset: 0 };
```
### Bug 2 — Garbage annotation anchored to `"\n"` (CREATE fires)
When double-clicking in a whitespace region, the browser produces a non-collapsed range whose `selection.toString()` is only newlines or spaces. The CREATE event fires, and the resulting annotation has `originalText: "\n"`, which:
- Renders as an invisible highlight
- Can never be re-anchored on reload
- Gets saved to the annotation store
**Minimal fix in `getDomRange`:**
```ts
// src/model/range/selection.ts
if (selection.isCollapsed || !selection.toString().trim()) {
return null;
}
```
## Reproduction
1. Open a plan with a heading (e.g. `# Test Heading`)
2. Double-click at the very end of the heading text (just past the last character)
3. Observe console error: `Cannot read properties of undefined (reading 'parentNode')`
Or:
1. Open a plan with paragraph text
2. Double-click in the whitespace between two blocks
3. Observe a toolbar appearing with empty selection text; clicking an action creates a garbage annotation anchored to `"\n"`
## Affected version
`@plannotator/web-highlighter@0.8.1`
## Local workaround
A capture-phase `mouseup` guard on the container element that collapses the selection before web-highlighter's bubble-phase handler sees it.
Contributor guide
Assessment
This issue has not been assessed yet.