From ae9b5037768692f07d25a51d75f8e7ee575b0974 Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Tue, 18 Aug 2026 18:44:54 +0200 Subject: [PATCH] fix(tells): load pinned tell history after login, not before it A pinned tell tab came up empty for the rest of the session, reported by a tester and reproduced from his config. RehydratePinnedTabs runs from a hosted service at plugin start. It queries tell history by character, and CurrentContentId is 0 until a character is logged in -- LastContentId only gets set from the framework tick. The game loads plugins at boot, so the normal path queries for character zero, finds nothing, and there was no second attempt: the service subscribed to Logout but not Login. It defers now when no character is available and completes on the login that follows. The pending flag keeps a later character switch from appending a second copy of the history to tabs that already have it. Two things hid this. Reloading the plugin in a running session, which is what development looks like, always has a character available. And it only shows up if you pin a tell tab at all -- the same path for non-temp tabs already handles the boot case explicitly, one file over: if (pluginInterface.Reason is not PluginLoadReason.Boot) manager.FilterAllTabsAsync(); Also surfaces FilterIncludePreviousSessions in the Chat tab, which decides whether the log shows anything from before the current session and had no control at all -- written only by the first-run wizard, and only if the user reached step 3. Skip the wizard and it stays false forever. It applies immediately rather than at next launch, since the user is looking at the window when they ask for it. --- HellionChat/AutoTellTabsService.cs | 34 +++++++++++++++++++ .../Ui/Components/Settings/Tabs/ChatTab.cs | 29 ++++++++++++++++ HellionChat/Ui/StyleEngine/SurfaceBackdrop.cs | 8 ++--- 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/HellionChat/AutoTellTabsService.cs b/HellionChat/AutoTellTabsService.cs index 13d8d13..6ea5682 100644 --- a/HellionChat/AutoTellTabsService.cs +++ b/HellionChat/AutoTellTabsService.cs @@ -39,6 +39,10 @@ internal sealed class AutoTellTabsService : IDisposable private bool _initialized; + // Set when Initialize ran before a character was available; cleared once the + // history has actually been loaded. + private bool _rehydratePending; + internal AutoTellTabsService( Plugin plugin, MessageManager messageManager, @@ -76,12 +80,30 @@ internal sealed class AutoTellTabsService : IDisposable RehydratePinnedTabs(); _messageManager.MessageProcessed += HandleTell; + Plugin.ClientState.Login += OnLogin; Plugin.ClientState.Logout += OnLogout; _initialized = true; } + // Deferred when the plugin starts before a character is logged in, which is + // the normal case: the game loads plugins at boot. CurrentContentId is 0 + // until then, so the history query would look up tells for character zero, + // find none, and leave every pinned tab blank for the whole session. + // + // Only visible to someone who actually pins a tell tab AND starts the game + // with the plugin already installed. Reloading the plugin in a running + // session -- what a developer does all day -- hides it completely. private void RehydratePinnedTabs() { + if (_messageManager.CurrentContentId == 0) + { + _logger.LogDebug("[Pin] Rehydrate deferred: no character yet, waiting for login"); + _rehydratePending = true; + return; + } + + _rehydratePending = false; + var pinned = Plugin.Config.Tabs.Count(TabLifecycleHelpers.IsInPinnedPool); _logger.LogDebug($"[Pin] Rehydrate scan: {pinned} pinned tab(s) found"); @@ -122,6 +144,7 @@ internal sealed class AutoTellTabsService : IDisposable return; } + Plugin.ClientState.Login -= OnLogin; Plugin.ClientState.Logout -= OnLogout; _messageManager.MessageProcessed -= HandleTell; _initialized = false; @@ -490,6 +513,17 @@ internal sealed class AutoTellTabsService : IDisposable } } + // Fires on the login that follows a boot-time start, and on every character + // switch after one. Guarded by the pending flag so a switch does not append + // a second copy of the history to tabs that already have it. + private void OnLogin() + { + if (!_rehydratePending) + return; + + RehydratePinnedTabs(); + } + private void OnLogout(int type, int code) { lock (TabsListLock) diff --git a/HellionChat/Ui/Components/Settings/Tabs/ChatTab.cs b/HellionChat/Ui/Components/Settings/Tabs/ChatTab.cs index 31ec8aa..36f1fd6 100644 --- a/HellionChat/Ui/Components/Settings/Tabs/ChatTab.cs +++ b/HellionChat/Ui/Components/Settings/Tabs/ChatTab.cs @@ -6,10 +6,12 @@ namespace HellionChat.Ui.Components.Settings.Tabs; internal sealed class ChatTab { + private readonly Plugin _plugin; private readonly SettingsWidgets _w; public ChatTab(Plugin plugin, TokenResolver resolver) { + _plugin = plugin; _w = new SettingsWidgets(plugin, new SettingsPalette(resolver)); } @@ -53,6 +55,33 @@ internal sealed class ChatTab ); } + if (_w.Section(ImGui.GetID("chat.history"u8), "History")) + { + // The one setting that decides whether the chat log shows anything + // from before this game session. It had no control at all: only the + // first-run wizard ever wrote it, and only if the user actually + // reached step 3 -- skip the wizard and it stays on its default of + // false, leaving the log empty on every launch with no way to fix it. + // + // Named for what it does. The stored name describes filtering, and + // the wizard's own "Load previous session on startup" checkbox + // writes a field nothing reads. + _w.ToggleRow( + ImGui.GetID("chat.history.previoussessions"u8), + "Show history from previous sessions", + "Off means the log starts empty each time the game launches and " + + "only fills with messages received since.", + () => Plugin.Config.FilterIncludePreviousSessions, + v => + { + Plugin.Config.FilterIncludePreviousSessions = v; + // Takes effect at once rather than on the next launch: the + // window is open and the user just asked for the history. + _plugin.MessageManager.FilterAllTabs(); + } + ); + } + if (_w.Section(ImGui.GetID("chat.commandhelp"u8), "Command help", open: false)) { _w.EnumComboRow( diff --git a/HellionChat/Ui/StyleEngine/SurfaceBackdrop.cs b/HellionChat/Ui/StyleEngine/SurfaceBackdrop.cs index ab4c223..f4d246a 100644 --- a/HellionChat/Ui/StyleEngine/SurfaceBackdrop.cs +++ b/HellionChat/Ui/StyleEngine/SurfaceBackdrop.cs @@ -57,8 +57,8 @@ internal sealed class SurfaceBackdrop // the full height is exactly the case that bands, because its handful of // alpha steps each cover twenty-odd pixels. var fade = MathF.Min(190f * Metrics.Scale, (max.Y - min.Y) * 0.45f); - dl.DrawEdgeTint(min, max, 0x00FFFFFFu | ((uint)(0x22 * strength * opacity) << 24), fade); - dl.DrawEdgeTint(min, max, (uint)(0x30 * strength * opacity) << 24, fade, fromBottom: true); + dl.DrawEdgeTint(min, max, 0x00FFFFFFu | ((uint)(0x10 * strength * opacity) << 24), fade); + dl.DrawEdgeTint(min, max, (uint)(0x38 * strength * opacity) << 24, fade, fromBottom: true); if (darken > 0f) dl.AddRectFilled(min, max, (uint)(0xFF * darken * opacity) << 24); @@ -69,8 +69,8 @@ internal sealed class SurfaceBackdrop dl.AddRectFilledMultiColor( min, new Vector2(max.X, min.Y + (max.Y - min.Y) * accentWashHeight), - ColourUtil.ApplyAlpha(accent, 0.07f * opacity), - ColourUtil.ApplyAlpha(accent, 0.035f * opacity), + ColourUtil.ApplyAlpha(accent, 0.045f * opacity), + ColourUtil.ApplyAlpha(accent, 0.02f * opacity), accent & 0x00FFFFFFu, accent & 0x00FFFFFFu );