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

  1. Skip creating TrackedChange entries whose range isEmpty() before adding them.
  2. After deletes, recompute or remove tracked changes whose spans were fully deleted.
  3. Validate offsets (range.start < range.end) when building ranges programmatically.
  4. 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

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


AI-assisted analysis of overleaf/overleaf@28ad3b03b7 (2026-09-03). Data as JSON: /api/errors/0d359e739fb04e0a. Report an issue: GitHub.