From cddd29a986ff68d9f940e0256d004c55e8783133 Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Wed, 13 May 2026 10:08:33 +0200 Subject: [PATCH] fix(tabs): pin indicator, history preload, drop Promote from temp menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Smoke-test round 2 feedback from Jin: - Promote-to-permanent label "Dauerhaft behalten" was indistinguishable from Pin in German, leading to misclicks that dropped the tell-target. Removed the menu entry from TempTabs entirely — Promote stays as a service method for future use, but the user-facing path is gone. Anyone who wants a regular tab can still create one via the existing "neuen Tab anlegen" flow. - No visual confirmation that pin took effect. Added a FontAwesome thumbtack overlay top-left of the sidebar icon, accent-coloured, and appended a "Pinned — survives relog" line to the hover tooltip. - Pinned tabs came back empty after a full disable/enable cycle because Tab.Messages is NonSerialized. RehydratePinnedTabs now also runs the same MessageStore-backed PreloadHistory the spawn path uses, so the recent conversation window reappears alongside the rehydrated TellTarget. Diagnose-logging on TryPin/Unpin/Promote/Rehydrate stays in so the next smoke can confirm at a glance which path fired from the Dalamud console. --- HellionChat/AutoTellTabsService.cs | 32 ++++++++++++- .../Resources/HellionStrings.Designer.cs | 1 + HellionChat/Resources/HellionStrings.de.resx | 7 ++- HellionChat/Resources/HellionStrings.resx | 5 ++- HellionChat/Ui/ChatLogWindow.cs | 45 +++++++++++++------ 5 files changed, 73 insertions(+), 17 deletions(-) diff --git a/HellionChat/AutoTellTabsService.cs b/HellionChat/AutoTellTabsService.cs index 340e309..4ad5057 100644 --- a/HellionChat/AutoTellTabsService.cs +++ b/HellionChat/AutoTellTabsService.cs @@ -64,18 +64,38 @@ internal sealed class AutoTellTabsService : IDisposable _initialized = true; } - private static void RehydratePinnedTabs() + private void RehydratePinnedTabs() { + var pinned = Plugin.Config.Tabs.Count(TabLifecycleHelpers.IsInPinnedPool); + Plugin.LogProxy.Info($"[Pin] Rehydrate scan: {pinned} pinned tab(s) found"); + foreach (var tab in Plugin.Config.Tabs) { if (!TabLifecycleHelpers.IsInPinnedPool(tab)) continue; + if (tab.TellTarget is null || !tab.TellTarget.IsSet()) + { + Plugin.LogProxy.Warning( + $"[Pin] Pinned tab '{tab.Name}' has no usable TellTarget " + + $"(Name={tab.TellTarget?.Name ?? ""} World={tab.TellTarget?.World ?? 0}). " + + "Chat input on this tab will be empty until the partner sends a tell or you /tell manually." + ); continue; + } tab.Channel ??= InputChannel.Tell; tab.CurrentChannel.Channel = InputChannel.Tell; tab.CurrentChannel.TellTarget = tab.TellTarget.Clone(); + + // MessageList is NonSerialized so pinned tabs come back empty. + // Preload the same history window the spawn path uses so the user + // sees the recent conversation, not a blank tab. + PreloadHistory(tab, tab.TellTarget.Name, tab.TellTarget.World, Guid.Empty); + + Plugin.LogProxy.Info( + $"[Pin] Rehydrated '{tab.Name}' -> Tell target {tab.TellTarget.Name}@{tab.TellTarget.World}" + ); } } @@ -436,6 +456,9 @@ internal sealed class AutoTellTabsService : IDisposable { if (!tab.IsTempTab || tab.IsPinned) { + Plugin.LogProxy.Info( + $"[Pin] TryPin skipped: IsTempTab={tab.IsTempTab} IsPinned={tab.IsPinned}" + ); return false; } @@ -449,6 +472,9 @@ internal sealed class AutoTellTabsService : IDisposable } tab.IsPinned = true; + Plugin.LogProxy.Info( + $"[Pin] Pinned tab '{tab.Name}' target={tab.TellTarget?.Name}@{tab.TellTarget?.World}" + ); _plugin.SaveConfig(); return true; } @@ -469,6 +495,7 @@ internal sealed class AutoTellTabsService : IDisposable } tab.IsPinned = false; + Plugin.LogProxy.Info($"[Pin] Unpinned tab '{tab.Name}'"); _plugin.SaveConfig(); } @@ -482,6 +509,9 @@ internal sealed class AutoTellTabsService : IDisposable tab.IsTempTab = false; tab.IsPinned = false; tab.TellTarget = TellTarget.Empty(); + Plugin.LogProxy.Info( + $"[Pin] Promoted tab '{tab.Name}' to permanent (tell-binding dropped)" + ); _plugin.SaveConfig(); } } diff --git a/HellionChat/Resources/HellionStrings.Designer.cs b/HellionChat/Resources/HellionStrings.Designer.cs index d0f6743..3282998 100644 --- a/HellionChat/Resources/HellionStrings.Designer.cs +++ b/HellionChat/Resources/HellionStrings.Designer.cs @@ -176,6 +176,7 @@ internal class HellionStrings internal static string PinTab_PromoteTooltip => Get(nameof(PinTab_PromoteTooltip)); internal static string PinTab_LimitReached => Get(nameof(PinTab_LimitReached)); internal static string PinTab_PinnedTooltip => Get(nameof(PinTab_PinnedTooltip)); + internal static string PinTab_PinTooltip => Get(nameof(PinTab_PinTooltip)); // Hellion Chat — Auto-Tell-Tabs Chat settings tab internal static string ChatLog_AutoTellTabs_Section_Title => Get(nameof(ChatLog_AutoTellTabs_Section_Title)); diff --git a/HellionChat/Resources/HellionStrings.de.resx b/HellionChat/Resources/HellionStrings.de.resx index 0d509b1..766e2bf 100644 --- a/HellionChat/Resources/HellionStrings.de.resx +++ b/HellionChat/Resources/HellionStrings.de.resx @@ -390,10 +390,10 @@ Tab lösen - Dauerhaft behalten + In Standard-Tab umwandeln - „Dauerhaft behalten" macht aus dem Tab einen regulären Tab, der zu allen channel-gefilterten Nachrichten passt — bei Bedarf danach umbenennen. + Wandelt den TempTell in einen regulären Tab um. Die Tell-Bindung an die Person geht verloren, der Tab fängt dann Nachrichten anhand der Channel-Filter ein. Für „Tab überlebt Relog" stattdessen „Tab anpinnen" wählen. Maximal {0} angepinnte Tell-Tabs erreicht. Erst einen lösen oder dauerhaft behalten. @@ -401,6 +401,9 @@ Angepinnt — überlebt Relog. + + Angepinnte Tabs überleben Relog und behalten die Bindung an die Tell-Person. + diff --git a/HellionChat/Resources/HellionStrings.resx b/HellionChat/Resources/HellionStrings.resx index 6809aa7..101a6eb 100644 --- a/HellionChat/Resources/HellionStrings.resx +++ b/HellionChat/Resources/HellionStrings.resx @@ -393,7 +393,10 @@ Promote to permanent - Promote turns this into a regular tab matching all channel-filtered messages — rename afterwards if needed. + Turns this TempTell into a regular tab. The tell binding to the partner is dropped — the tab will catch messages by its channel filters from now on. For "tab survives relog while staying bound to this partner", use Pin Tab instead. + + + Pinned tabs survive relog and stay bound to this conversation partner. Maximum of {0} pinned tell tabs reached. Unpin one first, or use Promote to permanent. diff --git a/HellionChat/Ui/ChatLogWindow.cs b/HellionChat/Ui/ChatLogWindow.cs index 440ad6d..bab4bfc 100644 --- a/HellionChat/Ui/ChatLogWindow.cs +++ b/HellionChat/Ui/ChatLogWindow.cs @@ -1871,11 +1871,34 @@ public sealed class ChatLogWindow : Window ); } + // Pin indicator: small thumbtack glyph top-left of the icon. + // Sits opposite the unread dot so they never collide. + if (tab.IsPinned) + { + var min = ImGui.GetItemRectMin(); + const float pinPadding = 2f; + var pinPos = new Vector2(min.X + pinPadding, min.Y + pinPadding); + using (Plugin.FontManager.FontAwesome.Push()) + { + ImGui + .GetWindowDrawList() + .AddText( + pinPos, + ColourUtil.RgbaToAbgr(theme.Colors.Accent), + FontAwesomeIcon.Thumbtack.ToIconString() + ); + } + } + // Tooltip mit Tab-Name + Unread-Counter beim Hover. if (ImGui.IsItemHovered()) { using var tt = ImRaii.Tooltip(); ImGui.TextUnformatted($"{tab.Name}{unread}"); + if (tab.IsPinned) + { + ImGui.TextUnformatted(HellionStrings.PinTab_PinnedTooltip); + } } DrawTabContextMenu(tab, tabI); @@ -2182,22 +2205,18 @@ public sealed class ChatLogWindow : Window if (svc.TryPin(tab)) ImGui.CloseCurrentPopup(); } - if (atCap && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) + if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) + { ImGui.SetTooltip( - string.Format( - HellionStrings.PinTab_LimitReached, - AutoTellTabsService.MaxPinnedTempTabs - ) + atCap + ? string.Format( + HellionStrings.PinTab_LimitReached, + AutoTellTabsService.MaxPinnedTempTabs + ) + : HellionStrings.PinTab_PinTooltip ); + } } - - if (ImGui.MenuItem(HellionStrings.PinTab_MenuPromote)) - { - svc.PromoteToPermanent(tab); - ImGui.CloseCurrentPopup(); - } - if (ImGui.IsItemHovered()) - ImGui.SetTooltip(HellionStrings.PinTab_PromoteTooltip); } internal readonly List PopOutDocked = [];