
One slot to rule them all
This round started as a simple wishlist: twelve inventory slots instead of eight, items that stay in the slot you put them in instead of packing from index 0, and drag-and-drop that swaps things when the target is occupied. All reasonable, all quick, I thought. Then I tested it, found three different ways to lose items, and ended up tearing out the whole slot architecture instead.
Where the bugs came from
The game had three separate slot components: InventoryCell, BagCell and EquipSlot. They shared roughly 90% of their logic, but each had its own slightly different copy of it. Drag-start keys, hover validation, tooltip wrapping, sprite rendering, all duplicated with small drift between the copies. Every bug I found lived exactly in that drift.
The nastiest one: dragging an armor piece onto a weapon slot silently deleted it. The old equip slot validated item fit inside its drop handler, but during unification that check got lost, so the equip action quietly failed while the source inventory slot was already nulled. Item gone. Another one made dragging require two clicks, and a third could lose items when you dropped something on empty background.
What surprised me most is how much there is to get right with something as boring as bags. In the game it is just: click, drag, done, feels natural. But underneath, every single gesture needs a decision. What happens when the target is occupied? Where does the displaced item go? What if it does not fit there? Can this item even go into this slot? What happens if I drop it on nothing at all? None of that is visible while playing, and that is exactly the point. The less you notice the rules, the more work went into them. I definitely did not expect a bag UI to carry this much invisible weight, and honestly, WOW.
One ItemSlot instead of three
The fix was to stop maintaining three copies. Now there is a single ItemSlot component with a kind prop: inventory, bag or equipment. The kind decides which dataTransfer keys get set, which drag sources are accepted, and what a valid drop even means. The component hands the parsed drag state back to its panel, and the panel decides the actual action. Inventory and bag items swap freely with whatever sits in the target slot, and equipping something puts the displaced item back into the exact slot the new item came from.
Same story for the sprite rendering. Four places in the codebase had their own copy of the same “draw an item sprite at a given size” logic. That is now one self-sufficient ItemSprite component that pulls what it needs from the HUD context itself.
The two-click bug taught me about React trees
The drag bug was the most interesting one. Sometimes a drag only worked on the second attempt, and my first fix made it worse. I had wrapped every slot in a tooltip component, and while dragging I conditionally removed that wrapper. Sounds harmless. But removing the wrapper changes the component tree around the draggable div, so React unmounts and remounts it right when the drag starts, and the browser cancels the whole gesture. The user sees a dead click.
The fix is almost embarrassingly simple: always render the wrapper, just flip it into a disabled state during drag. The tree never changes shape, so nothing remounts, and the drag survives. It took me a wrong fix, a suspicious extra state that made things worse, and a dive into the tooltip library’s source before I was sure the remount was the only culprit.
Drops outside the slots
Empty space needed a meaning too. Dropping an equipped item on the background now unequips it into the inventory, and dropping an inventory or bag item there opens a small confirmation dialog before it gets destroyed. No more accidental item deletion, but also no annoying “are you sure” popup every time you clean out your bags during normal play. And the escape key now closes everything in the right order, dialogs first, then panels, then the menu.
Everything checks out with a clean type check, and the swap flows feel good in practice. The next session will probably be about playing with the twelve slots I just gave myself, because a bigger inventory is only fun if there is something to fill it with. Follow along if you want to see what ends up in there.