fix(ui): raise pad redraw threshold above buffer cap to fix history scroll #133

Manually merged
jabber.developer2 merged 1 commits from fix/history-scroll-pad-redraw-storm into master 2026-06-08 13:05:10 +00:00
Collaborator

Summary

Paging up through a long chat history (flat-file backend) regressed after the upstream sync merge: scrolling lagged ~10 s per page, the view jumped/“got stuck”, and a block of messages was never drawn (reported ~30–170 missing, depending on history length). This restores smooth, lossless history scrolling with a one-line behavioural change plus a small win_redraw() cleanup.

Root cause

The merge replaced the fixed PAD_SIZE = 10000 pad with a dynamically grown pad and added _win_ensure_pad_capacity() with PAD_THRESHOLD = 3000. When the pad grows past the threshold, that helper triggers a full win_redraw() to reclaim space — but it is called from the hot print path (_win_print_internal) on every message.

PAD_THRESHOLD (3000) sits below the buffer line cap (PAD_SIZE - PAD_SIZE/10 = 9000, enforced by trimming in buffer.c). So for any chat longer than 3000 rendered lines, every print during a history fetch re-entered win_redraw():

  • chatwin_db_history() prepends a batch (MESSAGES_TO_RETRIEVE = 100) and normally does one redraw per fetch; the storm turned that into dozens of full-buffer redraws per page-up → the ~10 s lag.
  • Each mid-print redraw corrupted the per-entry y_start_pos/_lines bookkeeping. win_page_up() then computed the smooth-scroll offset from a stale first_entry->y_start_pos, so *page_start snapped to 0 — the viewport jumped to the top and skipped the messages in between (the “missing” block + the *page_start adjusted to 0 ... Previous value: -N log line firing mid-history).

The flat-file backend (_flatfile_get_previous_chat) is byte-identical across the regression range (SHA1 unchanged; last touched in an ancestor of the healthy commit). The empty-vs-success decision behaves the same in both builds, so WIN_SCROLL_REACHED_TOP is the real top, not a premature stop — the fault is entirely in the UI pad path.

Fix

  1. PAD_THRESHOLD 3000 → 12000. Above the ~9000 buffer cap, so the reclaim redraw can never fire during legitimate scrolling (pad in the scroll steady state is buffer->lines + 100 ≤ ~9100, and a 100-message prepend batch climbs at most ~a few hundred lines above that). It only fires to drop dead pad space accumulated by a long, append-only session that never scrolls or switches windows — which is exactly the bound the pre-merge code got from the fixed PAD_SIZE overflow-reset.
  2. win_redraw() sizes the pad once to buffer->lines + 100 instead of shrinking to PAD_MIN_HEIGHT and regrowing +100 per entry, removing the residual ~90 wresize() calls per redraw and restoring pre-merge smoothness.

Net diff: 4 insertions / 3 deletions in src/ui/window.c. _win_ensure_pad_capacity() and all call sites are unchanged vs master.

Parity with pre-merge (no regression)

Axis pre-merge (3b673150) this PR
History scroll 1 redraw/fetch, smooth, lossless same
Long append-only session bounded ~10000 (overflow-reset) bounded ~12000 (threshold)
Memory eager 10000×cols per window lazy pad (merge’s win, kept)

The only residual edge — a single message that renders ≥ PAD_THRESHOLD lines could re-enter the per-print redraw — already existed pre-merge (at ≥10000, via the overflow branch) and is tracked as a separate hardening task (see the dead-space reclaim issue).

Testing

  • Built on host (./bootstrap.sh && ./configure && make, -Werror clean).
  • Manually verified on the affected long flat-file history: scroll to the top is smooth (no multi-second stalls) and no messages are dropped/skipped.

Risk

Low. Restores the pre-merge mechanism at a safe threshold; single file; no API/header changes.

## Summary Paging up through a long chat history (flat-file backend) regressed after the upstream sync merge: scrolling lagged ~10 s per page, the view jumped/“got stuck”, and a block of messages was never drawn (reported ~30–170 missing, depending on history length). This restores smooth, lossless history scrolling with a one-line behavioural change plus a small `win_redraw()` cleanup. ## Root cause The merge replaced the fixed `PAD_SIZE = 10000` pad with a dynamically grown pad and added `_win_ensure_pad_capacity()` with `PAD_THRESHOLD = 3000`. When the pad grows past the threshold, that helper triggers a full `win_redraw()` to reclaim space — but it is called from the hot print path (`_win_print_internal`) on **every** message. `PAD_THRESHOLD (3000)` sits **below** the buffer line cap (`PAD_SIZE - PAD_SIZE/10 = 9000`, enforced by trimming in `buffer.c`). So for any chat longer than 3000 rendered lines, every print during a history fetch re-entered `win_redraw()`: - `chatwin_db_history()` prepends a batch (`MESSAGES_TO_RETRIEVE = 100`) and normally does **one** redraw per fetch; the storm turned that into dozens of full-buffer redraws per page-up → the ~10 s lag. - Each mid-print redraw corrupted the per-entry `y_start_pos`/`_lines` bookkeeping. `win_page_up()` then computed the smooth-scroll offset from a stale `first_entry->y_start_pos`, so `*page_start` snapped to 0 — the viewport jumped to the top and skipped the messages in between (the “missing” block + the `*page_start adjusted to 0 ... Previous value: -N` log line firing mid-history). The flat-file backend (`_flatfile_get_previous_chat`) is **byte-identical** across the regression range (SHA1 unchanged; last touched in an ancestor of the healthy commit). The empty-vs-success decision behaves the same in both builds, so `WIN_SCROLL_REACHED_TOP` is the real top, not a premature stop — the fault is entirely in the UI pad path. ## Fix 1. **`PAD_THRESHOLD` 3000 → 12000.** Above the ~9000 buffer cap, so the reclaim redraw can never fire during legitimate scrolling (pad in the scroll steady state is `buffer->lines + 100 ≤ ~9100`, and a 100-message prepend batch climbs at most ~a few hundred lines above that). It only fires to drop dead pad space accumulated by a long, append-only session that never scrolls or switches windows — which is exactly the bound the pre-merge code got from the fixed `PAD_SIZE` overflow-reset. 2. **`win_redraw()` sizes the pad once** to `buffer->lines + 100` instead of shrinking to `PAD_MIN_HEIGHT` and regrowing `+100` per entry, removing the residual ~90 `wresize()` calls per redraw and restoring pre-merge smoothness. Net diff: 4 insertions / 3 deletions in `src/ui/window.c`. `_win_ensure_pad_capacity()` and all call sites are unchanged vs `master`. ## Parity with pre-merge (no regression) | Axis | pre-merge (`3b673150`) | this PR | | --- | --- | --- | | History scroll | 1 redraw/fetch, smooth, lossless | same | | Long append-only session | bounded ~10000 (overflow-reset) | bounded ~12000 (threshold) | | Memory | eager 10000×cols per window | lazy pad (merge’s win, kept) | The only residual edge — a single message that renders ≥ `PAD_THRESHOLD` lines could re-enter the per-print redraw — already existed pre-merge (at ≥10000, via the overflow branch) and is tracked as a separate hardening task (see the dead-space reclaim issue). ## Testing - Built on host (`./bootstrap.sh && ./configure && make`, `-Werror` clean). - Manually verified on the affected long flat-file history: scroll to the top is smooth (no multi-second stalls) and no messages are dropped/skipped. ## Risk Low. Restores the pre-merge mechanism at a safe threshold; single file; no API/header changes.
jabber.developer2 added 1 commit 2026-06-08 09:34:28 +00:00
fix(ui): raise pad redraw threshold above buffer cap to fix history scroll
All checks were successful
CI Code / Check spelling (pull_request) Successful in 17s
CI Code / Check coding style (pull_request) Successful in 32s
CI Code / Linux (debian) (pull_request) Successful in 4m42s
CI Code / Linux (ubuntu) (pull_request) Successful in 4m50s
CI Code / Code Coverage (pull_request) Successful in 7m28s
CI Code / Linux (arch) (pull_request) Successful in 11m8s
b54b64b223
PAD_THRESHOLD (3000) sat below the buffer line cap (PAD_SIZE - PAD_SIZE/10 = 9000), so _win_ensure_pad_capacity() fired a full win_redraw() from the hot print path on every message once a chat exceeded 3000 rendered lines. During history paging this turned one redraw per fetch into dozens, corrupting per-entry y_start_pos (offset jumps, dropped/skipped messages) and causing ~10s lag per page. The flat-file backend was byte-identical across the regression; the fault was purely in the UI pad path from the upstream merge.

Raise PAD_THRESHOLD to 12000 (above the cap) so the reclaim redraw only fires to drop dead pad space in long append-only sessions, never during scrolling. Also size the pad once to buffer->lines+100 in win_redraw() instead of shrinking to PAD_MIN_HEIGHT and regrowing per entry, removing the residual per-redraw wresize churn.
jabber.developer reviewed 2026-06-08 12:54:24 +00:00
src/ui/window.c Outdated
@@ -2176,2 +2176,3 @@
int cols = getmaxx(window->layout->win);
wresize(window->layout->win, PAD_MIN_HEIGHT, cols);
int needed = window->layout->buffer->lines + 100;
wresize(window->layout->win, needed > PAD_MIN_HEIGHT ? needed : PAD_MIN_HEIGHT, cols);

why not use MAX macro from GLib?

why not use `MAX` macro from GLib?
jabber.developer marked this conversation as resolved
jabber.developer2 force-pushed fix/history-scroll-pad-redraw-storm from b54b64b223 to 3f6b8f69fd 2026-06-08 13:03:36 +00:00 Compare
jabber.developer2 manually merged commit 3f6b8f69fd into master 2026-06-08 13:05:10 +00:00
Sign in to join this conversation.
No description provided.