fix(ui): spell-bar drag-reorder works — the per-frame rebuild was destroying the dragged cell (#354)
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run

Everything already existed — the drag payloads, the favorite wire pair
(0x1E3 add-at-position / 0x1E4 remove, byte-confirmed against retail's
Event_AddSpellFavorite @0x006A0F70 and ACE), the insert-shift state
ops. The bug: lifting a favorite fires SpellbookChanged, the next
per-frame Tick rebuilt the bar, the rebuild flushed and recreated
every cell, and UiRoot's subtree-removal safety net canceled the
in-flight drag whose source had just been destroyed — one frame after
every lift, before any drop could land.

The rebuild now defers for the duration of the drag gesture, and the
drop ports retail's own -1-if-lifted-before-target index adjustment
(SpellCastSubMenu::AddFavorite @0x004C7060) so final positions are
byte-identical: insert-shift, not swap; drag-out still deletes (the
lift's removal stands on a missed drop, retail's shape). The
real-pointer-pipeline test fails against the pre-fix code with the
exact cancellation and passes after; a discriminator pins that
physical-item drop handlers reject the spell payload.

AP-172 files the one presentation divergence (mid-drag reflow happens
on release, not continuously) — renumbered from the agent's AP-171
draft, which collided with the same-day double-click row. #354 filed
and closed.

Clean-room complete solution: 11,541 passed / 4 skipped / 0 failed.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-08 18:28:47 +02:00
parent 0a996a1a91
commit 81a9d85a1d
5 changed files with 260 additions and 5 deletions

View file

@ -24,6 +24,42 @@ What does NOT go here:
- Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending.
- Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed.
## #354 — Spell-bar drag reorder did not work: lifting a favorite canceled the drag before the drop could land
**Status:** CLOSED 2026-08-08 — user gate finding ("I should be able to
rearrange spells on the spell bar. Like dragging one out and dropping it
in another position. That does not work today"), diagnosed and fixed same
session. Root cause: `SpellcastingUiController.BeginFavoriteDrag` performs
retail's press-time removal (`gmSpellcastingUI::RecvNotice_ItemListBeginDrag`
@0x004C7360`RemoveSpellFromMenu`, matching `PlayerModule::RemoveSpellFavorite`
@0x005D4910) — correct and pre-existing — but that removal fires
`SpellbookChanged`, and the very next per-frame `Tick()` (production drives
this unconditionally via `RetailUiRuntime.Tick`) called `Rebuild()`, which
flushes and recreates every favorite-bar cell (`UiItemList.Flush`
`RemoveChild`). `UiRoot`'s subtree-removal safety net
(`ClearSubtreeOwnership`) cancels any drag whose source widget is
destroyed — so the drag was silently canceled one frame after every lift,
before the user could complete a drop. Empirically confirmed: a real-pointer-
path test (`DragFavoriteOntoAnotherSlot_ThroughTheRealPointerPipeline_ReordersAndSyncsWire`)
driving `UiRoot.OnMouseDown`/`OnMouseMove`/a mid-drag `Tick`/`OnMouseUp` fails
with `screen.DragSource == null` against the pre-fix code, and passes after
it. Fix (`SpellcastingUiController.cs`): defer the favorite-list rebuild for
the whole drag gesture (`_favoriteDragActive`), and compensate the drop-time
target index for the resulting stale sibling numbering by porting retail's
own `SpellCastSubMenu::AddFavorite` @0x004C7060 index adjustment (decrement
the target by one when the lifted item's original index was before it) —
same insert-shift semantics `PlayerModule::AddSpellFavorite` @0x005D43E0's
`InsertPos` already implements, now reachable through a live drag. Recorded
as AP-172 in the divergence register (the mid-drag visual reflow now happens
on release rather than continuously, final positions/wire are retail-exact).
Tests: `SpellcastingUiControllerTests.cs` (the real-pointer-path reorder
test + a payload-discriminator sabotage check confirming a spell-favorite
payload is rejected by a physical `IItemListDragHandler`), `SpellbookTests.cs`
(`SetFavorite`/`RemoveFavorite` insert-shift unit coverage). Wire golden
bytes for `AddSpellFavorite`/`RemoveSpellFavorite` (opcodes 0x1E3/0x1E4) and
`RuntimeCharacterState.TryAddFavorite`/`TryRemoveFavorite` were already
covered and needed no change.
## #353 — Toolbar selected-object text: count field ignores authored HJustify; name field does not wrap to its authored two lines
**Status:** CLOSED 2026-08-08 — user-passed ("Ok slider bar looks ok!" + the wrap confirmed); the OneLine routing fix (4cfcc8b3) completed it. (RightAligned on the authored HJustify=2 entry; two stacked centered one-line labels wrapping at the authored 140 px via WrapNameTwoLines).

File diff suppressed because one or more lines are too long