fix(tell): couple CurrentTab to the active tab, retire LastTab
Plugin.CurrentTab now delegates to MainWindow.ActiveTab (fallback Tabs[0]) instead of the never-assigned LastTab index, so the game hooks, unread tracking, notification sounds, InputDisabled and Foray/Eureka paths all operate on the tab the user actually has selected. The dead LastTab/WantedTab fields and both WantedTab writes are removed. A reference-based MainWindow.ResetActiveTabIfRemoved repairs the active-tab reference on eviction/logout (immune to the SaveConfig temp-tab strip window). The worker-thread eviction path marshals it onto the framework thread so the strip mutation serializes with Draw; logout is already framework-thread. The Draw-seed gains a lazy re-seed for a wholesale config swap. Adds CurrentTabCouplingStep (headless) and the interactive CurrentTabGuidedStep self-test (step count 30 -> 32).
This commit is contained in:
@@ -119,6 +119,22 @@ internal sealed class MainWindow : Window
|
||||
|
||||
public Tab? ActiveTab => _activeTab;
|
||||
|
||||
// Re-anchors the active-tab reference when the tab it points at is removed
|
||||
// (eviction / logout). Reference compare, so it is immune to the SaveConfig
|
||||
// temp-tab strip window where a tab is briefly absent from Config.Tabs; the
|
||||
// re-seeded tab runs through OnTabActivated so a programmatic switch strips
|
||||
// stale tell state the way a click would.
|
||||
internal void ResetActiveTabIfRemoved(Tab removed)
|
||||
{
|
||||
if (!ReferenceEquals(_activeTab, removed))
|
||||
return;
|
||||
|
||||
var next = Plugin.Config.Tabs.Count > 0 ? Plugin.Config.Tabs[0] : null;
|
||||
_activeTab = next;
|
||||
if (next is not null)
|
||||
TabLifecycleHelpers.OnTabActivated(next, removed);
|
||||
}
|
||||
|
||||
// Internal accessors for self-tests so the probes can reach the live
|
||||
// component without exposing them as public surface.
|
||||
internal Components.Sidebar GetSidebarForSelfTest() => _sidebar;
|
||||
@@ -163,6 +179,18 @@ internal sealed class MainWindow : Window
|
||||
// (pre-coupling the detour wrote here); strip it like any activation.
|
||||
TabLifecycleHelpers.OnTabActivated(seeded, null);
|
||||
}
|
||||
else if (_activeTab is { } active && !Plugin.Config.Tabs.Contains(active))
|
||||
{
|
||||
// Active tab is no longer in the list (e.g. a wholesale config import
|
||||
// the service repair paths never see). Re-seed on the Draw thread. The
|
||||
// Contains read shares the pre-existing unsynchronized-Tabs-list
|
||||
// exposure that spec §6 defers (SaveConfig also strips from the worker
|
||||
// thread); this adds one more racing read, not a new hazard class.
|
||||
var reseed = Plugin.Config.Tabs.Count > 0 ? Plugin.Config.Tabs[0] : null;
|
||||
_activeTab = reseed;
|
||||
if (reseed is not null)
|
||||
TabLifecycleHelpers.OnTabActivated(reseed, active);
|
||||
}
|
||||
|
||||
var statusHeight = Components.StatusBar.Height;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user