From 59e86cd8dd461a76e44b03ed4680c53709418a74 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Tue, 5 May 2026 23:48:23 +0200 Subject: [PATCH] fix(config): preserve persistent-tab message history across settings save MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UpdateFrom replaced persistent tabs with Tab.Clone()s. Clone deliberately omits the NonSerialized Messages list to avoid shared mutable state on disk-load — but on a settings save (Plugin.Config.UpdateFrom(Mutable)) that path means every persistent tab loses its in-session chat history the moment the user clicks Save. Capture the live MessageList plus LastSendUnread counter by Identifier before the replace and restore them onto the cloned tabs. Tab.Clone() already preserves Identifier so the lookup matches one-to-one for unchanged tabs. New tabs added in settings get a fresh empty list, deleted tabs lose their history (both intended). Reported by Flo in-game 2026-05-05 — chat got wiped on every settings save during v1.2.0 testing in Limsa. --- HellionChat/Configuration.cs | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/HellionChat/Configuration.cs b/HellionChat/Configuration.cs index d874a17..091e94f 100755 --- a/HellionChat/Configuration.cs +++ b/HellionChat/Configuration.cs @@ -302,10 +302,33 @@ public class Configuration : IPluginConfiguration // never present in a disk-loaded copy. Keep the live temp tabs of // *this* configuration alive across an UpdateFrom so a settings // save (or sidebar-mode toggle) does not silently destroy the - // user's open tell conversations. Persistent tabs from `other` - // still get the regular clone-replace treatment. + // user's open tell conversations. + // + // For persistent tabs we go through Tab.Clone() which intentionally + // does NOT copy the NonSerialized Messages list (avoids shared + // mutable state on disk-load). On a settings save that means the + // chat history for every persistent tab would be wiped — bug + // reported by Flo 2026-05-05. We work around it by capturing the + // live MessageList (and LastSendUnread counter) by Identifier + // before the replace, then restoring it onto the freshly cloned + // tabs whose Identifier survives Tab.Clone(). New tabs added in + // settings get a fresh empty MessageList; deleted tabs lose their + // history (intended). var liveTempTabs = Tabs.Where(t => t.IsTempTab).ToList(); - Tabs = other.Tabs.Where(t => !t.IsTempTab).Select(t => t.Clone()).ToList(); + var livePersistentSession = Tabs + .Where(t => !t.IsTempTab) + .ToDictionary(t => t.Identifier, t => (t.Messages, t.LastSendUnread)); + + Tabs = other.Tabs.Where(t => !t.IsTempTab).Select(t => + { + var clone = t.Clone(); + if (livePersistentSession.TryGetValue(clone.Identifier, out var live)) + { + clone.Messages = live.Messages; + clone.LastSendUnread = live.LastSendUnread; + } + return clone; + }).ToList(); Tabs.AddRange(liveTempTabs); OverrideStyle = other.OverrideStyle;