Skip to content

Fix Menu click handler using wrong slot offset on page 2+ - #157

Merged
Travja merged 1 commit into
devfrom
claude/fix-156-menu-click-page-offset
Aug 11, 2026
Merged

Fix Menu click handler using wrong slot offset on page 2+#157
Travja merged 1 commit into
devfrom
claude/fix-156-menu-click-page-offset

Conversation

@claude

@claude claude Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #156. Menu/MenuManager's pagination framework had two places that disagreed about what a display slot index means, so clicks on page 2+ were routed to the wrong Slot.

  • Render (Menu.open(int page)) maps display slot i to the virtual slot page * inventory.getSize() + i.
  • Click (MenuManager.onInventoryClick) resolved clicks via Menu.getSlot(event.getSlot()) — the raw display slot, with no page offset.

On page 1 (page == 0) the two mappings coincide by accident, which is why this looked like a UX quirk rather than a bug. On page 2+ they diverge: the player sees the item at virtual slot page*size + i, but clicking it invokes whatever Slot was registered at virtual slot i — i.e. the same grid position on page 1. Since each Slot's click handlers are typically closures over that slot's own data, this is a misrouted click, not a dead one: it can silently mutate whatever page 1's slot at that position was bound to.

Page navigation buttons (getPrevButton/getNextButton, PreviousPageButton/NextPageButton) were unaffected — they call menu.open(getPage() ± 1) regardless of which Slot instance triggered them, so hitting "page 1's copy" of a nav button by mistake still behaved correctly.

Fix

Went with the "single source of truth" option rather than patching the offset into MenuManager directly:

  • Added Menu.getSlotOnCurrentPage(int displaySlot), backed by a new package-private Menu.toVirtualSlot(page, pageSize, displaySlot) helper.
  • MenuManager.onInventoryClick now calls getSlotOnCurrentPage(event.getSlot()) instead of raw getSlot(event.getSlot()).
  • Menu.open(int page)'s render loop now also goes through getSlotOnCurrentPage(i) (setting this.page = finalPage before the loop instead of after), so render and click resolve through the exact same code path and can't drift apart again — which is how this bug happened in the first place.

Scope check

Grepped the whole repo (not just codex-api) for other callers of Menu.getSlot(: the only one was MenuManager.onInventoryClick, now fixed. The other getSlot(...) call sites (YAMLMenu/YAMLListMenu) are a different, unrelated overload — getSlot(String function, T parameter, Player player) — used to build slot definitions from YAML, not to resolve a raw Bukkit slot.

Also checked for other raw-slot-to-Slot resolution paths, e.g. drag handling: MenuManager.onInventoryDrag only cancels the event when the holder is a Menu; it never resolves a Slot object, so it isn't affected by this bug.

This is a framework-level fix scoped to codex-api; no downstream consumers (e.g. divinity) were touched — they should just start working correctly once this ships.

What I verified / couldn't verify

  • Verified by manual trace against dev @ d880ccb: confirmed the offset mismatch, confirmed the only two resolution sites (getSlot in MenuManager, and the render loop in Menu.open), confirmed nav buttons and drag handling are unaffected, confirmed the other getSlot overload is unrelated.
  • Added MenuTest (codex-api/src/test/java/.../menu/MenuTest.java) covering Menu.toVirtualSlot's page-offset arithmetic directly — the exact computation that diverged between render and click before this fix.
  • Could not get a green Maven build in my sandbox: this environment's egress policy blocks the Minecraft-ecosystem repos the build needs (hub.spigotmc.org, jitpack.io, and others used for WorldGuard/WorldEdit/Citizens/MythicMobs/PlaceholderAPI/etc.), and no artifacts beyond stray POM-lookup stubs were pre-cached locally, so neither mvn compile nor mvn test (including the pre-existing ReplacerTest/VersionManagerTest) could run here — this is a sandbox networking limitation, not something about the change itself. I'd appreciate a maintainer or CI confirming compilation and running MenuTest for real.

Generated by Claude Code

Menu.open(int page) renders display slot i from virtual slot
(page * inventory.getSize() + i), but MenuManager.onInventoryClick
resolved clicks via Menu.getSlot(event.getSlot()) — the raw display
slot with no page offset applied. On page 1 the two happened to
coincide, but on page 2+ a click was routed to whatever Slot was
registered at that same grid position on page 1, silently invoking
the wrong handler instead of just failing to respond.

Add Menu.getSlotOnCurrentPage(int), backed by a shared
Menu.toVirtualSlot(page, pageSize, displaySlot) helper, and make
both the renderer (open) and MenuManager's click handler go through
it so they can't drift apart again. Nav buttons (getPrevButton/
getNextButton, PreviousPageButton/NextPageButton) were unaffected
since they call menu.open(page ± 1) regardless of which Slot
instance triggered them, which is why this presented as "content
doesn't react" rather than a routing bug.

Grepped the repo for other Menu.getSlot( callers and other raw-slot
resolution paths (e.g. inventory drag handling): none exist. The
other getSlot(...) call sites (YAMLMenu/YAMLListMenu) resolve a
different overload, getSlot(String, T, Player), and are unrelated.

Fixes #156
@Travja
Travja merged commit 02fcff9 into dev Aug 11, 2026
3 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.

Paginated Menu clicks resolve the wrong slot on page 2+ — no page offset in MenuManager.onInventoryClick

2 participants