Skip to content

fix: debounce text block gRPC saves to eliminate typing lag (#2218)#2219

Merged
ra3orblade merged 2 commits into
anyproto:developfrom
Toseef-Ahmad:fix/typing-lag-debounce-2218
May 18, 2026
Merged

fix: debounce text block gRPC saves to eliminate typing lag (#2218)#2219
ra3orblade merged 2 commits into
anyproto:developfrom
Toseef-Ahmad:fix/typing-lag-debounce-2218

Conversation

@Toseef-Ahmad

Copy link
Copy Markdown
Contributor

Root cause: Every keystroke in the text editor triggered a synchronous gRPC round-trip to middleware via setText() -> blockSetText() -> C.BlockTextSetText(). At normal typing speed (8-12 chars/sec), these piled up and blocked the main thread, causing visible freezes and buffered input.

Additionally, a dependency-less useEffect called setValue() twice per render cycle — each call performing full HTML parsing, mark processing, innerHTML replacement, and mention/emoji rendering — doubling the already-expensive DOM work on every keystroke echo from middleware.

Changes:

  • Debounce setText() in onKeyUpHandler with 300ms window. The contenteditable DOM already reflects typed text natively via the browser — only the gRPC persistence needs batching.
  • Remove duplicate setValue() call in the render-loop useEffect that was redundantly rebuilding the DOM a second time per render.
  • Flush debounce timer on critical paths (blur, keydown, paste, context menu open) to prevent stale overwrites and ensure no data loss.
  • Clean up debounce timer on component unmount.

Fixes #2218


  • I understand that contributing to this repository will require me to agree with the CLA

Description

What type of PR is this? (check all applicable)

  • 🍕 Feature
  • 🐛 Bug Fix
  • 📝 Documentation Update
  • 🎨 Style
  • 🧑‍💻 Code Refactor
  • 🔥 Performance Improvements
  • ✅ Test
  • 🤖 Build
  • 🔁 CI

Related Tickets & Documents

Mobile & Desktop Screenshots/Recordings

Added tests?

  • 👍 yes
  • 🙅 no, because they aren't needed
  • 🙋 no, because I need help

Added to documentation?

  • 📜 README.md
  • 📓 tech-docs
  • 🙅 no documentation needed

…#2218)

Root cause: Every keystroke in the text editor triggered a synchronous
gRPC round-trip to middleware via setText() -> blockSetText() ->
C.BlockTextSetText(). At normal typing speed (8-12 chars/sec), these
piled up and blocked the main thread, causing visible freezes and
buffered input.

Additionally, a dependency-less useEffect called setValue() twice per
render cycle — each call performing full HTML parsing, mark processing,
innerHTML replacement, and mention/emoji rendering — doubling the
already-expensive DOM work on every keystroke echo from middleware.

Changes:
- Debounce setText() in onKeyUpHandler with 300ms window. The
  contenteditable DOM already reflects typed text natively via the
  browser — only the gRPC persistence needs batching.
- Remove duplicate setValue() call in the render-loop useEffect that
  was redundantly rebuilding the DOM a second time per render.
- Flush debounce timer on critical paths (blur, keydown, paste,
  context menu open) to prevent stale overwrites and ensure no data
  loss.
- Clean up debounce timer on component unmount.

Fixes anyproto#2218
@Toseef-Ahmad

Copy link
Copy Markdown
Contributor Author

@ra3orblade Please look into this.

@ra3orblade

Copy link
Copy Markdown
Contributor

@ra3orblade Please look into this.

needs testing thoroughly, we had the timeout logic before and it did not work good. for example when leaving the page it should save forcefully imediately in blur on unmount, and similar cases.

Address maintainer review feedback: the unmount cleanup now
force-saves any pending debounced text to middleware before the
component is destroyed, preventing data loss when navigating away
from a page while typing.

Flush/save coverage:
- onBlur: clears timer + immediate setText
- onKeyDown: clears timer (structural keys do their own save)
- onPaste: via onKeyDown path
- context menu: clears timer + immediate setText
- unmount: clears timer + force blockSetText if dirty
@Toseef-Ahmad

Copy link
Copy Markdown
Contributor Author

@ra3orblade Hey, totally fair point — that's exactly the kind of thing that can go wrong with timeouts. I actually just pushed a fix for the unmount case (b61958a). Now if the component unmounts while a save is pending, it force-flushes to middleware immediately before cleanup.

The debounce only touches the onKeyUp typing path — blur, Enter, Backspace, paste, and page navigation all save synchronously like before. No timeout waiting on any of those.

Would appreciate it if you could take a look at the updated code when you get a chance 🙏

@Toseef-Ahmad

Copy link
Copy Markdown
Contributor Author

@ra3orblade Hi, I hope you are doing well. I know you are super busy, but please when you get sometime just looked into this. Thanks a lot.

@ra3orblade ra3orblade merged commit 7f411d0 into anyproto:develop May 18, 2026
3 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators May 18, 2026
@ra3orblade

Copy link
Copy Markdown
Contributor

The code itself looks fine, needs testing thoroughly anyway, so please double-check.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐞 Input/Typing Lag

2 participants