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

  1. Use exactly "before" or "after" for affinity, or omit the property if you do not need affinity
  2. Centralize the affinity value in a union type: type Affinity = "before" | "after" so the compiler rejects others
  3. Validate user/config-driven affinity values before passing them through
  4. 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

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


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/6c57aa70fef97549. Report an issue: GitHub.