Measured, both ways, in two real browser tabs on one document — last keystroke to the marker being visible in the other tab, which is what a person experiences rather than the debounce alone.
| | median | min | max | |---|---|---|---| | 2000ms (before) | 2032ms | 2028 | 2033 | | 250ms (after) | 284ms | 283 | 285 |
Five rounds each, same harness, same machine, localhost. The gap between the constant and the measurement is ~33ms in both — POST, broadcast, hub fan-out, SSE notify, ?since= fetch, Yjs apply and render. That figure being identical at both settings is the useful part: it says the transport is not the cost, the debounce was, which is what the earlier design claimed and had never actually measured.
7.2× faster, and the number is now under the threshold where a delay reads as the app thinking rather than responding.
One thing the measurement taught me, which is not a defect
The first run failed completely: five rounds, nothing arrived, while doc_updates showed the rows had been written. Saving worked; propagation did not.
The cause is the hibernation policy working exactly as designed. In headless Chromium the second tab is backgrounded, so document.hidden is true, and live-updates.mjs closes its stream immediately — a parked tab must stop holding its instance awake (§7.3, and the load-hidden case was added deliberately in the live-sync work). My harness was measuring a tab built not to listen.
The fix was Emulation.setFocusEmulationEnabled on both targets, which is the CDP override built for this, not a workaround. Worth stating plainly for anyone who later wonders why two tabs in one browser do not both update: the tab you are not looking at deliberately stops streaming, and catches up the moment you look at it — visibility fires, the stream reopens, and the connect-time event tells it what it missed. That is the intended behaviour and it is what keeps a document left open on Friday from pinning an instance all weekend.
Row rate
Unchanged in this measurement (five rounds, five rows) because each round is one short burst followed by a pause — the debounce coalesces exactly as it should. The eight-times figure in the commit message is the sustained-typing worst case, not what ordinary writing produces; real writing pauses, and every pause longer than 250ms closes a window that would otherwise have stayed open. The storage consequence is real but its worst case is rarer than the arithmetic suggests.