{"record":{"id":"6c57aa70fef97549","repo":"siyuan-note/siyuan","slug":"the-tracked-range-affinity-must-be-before-or-after","errorCode":null,"errorMessage":"The tracked range affinity must be before or after","messagePattern":"The tracked range affinity must be before or after","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"app/src/protyle/util/trackedRange.ts","lineNumber":1140,"sourceCode":"                handles.delete(item);\n            }\n        });\n        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,","sourceCodeStart":1122,"sourceCodeEnd":1158,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/app/src/protyle/util/trackedRange.ts#L1122-L1158","documentation":"Thrown by trackRange when options.affinity is provided but is neither \"before\" nor \"after\". Affinity controls which side the tracked range snaps to when content changes; only those two values are meaningful, so any other value is rejected as a TypeError. Omitting affinity entirely is valid (no affinity requested).","triggerScenarios":"Calling trackRange(protyle, range, {owner, affinity: \"near\"}) or any typo/other string (e.g. \"After\", \"around\") — anything not exactly \"before\" or \"after\".","commonSituations":"Typos or wrong casing in the affinity string; inventing a third mode not supported by the API; copying an option name from a different API.","solutions":["Use exactly \"before\" or \"after\" for affinity, or omit the property if you do not need affinity","Centralize the affinity value in a union type: type Affinity = \"before\" | \"after\" so the compiler rejects others","Validate user/config-driven affinity values before passing them through","Check ITrackRangeOptions docs for supported values"],"exampleFix":"// before\ntrackRange(protyle, range, {owner: plugin, affinity: \"near\"});\n// after\ntrackRange(protyle, range, {owner: plugin, affinity: \"before\"});","handlingStrategy":"validation","validationCode":"type Affinity = \"before\" | \"after\";\nfunction assertAffinity(a: string | undefined): asserts a is Affinity | undefined {\n  if (a !== undefined && a !== \"before\" && a !== \"after\") throw new TypeError(`bad affinity: ${a}`);\n}","typeGuard":"const isAffinity = (v: unknown): v is \"before\" | \"after\" => v === \"before\" || v === \"after\";","tryCatchPattern":"try {\n  trackRange(protyle, range, {owner: plugin, affinity});\n} catch (e) {\n  if (e instanceof TypeError && e.message.includes(\"affinity\")) {\n    trackRange(protyle, range, {owner: plugin}); // retry without affinity\n  } else throw e;\n}","preventionTips":["Type affinity as the union \"before\" | \"after\" so invalid values are compile errors","Omit affinity instead of inventing values like \"near\" or \"around\"","Validate config/user-supplied affinity strings before passing them through","Watch string casing: \"Before\" and \"After\" are rejected"],"tags":["protyle","arguments","validation"],"backgroundTag":"invalid-enum-value","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}