From c5a631da6f26fade7814fb925e35bcd6d96bbff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Mon, 23 Feb 2026 01:02:59 +0100 Subject: [PATCH] Grace period for local drafts. --- Wino.Mail.ViewModels/ComposePageViewModel.cs | 27 +++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Wino.Mail.ViewModels/ComposePageViewModel.cs b/Wino.Mail.ViewModels/ComposePageViewModel.cs index 538e4e2a..a8defbdd 100644 --- a/Wino.Mail.ViewModels/ComposePageViewModel.cs +++ b/Wino.Mail.ViewModels/ComposePageViewModel.cs @@ -34,6 +34,8 @@ public partial class ComposePageViewModel : MailBaseViewModel, IRecipient, IRecipient { + private static readonly TimeSpan LocalDraftRetryGracePeriod = TimeSpan.FromSeconds(15); + public Func> GetHTMLBodyFunction; // When we send the message or discard it, we need to block the mime update @@ -606,6 +608,7 @@ public partial class ComposePageViewModel : MailBaseViewModel, private async Task UpdatePendingOperationStateAsync() { var hasPendingOperation = false; + var keepBusyForInitialGracePeriod = false; if (CurrentMailDraftItem?.MailCopy == null || !CurrentMailDraftItem.MailCopy.IsDraft) { @@ -625,9 +628,17 @@ public partial class ComposePageViewModel : MailBaseViewModel, hasPendingOperation = synchronizer?.HasPendingOperation(CurrentMailDraftItem.MailCopy.UniqueId) ?? false; } + // Newly created local drafts can have a short period where request queue is empty + // while folder synchronization/mapping is still in progress. + // Keep progress visible during this grace period to prevent "Send to server" flicker. + if (!hasPendingOperation && CurrentMailDraftItem.MailCopy.IsLocalDraft) + { + keepBusyForInitialGracePeriod = IsWithinLocalDraftRetryGracePeriod(CurrentMailDraftItem.MailCopy); + } + await ExecuteUIThread(() => { - IsDraftBusy = hasPendingOperation; + IsDraftBusy = hasPendingOperation || keepBusyForInitialGracePeriod; NotifyComposeActionStateChanged(); }); } @@ -837,4 +848,18 @@ public partial class ComposePageViewModel : MailBaseViewModel, return currentDraftAccountId != Guid.Empty && currentDraftAccountId == accountId; } + + private bool IsWithinLocalDraftRetryGracePeriod(MailCopy localDraft) + { + if (localDraft == null || localDraft.CreationDate == default) + return false; + + var elapsed = DateTime.UtcNow - localDraft.CreationDate; + + // Clock skew safety. + if (elapsed < TimeSpan.Zero) + return true; + + return elapsed < LocalDraftRetryGracePeriod; + } }