From 45ce8e4e24eda579f69db5ee2a8834a970a04fab Mon Sep 17 00:00:00 2001 From: nahakubuilder Date: Sun, 30 Aug 2026 09:50:18 +0100 Subject: [PATCH] notification and deleted messages cleanup fix --- internal/db/db.go | 13 +++++++++++++ internal/handlers/api.go | 8 ++++++++ web/static/js/app.js | 10 ++++++---- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/internal/db/db.go b/internal/db/db.go index d37a11f..dc18b2a 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -2242,6 +2242,19 @@ func (d *DB) GetMessageIMAPInfo(messageID, userID int64) (remoteUID uint32, fold return remoteUID, folder.FullPath, account, err } +// GetMessageFolderID returns the local folder id a message currently belongs to — used to +// recompute that folder's sidebar count immediately after deleting the message, instead of +// leaving it stale until the next background sync happens to run. +func (d *DB) GetMessageFolderID(messageID, userID int64) (int64, error) { + var folderID int64 + err := d.sql.QueryRow(` + SELECT m.folder_id FROM messages m + JOIN email_accounts a ON a.id = m.account_id + WHERE m.id=? AND a.user_id=?`, messageID, userID, + ).Scan(&folderID) + return folderID, err +} + // GetMessageGraphInfo returns the Graph message ID (remote_uid as string), folder ID string, // and account for a Graph-backed message. Used by handlers for outlook_personal accounts. func (d *DB) GetMessageGraphInfo(messageID, userID int64) (graphMsgID string, folderGraphID string, account *models.EmailAccount, err error) { diff --git a/internal/handlers/api.go b/internal/handlers/api.go index 77323bb..55338df 100644 --- a/internal/handlers/api.go +++ b/internal/handlers/api.go @@ -1044,10 +1044,18 @@ func (h *APIHandler) deleteMessageEverywhere(userID, messageID int64) error { // Get message info before deleting from DB remoteID, _, remoteAcc, remoteErr := h.db.GetMessageGraphInfo(messageID, userID) uid, folderPath, account, imapErr := h.db.GetMessageIMAPInfo(messageID, userID) + folderID, folderErr := h.db.GetMessageFolderID(messageID, userID) if err := h.db.DeleteMessage(messageID, userID); err != nil { return err } + // Recompute the sidebar's folder-count badge right away from what's actually left in the + // local table, rather than leaving it at the pre-delete count until the next background + // sync happens to run — the eventual real sync (once the server-side delete/move below + // actually lands) will overwrite this with the authoritative count anyway. + if folderErr == nil { + h.db.UpdateFolderCounts(folderID) + } if remoteErr == nil && remoteAcc != nil && remoteAcc.Provider == models.ProviderOutlookPersonal { go graphpkg.New(remoteAcc).DeleteMessage(context.Background(), remoteID) diff --git a/web/static/js/app.js b/web/static/js/app.js index 0d3a86b..31a5397 100644 --- a/web/static/js/app.js +++ b/web/static/js/app.js @@ -144,10 +144,12 @@ async function init() { await loadFolders(); await loadLabels(); await loadMessages(); - // Seed poller ID so we don't notify on initial load - if (S.messages.length > 0) { - POLLER.lastKnownID = Math.max(...S.messages.map(m=>m.id)); - } + // Seed the poller from the same query /api/poll itself uses for "newest inbox message id" + // (not from whatever happens to be in the currently-loaded, paginated/date-sorted message + // list — that view can under-represent the true max id, which previously made every page + // load fire a false "new mail" notification for mail that had already been seen). + const seedPoll = await api('GET', '/poll?since=0'); + if (seedPoll) POLLER.lastKnownID = seedPoll.newest_id || 0; const p = new URLSearchParams(location.search); if (p.get('connected')) {