{"record":{"id":"c17efed05068b965","repo":"tldraw/tldraw","slug":"non-finite-coordinate-for-leaf-id-leaf-id","errorCode":null,"errorMessage":"Non-finite coordinate for leaf id: ${leaf.id}","messagePattern":"Non-finite coordinate for leaf id: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/commenting/src/clustering/mst.ts","lineNumber":22,"sourceCode":" * Return the Euclidean MST over the leaves, ordered by the canonical edge key:\n * distance, then normalized endpoint ids.\n */\nexport function mstEdges(leaves: readonly LeafInput[]): MstEdge[] {\n\tconst n = leaves.length\n\tconst ids = new Array<string>(n)\n\tconst xs = new Float64Array(n)\n\tconst ys = new Float64Array(n)\n\tconst seen = new Set<string>()\n\n\tlet root = 0\n\tfor (let i = 0; i < n; i++) {\n\t\tconst leaf = leaves[i]\n\t\tif (seen.has(leaf.id)) {\n\t\t\tthrow new Error(`Duplicate leaf id: ${leaf.id}`)\n\t\t}\n\t\tseen.add(leaf.id)\n\t\tif (!Number.isFinite(leaf.point.x) || !Number.isFinite(leaf.point.y)) {\n\t\t\tthrow new Error(`Non-finite coordinate for leaf id: ${leaf.id}`)\n\t\t}\n\t\tids[i] = leaf.id\n\t\txs[i] = leaf.point.x\n\t\tys[i] = leaf.point.y\n\t\tif (i > 0 && leaf.id < ids[root]) root = i\n\t}\n\n\tif (n < 2) return []\n\n\tconst inTree = new Uint8Array(n)\n\tconst bestD2 = new Float64Array(n)\n\tconst bestFrom = new Int32Array(n)\n\n\tinTree[root] = 1\n\tbestD2.fill(Number.POSITIVE_INFINITY)\n\tbestFrom.fill(-1)\n\n\tfor (let i = 0; i < n; i++) {","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/tldraw/tldraw/blob/b31086b44731a7d1d9d46be4163ac8ef7417321d/packages/commenting/src/clustering/mst.ts#L4-L40","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Filter or repair leaves before calling: skip leaves whose point is not finite, or clamp them to a known-good anchor.","Fix the upstream geometry resolver so it never emits NaN/Infinity.","Assert Number.isFinite on every coordinate in the builder as a guard."],"exampleFix":"// before\ncomputeClusterTable([\n  { id: 't1', point: { x: NaN, y: 0 } },\n], opts)\n// after\nconst clean = leaves.filter(\n  (l) => Number.isFinite(l.point.x) && Number.isFinite(l.point.y)\n)\ncomputeClusterTable(clean, opts)","handlingStrategy":"validation","validationCode":"const leaves = rawLeaves.filter(\n  (l) => Number.isFinite(l.point.x) && Number.isFinite(l.point.y)\n)\ncomputeClusterTable(leaves, opts)","typeGuard":"function hasFinitePoints(leaves: readonly { point: { x: number; y: number } }[]): boolean {\n  return leaves.every((l) => Number.isFinite(l.point.x) && Number.isFinite(l.point.y))\n}","tryCatchPattern":null,"preventionTips":["Validate pin anchors at the geometry resolver — never emit NaN coordinates.","Skip or clamp leaves whose anchor failed to resolve rather than forwarding them.","Add an assertion in the leaf builder: every coordinate is Number.isFinite."],"tags":["validation","clustering","input-data","geometry","precondition"],"backgroundTag":null,"analyzedSha":"b31086b44731a7d1d9d46be4163ac8ef7417321d","analyzedAt":"2026-08-12T17:05:39.947Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}