ui: SetClipsChildren, the verb an addon asked for by name and was refused in silence

Era's map canvas is built on one verb this client did not have. Its `ScrollContainer` is a plain
Frame with `SetClipsChildren(true)`, and everything below it is cropped to that rect.
`assets/ui/MapCanvas.lua` substituted a real ScrollFrame, because this client crops a ScrollFrame's
scroll child and every descendant of it — which covers the canvas and nothing parented to the
container beside it. On Era that distinction does not exist, so an addon has no reason to respect
it.

OpenMapClassic parents its pin layer exactly there and asks for the verb by name, behind an `if`
(`Map/Stitch.lua:1070`), so the absence was not a raise. Measured on grom's own config with the map
open: the container sits at L=1151 R=2153 and its seventeen direct children span L=-765 R=2153 —
about 1900 units off the left edge, cropped by nothing.

Implemented as a frame flag with `SetClipsChildren`/`DoesClipChildren` and the `clipChildren` XML
attribute; `effective_clip` now intersects the rect of every ancestor that sets it alongside the
ScrollFrame rule, so the two nest and compose. An ancestor rather than the frame itself: the verb
clips a frame's children, and stock art overhangs its own frame on purpose. `MapCanvas.lua` sets it
on the container too, so a layer parked there is cropped whether or not its addon thought to ask.

Not claimed as the whole of grom's map report: the screenshot behind it has not been reproduced
offline, because a capture's view is not the view his session was in. This is the part that was
provably drawing uncropped.

Also here, both from the same play-test:

- `C_Spell.CancelSpellByID` answered with `SpellStopCasting` only. pfUI's buff bar cancels with it
  on a right click where 1.12's calls `CancelPlayerBuff`, so with nothing in flight the click did
  nothing and said nothing — grom had never been able to right-click a buff off on this client. It
  now finds the id among the player's own auras and queues it on the same CMSG_CANCEL_AURA path,
  behind the same `cancel_authorized` gate, and falls through to the cast only when there is no
  such aura.

- `$WOW_CAPTURE_SAVED=<Realm>-<Character>` points a capture at a player's own saved variables. A
  capture is hermetic by design, but with addons and no saved variables it renders an interface
  nobody is configured to be looking at — which is why the world map came out correct in a capture
  while being wrong on his screen. It doubles as the identity because a capture never logs in and
  `pfUI_config` is SavedVariablesPerCharacter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
2026-11-03 09:46:32 -07:00
co-authored by Claude Opus 5
parent c9756de381
commit 5e2f1f3ac8
12 changed files with 426 additions and 26 deletions
+100
View File
@@ -2384,3 +2384,103 @@ write one alpha.** pfUI dims a unit frame to 0.25 for an out-of-range target, an
on. That is inherent in fading a frame whose alpha already means something — the same shape as the
two window managers on the world map — and it is why the switch is per-target rather than one
master toggle.
## `SetClipsChildren`: the verb an addon asked for by name and was refused in silence
Era's map canvas is built on one verb this client did not have. `WorldMapFrame.ScrollContainer`
there is a plain `Frame` with `SetClipsChildren(true)`, and everything below it — the canvas, the
pins, whatever an addon hangs off it — is cropped to its rect.
`assets/ui/MapCanvas.lua` substituted a real `ScrollFrame` for it, because this client crops a
ScrollFrame's **scroll child and every descendant of that child** (`script/clip.rs`), in the draw
pass and the hit-test from one shared source. That covers the canvas. It does not cover a frame
parented to the *container*, beside the canvas — and that is a normal thing to do, because on Era
the container crops everything either way.
OpenMapClassic does exactly that, and says so:
```lua
-- (on the map's view, which clips them as the canvas does, not on the canvas: see Pin sets)
local view = parent:GetParent() or parent
pinRoot = CreateFrame("Frame", nil, view)
pinRoot:SetAllPoints(view)
if pinRoot.SetClipsChildren then pinRoot:SetClipsChildren(true) end
```
The `if` is the whole problem: the verb was absent, the call was skipped, and the pin layer was
cropped by nothing at all. Measured on grom's own config, map open on Dun Morogh — the container
sits at `L=1151 R=2153`, and its seventeen direct children span **`L=-765 R=2153`**, about 1900
units off the left edge with nothing to stop them.
Now implemented as a real frame flag (`clips_children`), with `SetClipsChildren` /
`DoesClipChildren` and the `clipChildren` XML attribute, and `effective_clip` intersects the rect
of every **ancestor** that sets it, alongside the ScrollFrame rule it already had — so the two nest
and compose. An ancestor and not the frame itself: the verb clips a frame's *children*, and plenty
of stock art overhangs its own frame on purpose.
`MapCanvas.lua` now also sets it on the container, so a layer parked there is cropped whether or
not its addon thought to ask.
**Not yet shown end to end.** This is a measured gap with a measured fix and a test whose control
fails, but the screenshot that started it has not been reproduced offline: a capture renders this
map correctly with and without the change, because the capture's view is not the view grom's
session was in. The pin layer was definitely drawing uncropped; whether it is the whole of what he
sees is still open.
## A capture can now be pointed at a player's own saved variables
A capture is hermetic — `local_state::home()` answers `None` under `$WOW_CAPTURE`, so no config, no
saved variables, no addons — and that is what makes a baseline comparable between machines.
`$WOW_CAPTURE_ADDONS=1` already opened the addon folder back up.
The gap that cost a long detour here: with addons but no saved variables, a capture renders an
interface **nobody is configured to be looking at**. The world map came out correct in a capture
while being wrong on grom's screen, because no addon had been told to take the window over.
`$WOW_CAPTURE_SAVED` closes it, and doubles as the identity, because a capture never logs in and
`pfUI_config` is `SavedVariablesPerCharacter`:
```
WOW_CAPTURE_SAVED=1 account-scope files only
WOW_CAPTURE_SAVED=C_Thun-Gromgosh account and that character's
```
Off by default and never on for a baseline.
## `C_Spell.CancelSpellByID` stopped a cast, so right-clicking a buff never took it off
1.12's buff bar cancels with `CancelPlayerBuff(index)`. pfUI replaces that bar, and its buttons
cancel with `C_Spell.CancelSpellByID(this.spellId)` on a right click (`modules/buff.lua:153`,
behind `RegisterForClicks("RightButtonUp")`).
This client answered that verb with `SpellStopCasting` and nothing else — right for a cast, and
for a buff button it meant nothing was in flight, nothing happened, and no error either. Grom had
never once been able to right-click a buff off on this client.
It now looks for the id among the player's own auras first and queues it on the same
`CMSG_CANCEL_AURA` path `CancelPlayerBuff` and `CancelUnitBuff` use, behind the same
`cancel_authorized` gate — so a buff the reference refuses to drop is still refused, and a debuff
is still not cancellable this way. Only with no such aura does it fall through to stopping the
cast. `CancelSpellByName` keeps the cast-only path: it is handed no id, and `model.casting` carries
none either, so there is nothing to match against.
## Two writers on one alpha, again: the unit frame that strobed
The first cut of `modules/autofade.lua` pointed `EnableAutohide` at the unit frames. The player
frame then flickered several times a second.
`pfUI.uf:RefreshUnitState` is the authority on a unit frame's alpha — `alpha_visible`,
`alpha_offline`, `alpha_outrange` — and it reasserts that value the moment anything else moves it.
The autohide's own `OnUpdate` writes alpha every 0.05 s. Two writers, one value, each undoing the
other: the same shape as pfUI and OpenMapClassic both owning the world map window.
The fix is not to win the race but to stop having one. The fade now goes **through** that
authority: a factor per frame, applied by scaling the three `alpha_*` fields for the duration of
one refresh, with pfUI doing the `SetAlpha` as it always did. Measured: forty back-to-back
refreshes hold at one value instead of oscillating. It also composes, which the first cut could
not — an out-of-range target still dims *and* fades, where a second writer could only overwrite.
The four info panels moved onto the same page at the same time, the bottom-right one
(`pfUI.panel.right`, over the right chat frame) included. Those have had a fixed two-second
autohide on pfUI's Panel page for years; the Auto Fade page drives the same frames on the user's
own timeout, and hands one back rather than switching it off when the Panel page still wants it.