Skip to content

perf: reduce TDD page latency with draw batching and DOM optimizations#9067

Draft
ericokuma wants to merge 1 commit intomainfrom
cursor/APP-789-tdd-page-latency-6fd7
Draft

perf: reduce TDD page latency with draw batching and DOM optimizations#9067
ericokuma wants to merge 1 commit intomainfrom
cursor/APP-789-tdd-page-latency-6fd7

Conversation

@ericokuma
Copy link
Contributor

Addresses the TDD page latency issue (APP-789) where scrolling and hovering on the Time Dimension Detail table causes significant lag. The root causes were: multiple redundant draw() calls per animation frame, per-cell DOM event handler allocation on every draw cycle, and unnecessary object/array allocations in hot render paths.

Changes

1. Batch draw calls with requestAnimationFrame (TDDTable.svelte)

  • Introduced scheduleDraw() which coalesces multiple draw() requests into a single frame via requestAnimationFrame
  • Replaced all direct pivot?.draw() calls (from reactive blocks, hover handlers, mouseleave) with scheduleDraw()
  • Previously, a single hover event could trigger 2+ draws: one from handleMouseHover and another from the reactive block tracking highlightedColStart/highlightedColEnd

2. Remove per-cell event handlers (RegularTable.svelte)

  • Removed td.onmouseover and th.onmouseover/th.onfocus assignments from style_td and style_row_th
  • These were creating new closures for every visible cell on every draw cycle, triggering cellInspectorStore.updateValue on every mouse movement
  • Replaced with a single table-level event delegation handler via inspectCellValue()

3. Optimize styleListener DOM queries (RegularTable.svelte)

  • Replaced four separate querySelectorAll + Array.from calls with targeted querySelector("tbody")/querySelector("thead") lookups
  • Use direct for loops over NodeList instead of creating intermediate arrays

4. Reduce per-cell allocations (TDDTable.svelte)

  • Hoisted classesToRemove arrays to module-level constants (CELL_BG_CLASSES_TO_REMOVE, ROW_HEADER_BG_CLASSES_TO_REMOVE) instead of allocating on every renderCell call
  • Simplified class manipulation using classList.toggle() instead of conditional push + batch add

5. Fix hover condition bug (TDDTable.svelte)

  • Changed && to || in handleMouseHover so highlight updates when either row OR column changes, not only when both change simultaneously

6. Make setTimeout hack one-shot (TDDTable.svelte)

  • The setTimeout(pivot.draw, 0) workaround was re-triggering on every pivot reference change; now gated with initialExtraDrawDone flag

7. Optimize getClassForCell (util.ts)

  • Hoisted the background color constants to module scope, eliminating per-call object allocation

Impact

These changes primarily reduce the number of full table redraws during interaction (hover, scroll) and reduce the per-cell work during each draw cycle. For a typical TDD view with YTD daily data (~365 columns, 12 rows), this eliminates hundreds of redundant closure allocations and multiple redundant draw passes per frame.

Checklist:

  • Covered by tests
  • Ran it and it works as intended
  • Reviewed the diff before requesting a review
  • Checked for unhandled edge cases
  • Linked the issues it closes
  • Checked if the docs need to be updated. If so, create a separate Linear DOCS issue
  • Intend to cherry-pick into the release branch
  • I'm proud of this work!

Linear Issue: APP-789

Open in Web Open in Cursor 

Key changes:
- Batch multiple draw() calls using requestAnimationFrame (scheduleDraw)
  to coalesce redundant redraws into a single frame
- Remove per-cell onmouseover/onfocus handlers from RegularTable cells,
  use event delegation via a single table-level mouseover listener instead
- Optimize styleListener to reduce DOM queries (single querySelector
  per section instead of multiple querySelectorAll + Array.from)
- Pre-allocate class removal arrays as constants instead of creating
  new arrays on every renderCell call
- Fix hover condition bug (AND -> OR) so highlight updates when either
  row or column changes, not only when both change simultaneously
- Make the setTimeout initial draw hack one-shot instead of firing on
  every pivot reference change
- Hoist bgColors lookup table in getClassForCell to module scope to
  avoid object allocation per cell

Co-authored-by: ericokuma <ericokuma@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants