fix(tabs): preserve runtime channel across Settings Save, deep-clone seeded CurrentChannel
Smoke-test round 4 surfaced a clean reproducer: a Party or Linkshell tab with channel /p, then Settings → Save, popped the input back to /tell <pinned-partner> on the next interaction. Two bugs combined: 1. Configuration.UpdateFrom captured only Messages+LastSendUnread from the live state during the persistent-tab merge. CurrentChannel was not preserved, so a Settings save overwrote the runtime channel state with the settings-time snapshot. If the user switched channel in-game between Settings-open and Settings-save, that switch was lost. Live CurrentChannel now joins Messages and LastSendUnread in the per-Identifier preservation tuple. 2. TabSwitched seeded a new tab's CurrentChannel from previousTab via reference copy (`newTab.CurrentChannel = previousTab.CurrentChannel`). That left both tabs sharing the same UsedChannel instance, so a later mutation on one bled into the other — exactly the path that carried a pinned tell-target onto Party. Switched to a deep clone (UsedChannel.Clone(), same Cherry-Pick-Patch-B pattern from v1.4.6) plus a Debug log so the next smoke can confirm at a glance which previous tab donated its channel state. Pre-existing ChatTwo upstream pattern; v1.4.7 just made it visible because pinned tabs are now the kind of long-lived tell-target that sticks around for the seed path to grab.
This commit is contained in:
@@ -293,11 +293,14 @@ public class Configuration : IPluginConfiguration
|
|||||||
// not destroy open tell conversations. Pinned TempTabs are persistent
|
// not destroy open tell conversations. Pinned TempTabs are persistent
|
||||||
// and come through `other` like regular tabs; unpinned TempTabs are
|
// and come through `other` like regular tabs; unpinned TempTabs are
|
||||||
// session-only and held from the local state. For persistent tabs
|
// session-only and held from the local state. For persistent tabs
|
||||||
// (incl. pinned), capture live MessageList + LastSendUnread by
|
// (incl. pinned), capture live runtime state by Identifier and restore
|
||||||
// Identifier and restore them onto the freshly cloned tabs.
|
// it onto the freshly cloned tabs — CurrentChannel is critical because
|
||||||
|
// the user may have switched channel in-game between settings-open
|
||||||
|
// and settings-save, and we'd otherwise overwrite that with the
|
||||||
|
// settings-time snapshot.
|
||||||
var liveUnpinnedTempTabs = Tabs.Where(TabLifecycleHelpers.IsInUnpinnedPool).ToList();
|
var liveUnpinnedTempTabs = Tabs.Where(TabLifecycleHelpers.IsInUnpinnedPool).ToList();
|
||||||
var livePersistentSession = Tabs.Where(t => !TabLifecycleHelpers.IsInUnpinnedPool(t))
|
var livePersistentSession = Tabs.Where(t => !TabLifecycleHelpers.IsInUnpinnedPool(t))
|
||||||
.ToDictionary(t => t.Identifier, t => (t.Messages, t.LastSendUnread));
|
.ToDictionary(t => t.Identifier, t => (t.Messages, t.LastSendUnread, t.CurrentChannel));
|
||||||
|
|
||||||
Tabs = other
|
Tabs = other
|
||||||
.Tabs.Where(t => !t.IsTempTab || t.IsPinned)
|
.Tabs.Where(t => !t.IsTempTab || t.IsPinned)
|
||||||
@@ -308,6 +311,7 @@ public class Configuration : IPluginConfiguration
|
|||||||
{
|
{
|
||||||
clone.Messages = live.Messages;
|
clone.Messages = live.Messages;
|
||||||
clone.LastSendUnread = live.LastSendUnread;
|
clone.LastSendUnread = live.LastSendUnread;
|
||||||
|
clone.CurrentChannel = live.CurrentChannel;
|
||||||
}
|
}
|
||||||
return clone;
|
return clone;
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -441,11 +441,24 @@ public sealed class ChatLogWindow : Window
|
|||||||
|
|
||||||
private void TabSwitched(Tab newTab, Tab previousTab)
|
private void TabSwitched(Tab newTab, Tab previousTab)
|
||||||
{
|
{
|
||||||
// Use the fixed channel if set by the user, or set it to the current tabs channel if this tab wasn't accessed before
|
// Use the fixed channel if set by the user. Otherwise, if the new tab
|
||||||
|
// has no channel state yet (fresh from JSON, never selected this
|
||||||
|
// session), seed from the previous tab — but deep-clone so we don't
|
||||||
|
// share TellTarget with the previous tab. Without the clone, a later
|
||||||
|
// /tell on the new tab would mutate the pinned tab's TellTarget and
|
||||||
|
// the Party/Linkshell channel would pop back to the pinned tell-mark.
|
||||||
if (newTab.Channel is not null)
|
if (newTab.Channel is not null)
|
||||||
|
{
|
||||||
newTab.CurrentChannel.Channel = newTab.Channel.Value;
|
newTab.CurrentChannel.Channel = newTab.Channel.Value;
|
||||||
|
}
|
||||||
else if (newTab.CurrentChannel.Channel is InputChannel.Invalid)
|
else if (newTab.CurrentChannel.Channel is InputChannel.Invalid)
|
||||||
newTab.CurrentChannel = previousTab.CurrentChannel;
|
{
|
||||||
|
newTab.CurrentChannel = previousTab.CurrentChannel.Clone();
|
||||||
|
Plugin.LogProxy.Debug(
|
||||||
|
$"[Tab] '{newTab.Name}' seeded channel from '{previousTab.Name}' "
|
||||||
|
+ $"(Channel={newTab.CurrentChannel.Channel}, TellTarget={newTab.CurrentChannel.TellTarget?.ToTargetString() ?? "null"})"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
SetChannel(newTab.CurrentChannel.Channel);
|
SetChannel(newTab.CurrentChannel.Channel);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user