From 7c52e890e6adb796e42934b4b74c34773515b259 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sat, 2 May 2026 21:22:12 +0200 Subject: [PATCH] feat(privacy): mark cleanup preview as stale when whitelist changes The preview block caches the deletion estimate from the last refresh. When the user toggles whitelist channels afterwards the cached number no longer reflects the current selection. Snapshot the whitelist on refresh and detect drift on every frame; on drift, grey out the counts and surface a stale hint plus an emphasised refresh button. Sits alongside the existing Cleanup_Help_SavedNote, which warns about a different mismatch (mutable vs saved) and stays as-is. --- ChatTwo/Resources/HellionStrings.Designer.cs | 1 + ChatTwo/Resources/HellionStrings.de.resx | 3 ++ ChatTwo/Resources/HellionStrings.resx | 3 ++ ChatTwo/Ui/SettingsTabs/Privacy.cs | 43 ++++++++++++++++++-- 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/ChatTwo/Resources/HellionStrings.Designer.cs b/ChatTwo/Resources/HellionStrings.Designer.cs index 671109f..61ead5b 100644 --- a/ChatTwo/Resources/HellionStrings.Designer.cs +++ b/ChatTwo/Resources/HellionStrings.Designer.cs @@ -64,6 +64,7 @@ internal class HellionStrings internal static string Cleanup_Heading => Get(nameof(Cleanup_Heading)); internal static string Cleanup_Help_Intro => Get(nameof(Cleanup_Help_Intro)); internal static string Cleanup_Help_SavedNote => Get(nameof(Cleanup_Help_SavedNote)); + internal static string Cleanup_Preview_Stale => Get(nameof(Cleanup_Preview_Stale)); internal static string Retention_Help_SavedNote => Get(nameof(Retention_Help_SavedNote)); internal static string Cleanup_RefreshPreview => Get(nameof(Cleanup_RefreshPreview)); internal static string Cleanup_NoPreview => Get(nameof(Cleanup_NoPreview)); diff --git a/ChatTwo/Resources/HellionStrings.de.resx b/ChatTwo/Resources/HellionStrings.de.resx index d82ef5a..2a586ae 100644 --- a/ChatTwo/Resources/HellionStrings.de.resx +++ b/ChatTwo/Resources/HellionStrings.de.resx @@ -81,6 +81,9 @@ Der manuelle Lauf nutzt deine GESPEICHERTE Retention-Policy, nicht die Slider-Werte oben. Klicke zuerst Speichern, wenn der Lauf deine aktuellen Änderungen anwenden soll. + + Vorschau veraltet, deine Whitelist hat sich seit dem letzten Aktualisieren geändert. Klicke Aktualisieren, um neu zu berechnen. + Vorschau aktualisieren diff --git a/ChatTwo/Resources/HellionStrings.resx b/ChatTwo/Resources/HellionStrings.resx index 6617440..817c3c7 100644 --- a/ChatTwo/Resources/HellionStrings.resx +++ b/ChatTwo/Resources/HellionStrings.resx @@ -81,6 +81,9 @@ The manual sweep uses your SAVED retention policy, not the slider values above. Click Save first if you want the run to apply your current edits. + + Preview is out of date — your whitelist has changed since the last refresh. Click Refresh to recalculate. + Refresh preview diff --git a/ChatTwo/Ui/SettingsTabs/Privacy.cs b/ChatTwo/Ui/SettingsTabs/Privacy.cs index eb3bea3..6126230 100644 --- a/ChatTwo/Ui/SettingsTabs/Privacy.cs +++ b/ChatTwo/Ui/SettingsTabs/Privacy.cs @@ -3,6 +3,7 @@ using ChatTwo.Export; using ChatTwo.Privacy; using ChatTwo.Resources; using ChatTwo.Util; +using Dalamud.Interface.Colors; using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; @@ -55,6 +56,8 @@ internal sealed class Privacy : ISettingsTab private long CleanupKeepCount; private long CleanupDeleteCount; private bool CleanupRunning; + private bool CleanupPreviewStale; + private HashSet? CleanupPreviewSnapshot; // The retention-running state lives on Plugin so the auto-sweep and // this manual button see the same flag. UI reads stay lock-free @@ -484,6 +487,21 @@ internal sealed class Privacy : ISettingsTab ImGui.Spacing(); + // Drift-detection between the snapshot taken at last refresh + // and the current Mutable whitelist. Cleanup itself runs on + // the SAVED policy (Cleanup_Help_SavedNote covers that), but + // the user usually expects "the preview reflects what I just + // ticked" — so we surface the divergence instead of silently + // showing stale numbers. + if (CleanupPreviewSnapshot is not null + && !CleanupPreviewSnapshot.SetEquals(Mutable.PrivacyPersistChannels)) + { + CleanupPreviewStale = true; + } + + using (var emphasis = CleanupPreviewStale + ? ImRaii.PushColor(ImGuiCol.Button, ImGuiColors.HealerGreen with { W = 0.6f }) + : null) using (ImRaii.Disabled(CleanupRunning)) { if (ImGui.Button(HellionStrings.Cleanup_RefreshPreview)) @@ -496,10 +514,22 @@ internal sealed class Privacy : ISettingsTab return; } + if (CleanupPreviewStale) + { + ImGui.Spacing(); + ImGuiUtil.HelpText(HellionStrings.Cleanup_Preview_Stale); + } + ImGui.Spacing(); - ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_TotalStored, CleanupKeepCount + CleanupDeleteCount)); - ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_WillKeep, CleanupKeepCount)); - ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_WillDelete, CleanupDeleteCount)); + + using (var staleColor = CleanupPreviewStale + ? ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey) + : null) + { + ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_TotalStored, CleanupKeepCount + CleanupDeleteCount)); + ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_WillKeep, CleanupKeepCount)); + ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_WillDelete, CleanupDeleteCount)); + } using (var tree = ImRaii.TreeNode(HellionStrings.Cleanup_Breakdown)) { @@ -555,6 +585,13 @@ internal sealed class Privacy : ISettingsTab else CleanupDeleteCount += count; } + + // Snapshot the whitelist as it stood at preview-time so the + // render pass can flag the user about subsequent edits. Only + // updated on success — if the preview throws, the previous + // snapshot stays in place so stale-detection keeps working. + CleanupPreviewSnapshot = new HashSet(Mutable.PrivacyPersistChannels); + CleanupPreviewStale = false; } catch (Exception e) {