The sheet is dragged by the strip its tabs sit in #49

Manually merged
tvidal merged 4 commits from feat/049-the-sheet-is-dragged-by-the-strip-its-tabs-sit into master 2026-07-14 09:30:00 +02:00
Owner

The sheet is dragged by the strip its tabs sit in

Along the way:

  • The grip goes over the page, not under it
  • A hover keeps up with the thing it is describing
  • The stub refuses what the client refuses

Touches 44 files: README.md, SPEC-menu.md, scripts/check.sh, scripts/harness/client/01-widgets.lua, scripts/harness/client/02-place.lua, scripts/harness/client/02-text.lua and 38 more.

The sheet is dragged by the strip its tabs sit in Along the way: - The grip goes over the page, not under it - A hover keeps up with the thing it is describing - The stub refuses what the client refuses Touches 44 files: `README.md`, `SPEC-menu.md`, `scripts/check.sh`, `scripts/harness/client/01-widgets.lua`, `scripts/harness/client/02-place.lua`, `scripts/harness/client/02-text.lua` and 38 more.
tvidal approved these changes 2026-07-13 13:01:30 +02:00
tvidal left a comment

Dragging the sheet by the tab strip is what everybody tries first. One note on the section.

Dragging the sheet by the tab strip is what everybody tries first. One note on the section.
@ -38,0 +42,4 @@
-- press on a square. Aiming eight units in from the corner is where the window
-- itself is the frame under the pointer, which is the whole of what makes these
-- two handlers reachable at all.
local function letGo()
Owner

Eight units in from the corner, with the reason above it: any less and a square is the frame under the pointer. Put the number in a local so the two handlers in this section cannot drift to seven and nine.

Eight units in from the corner, with the reason above it: any less and a square is the frame under the pointer. Put the number in a local so the two handlers in this section cannot drift to seven and nine.
Author
Owner

Named INSET, and the comment moved onto it.

Named `INSET`, and the comment moved onto it.
Half a screen is a corner you have an opinion about, so the drag and the
saved point come back. Neither was there while the sheet was the whole
monitor, and that was right then: a frame the size of the screen is
already where it goes.

The drag must not be the frame. This one covers half the game with the
mouse off, and turning the mouse on to take a drag would swallow the left
button and the camera's right drag across all of it. So the grip is the
row the tabs already sit in, a title bar in everything but the paint. It
lights under the cursor and is not there otherwise, and it sits a level
under the page, so a tab is still a tab.

Placing runs through the snippet, because nineteen gear squares and the
window holding them are protected. Placeable carries a Placed flag so the
ship-in corner stays a default: Resize re-anchors a screen window out of
every pass, and a monitor swap and the zoom slider both land there, so
without the question a dropped sheet would walk back to the edge the next
time either moved.

Window.lua grew a Chrome function around the three numbers and three
frames that were loose in the constructor.
The drag did not work. The strip was at the bottom of the sheet's stack,
which put it under the content frame, under the pages the sheet lifts to
content plus ten, and under all nineteen gear squares. Nothing that lands
on the top of the sheet ever reached it.

It was written that way to keep the tabs pressable, and that is the wrong
half to give ground on: a strip under the page is not a compromise, it is
a strip nothing can touch. So the grip sits above the page and the tab row
is lifted one level over the grip in turn, which is the only frame that
has to beat it and now says so.

The harness asserted the broken order as if it were the requirement. It
could not tell the difference, because the stub has no hit testing: below
the page and unreachable are the same picture to it. Both halves are
checked now, since a grip over the tabs is its own bug.
A tooltip here was built once, at the moment the pointer arrived, and
never again. Nothing rebuilt it: the only refresh path in the addon was
Tip.Again, and its two callers were both the shift key on a gear
comparison. So the threat percentage on a mob you held through a whole
pull was the figure it had been on the frame you got there, and the
quest count did not move when you looted the thing. Blizzard's own box
re-runs its setter on an OnUpdate, which is why theirs ticks and ours
sat still. The complaint that produces is that the addon's tooltips are
slow. They were not slow. They were never redrawn.

Rebuilding on a tick is the obvious fix and it is wrong in both
directions at once. It throws the box away sixty times for every time
anything in it moved, and the cache that suggests itself instead is
already refused by Scan.lua's header for the reason written there: a
cache of the client's text buys a stale tooltip in exchange for nothing
anybody can measure. So neither side is cached. UI/Fresh.lua reads a
stamp instead, one cheap number per thing in the box that can move, and
builds the box again on the tick a stamp differs and on no other tick.

Flooring the stamp to the precision the box draws is what makes it
nearly free rather than merely cheaper. The threat line writes %d%%, so
its stamp is the whole percent, and a mob drifting between 61.2 and 61.8
is one number and no rebuild at all. A creature standing still is read
and dropped, which is the ordinary case, because what a player does is
hover something, read it and move on.

Who declares a stamp is whoever draws the moving thing. A source hooks a
line into the box and knows what makes that line move, so the stamp goes
on the source beside its fill, and a caller whose own subject moves puts
one on the subject. A table in Fresh.lua mapping a kind to what makes it
stale would be a second place to remember, months from the line it is
about, and it would be wrong the first time a source changed what it
draws. Two are declared: the threat percentage, and a counter on the
quest drop cache, which is the only thing that can move those lines.

Nothing in the head band has a stamp, on purpose. The head is the
client's own scanned text, and whether a word inside it moves is a
question about the client rather than about this addon. A stamp that
fired every second and changed no character on screen is exactly the
waste this is against, so there is none until somebody has watched a
head band go stale and can name the call that says so.

Its own file rather than a tick inside Tip.lua, because the rebuild has
to be marked cold to keep a hover's worth of allocation off the tick
path, and Tip.lua's one cold marker is already spoken for by the settle.

The gate caught the bug worth catching. The first tick of every hover
was rebuilding, because `held` still carried the last box's numbers and
the first comparison always found a difference. One wasted build per
hover, which is the cost the file exists to remove, arriving through the
front door. The stamps are primed at the arm now, with the comparison
off, and the section asserts both halves: a box is built again when
something in it moved, and is not when nothing did. Fifty ticks over a
still box rebuild it zero times and churn 0.00 KB.

Tip.HOLD comes down from 0.4 to 0.15. Four tenths of a second before a
hint appears reads as the addon thinking about it. What the number has
to be is the shortest gap that tells a hand stopping from a hand passing
through, and a pointer crossing a row of chips is over each of them for
a few dozen milliseconds.
A press now goes to a point on the screen and the stub works out which frame
would get it: shown, taking the mouse, inside its own rectangle with the hit
insets off it, topmost by strata then level then the later of two frames. Nine
handlers are wrapped at SetScript and refuse a call that did not come from that
delivery, so reaching OnClick or OnDragStart by name is an error rather than a
pass. OnDragStart fires only where RegisterForDrag named the button.

SetAttribute drops a write whose value is already there, and only a write that
changes something runs _onattributechanged. That is what UI/Placeable.lua's
moves counter exists for and nothing proved it was needed.

Secure snippets run, in a sandbox rather than as ordinary Lua. The body is
compiled and given an environment carrying the client's small library and frame
handles that answer the restricted API and refuse everything else by name. A
snippet reaching for StartMoving fails here the way it failed in the game. So
the character sheet's drag is a drag: grabbed on the strip, followed down the
screen by the snippet, dropped, and the point it wrote down read back.

What that cost, and what it found. Twenty of the eighty-one sections changed,
and fifty-six gestures that were handlers called by name are now presses and
drags at a point; twenty-one assertions came out and fifty-three went in. The
grip ordering the sheet shipped with is now unreachable rather than asserted.
Blizzard's minimap had no anchor and sat on the origin under half the addon.
UIParent had no size, so every anchor to the middle of the screen resolved to
its top left corner. A frame sized by two opposing anchors answered a width of
zero, which put a stepper's minus button eighty units outside the row it is
drawn in. Raise did nothing, so two open windows stacked in build order.
SetParent wrote the field and left the frame in its old parent's child list.

Two findings in the addon. ns.Tip.Hang hands the right button to the camera on
every frame it hangs a tooltip on, including the meter header, which answers the
right button itself: on 2.5.6 the call fails and the toggle works, on a client
that carries SetPassThroughButtons the toggle is dead. UI/Window.lua guards that
case and Tip.Hang does not. And the charge button ships holding CTRL-2, which is
also bar 3's ninth square, so that square gives its key up. Both were invisible
while the snippet was a string nobody ran and the call was a stub of a call this
client does not have.

The commit hook is gone. It ran check.sh and the harness on every commit, and
check.sh already runs the harness once per class and spec. Run ./scripts/check.sh
before you commit.
tvidal manually merged commit becdda46ef into master 2026-07-14 09:30:00 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
northgale/warriorkit!49
No description provided.