@@ -227,6 +227,26 @@ DF.DebugSubCommands = {}
227227-- This lookup is what the dispatcher gate consults so the two halves now match.
228228DF .DEBUG_SUB_DEV = {}
229229
230+ -- ☠ EVERY sub() NAME, dev or not. This is what stops "/df debug <word>" loading
231+ -- the settings addon for a command that lives entirely in this one.
232+ --
233+ -- The dispatcher's unknown-word fallback exists because a handful of debug tools
234+ -- (icons, colorhook, atlas, auraexp, memtest) register their slashes only when
235+ -- the companion loads, so an unrecognised word has to load it and retry. But
236+ -- "recognised" was read from DebugSlashBySub, which ONLY RegisterDebugSlash
237+ -- fills -- so all ~38 commands registered through sub() looked unknown and pulled
238+ -- in ~3 MB of settings UI before running a branch that never needed it. That is
239+ -- the whole saving of splitting the addon in two, spent on one diagnostic.
240+ --
241+ -- Worse for the probes that exist to MEASURE memory: loading the companion
242+ -- perturbs exactly what they report.
243+ --
244+ -- Safe against shadowing: no sub() name collides with a companion-registered
245+ -- slash, so this set can never swallow a word that genuinely needs the load. The
246+ -- sub() branches that DO need the companion (profiler, ...) call
247+ -- EnsureOptionsLoaded themselves, at the point of need.
248+ DF .DEBUG_SUB_KNOWN = {}
249+
230250-- ============================================================
231251-- CHAT OUTPUT HOUSE STYLE (DF:Out)
232252-- ============================================================
@@ -526,6 +546,9 @@ function DF:RegisterDebugSub(cmd, desc, devOnly, args, hidden)
526546 -- command cannot be marked dev in the listing while staying runnable on
527547 -- release — the two can no longer drift apart.
528548 if devOnly then DF .DEBUG_SUB_DEV [cmd ] = true end
549+ -- Registering IS the gate here too: a branch added later is covered on the
550+ -- day it is written, with no second list to keep in step.
551+ DF .DEBUG_SUB_KNOWN [cmd ] = true
529552end
530553
531554-- ============================================================
@@ -1219,8 +1242,13 @@ function DF:LightweightUpdateHighlight(highlightType)
12191242 end
12201243
12211244 if highlight and highlight :IsShown () then
1245+ -- ☠ This function writes the four line textures DIRECTLY, bypassing
1246+ -- ApplyHighlightStyle, so its cached style is stale the moment we touch
1247+ -- them. Drop it or the next full update sees a matching signature and
1248+ -- skips, leaving these drag-time values in place permanently.
1249+ if DF .InvalidateHighlightStyle then DF :InvalidateHighlightStyle (highlight ) end
12221250 highlight :SetAlpha (alpha )
1223-
1251+
12241252 -- Update border textures - check both naming conventions
12251253 local top = highlight .top or highlight .topLine
12261254 local bottom = highlight .bottom or highlight .bottomLine
@@ -1647,6 +1675,36 @@ function DF:GetClassColor(class)
16471675 return RAID_CLASS_COLORS [class ] or DEFAULT_CLASS_COLOR
16481676end
16491677
1678+ -- ============================================================
1679+ -- UNIT ROLE RESOLUTION
1680+ -- ============================================================
1681+ -- ☠ NEVER CALL UnitGroupRolesAssigned DIRECTLY FOR A GATE. Use this.
1682+ --
1683+ -- UnitGroupRolesAssigned answers "what role did the GROUP assign", not "what
1684+ -- does this player do". It returns "NONE" solo, in the open world, in open-world
1685+ -- groups, and in delves until something forces an assignment -- so a Holy
1686+ -- Paladin standing in Silvermoon reads as NONE, and any caller that maps NONE
1687+ -- onto DAMAGER decides a healer is a DPS. That is what made the resource bar's
1688+ -- Healers toggle inert while solo (the bar answered to the DPS toggle instead),
1689+ -- and why a delve only resolved the role after a spec change.
1690+ --
1691+ -- The player is the one unit we can do better for: GetSpecializationRole is
1692+ -- authoritative and always available. Other units expose no public spec API, so
1693+ -- they stay NONE and the caller keeps whatever fallback it had.
1694+ --
1695+ -- Returns nil for a unit that does not exist, otherwise a role token that may
1696+ -- still be "NONE" -- resolution only, no policy. Callers decide what NONE means.
1697+ function DF :GetUnitRole (unit )
1698+ if not unit or not UnitExists (unit ) then return nil end
1699+ local role = UnitGroupRolesAssigned and UnitGroupRolesAssigned (unit )
1700+ if (not role or role == " NONE" ) and UnitIsUnit and UnitIsUnit (unit , " player" )
1701+ and GetSpecialization and GetSpecializationRole then
1702+ local spec = GetSpecialization ()
1703+ if spec then role = GetSpecializationRole (spec ) or role end
1704+ end
1705+ return role
1706+ end
1707+
16501708-- Resolve the frame border colour: the static borderColor by default, or
16511709-- (Stage 2.1+) the unit's class / role colour with its own alpha slider when
16521710-- the canonical frameBorderColorSource picks one. Non-player / unknown-class
@@ -1701,17 +1759,10 @@ function DF:GetFrameBorderColor(frame, db)
17011759 if frame .dfIsTestFrame then
17021760 local testData = DF .GetTestUnitData and DF :GetTestUnitData (frame .index , frame .isRaidFrame )
17031761 role = testData and testData .role
1704- elseif frame .unit and UnitExists (frame .unit ) and UnitGroupRolesAssigned then
1705- role = UnitGroupRolesAssigned (frame .unit )
1706- -- UnitGroupRolesAssigned returns "NONE" outside instances where
1707- -- roles aren't assigned (solo, world content). For the player,
1708- -- fall back to spec role so role colour stays meaningful. Other
1709- -- units expose no public spec API; they stay on picker fallback.
1710- if (not role or role == " NONE" ) and UnitIsUnit and UnitIsUnit (frame .unit , " player" )
1711- and GetSpecialization and GetSpecializationRole then
1712- local spec = GetSpecialization ()
1713- if spec then role = GetSpecializationRole (spec ) end
1714- end
1762+ else
1763+ -- Player falls back to the spec role when the group assigned none;
1764+ -- other units stay NONE and drop to the picker fallback below.
1765+ role = DF :GetUnitRole (frame .unit )
17151766 end
17161767 local c = rc and role and role ~= " NONE" and (rc [role ] or rc [string.lower (role )])
17171768 if c then
@@ -2535,6 +2586,34 @@ function DF:DebugAuraFilters(unit)
25352586 o :Field (" AuraUtil.ShouldDisplayBuff" , hasSDB and " present" or " absent" , hasSDB and " good" or " warn" )
25362587 o :Field (" AuraUtil.ForEachAura" , hasFEA and " present" or " absent" , hasFEA and " good" or " warn" )
25372588
2589+ -- Which Edit Mode strategy is actually live. Deafening the container to
2590+ -- AURA_DATA_PROVIDER_SWITCH can only be PROVEN at runtime (it is a base widget
2591+ -- method on a forbidden-table object), so this reports the client's answer
2592+ -- rather than an assumption — see EDIT-MODE DEAFENING in Frames/AuraContainer.lua.
2593+ o :Section (" Edit Mode isolation" )
2594+ local ac = DF .AuraContainer
2595+ local deafOK = ac and ac ._providerDeafOK
2596+ if not ac or deafOK == nil then
2597+ o :Field (" strategy" , " not probed yet (no container built)" , " neutral" )
2598+ elseif deafOK then
2599+ o :Field (" strategy" , " deafened container — rows keep live auras" , " good" )
2600+ else
2601+ o :Field (" strategy" , " rebirth fallback — rebuild on switch (OOC), hide in combat" , " warn" )
2602+ end
2603+ if ac and ac ._providerDeafWhy then
2604+ o :Field (" unregister probe" , tostring (ac ._providerDeafWhy ), deafOK and " good" or " neutral" )
2605+ end
2606+ -- Populated the first time Edit Mode is opened. QUEUED means the client deferred
2607+ -- our re-entrant switch to after the in-flight dispatch (safe, and the inline reset
2608+ -- gains nothing); NESTED means it dispatched re-entrantly (zero blip, but containers
2609+ -- the outer dispatch had not reached can strand on the sample source).
2610+ if ac and ac ._inlineDispatch then
2611+ o :Field (" inline reset dispatch" , ac ._inlineDispatch ,
2612+ ac ._inlineDispatch == " QUEUED" and " good" or " warn" )
2613+ elseif ac then
2614+ o :Field (" inline reset dispatch" , " not observed yet (open Edit Mode once)" , " neutral" )
2615+ end
2616+
25382617 o :Section (" ShouldDisplayBuff per aura" )
25392618 if AuraUtil and AuraUtil .ForEachAura then
25402619 local rows = {}
@@ -3637,6 +3716,37 @@ DF._MainEventDispatcher = function(self, event, arg1)
36373716 DandersFramesCharDB .currentProfile = currentProfile
36383717 DandersFramesDB_v2 .currentProfile = currentProfile
36393718
3719+ -- Settings-window geometry moves from db.party to account-wide
3720+ -- windowState (see DF:GetWindowState for why). Seed once from whichever
3721+ -- profile is active at this login -- that is the window the user last
3722+ -- sized and scaled, so it is the only correct source.
3723+ if not DandersFramesDB_v2 .windowState then
3724+ local ws = {}
3725+ local src = DandersFramesDB_v2 .profiles [currentProfile ]
3726+ src = src and src .party
3727+ if type (src ) == " table" then
3728+ ws .scale , ws .width , ws .height = src .guiScale , src .guiWidth , src .guiHeight
3729+ ws .point , ws .relPoint , ws .x , ws .y = src .guiPoint , src .guiRelPoint , src .guiX , src .guiY
3730+ end
3731+ DandersFramesDB_v2 .windowState = ws
3732+ end
3733+ -- Clean up the legacy per-profile keys (no longer read anywhere). Same
3734+ -- shape as the languageOverride strip above: unconditional, so profiles
3735+ -- that were not the seed source are cleared too. Both modes -- only
3736+ -- db.party was ever read, but RaidDefaults is copied from PartyDefaults
3737+ -- so every profile carries a dead db.raid set as well.
3738+ for _ , profile in pairs (DandersFramesDB_v2 .profiles ) do
3739+ if type (profile ) == " table" then
3740+ for _ , modeKey in ipairs ({ " party" , " raid" }) do
3741+ local m = profile [modeKey ]
3742+ if type (m ) == " table" then
3743+ m .guiScale , m .guiWidth , m .guiHeight = nil , nil , nil
3744+ m .guiPoint , m .guiRelPoint , m .guiX , m .guiY = nil , nil , nil , nil
3745+ end
3746+ end
3747+ end
3748+ end
3749+
36403750 DF .db = DandersFramesDB_v2 .profiles [currentProfile ]
36413751
36423752 -- Ensure both modes exist in current profile
@@ -4973,15 +5083,23 @@ DF._MainEventDispatcher = function(self, event, arg1)
49735083 local dbgWord , dbgRest = rawMsg :match (" ^%s*[Dd][Ee][Bb][Uu][Gg]%s+(%S+)%s*(.-)%s*$" )
49745084 -- "on"/"off" are the logging toggle, not commands named on/off.
49755085 if dbgWord and dbgWord :lower () ~= " on" and dbgWord :lower () ~= " off" then
4976- local dbgKey = DF .DebugSlashBySub [dbgWord :lower ()]
5086+ local dbgLower = dbgWord :lower ()
5087+ local dbgKey = DF .DebugSlashBySub [dbgLower ]
49775088 -- Several debug tools live in the companion and register their
49785089 -- slashes only when it loads. If the word is unknown and the
49795090 -- companion is not in yet, load it and retry once -- otherwise
49805091 -- the first use of /df debug memtest fell through to the final
49815092 -- else and opened the settings window instead of the tool.
4982- if not dbgKey and not DF ._optionsAddonLoaded
5093+ --
5094+ -- ☠ DEBUG_SUB_KNOWN FIRST. A sub()-registered command is a branch
5095+ -- in THIS addon; it is recognised, it just is not in the slash
5096+ -- registry. Without this test every one of them read as unknown
5097+ -- and loaded the companion for nothing -- see the note at the
5098+ -- DEBUG_SUB_KNOWN declaration.
5099+ if not dbgKey and not DF .DEBUG_SUB_KNOWN [dbgLower ]
5100+ and not DF ._optionsAddonLoaded
49835101 and DF .EnsureOptionsLoaded and DF :EnsureOptionsLoaded () then
4984- dbgKey = DF .DebugSlashBySub [dbgWord : lower () ]
5102+ dbgKey = DF .DebugSlashBySub [dbgLower ]
49855103 end
49865104 if dbgKey and SlashCmdList [dbgKey ] then
49875105 SlashCmdList [dbgKey ](dbgRest or " " )
@@ -5176,15 +5294,14 @@ DF._MainEventDispatcher = function(self, event, arg1)
51765294 DF :ResetFullProfile ()
51775295 elseif msg == " resetgui" then
51785296 -- Reset GUI scale, size, and position to defaults
5179- if DF .db and DF .db .party then
5180- DF .db .party .guiScale = 1.0
5181- DF .db .party .guiWidth = 760
5182- DF .db .party .guiHeight = 520
5183- DF .db .party .guiPoint = nil
5184- DF .db .party .guiRelPoint = nil
5185- DF .db .party .guiX = nil
5186- DF .db .party .guiY = nil
5187- end
5297+ local ws = DF :GetWindowState ()
5298+ ws .scale = 1.0
5299+ ws .width = 760
5300+ ws .height = 520
5301+ ws .point = nil
5302+ ws .relPoint = nil
5303+ ws .x = nil
5304+ ws .y = nil
51885305 if DF .GUIFrame then
51895306 DF .GUIFrame :ClearAllPoints ()
51905307 DF .GUIFrame :SetPoint (" CENTER" , UIParent , " CENTER" , 0 , 0 )
0 commit comments