{"record":{"id":"cd8ccdcbb6b6b0b3","repo":"overleaf/overleaf","slug":"the-operation-didn-t-operate-on-the-whole-string","errorCode":null,"errorMessage":"The operation didn't operate on the whole string.","messagePattern":"The operation didn't operate on the whole string\\.","errorType":"validation","errorClass":"ApplyError","httpStatus":null,"severity":"error","filePath":"libraries/overleaf-editor-core/lib/operation/text_operation.js","lineNumber":326,"sourceCode":"        }\n        result += str.slice(inputCursor, inputCursor + op.length)\n        inputCursor += op.length\n      } else if (op instanceof InsertOp) {\n        file.comments.applyInsert(\n          new Range(result.length, op.insertion.length),\n          { commentIds: op.commentIds }\n        )\n        result += op.insertion\n      } else if (op instanceof RemoveOp) {\n        file.comments.applyDelete(new Range(result.length, op.length))\n        inputCursor += op.length\n      } else {\n        throw new UnprocessableError('Unknown ScanOp type during apply')\n      }\n    }\n\n    if (inputCursor !== str.length) {\n      throw new TextOperation.ApplyError(\n        \"The operation didn't operate on the whole string.\",\n        operation,\n        str\n      )\n    }\n\n    if (result.length > TextOperation.MAX_STRING_LENGTH) {\n      throw new TextOperation.TooLongError(operation, result.length)\n    }\n\n    file.trackedChanges.applyTextOperation(this)\n\n    file.content = result\n  }\n\n  /**\n   * @inheritdoc\n   * @param {number} length of the original string; non-negative","sourceCodeStart":308,"sourceCodeEnd":344,"githubUrl":"https://github.com/overleaf/overleaf/blob/28ad3b03b71cb4311decdcb55c36b33ec10d72db/libraries/overleaf-editor-core/lib/operation/text_operation.js#L308-L344","documentation":"After applying all ops, apply() verifies the input cursor consumed exactly the whole string. If retains+deletes did not cover every character, some text would be silently dropped, so the library throws ApplyError instead. This catches operations whose baseLength is smaller than the document.","triggerScenarios":"file.apply(op) where op.baseLength < str.length — e.g. an operation computed against a shorter/older document, an op missing trailing retains, or applying an op to a file that grew (e.g. a concurrent insert landed first).","commonSituations":"Concurrent editing where a peer's insertion wasn't transformed into this op; ops serialized without trailing retains trimmed correctly by another tool; applying an op built for a truncated preview of the document.","solutions":["Transform the operation against any concurrently applied ops before applying (TextOperation.transform)","Recompute the operation against the current content so baseLength equals str.length","Validate baseLength === content.length before apply and reject mismatches early","Restore file/order of ops so the original op applies to the exact document version it was computed against"],"exampleFix":"// before\nfile.apply(opBuiltForShorterDoc) // leaves trailing text unconsumed\n// after\nconst [opA] = TextOperation.transform(opBuiltForShorterDoc, concurrentOp)\nfile.apply(opA)","handlingStrategy":"validation","validationCode":"function fullyConsumes(file, op) {\n  const covered = op.ops.reduce((n, o) => (o instanceof RetainOp || o instanceof RemoveOp) ? n + o.length : n, 0)\n  return covered === file.getContent().length\n}","typeGuard":null,"tryCatchPattern":"try {\n  file.apply(op)\n} catch (err) {\n  if (err.name === 'ApplyError' && err.message.includes(\"didn't operate on the whole string\")) {\n    logger.warn({ baseLength: op.baseLength }, 'op does not cover whole string')\n    const [transformed] = TextOperation.transform(op, concurrentOps)\n    file.apply(transformed)\n  } else { throw err }\n}","preventionTips":["Transform ops against all concurrent operations before applying","Verify baseLength equals current content length before apply","Never build ops from truncated or partial document snapshots"],"tags":["ot","apply","concurrency"],"backgroundTag":"ot-base-length-mismatch","analyzedSha":"28ad3b03b71cb4311decdcb55c36b33ec10d72db","analyzedAt":"2026-09-03T02:10:22.807Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T07:17:11.731Z"}