overleaf/overleaf · error · Error

Adjacent skip components should be combined

Error message

Adjacent skip components should be combined

What it means

text-tp2 requires that consecutive numeric (skip) components be merged into a single number: [3, 4] must be [7]. checkOp tracks the previous component (`last`) and throws if it sees a skip immediately following another skip. Normalized ops from this library never contain adjacent skips, so finding them means the op was hand-built or altered.

Source

Thrown at services/document-updater/app/js/sharejs/types/text-tp2.js:102

          ) {
            throw new Error('Inserts must insert a string or a +ive number')
          }
        } else if (c.d !== undefined) {
          if (typeof c.d !== 'number' || !(c.d > 0)) {
            throw new Error('Deletes must be a +ive number')
          }
        } else {
          throw new Error('Operation component must define .i or .d')
        }
      } else {
        if (typeof c !== 'number') {
          throw new Error('Op components must be objects or numbers')
        }
        if (!(c > 0)) {
          throw new Error('Skip components must be a positive number')
        }
        if (typeof last === 'number') {
          throw new Error('Adjacent skip components should be combined')
        }
      }

      result.push((last = c))
    }
    return result
  })()
}

// Take the next part from the specified position in a document snapshot.
// position = {index, offset}. It will be updated.
type._takeDoc = takeDoc = function (
  doc,
  position,
  maxlength,
  tombsIndivisible
) {
  if (position.index >= doc.data.length) {

View on GitHub (pinned to 28ad3b03b7)

Solutions

  1. Sum adjacent numeric components into one before submitting the op (normalize the op).
  2. When appending a skip, check if the previous component is a number and add to it instead of pushing.
  3. Only interleave skips with insert/delete components; never emit two skips in a row.

Example fix

// before
op.push(3)
op.push(4)
// after
const last = op[op.length - 1]
if (typeof last === 'number') op[op.length - 1] = last + 4
else op.push(4)
Defensive patterns

Strategy: validation

Validate before calling

function isNormalized(op) {
  for (let i = 1; i < op.length; i++) {
    if (typeof op[i] === 'number' && typeof op[i - 1] === 'number') return false
  }
  return true
}
if (!isNormalized(op)) op = mergeAdjacentSkips(op)

Type guard

function hasAdjacentSkips(op) {
  return op.some((c, i) => i > 0 && typeof c === 'number' && typeof op[i - 1] === 'number')
}

Try / catch

try {
  tp2type.apply(doc, op)
} catch (e) {
  if (e.message === 'Adjacent skip components should be combined') {
    tp2type.apply(doc, mergeAdjacentSkips(op))
  } else throw e
}

Prevention

When it happens

Trigger: Pushing two numeric components back-to-back into an op array before calling type.apply / type.transform / type.prune; concatenating two ops without normalization; diffing algorithms that emit a skip per unchanged run instead of one merged skip.

Common situations: Hand-rolled diff/merge tools producing unnormalized ops; code that appends to an op whose last component is already a skip without checking; porting ops from other OT types into tp2; middleware that rewrites ops in transit.

Related errors


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