overleaf/overleaf · error · Error
Tracked changes range cannot be empty
Error message
Tracked changes range cannot be empty
What it means
During the same _mergeRanges normalization pass, after confirming no overlaps, the list asserts that every tracked change covers a non-empty range. An empty range (start === end) is meaningless for a tracked change, so the list refuses it and throws.
Source
Thrown at libraries/overleaf-editor-core/lib/file_data/tracked_change_list.js:135
*
* @private
* @returns {void}
*/
_mergeRanges() {
if (this._trackedChanges.length < 2) {
return
}
// ranges are non-overlapping so we can sort based on their first indices
this._trackedChanges.sort((a, b) => a.range.start - b.range.start)
const newTrackedChanges = [this._trackedChanges[0]]
for (let i = 1; i < this._trackedChanges.length; i++) {
const last = newTrackedChanges[newTrackedChanges.length - 1]
const current = this._trackedChanges[i]
if (last.range.overlaps(current.range)) {
throw new Error('Ranges cannot overlap')
}
if (current.range.isEmpty()) {
throw new Error('Tracked changes range cannot be empty')
}
if (last.canMerge(current)) {
newTrackedChanges[newTrackedChanges.length - 1] = last.merge(current)
} else {
newTrackedChanges.push(current)
}
}
this._trackedChanges = newTrackedChanges
}
/**
* Apply an insert operation
*
* @param {number} cursor
* @param {string} insertedText
* @param {{tracking?: TrackingProps}} opts
*/
applyInsert(cursor, insertedText, opts = {}) {View on GitHub (pinned to 28ad3b03b7)
Solutions
- Skip creating TrackedChange entries whose range isEmpty() before adding them.
- After deletes, recompute or remove tracked changes whose spans were fully deleted.
- Validate offsets (range.start < range.end) when building ranges programmatically.
- Filter out empty ranges from any deserialized/persisted tracked-change data before loading.
Example fix
// before
list.add(new TrackedChange(new Range(pos, pos), tracking))
// after
const range = new Range(start, end)
if (!range.isEmpty()) { list.add(new TrackedChange(range, tracking)) } Defensive patterns
Strategy: validation
Validate before calling
// drop empty ranges before adding const usable = candidate.filter(tc => !tc.range.isEmpty())
Try / catch
try {
list.add(tc)
} catch (err) {
if (err.message !== 'Tracked changes range cannot be empty') throw err
// skip: the change spanned only deleted content
} Prevention
- Assert range.start < range.end when constructing ranges
- Remove tracked changes whose entire span was deleted instead of keeping a zero-length range
- Filter empty ranges when importing persisted tracked-change data
When it happens
Trigger: Adding a TrackedChange whose Range has start === end, or an operation (applyInsert/applyDelete/applyRetain/applyTextOperation/add) that shrinks a tracked change to zero length, causing _mergeRanges to encounter an empty range.
Common situations: Deleting the entire span covered by a tracked change and reusing the stale (now empty) range; constructing ranges from computed offsets where start and end coincide after a delete; off-by-one offset arithmetic.
Related errors
- Ranges cannot overlap
- Cannot merge tracked changes
- too many comments or tracked changes
- must be a string?
- Document has comments or tracked changes
AI-assisted analysis of overleaf/overleaf@28ad3b03b7 (2026-09-03).
Data as JSON: /api/errors/0d359e739fb04e0a.
Report an issue: GitHub.