{"record":{"id":"5c5c05cc0b50b534","repo":"overleaf/overleaf","slug":"adjacent-skip-components-should-be-combined","errorCode":null,"errorMessage":"Adjacent skip components should be combined","messagePattern":"Adjacent skip components should be combined","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"services/document-updater/app/js/sharejs/types/text-tp2.js","lineNumber":102,"sourceCode":"          ) {\n            throw new Error('Inserts must insert a string or a +ive number')\n          }\n        } else if (c.d !== undefined) {\n          if (typeof c.d !== 'number' || !(c.d > 0)) {\n            throw new Error('Deletes must be a +ive number')\n          }\n        } else {\n          throw new Error('Operation component must define .i or .d')\n        }\n      } else {\n        if (typeof c !== 'number') {\n          throw new Error('Op components must be objects or numbers')\n        }\n        if (!(c > 0)) {\n          throw new Error('Skip components must be a positive number')\n        }\n        if (typeof last === 'number') {\n          throw new Error('Adjacent skip components should be combined')\n        }\n      }\n\n      result.push((last = c))\n    }\n    return result\n  })()\n}\n\n// Take the next part from the specified position in a document snapshot.\n// position = {index, offset}. It will be updated.\ntype._takeDoc = takeDoc = function (\n  doc,\n  position,\n  maxlength,\n  tombsIndivisible\n) {\n  if (position.index >= doc.data.length) {","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/overleaf/overleaf/blob/28ad3b03b71cb4311decdcb55c36b33ec10d72db/services/document-updater/app/js/sharejs/types/text-tp2.js#L84-L120","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Sum adjacent numeric components into one before submitting the op (normalize the op).","When appending a skip, check if the previous component is a number and add to it instead of pushing.","Only interleave skips with insert/delete components; never emit two skips in a row."],"exampleFix":"// before\nop.push(3)\nop.push(4)\n// after\nconst last = op[op.length - 1]\nif (typeof last === 'number') op[op.length - 1] = last + 4\nelse op.push(4)","handlingStrategy":"validation","validationCode":"function isNormalized(op) {\n  for (let i = 1; i < op.length; i++) {\n    if (typeof op[i] === 'number' && typeof op[i - 1] === 'number') return false\n  }\n  return true\n}\nif (!isNormalized(op)) op = mergeAdjacentSkips(op)","typeGuard":"function hasAdjacentSkips(op) {\n  return op.some((c, i) => i > 0 && typeof c === 'number' && typeof op[i - 1] === 'number')\n}","tryCatchPattern":"try {\n  tp2type.apply(doc, op)\n} catch (e) {\n  if (e.message === 'Adjacent skip components should be combined') {\n    tp2type.apply(doc, mergeAdjacentSkips(op))\n  } else throw e\n}","preventionTips":["When appending a skip, always try to merge into the previous numeric component first.","Run ops through a normalize step before persisting or sending.","Never concatenate two ops arrays directly; rebuild with merging."],"tags":["ot","operation-validation","normalization","document-updater"],"backgroundTag":"unnormalized-operation","analyzedSha":"28ad3b03b71cb4311decdcb55c36b33ec10d72db","analyzedAt":"2026-09-03T02:10:22.807Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T07:17:11.731Z"}