fix(chat): stream older messages on scroll-up too (no synchronous batch)

on_scroll() loaded a full MESSAGES_PER_PAGE batch synchronously under the LVGL
lock, which froze scrolling. Make it trigger the same incremental
tick_background_fill() streaming as the open path instead. _bg_fill_active is now
std::atomic since on_scroll() (LVGL task) sets it while tick_background_fill()
(main loop) reads it; the target is written before the flag for visibility.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
This commit is contained in:
torlando-agent[bot]
2026-06-20 01:07:51 -04:00
co-authored by Claude Opus 4.8
parent f8bbfba3e3
commit 13b3d7d73a
2 changed files with 20 additions and 8 deletions
+14 -7
View File
@@ -260,11 +260,12 @@ void ChatScreen::refresh() {
create_message_bubble(item);
}
// Queue the rest of the first page to stream in on the main loop.
// Queue the rest of the first page to stream in on the main loop. Set the
// target before activating so tick sees a consistent target.
_bg_fill_target = (_all_message_hashes.size() > MESSAGES_PER_PAGE)
? _all_message_hashes.size() - MESSAGES_PER_PAGE
: 0;
_bg_fill_active = (_display_start_idx > _bg_fill_target);
_bg_fill_active.store(_display_start_idx > _bg_fill_target);
// Scroll to bottom
lv_obj_scroll_to_y(_message_list, LV_COORD_MAX, LV_ANIM_OFF);
@@ -274,17 +275,17 @@ void ChatScreen::refresh() {
// the main loop. Each tick prepends a small batch under a brief LVGL lock, so a
// large conversation fills in without freezing the UI or holding the lock long.
void ChatScreen::tick_background_fill() {
if (!_bg_fill_active) {
if (!_bg_fill_active.load()) {
return;
}
if (_display_start_idx <= _bg_fill_target) {
_bg_fill_active = false;
_bg_fill_active.store(false);
return;
}
size_t remaining = _display_start_idx - _bg_fill_target;
load_more_messages(remaining < BG_FILL_BATCH ? remaining : BG_FILL_BATCH);
if (_display_start_idx <= _bg_fill_target) {
_bg_fill_active = false;
_bg_fill_active.store(false);
}
}
@@ -356,8 +357,14 @@ void ChatScreen::on_scroll(lv_event_t* event) {
// Check if scrolled to top
lv_coord_t scroll_y = lv_obj_get_scroll_y(screen->_message_list);
if (scroll_y <= 5) { // Near top (with small threshold)
screen->load_more_messages();
if (scroll_y <= 5 && screen->_display_start_idx > 0 && !screen->_bg_fill_active.load()) {
// Near the top: stream the next page in incrementally on the main loop
// (tick_background_fill) rather than loading a full batch synchronously
// under the LVGL lock here, which froze scrolling. Target before active.
screen->_bg_fill_target = (screen->_display_start_idx > MESSAGES_PER_PAGE)
? screen->_display_start_idx - MESSAGES_PER_PAGE
: 0;
screen->_bg_fill_active.store(true);
}
}
+6 -1
View File
@@ -11,6 +11,7 @@
#include <deque>
#include <map>
#include <functional>
#include <atomic>
#include <microReticulum/Bytes.h>
#include "LXMF/LXMessage.h"
#include "LXMF/MessageStore.h"
@@ -192,7 +193,11 @@ private:
static constexpr size_t BG_FILL_BATCH = 2; // older messages streamed per tick
static constexpr size_t MAX_DISPLAYED_MESSAGES = 50; // Cap to prevent memory exhaustion
bool _loading_more; // Prevent concurrent loads
bool _bg_fill_active = false; // streaming the rest of the page in
// Streaming state. _bg_fill_active is atomic because on_scroll() (LVGL task)
// and refresh() may set it while tick_background_fill() (main loop) reads it;
// writers always set _bg_fill_target BEFORE _bg_fill_active so the target is
// visible once active is observed true.
std::atomic<bool> _bg_fill_active{false}; // streaming the rest of the page in
size_t _bg_fill_target = 0; // _display_start_idx to fill down to
// Load more messages (infinite scroll + background fill)