Skip to content

Fix the clipped stance row, overlapping alerts, and the missing Linux tray icon - #23

Merged
JDS300 merged 5 commits into
mainfrom
fix/stance-row-clipped-in-player-window
Aug 16, 2026
Merged

Fix the clipped stance row, overlapping alerts, and the missing Linux tray icon#23
JDS300 merged 5 commits into
mainfrom
fix/stance-row-clipped-in-player-window

Conversation

@JDS300

@JDS300 JDS300 commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Three separate defects found while testing the release candidate, all confirmed fixed in-game or against a packaged build.

The stance row was clipped out of the player frame

PlayerSubWindow is pinned to (70, 9, 3, 3) so the themed frame cannot cover the native attack perimeter, leaving it 128px tall inside the window — but its bordered rounded template consumes ~12px, so the client area is ~12px shorter than the box it occupies. The layout was authored as if the client were the full box, so the bottom row (Mage Hunter / Defensive, Invocation / Over Channel / Recovery) never had room and was cut off.

Measured from an in-game screenshot rather than inferred: the visible text ended 101px below the sub-window's top edge, which is what pinned the client height down.

  • The row now anchors to the sub-window's bottom, so the remaining uncertainty in that ~12px estimate shifts it rather than hiding it.
  • PlayerWindow goes 360x193 → 360x207, which leaves a 4px gap between the AA gauge and the row. TargetWindow stays 193: it has no stance row and only shared the height for symmetry.

Applied to all seven player-window variants in both skins, which were byte-identical here. Regenerates the 24 layout profiles and the Glass payload, and moves the pinned contracts in restyle_combat, audit_combat_ui and the layout generator.

Anchoring alone was tried first and made it worse: it kept the row on screen but slid it under PW_AltAdvGauge, which is listed after it in Pieces and therefore draws over it. The height is what it actually needed.

Alert cards landed on the crowd-control panel

The control panel is auto-placed on whichever side has room — above, right, left or below — and is click-through, so it cannot be dragged clear. positionAlertWindow only knew how to dodge it in the "above" case, so the other three placements put alert cards straight over the timers the panel exists to show.

Placement moves to electron/alert-placement.ts and the dodge is generalised to every side. auto skips any side the panel occupies; an explicit Alert location is honoured but pushed clear rather than drawn over. With no panel on screen the behaviour is unchanged.

No tray icon on Linux

PortableUpdateService resolved the Windows PowerShell path in its constructor, and resolveWindowsPowerShell throws whenever SystemRoot and WINDIR are absent — always, off Windows. main.ts calls initializeUpdateServices() synchronously inside app.whenReady(), ahead of createWindow() and ensureTray(), so that throw aborted the entire ready handler. No engine, no window, no tray icon, and the only trace was an UnhandledPromiseRejectionWarning.

Introduced by the upstream secure-updates merge, which is why v0.3.4 has a tray and everything since does not. The path is now resolved where it is used, in the Windows-only installAndRelaunch.

Measured on a KDE session against packaged AppImages, with the stale single-instance lock holder cleared between runs:

build engine window tray
with the fix started shown item registered
without it none none none

The release-candidate identity overrides are not involved — the passing build carries all of them, including desktopName=loremaster-rc.desktop.

Tests

scripts/test-updater-startup.cjs builds both update services with SystemRoot and WINDIR removed, so the startup crash is caught on any platform rather than only where it happens to run. scripts/test-alert-placement.cjs covers the placement maths, including the panel-above case that used to be the only one handled. Both now run in both CI jobs. The combat audit gains a check that the stance labels stay bottom-anchored, treating an absent anchor tag as the top-anchored default that caused the original bug.

Quality gate ALL PASS. Player-frame height and tray icon both confirmed in-game.

🤖 Generated with Claude Code

JDS300 and others added 5 commits August 15, 2026 10:48
PlayerSubWindow is pinned to (70, 9, 3, 3) so the themed frame cannot cover
the native attack perimeter, which leaves it 114px tall inside the 193px
window -- minus whatever the bordered draw template insets. The stance and
invocation row is the bottom-most content and was anchored 96..112 from the
TOP, so its lower edge fell outside the client area and the row was clipped:
Mage Hunter, Defensive, Invocation, Over Channel and Recovery were all
unreadable.

Anchor both edges to the sub-window's bottom instead. The row keeps its 16px
height and now rides 2px above the lower edge, so it stays visible at the
193px minimum and at any size the player resizes to, rather than depending on
a draw-template inset nothing in the XML can see.

Applied to all seven player-window variants in both skins; the two were
already byte-identical here. No window size and no layout profile changes, so
saved player layouts are untouched.

The audit gains a check that both labels anchor to the bottom, treating an
absent anchor tag as the top-anchored default that caused this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Measured from an in-game screenshot rather than inferred: with the row
anchored to the sub-window's bottom, the visible text ended 101px below the
sub-window's top edge, which puts PlayerSubWindow's client area at ~116px
against the 128px box it occupies. The bordered rounded template eats ~12px
that nothing in the XML exposes, so the layout -- authored as if the client
were the full box -- never had room for the bottom row.

At 193px the client was ~102px while the content above the stance row already
ended at 94, leaving 8px for a 16px row. Bottom-anchoring alone could not fix
that: it kept the row on screen but slid it under PW_AltAdvGauge, which is
listed after it in Pieces and therefore draws over it, so the stance text came
out as a 3px sliver. Worse than clipped.

PlayerWindow goes to 360x207, which puts the client at ~116px and leaves a 4px
gap between the AA gauge and the row. TargetWindow stays 360x193: it has no
stance row, and only shared the height for symmetry.

The row keeps its bottom anchoring, so the remaining uncertainty in that ~12px
border estimate shifts the row rather than hiding it.

Regenerates the 24 layout profiles and the Glass payload, and moves the pinned
player-frame contracts in restyle_combat, audit_combat_ui and the layout
generator to 207.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The control panel is auto-placed on whichever side of the window has room --
above, right, left or below -- and it is click-through, so a player cannot
drag it out of the way. positionAlertWindow only knew how to dodge it in the
"above" case: companionAbove was the sole avoidance and it is true only when
the panel sits entirely above the window. Land the panel to the right, left or
below and the alert cards were placed straight over it, hiding the very
crowd-control timers the panel exists to show.

Move the placement maths into electron/alert-placement.ts and generalise the
dodge to every side. "auto" now skips any side the panel occupies, and an
explicit Alert location is honoured but pushed clear of the panel rather than
drawn over it. With no panel on screen the result is unchanged: auto still
prefers above.

Covered by scripts/test-alert-placement.cjs, including the panel-above case
that used to be the only one handled, so that behaviour cannot regress.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PortableUpdateService resolved the Windows PowerShell path in its
constructor, and resolveWindowsPowerShell throws whenever SystemRoot and
WINDIR are absent -- which is always, off Windows. main.ts calls
initializeUpdateServices() synchronously inside app.whenReady(), ahead of
createWindow() and ensureTray(), so that throw aborted the whole ready
handler: no engine, no window, no tray icon. The only trace was an
UnhandledPromiseRejectionWarning.

Resolve the PowerShell path where it is actually used instead -- the Windows
portable-replacement path in installAndRelaunch -- so construction is
platform-neutral and the failure, when it matters, still surfaces to the
caller performing the update.

Measured on a KDE session against packaged AppImages, with the stale
single-instance lock holder cleared between runs:

  with this fix     engine started, window shown, tray item registered
  without it        none of the three

The RC identity overrides are not involved: the passing build carries all of
them, including desktopName=loremaster-rc.desktop.

scripts/test-updater-startup.cjs builds both update services with SystemRoot
and WINDIR removed, so this is caught on any platform rather than only on the
machine that runs it. It and the alert-placement suite now run in both CI
jobs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The new placement clamped each candidate into the work area before testing
whether it fitted, and a clamped rectangle always fits -- so the test was a
tautology and "auto" chose "above" no matter how little room was there. With
the seed near a screen edge the clamped rectangle landed directly on it. The
alert window is always-on-top, so the seed could not be opened, expanded or
dragged: analyze and settings became unreachable.

Rank candidates before clamping, and treat covering the seed as disqualifying.
Every side now offers two candidates -- its natural position and that position
pulled back on screen -- and they are ranked together: fully clear first, then
on-screen and off the seed, then merely off the seed. Staying off the seed
outranks honouring an explicit Alert location, because an unreachable seed
costs the whole application.

The old space-based preference is restored too: above wins only while it has
at least as much room as below.

The previous tests missed this because the fixture put the window at (800,500)
with room on every side, and asserting "inside the work area" after clamping
proved nothing. The suite now sweeps the seed across nine screen positions --
corners, edges and centre -- against every anchor, at the real 128x74 seed and
420x112 alert sizes, and asserts the alert never intersects it: 45 cases.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@JDS300
JDS300 merged commit 0a45bb2 into main Aug 16, 2026
6 checks 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.

1 participant