fix(closeout): address closure-review findings

- gate keybind pill-sync on IsChannelOrExistingLinkshell so an empty
  linkshell slot no longer desyncs the pill from the real send channel
- close manually-popped pop-out windows on logout via an IsOpen filter
  instead of the PopOut flag (which manual pops never set)
- read the router's tell-tab lookup through a lock-wrapped accessor so the
  framework thread cannot enumerate Config.Tabs mid worker-thread mutation
- add a "switch on every tell" toggle (default on) and make the auto-open
  mode pick the matching layout, so Sidebar vs Top-tab are distinct
- comment corrections (stale/contradictory text, TEST-MIRROR path depth)
This commit is contained in:
2026-06-16 09:04:18 +02:00
parent 49f5119b17
commit 6f71b09331
9 changed files with 78 additions and 29 deletions
+24 -3
View File
@@ -9,7 +9,7 @@ namespace HellionChat.Services;
// that service owns tab CREATION + lifecycle; this only REVEALS/pops the tab it
// finds. Popout guards on pool.IsOpen so it never double-pops a tab the
// AutoTellTabsOpenAsPopout path already opened. Subscribes to the resolved
// MessageManager.MessageProcessed stream (partner already extracted), not the raw
// MessageManager.MessageProcessed stream (a resolved Message), not the raw
// IChatGui event, and defers the reveal one tick so the tab exists regardless of
// subscriber order. Wired by TellRouterServiceInitHostedService.
internal sealed class TellRouterService : IDisposable
@@ -69,7 +69,9 @@ internal sealed class TellRouterService : IDisposable
// mutation) is serialized with Draw (reference_dalamud_framework_thread).
Plugin.Framework.RunOnFrameworkThread(() =>
{
var tab = AutoTellTabsService.FindTempTab(name, world);
// Lock-safe lookup: AutoTellTabs mutates Config.Tabs under its lock on the
// worker thread, so we read through its guarded accessor, not the static.
var tab = Plugin.Instance.AutoTellTabsService?.FindTempTabSafe(name, world);
if (tab == null)
return; // nothing to reveal (auto-tell-tabs off -> no tab created)
@@ -77,7 +79,26 @@ internal sealed class TellRouterService : IDisposable
{
case TellAutoOpenMode.Sidebar:
case TellAutoOpenMode.TopTab:
Plugin.Instance.MainWindow?.ActivateTab(tab);
// Switching to the tab on every tell is user-gated
// (TellAutoOpenSwitchAlways, default on); when off the tab still
// appears with its unread badge but the active tab is left alone.
// The mode also picks the layout, so Sidebar vs TopTab are actually
// distinct outcomes, not the same ActivateTab.
if (Plugin.Config.TellAutoOpenSwitchAlways)
{
var wantLayout =
mode == TellAutoOpenMode.TopTab
? MainWindowLayoutMode.TopTabs
: MainWindowLayoutMode.Sidebar;
if (Plugin.Config.MainWindowLayoutMode != wantLayout)
{
Plugin.Config.MainWindowLayoutMode = wantLayout;
Plugin.Instance.SaveConfig();
}
Plugin.Instance.MainWindow?.ActivateTab(tab);
}
break;
case TellAutoOpenMode.Popout:
// IsOpen-guard: don't double-pop a tab the AutoTellTabsOpenAsPopout