fix(settings): show which channels get their history deleted

Three of the four first-run profiles switch the retention sweep on and write a
per-channel deletion policy. The window only ever showed the global default, so
whatever the wizard decided about individual channels was invisible from the
moment the wizard closed.

The tab now lists every channel whose retention differs from the global value,
tagged by where the number comes from, plus a button to drop the custom ones. It
also finally uses the strings written for this screen, sweep description and
default help included.

Read-only, deliberately. Zero means "keep forever" as a global default and
"delete this channel's entire history" as a per-channel value, because the
default takes a separate SQL branch while a mapped channel gets cutoff = now. No
shipped profile contains a zero and no UI could set one, so nothing is broken
today -- but an editor cannot be offered until those two meanings agree. Noted
at the branch in DeleteByRetentionPolicy.

The default slider now goes down to 0, which is the value that means "never
delete anything". Its own label has been promising "0 = never" while the slider
started at 1.
This commit is contained in:
2026-08-18 14:43:34 +02:00
parent 588b886537
commit 6397916712
2 changed files with 67 additions and 3 deletions
+6
View File
@@ -529,6 +529,12 @@ internal class MessageStore : IDisposable
var index = 0;
foreach (var (type, days) in chatTypeDaysMap)
{
// Careful: 0 here is NOT the "keep forever" it means for
// defaultDays below. A per-channel 0 puts the cutoff at now
// and deletes the channel's entire history. No profile ships
// a 0 and no UI can set one, which is why this is a comment
// and not a guard -- but any editor added later has to
// reconcile the two meanings before it exposes the value.
var cutoff = nowMs - days * 86400000L;
var typeParam = $"$type{index}";
var cutoffParam = $"$cutoff{index}";
@@ -1,5 +1,7 @@
using Dalamud.Bindings.ImGui;
using HellionChat.Code;
using HellionChat.Resources;
using HellionChat.Util;
namespace HellionChat.Ui.Components.Settings.Tabs;
@@ -19,17 +21,24 @@ internal sealed class DataPrivacyTab
if (ImGui.CollapsingHeader("Logging", ImGuiTreeNodeFlags.DefaultOpen))
{
_w.Toggle(
"Enable retention sweep",
HellionStrings.Retention_Enabled_Name,
() => Plugin.Config.RetentionEnabled,
v => Plugin.Config.RetentionEnabled = v
);
ImGuiUtil.HelpMarker(HellionStrings.Retention_Enabled_Description);
// Down to 0, which DeleteByRetentionPolicy reads as "keep forever"
// for channels without an override. The slider started at 1, so the
// one value that means "never delete anything" was unreachable
// through the UI while the label promised it.
_w.SliderInt(
"Default retention (days)",
HellionStrings.Retention_Default_Label,
() => Plugin.Config.RetentionDefaultDays,
v => Plugin.Config.RetentionDefaultDays = v,
1,
0,
365
);
ImGuiUtil.HelpMarker(HellionStrings.Retention_Default_Help);
// RetentionLastRunAt defaults to MinValue on a fresh install, which
// would render as "0001-01-01 00:00" and look like a bug; the "Never"
@@ -41,6 +50,8 @@ internal sealed class DataPrivacyTab
? "Never"
: Plugin.Config.RetentionLastRunAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm");
ImGui.TextDisabled($"Last run: {lastRun}");
DrawRetentionOverrides();
}
if (ImGui.CollapsingHeader("Privacy filter", ImGuiTreeNodeFlags.DefaultOpen))
@@ -66,6 +77,53 @@ internal sealed class DataPrivacyTab
}
}
// Read-only on purpose. Three of the four wizard profiles write a
// per-channel policy and switch the sweep on, and until now the window only
// showed the global default -- so whatever the wizard decided about your
// channels was invisible from here on.
//
// Editing them is a separate matter: 0 means "keep forever" as a global
// default but "delete everything" as a per-channel value, and no UI should
// offer that until the two agree.
private void DrawRetentionOverrides()
{
var overrides = Plugin.Config.RetentionPerChannelDays;
ImGui.Spacing();
ImGui.TextUnformatted(HellionStrings.Retention_Tree_Heading);
var shown = 0;
foreach (var type in EnumValues<ChatType>.All)
{
var days = Plugin.Config.GetRetentionDays(type);
if (days == Plugin.Config.RetentionDefaultDays && !overrides.ContainsKey(type))
continue;
var tag = overrides.ContainsKey(type)
? HellionStrings.Retention_Tag_Override
: HellionStrings.Retention_Tag_Spec;
ImGui.TextDisabled($" {type.Name()}: {days} d {tag}");
shown++;
}
if (shown == 0)
{
ImGui.TextDisabled($" {HellionStrings.Retention_Tag_Global}");
return;
}
if (overrides.Count > 0 && ImGui.Button(HellionStrings.Retention_Clear_Overrides))
{
// Same lock the sweep takes when it snapshots the policy, so a clear
// cannot cut its enumeration short.
lock (_plugin.ConfigMapsLock)
{
overrides.Clear();
}
_plugin.SaveConfig();
}
}
private void DrawPrivacyPersistChannelsGrid()
{
// HashSet<ChatType>: iterate Enum.GetValues<ChatType>() for stable