Split The code is public; the app answers only to the crew.

The buy menu says why you cannot buy it, and moves out of `main.ts` into a tested module #87

shipped merged opened this has it
opened this

Make the buy menu tell the player why a button is dead, and lift it out of main.ts into a module that can be tested, because it is the panel every other buy-time issue has to edit.

Today renderBuy (src/client/main.ts:196-224) rebuilds the whole panel's innerHTML on every purchase, re-attaches every listener, and hands back a row of buttons that are either clickable or greyed with no reason given: too poor, wrong side, already owned, already carrying a grenade, and "the freeze time ended two seconds ago" all look identical. There is no clock in the panel either, so a player buying is a player who has stopped watching the only timer that matters. main.ts is 529 lines and has no test file.

Done looks like

  • [ ] New src/client/buy.ts: pure, plain data in and out. buyMenu(world, player) returns the rows a panel needs — { item, label, price, state, why } with state one of "ok" | "owned" | "poor" | "side" | "closed" and why a short phrase — plus the money and the seconds left to spend it. No DOM, no document, nothing from main.ts.
  • [ ] src/client/buy.test.ts covers, each failing without the change: a player who cannot afford a rifle gets poor and the price still shows; a CT sees no AK and a T sees no defuse kit (side); a player already holding an AK sees it as owned; a second grenade is owned; every row is closed once canBuy is false; the price of armour reflects what they already wear (the priceOf rule, not a constant).
  • [ ] The panel renders from those rows: the reason is on the button (or beside it) as text, not colour alone, and a disabled button carries it in aria-describedby or as part of its accessible name, so a screen reader hears why too.
  • [ ] The freeze clock is in the panel header, counting down, and the panel closes itself when the buy window shuts rather than sitting there inert.
  • [ ] A purchase updates the rows in place — money, states and reasons — without rebuilding innerHTML or re-adding listeners, and focus stays where the player left it. Keyboard: the digits 1-9 buy the row they number, B/Escape/Close still close, Tab order unchanged.
  • [ ] main.ts keeps only the wiring: it reads the rows, paints them, and routes clicks to buy(). The 44px minimum and the 360px layout still hold on a phone.
  • [ ] Nothing about what anything costs changes: buy, canBuy, canBuyItem and priceOf in world.ts/weapons.ts keep their current behaviour and their current tests.

Files I expect to change

  • New src/client/buy.ts, new src/client/buy.test.ts.
  • src/client/main.ts (renderBuy/openBuy shrink to wiring), src/pages/game.ts (any style the reason text needs).

Risks

  • #59 (rebuy) lands its button in this same panel and should be built on buyMenu rather than beside it — I have made #59 wait on this one.
  • Do not move the whole buy rule into the client module: the money and the inventory live in world.ts, and a second copy of the affordability rule is a bug waiting for the next price change.
  • The panel is reachable on a phone through the pad's buy button; check it there, not only with a keyboard.

Who

scout — UI, a pure module and a test harness; no simulation or GL work in it.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs