{"record":{"id":"63733ba1bc8051f8","repo":"tldraw/tldraw","slug":"tu-must-be-finite-and-greater-than-tc","errorCode":null,"errorMessage":"Tu must be finite and greater than Tc","messagePattern":"Tu must be finite and greater than Tc","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/commenting/src/clustering/computeClusterTable.ts","lineNumber":63,"sourceCode":"\t\tmaxSplitZoom: opts.maxSplitZoom,\n\t})\n\n\treturn { events, leaves: leafNodes }\n}\n\nfunction resolveOptions(options: ClusterOptions): ResolvedClusterOptions {\n\tconst Tc = options.Tc ?? 22\n\tconst Tu = options.Tu ?? 1.2 * Tc\n\tconst eps = options.eps ?? 0.7\n\tconst Dmax = options.Dmax ?? 3.75 * Tc\n\tconst maxSplitZoom = options.maxSplitZoom ?? 6\n\tconst { minZoom, maxZoom } = options\n\n\tif (!Number.isFinite(Tc) || Tc <= 0) {\n\t\tthrow new Error('Tc must be finite and greater than 0')\n\t}\n\tif (!Number.isFinite(Tu) || Tu <= Tc) {\n\t\tthrow new Error('Tu must be finite and greater than Tc')\n\t}\n\tif (!Number.isFinite(eps) || eps < 0) {\n\t\tthrow new Error('eps must be finite and greater than or equal to 0')\n\t}\n\tif (!Number.isFinite(Dmax) || Dmax < Tc) {\n\t\tthrow new Error('Dmax must be finite and greater than or equal to Tc')\n\t}\n\tif (!Number.isFinite(minZoom) || minZoom <= 0) {\n\t\tthrow new Error('minZoom must be finite and greater than 0')\n\t}\n\tif (!Number.isFinite(maxZoom) || maxZoom <= minZoom) {\n\t\tthrow new Error('maxZoom must be finite and greater than minZoom')\n\t}\n\tif (!Number.isFinite(maxSplitZoom) || maxSplitZoom <= 0) {\n\t\tthrow new Error('maxSplitZoom must be finite and greater than 0')\n\t}\n\n\treturn { Tc, Tu, eps, Dmax, minZoom, maxZoom, maxSplitZoom }","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/tldraw/tldraw/blob/b31086b44731a7d1d9d46be4163ac8ef7417321d/packages/commenting/src/clustering/computeClusterTable.ts#L45-L81","documentation":"Thrown by the commenting clustering module when Tu (the uncluster threshold — the minimum temporal/spatial separation above which clusters split apart) is not finite or is not greater than Tc. Tu must exceed Tc because clustering contracts below Tc and splits above Tu; Tu <= Tc would collapse the two regimes into a contradiction.","triggerScenarios":"Passing options.Tu less than or equal to options.Tc, or leaving Tu unset while a custom Tc larger than the default Tu multiple (1.2*Tc) is provided (not possible by default, but a future change could). Explicitly passing Tu = NaN or Infinity. The most common case is Tu <= Tc after both are customized independently.","commonSituations":"A host tunes Tc up (e.g. to 40) and also sets Tu to a fixed value (e.g. 30) that no longer exceeds Tc. A config preset has Tu and Tc swapped. Tu is derived from a formula that can drop below Tc under certain canvas conditions.","solutions":["Ensure Tu > Tc; if customizing Tc, either omit Tu (so it defaults to 1.2*Tc) or compute Tu from Tc: `Tu = Tc * 1.2`.","Validate the relationship before calling: `if (!(Tu > Tc)) throw ...` in your own config layer.","Review config presets to confirm Tu/Tc ordering matches the clustering contract.","Clamp: `const Tu = Math.max(options.Tu ?? 1.2 * Tc, Tc + 1)`."],"exampleFix":"// before\nconst Tu = options.Tu ?? 1.2 * Tc\nif (!Number.isFinite(Tu) || Tu <= Tc) {\n  throw new Error('Tu must be finite and greater than Tc')\n}\n\n// caller side: derive Tu from Tc so the invariant always holds\nconst Tc = 40\ncomputeClusterTable(leaves, { Tc, Tu: Tc * 1.2, minZoom, maxZoom })","handlingStrategy":"validation","validationCode":"function assertValidTu(Tc: number, options: ClusterOptions): void {\n  const Tu = options.Tu ?? 1.2 * Tc\n  if (!Number.isFinite(Tu) || Tu <= Tc) {\n    throw new Error(`options.Tu must be finite and > Tc (${Tc}). Got ${Tu}. Omit Tu to default to 1.2*Tc.`)\n  }\n}","typeGuard":"function isValidTu(Tu: unknown, Tc: number): boolean {\n  return typeof Tu === 'number' && Number.isFinite(Tu) && Tu > Tc\n}","tryCatchPattern":null,"preventionTips":["If you customize Tc, omit Tu so it scales automatically (1.2 * Tc).","Review config presets to confirm Tu > Tc whenever both are set.","Derive Tu from Tc in your config layer: `Tu = Tc * 1.2`, rather than hardcoding both."],"tags":["commenting","clustering","validation","config"],"backgroundTag":null,"analyzedSha":"b31086b44731a7d1d9d46be4163ac8ef7417321d","analyzedAt":"2026-08-12T17:05:39.947Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}