Skip to content

Fix iPhone history lag: cap web client history buffers and coalesce renders#255

Open
serrebidev wants to merge 1 commit into
XGDevGroup:mainfrom
serrebidev:fix/web-mobile-history-perf
Open

Fix iPhone history lag: cap web client history buffers and coalesce renders#255
serrebidev wants to merge 1 commit into
XGDevGroup:mainfrom
serrebidev:fix/web-mobile-history-perf

Conversation

@serrebidev

Copy link
Copy Markdown

Problem

On iPhone, tapping a menu choice grows to 10–20s of lag after a session has run for a while. Desktop is unaffected.

Root cause is the web client's history system:

  • store.addHistory() pushes to history buffers with no cap, and every non-all line is also mirrored into the all buffer (the default visible one) — so buffers grow with the entire session.
  • history.js render() runs on every notify() and rebuilds the whole <textarea> via lines.join("\n") plus a forced reflow (scrollTop = scrollHeight). A single tap emits a burst of speak/chat lines, each firing its own notify(), so cost per tap ≈ (lines added) × (total history) — quadratic and growing all session.
  • Mobile is hit hardest: renderMobileVisibility() shows the growing #history-log <p> list that desktop hides, so phones pay for both the textarea rebuild and an unbounded visible DOM, on slower hardware.

Fix

  • store.js — cap each history buffer at 500 lines (FIFO trim from the front). Bounds memory and makes every render O(cap) instead of O(session). History navigation indexes from the end, so front-trimming never disturbs scrollback position.
  • ui/history.js — coalesce renders into one requestAnimationFrame (a K-line burst → 1 render, not K), skip the DOM work entirely when the visible buffer is unchanged (keeps menu navigation cheap), and rebuild the capped log from scratch (the old count-based incremental append silently broke once the buffer trimmed from the front).

Testing

  • Focused unit check of the cap (600 pushes → length 500, oldest dropped, all mirror capped, direct-all path not double-pushed): pass.
  • Existing web tests (tests/markdown_viewer_security.test.mjs): 2/2 pass.

Notes

  • Intentionally did not bump version.js in this PR to avoid merge conflicts in the integration branch; asset freshness is already handled by the server's Cache-Control: no-cache, must-revalidate + ETag.

🤖 Generated with Claude Code

…enders

On iPhone, tapping a choice grew to 10-20s of lag after a session ran a
while (desktop unaffected). Root cause is the web client's history system:

- store.addHistory() pushed to buffers with no cap, and every non-"all"
  line is also mirrored into the default-visible "all" buffer, so buffers
  grew with the entire session.
- history.js render() runs on every notify() and rebuilds the whole
  <textarea> via join() plus a forced reflow. One tap emits a burst of
  speak/chat lines, each firing notify(), so cost per tap is
  O(lines added * total history) and grows all session.
- Mobile is worst hit: it also shows the growing #history-log <p> list
  that desktop hides, on slower hardware.

Fix:
- store.js: cap each history buffer at 500 lines (FIFO trim from front).
- ui/history.js: coalesce renders into one requestAnimationFrame, skip the
  DOM work when the visible buffer is unchanged, and rebuild the capped log
  cleanly (the old incremental append broke once the buffer front-trimmed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.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.

1 participant