tldraw/tldraw · error · Error

Non-finite coordinate for leaf id: ${leaf.id}

Error message

Non-finite coordinate for leaf id: ${leaf.id}

What it means

Thrown by mstEdges() (called from computeClusterTable) when a leaf's point.x or point.y is NaN or +/-Infinity. LeafInput.point must be finite — the MST distance math and the cluster centroid/bbox accumulators assume finite coordinates; a single non-finite value propagates Infinity into every distance and silently breaks clustering. The check fires per-leaf in input order.

Source

Thrown at packages/commenting/src/clustering/mst.ts:22

 * Return the Euclidean MST over the leaves, ordered by the canonical edge key:
 * distance, then normalized endpoint ids.
 */
export function mstEdges(leaves: readonly LeafInput[]): MstEdge[] {
	const n = leaves.length
	const ids = new Array<string>(n)
	const xs = new Float64Array(n)
	const ys = new Float64Array(n)
	const seen = new Set<string>()

	let root = 0
	for (let i = 0; i < n; i++) {
		const leaf = leaves[i]
		if (seen.has(leaf.id)) {
			throw new Error(`Duplicate leaf id: ${leaf.id}`)
		}
		seen.add(leaf.id)
		if (!Number.isFinite(leaf.point.x) || !Number.isFinite(leaf.point.y)) {
			throw new Error(`Non-finite coordinate for leaf id: ${leaf.id}`)
		}
		ids[i] = leaf.id
		xs[i] = leaf.point.x
		ys[i] = leaf.point.y
		if (i > 0 && leaf.id < ids[root]) root = i
	}

	if (n < 2) return []

	const inTree = new Uint8Array(n)
	const bestD2 = new Float64Array(n)
	const bestFrom = new Int32Array(n)

	inTree[root] = 1
	bestD2.fill(Number.POSITIVE_INFINITY)
	bestFrom.fill(-1)

	for (let i = 0; i < n; i++) {

View on GitHub (pinned to b31086b447)

Solutions

  1. Filter or repair leaves before calling: skip leaves whose point is not finite, or clamp them to a known-good anchor.
  2. Fix the upstream geometry resolver so it never emits NaN/Infinity.
  3. Assert Number.isFinite on every coordinate in the builder as a guard.

Example fix

// before
computeClusterTable([
  { id: 't1', point: { x: NaN, y: 0 } },
], opts)
// after
const clean = leaves.filter(
  (l) => Number.isFinite(l.point.x) && Number.isFinite(l.point.y)
)
computeClusterTable(clean, opts)
Defensive patterns

Strategy: validation

Validate before calling

const leaves = rawLeaves.filter(
  (l) => Number.isFinite(l.point.x) && Number.isFinite(l.point.y)
)
computeClusterTable(leaves, opts)

Type guard

function hasFinitePoints(leaves: readonly { point: { x: number; y: number } }[]): boolean {
  return leaves.every((l) => Number.isFinite(l.point.x) && Number.isFinite(l.point.y))
}

Prevention

When it happens

Trigger: Calling computeClusterTable with a leaf whose point is { x: NaN, y: 0 }, { x: Infinity, y: 0 }, or any coordinate produced by a failed arithmetic operation upstream. Also reachable via mstEdges() directly.

Common situations: Resolving a pin anchor from a shape whose geometry returned NaN (e.g. zero-size bounding box center before layout); JSON-parsing a missing coordinate as undefined then arithmetic on it; a migration that left point as null/NaN for old records.

Related errors


AI-assisted analysis of tldraw/tldraw@b31086b447 (2026-08-12). Data as JSON: /api/errors/c17efed05068b965. Report an issue: GitHub.