Automattic / Automattic/gutenberg-sync-engines

Typing quickly in a table still briefly freezes the typist's browser

Open
#59 0 comments 0 reactions 0 assignees View on GitHub
agent:parked
Dominant language
JavaScript
Stars
2
Forks
0
Avg merge
11h 22m
Merged PRs (30d)
49

Description

## What happens now

When someone types quickly into a table while collaborating on a post, their own browser tab briefly stops responding — for one to three seconds on a slow or busy computer. Nothing is lost and everything catches up when the pause ends, but during the pause the tab is frozen: the letters stop appearing, and the other person stops receiving updates. This is the small remainder of a much bigger freeze that was fixed in #58 (issue #37): the fix removed about four fifths of the pause, and this issue is about the fifth that is left. The pause grows with the size of the post, so a much larger document could bring the multi-second freezes back even after that fix.

## Example

There is no user-visible failure to reproduce today — the pause is short enough that every test passes. It is visible with the measurement tools added on branch `loop/37`:

1. Start the test site and run the collaboration test slowed down three times, with the stall recorder on: `WP_BASE_URL=http://localhost: RTC_E2E_CPU_THROTTLE=3 npm run test:e2e -- collaboration-multi-client-content --trace on -g "intent-log.*mix of block types"`.
2. Open the saved test trace and read the `long-tasks-page2` attachment — a list of every moment the typing user's browser stopped responding, with durations in milliseconds.

**What you see:** one pause of 3,000-7,400 milliseconds during the table part of the test (at three-times slowdown; roughly 1,000-2,500 milliseconds at normal speed).

**What you expected:** no pause a person could notice — nothing beyond a couple of hundred milliseconds.

## What should happen instead

Typing should never visibly freeze the typist's own browser, no matter how big the post is or how fast they type.

## How we will know it is done

The decision below has to be made first, so there is no command yet — needs a test. Once a direction is chosen, the check should be: run the step from the example above and confirm the biggest recorded pause on the typing user's page stays under about one second at three-times slowdown, and the existing gate from #37 keeps passing twice in a row: `npm run test:e2e -- --repeat-each=8 -g "mix of block types"`.

**Which decision is missing, and who makes it:** the repository owner must pick a direction (or both) before an agent can start, because each one touches code agents may not change on their own. Direction one: make fewer copies (the change lives in the frozen shared code, like the #37 fix did — see notes). Direction two: make the copies smaller by sending less per keystroke (this changes what travels over the network, also reserved for humans).

## Notes for whoever picks this up

**Where the remaining pause comes from, in detail.** The collaboration engine keeps every person's edits consistent by replaying a shared history of small changes. To merge safely, it must be able to look at the document as it stood at several points in that history — so before applying each incoming change, it makes a complete copy of the whole document and applies the change to the copy, leaving the original untouched. That is `applyIntent` in `src/engines/intent-log/reducer.js` calling `cloneDocument` in `src/engines/intent-log/document.js`, driven by the client's replay in `src/engines/intent-log/client.js`. Issue #37 made each copy about five times cheaper (the copy used to go through a very slow WordPress-supplied replacement for the browser's `structuredClone` function; it is now a hand-written walk over plain data). What #37 did NOT change is HOW MANY copies are made and HOW BIG each one is: still one full-document copy per applied change. A post-fix CPU profile (attached to the #37 trail) shows the remaining pause is almost entirely this copying — our own fast copy, just called very many times over a large document.

**Why tables make it so much worse.** In this engine, one keystroke in an ordinary paragraph produces one recorded change. One keystroke in a table cell or a table caption produces three — and two of those three carry the ENTIRE table's HTML inside them, because the table's markup is tracked as one formatted-text object that gets switched off and back on around every edit. So a 20-keystroke burst in a table caption means roughly 60 changes to replay, each triggering a full copy of a document whose table text is itself large. Copies-per-burst times bytes-per-copy is the multiplication that produces the pause. This three-changes-per-keystroke behavior was confirmed by direct measurement on 2026-08-24 (see #37's comment trail).

**The two directions, with their constraints.** Direction one, fewer or cheaper copies: for example, copying only the parts of the document a change actually touches and sharing the untouched parts between versions (copy-on-write), or caching document states so replay does not re-copy from scratch. All of that code is in the frozen intent-log core (`src/engines/intent-log/`), which agents may not modify without an explicit human go-ahead recorded on the issue — the same authorization step #37 went through for its one-line change; this direction is larger. The PHP server has the same replay structure (`includes/engines/intent-log/`), but PHP copies arrays by value automatically, so the two sides do not need to change in lockstep for this — behavior, not bytes, is the contract. Direction two, smaller changes: stop re-carrying the whole table HTML in the two extra per-keystroke changes. That means changing what a recorded change contains — the shape of what travels over the network and sits in storage — which the loop's rules reserve for humans; #37's notes also record the constraint that merging three changes into one would need either a new payload format or an extension of the frozen change vocabulary (`replace_attr_content` clears formatting and has no formatting payload).

**What is already ruled out.** This is NOT the WordPress polyfill problem — that was #37, fixed and verified in #58 with an A/B test. Do not revisit the server's history-trimming either; `docs/plan/history.md` lists trim-timing fixes as a dead end, and #37's trail shows the trim was not involved. The measurement tools (the slow-down switch `RTC_E2E_CPU_THROTTLE`, the pause recorder, and the CPU profiler switch `RTC_E2E_CPU_PROFILE`) live in `tests/e2e/config/collaboration-fixtures.ts` and attach their findings to every failing test automatically; #37's comments explain how to read them.

**Numbers for calibration** (all at three-times slowdown, from the #37 verification runs): before the #37 fix the pause was 15,000-37,000 ms; after it, 3,000-7,400 ms; the test suite's timeouts start biting at about 10,000 ms, which is why today's pause passes every test but leaves little headroom for bigger documents or slower machines.

Related: #37, #58, #38 (separate table-rendering symptom, different mechanism).

Contributor guide

No contributing guide indexed for this repository

Research direction

First obtain the repository owner's decision between reducing document copies in src/engines/intent-log/ or reducing per-keystroke payloads, since both areas are restricted. Read reducer.js, document.js, client.js, and tests/e2e/config/collaboration-fixtures.ts, then run the documented collaboration test with RTC_E2E_CPU_THROTTLE=3 and inspect the long-tasks-page2 attachment. Done means the largest typing-page pause stays under about one second at three-times slowdown and the existing repeat-each gate passes twice.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, php
Domain
distributed-systems, performance
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.