From c12a1bc355f90c6f3ce73f28d1a0b4f1ad41a7ca Mon Sep 17 00:00:00 2001 From: "jabber.developer2" Date: Sun, 10 May 2026 15:09:32 +0300 Subject: [PATCH 1/5] fix(ui): reset paged flag at buffer bottom for non-chat windows --- src/ui/window.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ui/window.c b/src/ui/window.c index 139a8b88..94550f9a 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -848,6 +848,14 @@ win_page_down(ProfWin* window, int scroll_size) window->layout->paged = 0; window->layout->unread_msg = 0; } + + // Non-chat windows have no DB to fetch from, so WIN_SCROLL_REACHED_BOTTOM + // never fires; reset paged once the buffer's last line is on screen. + if (window->type != WIN_CHAT + && (total_rows - *page_start) <= page_space + 1) { + window->layout->paged = 0; + window->layout->unread_msg = 0; + } } void -- 2.49.1 From ef0dd74dc183b954bf156ef8324c6f2f13479935 Mon Sep 17 00:00:00 2001 From: "jabber.developer2" Date: Fri, 15 May 2026 19:41:10 +0300 Subject: [PATCH 2/5] fix(ui): preserve messages in non-chat windows while scrolled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _win_printf used to drop the buffer append and pad render for any window in paged state, but only WIN_CHAT can recover lost messages via chatwin_db_history on scroll-down. WIN_MUC, WIN_PRIVATE and WIN_AI have no such fallback, so incoming and outgoing messages were silently lost when the user was viewing history. Suppress the buffer/render only for WIN_CHAT; for the rest, still bump unread_msg but let the message land in the buffer so it becomes visible on scroll-down. This also removes the manual paged/unread_msg reset before printing the user message in cl_ev_send_ai_msg — it was a local workaround for the same drop and is no longer needed. The buffer-bottom reset in win_page_down (c167f487) is still required: it is the only path that clears paged and unread_msg for non-chat windows when the user scrolls back to the bottom (WIN_SCROLL_REACHED_BOTTOM is set only on the is_chat DB branch). --- src/event/client_events.c | 6 ------ src/ui/window.c | 9 +++++++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/event/client_events.c b/src/event/client_events.c index 6adfc612..b90218db 100644 --- a/src/event/client_events.c +++ b/src/event/client_events.c @@ -301,12 +301,6 @@ cl_ev_send_ai_msg(ProfAiWin* aiwin, const char* const message, const char* const return; } - /* Reset paged flag before printing user message. - * If the user scrolled up to view history, paged=1 would suppress - * the message in _win_printf(). Reset it here so the message displays. */ - aiwin->window.layout->paged = 0; - aiwin->window.layout->unread_msg = 0; - // Display user message in AI window. win_print_outgoing(&aiwin->window, ">>", id, NULL, message); diff --git a/src/ui/window.c b/src/ui/window.c index 94550f9a..3ee21b43 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -1809,10 +1809,15 @@ static void _win_printf(ProfWin* window, const char* show_char, int pad_indent, GDateTime* timestamp, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message_id, const char* const message, ...) { - /* Prevent printing and buffer update when user is viewing message history [SCROLLING]*/ + /* While the user is scrolled, bump the unread indicator. Only WIN_CHAT + * skips the buffer/render entirely — it can recover the message via + * chatwin_db_history on scroll-down. Other window types have no DB + * fallback, so the message must still be appended to the buffer and pad. */ if (window->layout->paged && wins_is_current(window)) { window->layout->unread_msg++; - return; + if (window->type == WIN_CHAT) { + return; + } } if (timestamp == NULL) { -- 2.49.1 From 506794474aa59a642f26cb47f6ed9b627f8602bd Mon Sep 17 00:00:00 2001 From: Jabber Developer Date: Sat, 16 May 2026 12:24:36 +0000 Subject: [PATCH 3/5] fix(ui): handle ai window scrolling in titlebar Add missing scroll rendering for AI windows in the title bar draw function. This ensures AI windows display their scrolled state consistently with other non-chat window types. --- src/ui/titlebar.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ui/titlebar.c b/src/ui/titlebar.c index bed87b4a..f5fddbee 100644 --- a/src/ui/titlebar.c +++ b/src/ui/titlebar.c @@ -46,6 +46,7 @@ #include "ui/ui.h" #include "ui/titlebar.h" #include "ui/inputwin.h" +#include "ui/win_types.h" #include "ui/window_list.h" #include "ui/window.h" #include "ui/screen.h" @@ -252,6 +253,10 @@ _title_bar_draw(void) _show_muc_privacy(mucwin); _show_attention(current, mucwin->has_attention); _show_scrolled(current); + } else if (current && current->type == WIN_AI) { + ProfAiWin* aiwin = (ProfAiWin*)current; + assert(aiwin->memcheck == PROFAIWIN_MEMCHECK); + _show_scrolled(current); } _show_self_presence(); -- 2.49.1 From 0f24910685164800fbdc60398db1c05f853fa979 Mon Sep 17 00:00:00 2001 From: "jabber.developer2" Date: Sat, 16 May 2026 17:06:31 +0300 Subject: [PATCH 4/5] fix(ui): preserve WIN_CHAT messages when DB cannot replay them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _win_printf skipped buffer/pad append for WIN_CHAT while the user was scrolled, relying on chatwin_db_history() to replay missed messages on scroll-down. That replay is unavailable when PREF_DBLOG is "off" (no write) or "redact" ([REDACTED] body), or when the backend failed to initialize — in those cases the message was lost. Introduce log_database_can_recover_messages() and gate the WIN_CHAT early return on it: when recovery is impossible, fall through and append to the buffer just like non-chat windows do. Also treat DB_RESPONSE_ERROR in win_page_down the same as DB_RESPONSE_EMPTY so the [SCROLLED] indicator doesn't hang when the backend is dead. --- src/database.c | 16 ++++++++++++++++ src/database.h | 3 +++ src/ui/window.c | 15 +++++++++------ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/database.c b/src/database.c index b97e8539..0f3c8dc7 100644 --- a/src/database.c +++ b/src/database.c @@ -61,6 +61,22 @@ integrity_issue_free(integrity_issue_t* issue) } } +gboolean +log_database_can_recover_messages(void) +{ + if (!active_db_backend) { + return FALSE; + } + auto_gchar gchar* pref_dblog = prefs_get_string(PREF_DBLOG); + if (g_strcmp0(pref_dblog, "off") == 0) { + return FALSE; + } + if (g_strcmp0(pref_dblog, "redact") == 0) { + return FALSE; + } + return TRUE; +} + gboolean log_database_init(ProfAccount* account) { diff --git a/src/database.h b/src/database.h index c897230f..2204c0b0 100644 --- a/src/database.h +++ b/src/database.h @@ -109,6 +109,9 @@ ProfMessage* log_database_get_limits_info(const gchar* const contact_barejid, gb void log_database_close(void); GSList* log_database_verify_integrity(const gchar* const contact_barejid); +// FALSE if no backend, or PREF_DBLOG is "off" / "redact". +gboolean log_database_can_recover_messages(void); + // Cross-backend export/import (requires HAVE_SQLITE) #ifdef HAVE_SQLITE int log_database_export_to_flatfile(const gchar* const contact_jid); diff --git a/src/ui/window.c b/src/ui/window.c index 3ee21b43..bf84294f 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -813,7 +813,9 @@ win_page_down(ProfWin* window, int scroll_size) auto_gchar gchar* start = g_date_time_format_iso8601(last_entry->time); auto_gchar gchar* end_date = g_date_time_format_iso8601(now); db_history_result_t db_response = chatwin_db_history((ProfChatWin*)window, start, end_date, FALSE); - if (db_response == DB_RESPONSE_EMPTY) + // Treat transient DB error (e.g. no active backend) the same + // as EMPTY: no rows can be fetched, so stop indicating [SCROLLED]. + if (db_response == DB_RESPONSE_EMPTY || db_response == DB_RESPONSE_ERROR) *scroll_state = WIN_SCROLL_REACHED_BOTTOM; // similar to page_up (see explanation there) @@ -1809,13 +1811,14 @@ static void _win_printf(ProfWin* window, const char* show_char, int pad_indent, GDateTime* timestamp, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message_id, const char* const message, ...) { - /* While the user is scrolled, bump the unread indicator. Only WIN_CHAT - * skips the buffer/render entirely — it can recover the message via - * chatwin_db_history on scroll-down. Other window types have no DB - * fallback, so the message must still be appended to the buffer and pad. */ + /* While the user is scrolled, bump the unread indicator. WIN_CHAT + * skips the buffer/render only when the DB will preserve the message + * verbatim — chatwin_db_history on scroll-down will replay it. When + * the DB is off/redact/dead we cannot recover, so fall through and + * append to the buffer just like non-chat windows. */ if (window->layout->paged && wins_is_current(window)) { window->layout->unread_msg++; - if (window->type == WIN_CHAT) { + if (window->type == WIN_CHAT && log_database_can_recover_messages()) { return; } } -- 2.49.1 From ac7d6c31f05df3e9e9df1dd9c8984301b81a2e25 Mon Sep 17 00:00:00 2001 From: "jabber.developer2" Date: Sat, 16 May 2026 17:57:28 +0300 Subject: [PATCH 5/5] fix(ui): address PR review on WIN_CHAT scroll handling - Drop DB_RESPONSE_ERROR -> REACHED_BOTTOM in win_page_down: a transient backend error (e.g. sqlite lock) should not force the scroll state forward, or the user would get stuck mid-conversation expecting more rows that did not load. - Trim the _win_printf comment to a single WHY line; the rest belongs in the originating commit message. --- src/ui/window.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/ui/window.c b/src/ui/window.c index bf84294f..51f36872 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -813,9 +813,7 @@ win_page_down(ProfWin* window, int scroll_size) auto_gchar gchar* start = g_date_time_format_iso8601(last_entry->time); auto_gchar gchar* end_date = g_date_time_format_iso8601(now); db_history_result_t db_response = chatwin_db_history((ProfChatWin*)window, start, end_date, FALSE); - // Treat transient DB error (e.g. no active backend) the same - // as EMPTY: no rows can be fetched, so stop indicating [SCROLLED]. - if (db_response == DB_RESPONSE_EMPTY || db_response == DB_RESPONSE_ERROR) + if (db_response == DB_RESPONSE_EMPTY) *scroll_state = WIN_SCROLL_REACHED_BOTTOM; // similar to page_up (see explanation there) @@ -1811,13 +1809,9 @@ static void _win_printf(ProfWin* window, const char* show_char, int pad_indent, GDateTime* timestamp, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message_id, const char* const message, ...) { - /* While the user is scrolled, bump the unread indicator. WIN_CHAT - * skips the buffer/render only when the DB will preserve the message - * verbatim — chatwin_db_history on scroll-down will replay it. When - * the DB is off/redact/dead we cannot recover, so fall through and - * append to the buffer just like non-chat windows. */ if (window->layout->paged && wins_is_current(window)) { window->layout->unread_msg++; + // Skip buffer only when DB can replay the message on scroll-down. if (window->type == WIN_CHAT && log_database_can_recover_messages()) { return; } -- 2.49.1