siyuan-note/siyuan · error · TypeError
The tracked range affinity must be before or after
Error message
The tracked range affinity must be before or after
What it means
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).
Source
Thrown at app/src/protyle/util/trackedRange.ts:1140
handles.delete(item);
}
});
if (handles?.size === 0) {
ownedHandles.delete(state.owner);
}
}
};
export const trackRange = (protyle: IProtyle, range: Range,
options: ITrackRangeOptions): ITrackedRangeHandle => {
if (destroyedProtyles.has(protyle)) {
throw new Error("Cannot track a range in a destroyed Protyle instance");
}
if (!options?.owner || !["function", "object"].includes(typeof options.owner)) {
throw new TypeError("The tracked range owner is required");
}
if (options.affinity && !["after", "before"].includes(options.affinity)) {
throw new TypeError("The tracked range affinity must be before or after");
}
if (unloadingPlugins.has(options.owner)) {
throw new Error("Cannot track a range for an unloaded plugin");
}
const rangeSnapshot = getRangeSnapshot(protyle, range);
if (!rangeSnapshot) {
throw new TypeError("The range must be inside one editable source block of this Protyle instance");
}
const targetTokens = rangeSnapshot.stream.tokens.slice(rangeSnapshot.start, rangeSnapshot.end);
if (!range.collapsed && targetTokens.length === 0) {
throw new TypeError("The tracked range must contain semantic content");
}
const handle = Object.freeze({id: `tracked-range-${++handleSequence}`});
const trackedRange = range.cloneRange();
const state: ITrackedRangeState = {
range: trackedRange,
startContainer: trackedRange.startContainer,
endContainer: trackedRange.endContainer,View on GitHub (pinned to 8641553a1f)
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
Example fix
// before
trackRange(protyle, range, {owner: plugin, affinity: "near"});
// after
trackRange(protyle, range, {owner: plugin, affinity: "before"}); Defensive patterns
Strategy: validation
Validate before calling
type Affinity = "before" | "after";
function assertAffinity(a: string | undefined): asserts a is Affinity | undefined {
if (a !== undefined && a !== "before" && a !== "after") throw new TypeError(`bad affinity: ${a}`);
} Type guard
const isAffinity = (v: unknown): v is "before" | "after" => v === "before" || v === "after";
Try / catch
try {
trackRange(protyle, range, {owner: plugin, affinity});
} catch (e) {
if (e instanceof TypeError && e.message.includes("affinity")) {
trackRange(protyle, range, {owner: plugin}); // retry without affinity
} else throw e;
} Prevention
- 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
When it happens
Trigger: Calling trackRange(protyle, range, {owner, affinity: "near"}) or any typo/other string (e.g. "After", "around") — anything not exactly "before" or "after".
Common situations: 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.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- The tracked range owner is required
- invalid tool arguments: %w
- topic and event required
- method name and function required
- method name required
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/6c57aa70fef97549.
Report an issue: GitHub.