Skip to content

W-050: DND-F02 turn UI

Type: feature · Size: S · Epic: E-dnd-m1 · Target: apps/demo-dnd · Breaking: no Depends: W-019, W-020 Refs: brief DND-F02; demo-dnd spec DND-036..039; W-020 (ECS turn loop); W-019 (game area and key rule)

Goal

Add the turn UI of apps/demo-dnd on the W-020 ECS: the initiative panel with "Round N", one item per combatant in initiative order (name, initiative value, side as text, hp/max, and aria-current with "Active" on the active one), a "Fallen" list, and "(new)" next to combatants in Cues.defeated. Also the "End turn" button in an "Actions" group (key E, disabled with aria-disabled when endTurn would return false), and the victory or defeat banner (role="status") with a "Restart" button and key R. Restart calls restartEncounter and moves focus to the game area, and focus moves there too when a focused action button becomes disabled (DND-037). The Attack button is W-051, and the Poison Strike button is W-023.

Steps

  • S1 (DND-036, DND-037): Initiative view in src/ui/initiative/: region "Initiative" with "Round N" and one item per entry of initiativeOrder (name, initiative value, side as text hero/monster, hp/max), the active item with aria-current="true" and the text "Active", a separate "Fallen" list for Defeated combatants, and "(new)" next to names in Cues.defeated. Re-render through koota/react hooks (useQuery, useTrait on Encounter, Health, Cues), not by reading initiativeOrder once; mount it in App.tsx beside the game area. Outcome banner (role="status", text "Victory" or "Defeat", shown only when status is not 'ongoing') with a "Restart" button; the button and key R on the game area (only when status is not 'ongoing', under the DND-016 key rule) call restartEncounter and move focus to the game area. Component tests with a real world in WorldProvider, state set through setupEncounter and a scripted Rng: order, round, active item, hp, Fallen list, "(new)" present after the defeating hero action and the monster turns that follow, gone after the next accepted hero action; banner text for victory and defeat, none while 'ongoing'; Restart and R call restartEncounter, focus is on the game area, and the panel shows round 1, the new party, no old entity and an empty log (OQ13 canary); R on a button inside the game area does nothing; files: apps/demo-dnd/src/ui/initiative/Initiative.tsx, apps/demo-dnd/src/ui/initiative/Outcome.tsx, apps/demo-dnd/src/ui/initiative/Initiative.test.tsx, apps/demo-dnd/src/ui/initiative/Outcome.test.tsx, apps/demo-dnd/src/ui/input/GameArea.tsx, apps/demo-dnd/src/App.tsx
  • S2 (DND-037, DND-038): "Actions" group in src/ui/actions/ (inside the game area) with the "End turn" button, which calls endTurn, and key E on the game area under the DND-016 key rule; the button is disabled with aria-disabled exactly when endTurn would return false (status not 'ongoing' or the active combatant is not a hero). When status becomes not 'ongoing' while an action button has focus, focus moves to the game area (DND-037). Component tests with a real world: click and E each call endTurn once and advance the round or the active item; disabled and aria-disabled during a monster turn or after victory or defeat; a focused "End turn" that ends the encounter hands focus to the game area and R then restarts; Enter on the focused button calls endTurn once and nothing else (DND-107 pattern, no preventDefault by the game); E with focus on a button does nothing; files: apps/demo-dnd/src/ui/actions/ActionsGroup.tsx, apps/demo-dnd/src/ui/actions/ActionsGroup.test.tsx, apps/demo-dnd/src/ui/input/GameArea.tsx
  • S3 (acceptance): run pnpm turbo run typecheck lint test --filter=demo-dnd, then the spec's acceptance checklist item A2 (DND-039): monster turns appear in the log and on the grid in initiative order immediately, and the 12×12 / ~49-combatant round of logic takes under 16 ms (the benchmark is temporary and not committed)

Log

  • 2026-11-01 created from the split of W-020 (Triage). Planner's draft: DND-036..039, S, 3 steps. Held in Later until Depends are done
  • 2026-11-01 OQ13 canary (DND-037) moved here from the W-020 Log: if the banner does not show the new encounter after restart, the restart design changes (DND-034). Triage decides, not this item
  • 2026-11-01 Depends: added W-019 (S1 edits files W-019 creates) (Triage, user-confirmed after board review)
  • 2026-11-01 steps S1..S3 written (Planner). Open: endTurn has no "would return false" predicate in the spec's action list, so S2 derives it in the view from Encounter.status and the active combatant's Hero tag (and acted is irrelevant to endTurn, DND-029)
  • 2026-11-01 held: waits on the OQ6 decision after the W-039 S1 spike (decision logged in W-039 first) (Triage)