tldraw's Editor.ts carries the highest activity risk — 3 functions to address first

Three of tldraw's top five activity-risk hotspots live in a single file, Editor.ts, combining extreme cyclomatic complexity with sustained recent commit churn.

Stephen Collins ·
oss typescript refactoring code-health

Antipatterns Detected

complex_branching5exit_heavy5deeply_nested4god_function4long_function4hub_function1

Key Points

What is a god function and why does it matter in tldraw?

A god function is one that handles too many distinct responsibilities in a single body of code, resulting in high fan-out — it calls a large number of other functions across many subsystems. In tldraw, `putContentOntoCurrentPage` calls 51 distinct functions, meaning a change anywhere in its logic can produce unexpected ripple effects across half the codebase. That broad coupling makes the function hard to test in isolation and expensive to change safely.

How do I reduce cyclomatic complexity in TypeScript?

Extract each major branch group into a named private function with a single, clear responsibility — this replaces one large decision tree with several smaller, independently testable ones. For deeply nested conditions specifically, early-return guards (also called guard clauses) can flatten the structure significantly without changing behavior.

Is tldraw actively maintained?

The activity data strongly suggests yes: the top hotspots each carry recent commit activity levels above 17, indicating high recent commit frequency across the most structurally complex parts of the codebase. That level of sustained churn on a 14,187-function codebase points to active feature development and iteration, not a dormant project.

How do I reproduce this analysis?

Run the Hotspots CLI against the tldraw/tldraw repository at commit 3383b00 to reproduce the exact scores and rankings shown here.

What does activity-weighted risk mean?

Complexity × recent commit frequency — functions that are hard to understand AND actively changing are the highest priority for refactoring.

In tldraw/tldraw at commit 3383b00, three of the five highest activity-risk functions — putContentOntoCurrentPage, getShapeAtPoint, and _flushEventForTick — all live in packages/editor/src/lib/editor/Editor.ts, and each carries a recent commit activity above 17, meaning recent commit frequency alone flags them as actively evolving code, not dormant complexity. That combination is what separates a refactoring backlog item from a live regression risk: getShapeAtPoint, for example, has a cyclomatic complexity of 59 and a recent commit activity of 17.93, so every new commit lands inside a function with 59 independent execution paths. Across 14,187 total functions, 699 have reached critical band — roughly 1 in 20 functions in this codebase is simultaneously structurally complex and actively changing.

The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).

Top 5 Hotspots

FunctionFileRiskCCNDFO
putContentOntoCurrentPagepackages/editor/src/lib/editor/Editor.ts19.336751
putExcalidrawContentpackages/tldraw/src/lib/utils/excalidraw/putExcalidrawContent.ts18.841648
getShapeAtPointpackages/editor/src/lib/editor/Editor.ts18.859712
fetchapps/mcp-app/src/cloudflare-worker.ts18.71749
_flushEventForTickpackages/editor/src/lib/editor/Editor.ts18.2122535

Codemod / Tooling Files in Results

The function fetch in apps/mcp-app/src/cloudflare-worker.ts (activity-weighted risk 18.66) is application infrastructure — a Cloudflare Worker HTTP handler — rather than core tldraw editor or library code. It scores highly because it is a hub function routing many request types (CC 17, fan-out 9) and has seen significant recent activity (recent commit activity 13.56), but it is operationally separate from the editor packages. To exclude it from future library-focused analyses, add "exclude": ["apps/mcp-app/**"] to your .hotspotsrc.json.

Hotspot Analysis

_flushEventForTick — packages/editor/src/lib/editor/Editor.ts

Based on its name and location in the core Editor, _flushEventForTick almost certainly processes and dispatches accumulated input or state-change events on each animation or update tick — a timing-sensitive, central coordination point. Its cyclomatic complexity of 122 is extreme by any standard: that figure represents 122 independent execution paths, each a required test case and a potential bug surface. With a recent commit activity of 17.3 and nesting depth of 5, this function is being actively modified inside logic that is already at the outer limit of what a human reviewer can reason about in a single pass, making every commit here a regression risk. Its fan-out of 35 means changes propagate to at least 35 other call sites or subsystems.

Recommendation: Before any refactoring, write characterization tests that lock down current observable behavior across the major event categories this function handles. Then extract distinct event-type handlers as named private methods — reducing the monolithic branch tree into individually testable units and bringing CC down from the extreme range.

getShapeAtPoint — packages/editor/src/lib/editor/Editor.ts

From its name and position in the editor core, getShapeAtPoint likely implements hit-testing logic — determining which shape, if any, occupies a given canvas coordinate, likely accounting for layering, visibility, and shape type. A cyclomatic complexity of 59 indicates a large number of conditional branches, consistent with handling many shape types, edge cases, and spatial conditions; combined with a nesting depth of 7, the branching is also structurally deep, not just wide. With a recent commit activity of 17.93 — the highest raw activity score of any function in the top five — this function is being touched frequently while already carrying 59 paths through it, making it the single most active regression surface in the dataset.

Recommendation: Map the CC-59 branch tree to explicit shape-type categories and extract per-category hit-test logic into separate, independently testable functions. Review whether any of the 7 nesting levels can be flattened with early-return guards, which would also reduce the exit-heavy test burden.

putContentOntoCurrentPage — packages/editor/src/lib/editor/Editor.ts

This function’s name and location suggest it orchestrates placing arbitrary content — shapes, assets, or imported data — onto the active canvas page, a task that likely requires resolving IDs, adjusting positions, handling conflicts, and invoking multiple subsystems. Its fan-out of 51 is the highest in the top three Editor.ts hotspots, meaning it directly calls 51 distinct functions — a god-function profile that gives it an exceptionally wide blast radius. At CC 36 and nesting depth 7, with a recent commit activity of 18.43 confirming it is under active development, any single commit risks introducing subtle regressions across the broad surface it coordinates.

Recommendation: Audit the 51 fan-out dependencies to identify which subsystems this function bridges — asset handling, ID resolution, layout, history — and introduce facade or service boundaries so that each concern can be modified and tested independently without touching the central orchestrator.

Patterns Found

Antipatterns detected across the top functions in this snapshot:

PatternOccurrences
complex_branching5
exit_heavy5
deeply_nested4
god_function4
long_function4
hub_function1

These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.

Key Takeaways

  • _flushEventForTick in Editor.ts has a cyclomatic complexity of 122 — write characterization tests immediately, before any refactor attempt, to prevent regressions in the tick-event dispatch path.
  • getShapeAtPoint carries the highest raw activity score (recent commit activity 17.93) of any function in the dataset and a CC of 59; it is the single most urgent target for decomposition and should be the first function covered in a hit-testing test suite.
  • putContentOntoCurrentPage calls 51 distinct functions (fan-out 51), giving it the widest blast radius in the top hotspots — map its dependency graph before modifying it and consider introducing service boundaries to contain future changes.
  • Three critical hotspots sharing a single file (Editor.ts) is an architectural concentration signal: consider whether the Editor class has grown beyond a single responsibility and whether a phased extraction into focused subsystems is warranted.

Reproduce This Analysis

git clone https://github.com/tldraw/tldraw
cd tldraw
git checkout 3383b00fff8351606f5432a608e769b9e7dce122
hotspots analyze . --mode snapshot --explain-patterns --force

To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.

Hotspots highlights structural and activity risk — not “bad code.” Findings are a prioritization aid, not a bug predictor. Editorial policy →

Run this on your own codebase

Hotspots runs locally in under a minute — no account, no data leaves your machine.

macOS
$ brew install Stephen-Collins-tech/tap/hotspots
Linux / cargo
$ cargo install hotspots-cli
Run in any repo
$ hotspots analyze .
★ Star on GitHub

Related Analyses