Skip to content

Fix(Unit Frames): keep the frame built when Visibility is set to Never - #1888

Merged
EllesmereGaming merged 6 commits into
EllesmereGaming:mainfrom
dfrisone:fix/uf-visibility-override-stale-driver
Sep 2, 2026
Merged

Fix(Unit Frames): keep the frame built when Visibility is set to Never#1888
EllesmereGaming merged 6 commits into
EllesmereGaming:mainfrom
dfrisone:fix/uf-visibility-override-stale-driver

Conversation

@dfrisone

Copy link
Copy Markdown
Contributor

Bug: https://discord.com/channels/585577383847788554/1484909708392530121

Issue: with a spec override setting the player frame's Visibility to Never for healer or tank, switching back to a spec without the override left the frame hidden until /reload. It looks intermittent because it depends on which spec the session started on.

Root cause: the Visibility dropdown wrote enabledFrames[unit] alongside barVisibility, and that key is build-time. ns.GetUnitFrameSource reports the unit hidden and InitializeFrames skips the spawn, once, at login. A spec override applies a couple of frames later, so a spec carrying Never left the frame uncreated, and switching back wrote the key true again with no frame to show.

Fix: Visibility no longer touches enabledFrames, so the frame is always built and Never hides it the way in_combat and mouseover do, pinned on a state-visibility driver so it holds in combat too. A migration clears the flag the old pairing wrote and strips it from existing override stores. Verified in game by the reporter.

Visibility "Never" wrote enabledFrames[unit] = false alongside barVisibility, and
that key is a build-time decision: ns.GetUnitFrameSource reports the unit
"hidden" and InitializeFrames skips the spawn entirely. A Spec Override carrying
"Never" therefore left the frame uncreated for the whole session -- switching
back to a spec without the override wrote enabledFrames true again and ran the
reload pass, but there was no frame to show and only a /reload brought it back.

Visibility is a runtime axis now and leaves enabledFrames alone, so the frame is
always built and "never" is hidden the same way "in_combat" and "mouseover" are:
ToggleFrame's group trio and UpdateFrameVisibility's secure bucket both already
handle it, and both reverse when the override lifts. enabledFrames goes back to
meaning what the Enable Pet / Target of Target / Focus Target / Boss toggles say,
which is why SetUnitFrameSource no longer rewrites barVisibility to keep the two
in sync -- picking a frame source would otherwise clear a user's "Never".

Two things ride on the old pairing and are kept: a unit on Blizzard's frame set
to "Never" still resolves "hidden", since suppressing Blizzard's frame is the
only way to honour it when we spawn nothing of our own; and Blizzard's player
cast bar is still left alone under "Never", because ours is built but hides with
the frame. A profile migration clears the enabledFrames flag the old pairing
wrote for player/target/focus -- those three have no Enable toggle of their own,
so a stored false there can only have come from it.
The migration cleared the base enabledFrames flag but left the same fkey banked
in the spec and conditional override stores, where auto-capture put it while the
two keys were written together. Nothing blacklists it, so the next spec apply
wrote false back and the frame went missing again at the following login -- and
with Visibility no longer writing the key there was no way back from the UI.
Strip it for player/target/focus, the same way the Dragon Riding migration does;
the other units keep theirs, since their Enable toggles own that key.

Under a leftover Match Any selection the engine evaluates the scalar and answers
false rather than nil for "never", so the secure bucket read it as engine-owned
and kept the frame shown at alpha 0 -- invisible but still eating clicks. Never
is exclusive, so it now settles the verdict ahead of the engine.

A frame Visibility hides outright is built but never on screen, so its unlock
mover had nothing to drag. Gate the unit and cast bar elements on isHidden,
which is re-read on every unlock-mode open, so lifting the hide needs no reload.
Four places treated Visibility "Never" as "no frame exists", which was true only
because it used to clear enabledFrames:

ResolveVisResting had no alpha case for it, so the mode rested on the Show/Hide
bucket alone -- and that bucket is lockdown-gated, leaving a Never picked (or
applied by an override) in combat at full alpha until PLAYER_REGEN_ENABLED.

The companion mini frame inherited only the parent's alpha, so a "never" parent
left an invisible click blocker; pin its driver the way the disabled-parent
branch already does.

A class resource bar unlocked from the frame is parented to UIParent and outlived
its owner. Only Never takes it along -- the other hiding modes have always kept
their own bar visible.

GetMiniDonorSettings picked the mini frames' border/texture/font donor off
enabledFrames, so a focus set to Never used to fall out for free and now has to
be checked directly.
The Show/Hide bucket that hid a "never" frame is lockdown-gated, so acquiring a
target in combat let the unit watch show an alpha-0 click blocker until regen.
Never is terminal, so it pins state-visibility to a constant hide instead -- the
same lever the companion mini already uses -- and the watch cannot out-vote it.

The unlock movers gated on the element's own barVisibility, which the minis and
an unlocked class resource bar do not have; map each to the frame whose
Visibility governs it, with Always Show Pet opting out as it does everywhere.

The options file carried two more copies of the mini-frame donor rule, so a focus
set to Never fell out of the live frames' inheritance but not the preview's.
Both now route through the runtime resolver.
…write

GetMiniDonorSettings runs from the mini frames' OnEnter/OnLeave, so the local
helper it grew allocated a closure on every hover for no reason; back to plain
branches.

The class resource bar alpha is written from the visibility pass, which runs on
every target change, and for the "blizzard" style the bar is Blizzard's own
frame. Write it only when it actually moves.
An applied Visibility override replaces the whole shared setting, "never"
included, so the new never handling cannot read the stored scalar: a shared
Never under an override of Always would pin the secure driver to a constant
hide and the frame would stay missing, and an override of Never would leave the
unlocked class resource bar on screen.

The visibility pass now derives one override-aware verdict and both driver pins,
the class resource bar and the Show/Hide bucket read it. The other "never" reads
this branch added -- Blizzard frame source, the mini donor, player cast bar
suppression, the unlock movers -- go through ns.VisEffective for the same reason.

VisUnitDisabled drops its override carve-out with it. That carve-out existed
because Visibility wrote enabledFrames; now that it does not, the flag means only
the "Enable X Frame" toggles, and honouring an override there would resurrect a
frame the user deliberately disabled.
@dfrisone
dfrisone force-pushed the fix/uf-visibility-override-stale-driver branch from e06b7d6 to 3f290e5 Compare September 2, 2026 04:26
@EllesmereGaming
EllesmereGaming merged commit ef75817 into EllesmereGaming:main Sep 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants