Files
Lukas 46b444caba Refactor combatant row: single-click rename, book icon for stat blocks
Replace 250ms click timer and double-click detection with immediate
single-click rename for all combatant types. Add a BookOpen icon before
the name on bestiary rows as the dedicated stat block trigger. Remove
auto-show stat block on turn advance. Update specs to match: consistent
collapse/expand terminology, book icon requirements, no row-click stat
block behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 11:14:28 +01:00

39 KiB

Feature Specification: Combatant State

Feature Branch: 003-combatant-state Created: 2026-03-03 Status: Implemented


Overview

Combatant State covers all per-combatant data tracked during an encounter: hit points, armor class, conditions, concentration, and initiative.

Structure: This spec is organized by topic area. Each topic section contains its own user scenarios, requirements, and edge cases. The Combatant Row Layout section covers cross-cutting UI concerns.

Domain Model Reference

interface Combatant {
  readonly id: CombatantId;          // branded string
  readonly name: string;
  readonly initiative?: number;       // integer, undefined = unset
  readonly maxHp?: number;            // positive integer
  readonly currentHp?: number;        // 0..maxHp
  readonly ac?: number;               // non-negative integer
  readonly conditions?: readonly ConditionId[];
  readonly isConcentrating?: boolean;
  readonly creatureId?: CreatureId;   // link to bestiary entry
}

Hit Points

User Stories

Story HP-1 — Set Max HP (P1) As a game master, I want to assign a maximum HP value to a combatant so that I can track their health during the encounter.

Acceptance scenarios:

  1. Given a combatant exists, When the user sets max HP to a positive integer, Then the combatant's max HP is stored and current HP defaults to that value.
  2. Given a combatant has max HP 20 and current HP 20, When the user lowers max HP to 15, Then current HP is clamped to 15.
  3. Given a combatant has max HP 20 and current HP 20 (full health), When the user increases max HP to 30, Then current HP increases to 30 (stays at full).
  4. Given a combatant has max HP 20 and current HP 12 (not full), When the user increases max HP to 30, Then current HP remains at 12.
  5. Given the max HP inline edit is active, When the user clears the field and confirms, Then max HP is unset and HP tracking is removed entirely.

Story HP-2 — Apply HP Delta (P1) As a game master in the heat of combat, I want to type a damage or healing number and immediately apply it to a combatant's HP so that I can keep up with fast-paced encounters without mental arithmetic.

Acceptance scenarios:

  1. Given a combatant has 20/20 HP, When the user types 7 into the delta input and presses Enter, Then current HP decreases to 13.
  2. Given a combatant has 10/20 HP, When the user types 15 and presses Enter, Then current HP is clamped to 0.
  3. Given a combatant has 10/20 HP and types 5 then clicks the heal button, Then current HP increases to 15.
  4. Given a combatant has 18/20 HP and types 10 then clicks the heal button, Then current HP is clamped to 20.
  5. Given any confirmed delta, Then the input field clears automatically and is ready for the next entry.
  6. Given the user types 0 and presses Enter, Then the input is rejected and HP remains unchanged.
  7. Given the delta input is focused and the user presses Escape, Then the input clears without applying any change.

Story HP-3 — Click-to-Adjust Popover (P1) As a DM running combat, I want to click on a combatant's current HP value to open a small adjustment popover so that the combatant row is visually clean and I can still quickly apply damage or healing when needed.

Acceptance scenarios:

  1. Given a combatant with max HP set, When viewing the row at rest, Then only the current HP number and max HP are visible — no delta input or action buttons.
  2. Given a combatant with max HP set, When the user clicks the current HP number, Then a small popover opens with an auto-focused numeric input and Damage/Heal buttons.
  3. Given the HP popover is open with a valid number, When the user presses Enter, Then damage is applied and the popover closes.
  4. Given the HP popover is open with a valid number, When the user presses Shift+Enter, Then healing is applied and the popover closes.
  5. Given the HP popover is open, When the user presses Escape, Then the popover closes without changes.
  6. Given the HP popover is open, When the user clicks outside, Then the popover closes without changes.
  7. Given a combatant with no max HP set, When viewing the row, Then the HP area shows only the max HP clickable placeholder — no current HP value.

Story HP-4 — Direct HP Entry (P2) As a game master, I want to type a specific absolute current HP value directly so I can apply large corrections in one action.

Acceptance scenarios:

  1. Given a combatant has max HP 50, When the user types 35 into the current HP field, Then current HP is set to 35.
  2. Given a combatant has max HP 50, When the user types 60, Then current HP is clamped to 50.
  3. Given a combatant has max HP 50, When the user types -5, Then current HP is clamped to 0.

Story HP-5 — HP Status Indicators (P1) As a game master, I want to see at a glance which combatants are bloodied or unconscious so I can narrate the battle and make tactical decisions without mental math.

Acceptance scenarios:

  1. Given a combatant has max HP 20 and current HP 10 (at half), Then no bloodied indicator is shown (10 is not less than 20/2 = 10).
  2. Given a combatant has max HP 20 and current HP 9 (below half), Then the bloodied indicator is visible (amber color treatment on HP value).
  3. Given a combatant has max HP 21 and current HP 10 (below 10.5), Then the bloodied indicator is shown.
  4. Given a combatant has max HP 20 and current HP 0, Then the unconscious/dead indicator is shown (red color; row visually muted).
  5. Given a combatant at 0 HP is healed above 0, Then the unconscious indicator is removed and the correct status is applied.
  6. Given a combatant has no max HP set, Then no status indicator is shown.
  7. Given a combatant at full HP 20/20, When 11 damage is dealt (-> 9/20), Then the indicator transitions to bloodied.
  8. Given a bloodied combatant 5/20, When 5 damage is dealt (-> 0/20), Then the indicator transitions to unconscious/dead.
  9. Given an unconscious combatant 0/20, When 15 HP is healed (-> 15/20), Then the indicator transitions directly to healthy (skips bloodied since 15 > 10).

Story HP-6 — HP Persists Across Reloads (P2) As a game master, I want HP values to survive page reloads so that I do not lose health tracking mid-session.

Acceptance scenarios:

  1. Given a combatant has max HP 30 and current HP 18, When the page is reloaded, Then both values are restored exactly.

Requirements

  • FR-001: Each combatant MAY have an optional maxHp value (positive integer >= 1). HP tracking is optional per combatant.
  • FR-002: When maxHp is first set, currentHp MUST default to maxHp.
  • FR-003: currentHp MUST be clamped to [0, maxHp] at all times.
  • FR-004: The system MUST provide an inline HP delta input per combatant (hidden behind a click-to-open popover on the current HP value).
  • FR-005: The HP popover MUST contain a single auto-focused numeric input and Damage and Heal action buttons.
  • FR-006: Pressing Enter in the popover MUST apply the entered value as damage; Shift+Enter MUST apply it as healing; Escape MUST dismiss without change; clicking outside MUST dismiss without change.
  • FR-007: When a damage value is confirmed, the system MUST subtract the entered amount from currentHp, clamping to 0.
  • FR-008: When a healing value is confirmed, the system MUST add the entered amount to currentHp, clamping to maxHp.
  • FR-009: After any delta is applied, the input MUST clear automatically.
  • FR-010: The delta input MUST only accept positive integers. Zero, negative, and non-numeric values MUST be rejected.
  • FR-011: Direct editing of the absolute currentHp value MUST remain available alongside the delta input.
  • FR-012: maxHp MUST display as compact static text with click-to-edit. The value is committed on Enter or blur; Escape cancels. Intermediate editing (clearing the field to retype) MUST NOT affect currentHp until committed.
  • FR-013: When maxHp is reduced below currentHp, currentHp MUST be clamped to the new maxHp.
  • FR-014: When maxHp increases and the combatant was at full health, currentHp MUST increase to match the new maxHp.
  • FR-015: When maxHp increases and the combatant was NOT at full health, currentHp MUST remain unchanged (unless clamped by FR-013).
  • FR-016: maxHp MUST reject zero, negative, and non-integer values.
  • FR-017: HP values MUST persist across page reloads via the existing persistence mechanism.
  • FR-018: The HP status MUST be derived as a pure domain computation: healthy (currentHp >= maxHp / 2), bloodied (0 < currentHp < maxHp / 2), unconscious (currentHp <= 0). The status is not stored — computed on demand.
  • FR-019: The HP area MUST display the bloodied color treatment (amber) on the current HP value when status is bloodied.
  • FR-020: The HP area MUST display the unconscious color treatment (red) and the combatant row MUST appear visually muted when status is unconscious.
  • FR-021: Status indicators MUST NOT be shown when maxHp is not set.
  • FR-022: Visual status indicators MUST update within the same interaction frame as the HP change — no perceptible delay.

Edge Cases

  • maxHp of 1: at 1/1 the combatant is healthy; at 0/1 unconscious. No bloodied state is possible.
  • maxHp of 2: at 1/2 the combatant is healthy (1 is not strictly less than 1); at 0/2 unconscious.
  • When maxHp is cleared, currentHp is also cleared; the combatant returns to the no-HP state.
  • Entering a non-numeric value in any HP field is rejected; the previous value is preserved.
  • Entering a very large number (e.g., 99999) is applied normally; clamping prevents invalid state.
  • Submitting an empty delta input applies no change; the input remains ready.
  • When the user rapidly applies multiple deltas, each is applied sequentially; none are lost.
  • HP tracking is entirely absent for combatants with no maxHp set — no HP controls are shown.
  • There is no temporary HP in the MVP baseline.
  • There is no death/unconscious game mechanic triggered at 0 HP; the system displays the state only.
  • There is no damage type tracking, resistance/vulnerability calculation, or hit log in the MVP baseline.
  • There is no undo/redo for HP changes in the MVP baseline.

Armor Class

User Stories

Story AC-1 — Set and Display AC (P1) As a game master, I want to assign an Armor Class value to a combatant and see it displayed as a shield shape so I can reference it at a glance during combat.

Acceptance scenarios:

  1. Given a combatant exists, When the user clicks the AC shield area and enters 17, Then the combatant's AC is stored and the shield shape displays "17".
  2. Given a combatant with AC 15, When viewing the row, Then the AC number is displayed inside a shield-shaped outline (not a separate icon + number).
  3. Given a combatant with no AC set, When viewing the row, Then the shield shape is shown in an empty/placeholder state.
  4. Given multiple combatants with different AC values, When viewing the encounter list, Then each displays its own correct AC.

Story AC-2 — Edit AC (P2) As a game master, I want to edit an existing combatant's AC inline so I can correct or update it without navigating away.

Acceptance scenarios:

  1. Given a combatant with AC 15, When the user clicks the shield, Then an inline input appears pre-filled with 15 and selected.
  2. Given the inline AC edit is active, When the user types 18 and presses Enter, Then AC updates to 18 and the display returns to static mode.
  3. Given the inline AC edit is active, When the user blurs, Then AC is committed and the display returns to static mode.
  4. Given the inline AC edit is active, When the user presses Escape, Then the edit is cancelled and the original value is preserved.
  5. Given the inline AC edit is active, When the user clears the field and presses Enter, Then AC is unset and the shield shows an empty state.

Requirements

  • FR-023: Each combatant MAY have an optional ac value, a non-negative integer (>= 0).
  • FR-024: AC MUST be displayed inside a shield-shaped visual element. The separate shield icon is replaced by the shield shape itself.
  • FR-025: The shield shape MUST be shown in all cases (set, unset/empty state). No AC value means an empty-state shield, not hidden.
  • FR-026: Clicking the shield MUST open an inline edit input with the current value pre-filled and selected.
  • FR-027: The inline AC edit MUST commit on Enter or blur and cancel on Escape.
  • FR-028: Clearing the AC field and confirming MUST unset AC.
  • FR-029: AC MUST reject negative values. Zero is a valid AC.
  • FR-030: AC values MUST persist via the existing persistence mechanism.
  • FR-031: The AC shield MUST scale appropriately for single-digit, double-digit, and any valid AC values.

Edge Cases

  • AC 0 is valid and MUST be displayed.
  • Negative AC is not accepted; the input is rejected.
  • MVP baseline does not include AC-based calculations (to-hit comparisons, conditional formatting based on AC thresholds).

Conditions & Concentration

User Stories

Story CC-1 — Add a Condition (P1) As a DM running an encounter, I want to quickly apply a condition to a combatant so I can track status effects during combat.

Acceptance scenarios:

  1. Given a combatant row is not hovered and has no conditions, Then no condition UI is visible.
  2. Given a combatant row is hovered, When no conditions are active, Then a "+" button appears inline after the creature name.
  3. Given the "+" button is visible, When the user clicks it, Then a compact condition picker opens showing all 15 conditions as icon + label pairs.
  4. Given the picker is open, When the user clicks a condition, Then it is toggled on and its icon appears inline after the creature name.
  5. Given the picker is open with active conditions already marked, When viewing the picker, Then active conditions are visually distinguished from inactive ones.

Story CC-2 — Remove a Condition (P1) As a DM, I want to remove a condition from a combatant when the effect ends so the tracker stays accurate.

Acceptance scenarios:

  1. Given a combatant has active conditions, When the user clicks an active condition icon tag inline, Then the condition is removed and the icon disappears.
  2. Given the condition picker is open, When the user clicks an active condition, Then it is toggled off and removed from the row.
  3. Given a combatant with one condition and it is removed, Then only the hover-revealed "+" button remains.

Story CC-3 — View Condition Name via Tooltip (P2) As a DM, I want to hover over a condition icon to see its name so I can identify conditions without memorizing icons.

Acceptance scenarios:

  1. Given a combatant has an active condition, When the user hovers over its icon, Then a tooltip shows the condition name (e.g., "Blinded").
  2. Given the user moves the cursor away from the icon, Then the tooltip disappears.

Story CC-4 — Multiple Conditions (P2) As a DM, I want to apply multiple conditions to a single combatant so I can track complex combat situations.

Acceptance scenarios:

  1. Given a combatant with one condition, When another is added, Then both icons appear inline.
  2. Given a combatant with many conditions, When viewing the row, Then icons wrap within the name column without increasing row width; row height may increase.
  3. Given "poisoned" was applied first and "blinded" second, When viewing the row, Then "blinded" appears before "poisoned" (fixed definition order, not insertion order).

Story CC-5 — Toggle Concentration (P1) As a DM, I want to mark a combatant as concentrating on a spell by clicking a Brain icon in the row gutter so I can track spells requiring concentration.

Acceptance scenarios:

  1. Given a combatant row is not hovered and concentration is inactive, Then the Brain icon is hidden.
  2. Given a combatant row is hovered and concentration is inactive, Then the Brain icon appears in a muted/faded style.
  3. Given the Brain icon is visible, When the user clicks it, Then concentration activates and the icon remains visible with an active style.
  4. Given concentration is active and the row is not hovered, Then the Brain icon remains visible.
  5. Given concentration is active, When the user clicks the Brain icon again, Then concentration deactivates and the icon hides (unless the row is still hovered).
  6. Given the Brain icon is visible, When the user hovers over it, Then a tooltip reading "Concentrating" appears.

Story CC-6 — Visual Feedback for Concentration (P2) As a DM, I want concentrating combatants to have a visible row accent so I can identify them at a glance without interacting.

Acceptance scenarios:

  1. Given concentration is active, When viewing the encounter tracker, Then the combatant row shows a colored left border accent (border-l-purple-400).
  2. Given concentration is inactive, Then no concentration accent is shown.
  3. Given concentration is toggled off, Then the left border accent disappears immediately.

Story CC-7 — Damage Pulse Alert (P3) As a DM, I want a visual alert when a concentrating combatant takes damage so I remember to call for a concentration check.

Acceptance scenarios:

  1. Given a combatant is concentrating, When the combatant takes damage (HP reduced), Then the Brain icon and row accent briefly pulse/flash for 700 ms.
  2. Given a combatant is concentrating, When the combatant is healed, Then no pulse/flash occurs.
  3. Given a combatant is NOT concentrating, When damage is taken, Then no pulse/flash occurs.
  4. Given a concentrating combatant takes damage, When the animation completes, Then the row returns to its normal concentration-active appearance.

Requirements

  • FR-032: The MVP MUST support the following 15 standard D&D 5e conditions: blinded, charmed, deafened, exhaustion, frightened, grappled, incapacitated, invisible, paralyzed, petrified, poisoned, prone, restrained, stunned, unconscious.

  • FR-033: Each condition MUST have a fixed icon and color mapping (Lucide icons; no emoji):

    Condition Icon Color
    Blinded EyeOff neutral
    Charmed Heart pink
    Deafened EarOff neutral
    Exhaustion BatteryLow amber
    Frightened Siren orange
    Grappled Hand neutral
    Incapacitated Ban gray
    Invisible Ghost violet
    Paralyzed ZapOff yellow
    Petrified Gem slate
    Poisoned Droplet green
    Prone ArrowDown neutral
    Restrained Link neutral
    Stunned Sparkles yellow
    Unconscious Moon indigo
  • FR-034: Active condition icons MUST appear inline after the creature name within the same row, not on a separate line.

  • FR-035: Conditions MUST be displayed in the fixed definition order (blinded -> unconscious), regardless of application order.

  • FR-036: The "+" condition button MUST be hidden by default and appear only on row hover (or touch/focus on touch devices).

  • FR-037: Clicking "+" MUST open the compact condition picker showing all conditions as icon + label pairs.

  • FR-038: Clicking a condition in the picker MUST toggle it on or off.

  • FR-039: Clicking an active condition icon tag in the row MUST remove that condition.

  • FR-040: Hovering an active condition icon MUST show a tooltip with the condition name.

  • FR-041: Condition icons MUST NOT increase the row's width; row height MAY increase to accommodate wrapping.

  • FR-042: The condition picker MUST close when the user clicks outside of it.

  • FR-043: Conditions MUST persist as part of combatant state (surviving page reload).

  • FR-044: The condition data model MUST be extensible for future additions (e.g., mechanical effects, descriptions).

  • FR-045: isConcentrating MUST be stored as an optional boolean on the combatant, separate from the conditions array.

  • FR-046: Concentration MUST NOT appear in or interact with the condition tag system.

  • FR-047: The Brain icon toggle MUST be hidden at row rest and revealed on hover (same hover pattern as the "+" button).

  • FR-048: The Brain icon MUST remain visible whenever concentration is active, regardless of hover state.

  • FR-049: A tooltip reading "Concentrating" MUST appear when hovering the Brain icon.

  • FR-050: The active Brain icon MUST use text-purple-400; the inactive (hover-revealed) Brain icon MUST use a muted style (text-muted-foreground opacity-50).

  • FR-051: The concentration left border accent MUST use border-l-purple-400.

  • FR-052: The concentration toggle's clickable area MUST extend to fill the full gutter between the left border and the initiative column.

  • FR-053: When a concentrating combatant takes damage, the Brain icon and row accent MUST briefly pulse/flash for 700 ms.

  • FR-054: The pulse animation MUST NOT trigger on healing or when concentration is inactive.

  • FR-055: Concentration MUST persist across page reloads via existing storage.

Edge Cases

  • When all 15 conditions are applied, icons wrap within the row; row height increases but width does not.
  • When a combatant is removed, all its conditions and concentration state are discarded.
  • When the picker is open and the user clicks outside, it closes.
  • When a condition is toggled on then immediately off in the picker, it does not appear in the row.
  • When concentration is toggled during an active pulse animation, the animation cancels and the new state applies immediately.
  • Multiple combatants may concentrate simultaneously; concentration is independent per combatant.
  • Conditions have no mechanical effects in the MVP baseline (no auto-disadvantage, no automation).

Initiative

User Stories

Story INI-1 — Set Initiative Value (P1) As a game master running an encounter, I want to assign an initiative value to a combatant so that the encounter's turn order reflects each combatant's rolled initiative.

Acceptance scenarios:

  1. Given a combatant with no initiative set, When the user sets initiative to 15, Then the combatant has initiative value 15.
  2. Given a combatant with initiative 15, When the user changes initiative to 8, Then the combatant has initiative value 8.
  3. Given a combatant with no initiative set, When the user attempts to set a non-integer value, Then the system rejects the input and initiative remains unset.
  4. Given a combatant with initiative 15, When the user clears initiative, Then the initiative is unset and the combatant moves to the end of the turn order.
  5. Given a combatant has an initiative value displayed as plain text, When the user clicks it, Then an inline editor opens to change or clear it.

Story INI-2 — Roll Initiative for a Single Combatant (P1) As a DM, I want to click a d20 icon next to a combatant's initiative slot to randomly roll initiative (1d20 + initiative modifier) so the result is immediately placed into the initiative field and the tracker re-sorts.

Acceptance scenarios:

  1. Given a combatant linked to a bestiary creature (e.g., Aboleth, initiative modifier +7) with no initiative, When the user clicks the d20 icon, Then a random value in the range [8, 27] is stored as initiative and the list re-sorts descending.
  2. Given a combatant NOT linked to a bestiary creature, When viewing the row, Then the initiative slot shows "--" (clickable to type a value manually) — no d20 button.
  3. Given a combatant whose initiative modifier is negative (e.g., -2), When the d20 button is clicked, Then the result ranges from -1 to 18.
  4. Given a combatant already has an initiative value, Then the d20 button is replaced by the value as plain text; clicking it opens the inline editor.

Story INI-3 — Roll Initiative for All Eligible Combatants (P2) As a DM, I want a "Roll All Initiative" button in the top bar to roll for all bestiary combatants at once so I can set up the initiative order quickly at the start of combat.

Acceptance scenarios:

  1. Given 3 bestiary combatants (no initiative) and 1 manual combatant, When the roll-all button is clicked, Then all 3 bestiary combatants receive rolled initiative values; the manual combatant is unchanged.
  2. Given bestiary combatants that already have initiative values, When the roll-all button is clicked, Then those combatants are skipped; only bestiary combatants without initiative are rolled.
  3. Given no bestiary combatants, When the roll-all button is clicked, Then no changes occur.

Story INI-4 — Display Initiative Modifier in Stat Block (P1) As a DM viewing a creature's stat block, I want to see the creature's initiative modifier and passive initiative so I can reference it when rolling.

Acceptance scenarios:

  1. Given a creature with DEX 9, CR 10, initiative proficiency multiplier 2 (e.g., Aboleth), When the stat block is displayed, Then the initiative line shows "Initiative +7 (17)" (-1 + 2x4 = +7; passive = 17).
  2. Given a creature with proficiency multiplier 1, Then the initiative modifier includes 1x the proficiency bonus.
  3. Given a creature with no initiative field in bestiary data, Then only the DEX modifier is shown (e.g., DEX 14 -> "Initiative +2 (12)").
  4. Given a creature with a negative initiative modifier (e.g., DEX 8, no proficiency), Then the line uses a minus sign (e.g., "Initiative -1 (9)").
  5. Given a combatant without bestiary data, Then no initiative line is shown in the stat block.

Story INI-5 — Combatants Without Initiative (P2) As a game master, I want combatants without initiative set to appear at the end of the turn order so the encounter remains usable while I am still entering values.

Acceptance scenarios:

  1. Given combatants A (initiative 15), B (no initiative), C (initiative 10), Then order is A, C, B.
  2. Given A (no initiative) and B (no initiative), Then their relative order is preserved from when they were added.
  3. Given combatant A (no initiative), When initiative is set to 12, Then A moves to its correct sorted position.

Requirements

  • FR-056: System MUST allow setting an integer initiative value for any combatant.
  • FR-057: System MUST allow changing an existing initiative value.
  • FR-058: System MUST allow clearing (unsetting) a combatant's initiative value.
  • FR-059: System MUST reject non-integer initiative values and return a domain error.
  • FR-060: System MUST accept zero and negative integers as valid initiative values.
  • FR-061: System MUST automatically reorder combatants highest-to-lowest initiative whenever a value is set, changed, or cleared.
  • FR-062: Combatants without initiative MUST be placed after all combatants with initiative values.
  • FR-063: System MUST use a stable sort so combatants with equal initiative (or multiple without initiative) retain their relative order.
  • FR-064: System MUST preserve the active combatant's turn through reorders — the active turn tracks combatant identity, not list position.
  • FR-065: System MUST emit a domain event when a combatant's initiative is set or changed.
  • FR-066: System MUST display a d20 icon button in the initiative slot for every combatant that has a linked bestiary creature and does not yet have an initiative value.
  • FR-067: System MUST NOT display the d20 button for combatants without a linked bestiary creature; they see a "--" placeholder that is clickable to type a value.
  • FR-068: When the d20 button is clicked, the system MUST generate a uniform random integer in [1, 20], add the creature's initiative modifier, and set the result as the initiative value.
  • FR-069: The initiative modifier MUST be calculated as: DEX modifier + (proficiency multiplier x proficiency bonus), where DEX modifier = floor((DEX - 10) / 2) and proficiency bonus is derived from challenge rating.
  • FR-070: The initiative proficiency multiplier MUST be read from initiative.proficiency in bestiary data (0 if absent, 1 for proficiency, 2 for expertise).
  • FR-071: System MUST provide a "Roll All Initiative" button in the top bar. Clicking it MUST roll for every bestiary-linked combatant that does not already have an initiative value; all others MUST be left unchanged.
  • FR-072: After any initiative roll (single or batch), the encounter list MUST re-sort descending per existing behavior.
  • FR-073: Once a combatant has an initiative value, the d20 button is replaced by the value as plain text using a click-to-edit pattern (consistent with AC and name editing).
  • FR-074: The stat block header MUST display initiative in the format "Initiative +X (Y)" where X is the modifier (with + or - sign) and Y = 10 + X.
  • FR-075: The initiative display in the stat block MUST be positioned adjacent to the AC value, matching Monster Manual 2024 layout.
  • FR-076: No initiative line MUST be shown in the stat block for combatants without bestiary data.
  • FR-077: The initiative display in the stat block is display-only. It MUST NOT modify encounter turn order or trigger rolling automatically.
  • FR-078: The d20 roll-initiative icon MUST be displayed larger than 20x20 px while remaining contained within the initiative column.
  • FR-079: The "Roll All Initiative" d20 in the top bar MUST be sized at 24 px; the clear-encounter (trash) icon MUST be sized at 20 px. Both are visually grouped together, separated from turn navigation controls by spacing.

Edge Cases

  • Setting initiative to zero is valid and treated normally in sorting.
  • Negative initiative values are valid (some game systems use them).
  • When a combatant is added without initiative during an ongoing encounter, it appears at the end of the order.
  • When all combatants have the same initiative value, their insertion order is preserved.
  • When the active combatant's own initiative changes causing a reorder, the active turn still points to that combatant.
  • When another combatant's initiative changes causing a reorder, the active turn still points to the current active combatant.
  • When a combatant's initiative modifier produces a roll result of 0 or negative, the value is stored as-is.
  • When multiple combatants roll the same initiative, ties are resolved by preserving relative insertion order.
  • A minus sign is used for negative modifiers in the stat block display, not a hyphen.
  • When a creature has DEX producing a modifier of exactly 0 and no initiative proficiency, display "Initiative +0 (10)".
  • For manually-added combatants: no initiative modifier is available, so no d20 button and no stat block initiative line.
  • The passive initiative value shown in the stat block is reference-only; only the active modifier (+X) is used for rolling.
  • Random number generation for dice rolls uses standard browser randomness; cryptographic randomness is not required.

Combatant Row Layout

User Stories

Story ROW-1 — Compact Resting State (P1) As a DM, I want each combatant row to display a minimal, uncluttered view at rest so I can scan the encounter list quickly during play.

Acceptance scenarios:

  1. Given a combatant row is not hovered, Then no delta input, action buttons, "+" condition button, remove button, or Brain icon are visible.
  2. Given a combatant has no conditions, Then the row occupies exactly one line height at rest.
  3. Given a combatant has conditions applied, Then condition icons appear inline after the creature name on the same row (not a separate line).

Story ROW-2 — Hover Reveals Controls (P1) As a DM, I want secondary controls to appear when I hover over a row so they are accessible without cluttering the resting view.

Acceptance scenarios:

  1. Given any combatant row, When hovered, Then the "+" condition button appears inline after the name/conditions.
  2. Given any combatant row, When hovered, Then the remove (x) button becomes visible.
  3. Given any combatant row, When hovered and concentration is inactive, Then the Brain icon becomes visible.
  4. Given the remove button appears on hover, Then no layout shift occurs — space is reserved.

Story ROW-3 — Book Icon Opens Stat Block (P1) As a DM, I want a dedicated book icon on bestiary combatant rows so I can open the stat block with an explicit, discoverable control — while clicking the name always starts a rename.

Acceptance scenarios:

  1. Given a combatant has a linked bestiary creature, When the user views the row, Then a small BookOpen icon is visible next to the name.
  2. Given a combatant does NOT have a linked creature, When the user views the row, Then no BookOpen icon is displayed.
  3. Given a bestiary combatant row, When the user clicks the BookOpen icon, Then the stat block panel opens for that creature.
  4. Given the user clicks the combatant's name, Then inline rename mode is entered — the stat block does NOT open.
  5. Given the stat block is already open for a creature, When the user clicks its BookOpen icon again, Then the panel closes (toggle behavior).

Requirements

  • FR-080: Condition icons MUST render inline after the creature name within the same row.
  • FR-081: The "+" condition button MUST be hidden at rest and appear on row hover (or touch/focus on touch devices).
  • FR-082: The remove (x) button MUST be hidden at rest and appear on row hover (or touch/focus on touch devices).
  • FR-083: Layout space for the remove button MUST be reserved so appearing/disappearing does not cause layout shifts.
  • FR-084: Bestiary-linked combatant rows MUST display a BookOpen icon as the dedicated stat block trigger (see also specs/004-bestiary/spec.md, FR-062).
  • FR-085: Clicking the combatant name MUST enter inline rename mode, not open the stat block.
  • FR-086: Non-bestiary combatant rows MUST NOT display the BookOpen icon.
  • FR-087: The BookOpen icon MUST have a tooltip ("View stat block") and aria-label="View stat block" for accessibility.
  • FR-088: All existing interactions (condition add/remove, HP adjustment, AC editing, initiative editing/rolling, concentration toggle, combatant removal) MUST continue to work.
  • FR-089: Browser scrollbars MUST be styled to match the dark UI theme (thin, dark-colored scrollbar thumbs).
  • FR-090: Turn navigation (Previous/Next) MUST use StepBack/StepForward icons in outline button style with foreground-colored borders. Utility actions (d20/trash) MUST use ghost button style to create clear visual hierarchy.
  • FR-091: Previous and Next turn buttons MUST be positioned at the far left and far right of the top bar respectively, with the round/combatant info centered between them.
  • FR-092: The "Initiative Tracker" heading MUST be removed to maximize vertical space for combatants.
  • FR-093: All interactive elements in the row MUST remain keyboard-accessible (focusable and operable via keyboard).
  • FR-094: On touch devices, hover-only controls ("+" button, "x" button) MUST remain accessible via tap or focus.

Edge Cases

  • When a combatant has so many conditions that they exceed the available inline space, they wrap within the name column; row height increases but width does not.
  • The condition picker dropdown positions relative to the "+" button, flipping vertically if near the viewport edge.
  • When the stat block panel is already open and the user clicks the same BookOpen icon again, the panel closes.
  • Clicking the combatant name starts inline rename; it does not open the stat block.
  • Tablet-width screens (>= 768 px / Tailwind md): popovers and inline edits MUST remain accessible and not overflow or clip.

Success Criteria (mandatory)

  • SC-001: Users can set max HP and adjust current HP for any combatant in under 5 seconds per action.
  • SC-002: currentHp never exceeds maxHp or drops below 0, regardless of input method.
  • SC-003: HP values survive a full page reload without data loss.
  • SC-004: A user can apply damage to a combatant in 2 interactions or fewer (click HP -> type number -> Enter).
  • SC-005: A user can apply healing in 3 interactions or fewer (click HP -> type number -> click Heal).
  • SC-006: Users can identify bloodied combatants at a glance without reading HP numbers — 100% of combatants below half HP display a distinct amber treatment.
  • SC-007: Users can identify unconscious/dead combatants at a glance — 100% of combatants at 0 HP or below display a distinct red treatment that differs from the bloodied indicator.
  • SC-008: Visual status indicators update within the same interaction frame as the HP change.
  • SC-009: Users can set an AC value for any combatant within the existing edit workflow with no additional steps.
  • SC-010: AC is visible at a glance in the encounter list without expanding or hovering.
  • SC-011: A condition can be added to a combatant in 2 clicks or fewer (click "+", click condition).
  • SC-012: A condition can be removed in 1 click (click the active icon tag).
  • SC-013: All 15 D&D 5e conditions are available and visually distinguishable by icon and color.
  • SC-014: Condition state survives a full page reload without data loss.
  • SC-015: Users can toggle concentration on/off for any combatant in a single click.
  • SC-016: Concentrating combatants are visually distinguishable from non-concentrating combatants at a glance.
  • SC-017: When a concentrating combatant takes damage, the visual pulse alert draws attention within the same interaction flow.
  • SC-018: Concentration state survives a full page reload.
  • SC-019: Users can set initiative for any combatant in a single action.
  • SC-020: After any initiative change, the encounter list immediately reflects the correct descending sort.
  • SC-021: A single combatant's initiative can be rolled with one click (the d20 button).
  • SC-022: All eligible combatants' initiative can be rolled with one click (roll-all button).
  • SC-023: Manual combatants (no stat block) are never affected by roll actions.
  • SC-024: Every bestiary creature displays an initiative value in its stat block matching D&D Beyond / Monster Manual 2024.
  • SC-025: The initiative line is visible without scrolling in the stat block header.
  • SC-026: Each combatant row without conditions takes up exactly one line height at rest.
  • SC-027: The DM can open a stat block by clicking anywhere on the combatant name area without needing a dedicated icon.
  • SC-028: The AC number is visually identifiable as armor class through the shield shape alone.
  • SC-029: No layout shift occurs when hovering/unhovering rows.
  • SC-030: All HP, AC, initiative, condition, and concentration interactions remain fully operable using only a keyboard.