{"record":{"id":"b5d471a18c244627","repo":"siyuan-note/siyuan","slug":"cannot-track-a-range-for-an-unloaded-plugin","errorCode":null,"errorMessage":"Cannot track a range for an unloaded plugin","messagePattern":"Cannot track a range for an unloaded plugin","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/protyle/util/trackedRange.ts","lineNumber":1143,"sourceCode":"        if (handles?.size === 0) {\n            ownedHandles.delete(state.owner);\n        }\n    }\n};\n\nexport const trackRange = (protyle: IProtyle, range: Range,\n                           options: ITrackRangeOptions): ITrackedRangeHandle => {\n    if (destroyedProtyles.has(protyle)) {\n        throw new Error(\"Cannot track a range in a destroyed Protyle instance\");\n    }\n    if (!options?.owner || ![\"function\", \"object\"].includes(typeof options.owner)) {\n        throw new TypeError(\"The tracked range owner is required\");\n    }\n    if (options.affinity && ![\"after\", \"before\"].includes(options.affinity)) {\n        throw new TypeError(\"The tracked range affinity must be before or after\");\n    }\n    if (unloadingPlugins.has(options.owner)) {\n        throw new Error(\"Cannot track a range for an unloaded plugin\");\n    }\n    const rangeSnapshot = getRangeSnapshot(protyle, range);\n    if (!rangeSnapshot) {\n        throw new TypeError(\"The range must be inside one editable source block of this Protyle instance\");\n    }\n    const targetTokens = rangeSnapshot.stream.tokens.slice(rangeSnapshot.start, rangeSnapshot.end);\n    if (!range.collapsed && targetTokens.length === 0) {\n        throw new TypeError(\"The tracked range must contain semantic content\");\n    }\n    const handle = Object.freeze({id: `tracked-range-${++handleSequence}`});\n    const trackedRange = range.cloneRange();\n    const state: ITrackedRangeState = {\n        range: trackedRange,\n        startContainer: trackedRange.startContainer,\n        endContainer: trackedRange.endContainer,\n        exactTarget: getExactlySelectedElement(trackedRange),\n        collapsed: trackedRange.collapsed,\n        affinity: options.affinity || \"before\",","sourceCodeStart":1125,"sourceCodeEnd":1161,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/app/src/protyle/util/trackedRange.ts#L1125-L1161","documentation":"trackRange precondition: options.owner is registered in unloadingPlugins, i.e. the plugin that would own the tracked range is mid-unload. Tracking a range on behalf of a plugin being unloaded would leak a handle nobody can release, so it is refused.","triggerScenarios":"Thrown at app/src/protyle/util/trackedRange.ts:1143 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Stop creating tracked ranges during plugin unload","Release existing handles in the plugin's onUnload callback","Re-track after the plugin is fully re-loaded"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}