hypothesis / hypothesis/client
TextQuoteSelector fuzzy fallback can silently re-anchor to a lookalike quote in a different location after the original text is edited
- Dominant language
- Mustache
- Stars
- 730
- Forks
- 224
- PR merge metrics
- No merged PRs in 30d
Description
**Spec clause:** [W3C Web Annotation Data Model — Text Quote Selector](https://www.w3.org/TR/annotation-model/#text-quote-selector). The selector is defined by `exact`, `prefix`, and `suffix`; prefix/suffix exist specifically to disambiguate among multiple occurrences of `exact` in the document.
**Expected behavior:** Hypothesis's own blog states the design intent directly: "Hypothesis already deals with minor changes to a document thanks to our fuzzy anchoring algorithm... But sometimes documents change a lot. If an annotation is anchored to a sentence in a paragraph and the whole paragraph is deleted, then even the smartest algorithm isn't going to help — the original text is no longer present and that annotation will fail to anchor. When annotations fail to anchor, we call them orphans." ([web.hypothes.is/blog/showing-orphaned-annotations](https://web.hypothes.is/blog/showing-orphaned-annotations/)) This case should have produced an orphan. It didn't — it produced a confident, wrong match.
**Minimal witness:**
```html
Team Alpha reported that the new deployment reduced latency by twelve percent across all regions.
Team Beta reported that the new deployment reduced latency by twelve percent across all regions.
{ "exact": "reduced latency by twelve percent across all regions",
"prefix": "eported that the new deployment ",
"suffix": ".Team Beta reported that the new" }
Team Alpha reported that the new deployment reduced latency by twenty percent across all regions after further optimization.
Team Beta reported that the new deployment reduced latency by twelve percent across all regions.
```
**Expected vs. actual:** Per Hypothesis's own stated design, this should orphan — the original text no longer exists at its captured location. Actual: `toRange()` returns a confident, non-null match — silently re-anchored inside `
`, a different paragraph with different authorship, based on a decoy occurrence. No signal is surfaced that the match moved.
**Mechanism:** the captured prefix (`"eported that the new deployment "`) is identical in both paragraphs — the disambiguating token (Alpha vs Beta) sits outside the fixed 32-character context window, so the fuzzy search can't distinguish them once the original location's exact text is gone.
**Affected-implementation matrix:**
| Strategy | Result on this case |
|---|---|
| DOM-path (XPath+offset) | Fails at the correct location — wrong content, doesn't move elements |
| Text-position (global offset) | Same — wrong content, correctly contained |
| `web-highlighter` (tag+index+offset) | Same — wrong content, correctly contained |
| `dom-anchor-text-quote` (fuzzy text-quote) | Confidently anchors in a different element entirely |
**Related work, checked before filing:**
- #954 in `product-backlog` — closed, resolved via #2720. Its example is a pre-existing duplicate at capture time plus a PDF cross-page-boundary case — different from this report, where the text is unique at capture and only becomes ambiguous after a later edit.
- #2720, "Change anchored range representation to fix overlapping highlight issues" — reviewed directly. Different mechanism, doesn't cover this: it fixes a cross-annotation side effect (a cached, resolved `Range` silently invalidated when painting a *different* annotation's highlight mutates the DOM), fixed by moving to just-in-time resolution. No changes to `dom-anchor-text-quote` or the fuzzy-matching algorithm. This report is about the matching algorithm's disambiguation logic when the page author edits the source text — a different layer and cause, no file overlap with #2720.
**Oracle citation:** `TextQuoteSelector`'s own spec text (disambiguation is the stated purpose of prefix/suffix) plus Hypothesis's own orphans-design blog post (large changes should orphan, not silently reattach).
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by locating the dom-anchor-text-quote fuzzy-matching implementation and its tests, then reproduce the minimal HTML witness with toRange(). Add a regression test showing that the edited Alpha sentence becomes an orphan rather than matching the Beta paragraph; done means no confident cross-paragraph re-anchoring occurs.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- frontend, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100