Compare commits

...

1 Commits

Author SHA1 Message Date
Lukas
48795071f7 Hide concentration UI in PF2e mode
All checks were successful
CI / check (push) Successful in 2m26s
CI / build-image (push) Successful in 18s
PF2e uses action-based spell sustaining, not damage-triggered
concentration checks. The Brain icon, purple border accent, and
damage pulse animation are now hidden when PF2e is active, and
the freed gutter column is reclaimed for row content. Concentration
state is preserved so switching back to D&D restores it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 00:25:54 +02:00
2 changed files with 44 additions and 19 deletions

View File

@@ -10,6 +10,7 @@ import { Brain, Pencil, X } from "lucide-react";
import { type Ref, useCallback, useEffect, useRef, useState } from "react";
import { useEncounterContext } from "../contexts/encounter-context.js";
import { useInitiativeRollsContext } from "../contexts/initiative-rolls-context.js";
import { useRulesEditionContext } from "../contexts/rules-edition-context.js";
import { useSidePanelContext } from "../contexts/side-panel-context.js";
import { useLongPress } from "../hooks/use-long-press.js";
import { cn } from "../lib/utils.js";
@@ -415,12 +416,14 @@ function InitiativeDisplay({
function rowBorderClass(
isActive: boolean,
isConcentrating: boolean | undefined,
isPf2e: boolean,
): string {
if (isActive && isConcentrating)
const showConcentration = isConcentrating && !isPf2e;
if (isActive && showConcentration)
return "border border-l-2 border-active-row-border border-l-purple-400 bg-active-row-bg card-glow";
if (isActive)
return "border border-l-2 border-active-row-border bg-active-row-bg card-glow";
if (isConcentrating)
if (showConcentration)
return "border border-l-2 border-transparent border-l-purple-400";
return "border border-l-2 border-transparent";
}
@@ -455,6 +458,8 @@ export function CombatantRow({
const { selectedCreatureId, showCreature, toggleCollapse } =
useSidePanelContext();
const { handleRollInitiative } = useInitiativeRollsContext();
const { edition } = useRulesEditionContext();
const isPf2e = edition === "pf2e";
// Derive what was previously conditional props
const isStatBlockOpen = combatant.creatureId === selectedCreatureId;
@@ -495,12 +500,16 @@ export function CombatantRow({
const tempHpDropped =
prevTempHp !== undefined && (combatant.tempHp ?? 0) < prevTempHp;
if ((realHpDropped || tempHpDropped) && combatant.isConcentrating) {
if (
(realHpDropped || tempHpDropped) &&
combatant.isConcentrating &&
!isPf2e
) {
setIsPulsing(true);
clearTimeout(pulseTimerRef.current);
pulseTimerRef.current = setTimeout(() => setIsPulsing(false), 1200);
}
}, [currentHp, combatant.tempHp, combatant.isConcentrating]);
}, [currentHp, combatant.tempHp, combatant.isConcentrating, isPf2e]);
useEffect(() => {
if (!combatant.isConcentrating) {
@@ -518,12 +527,20 @@ export function CombatantRow({
ref={ref}
className={cn(
"group rounded-lg pr-3 transition-colors",
rowBorderClass(isActive, combatant.isConcentrating),
rowBorderClass(isActive, combatant.isConcentrating, isPf2e),
isPulsing && "animate-concentration-pulse",
)}
>
<div className="grid grid-cols-[2rem_3rem_auto_1fr_auto_2rem] items-center gap-1.5 py-3 sm:grid-cols-[2rem_3.5rem_auto_1fr_auto_2rem] sm:gap-3 sm:py-2">
{/* Concentration */}
<div
className={cn(
"grid items-center gap-1.5 py-3 sm:gap-3 sm:py-2",
isPf2e
? "grid-cols-[3rem_auto_1fr_auto_2rem] pl-3 sm:grid-cols-[3.5rem_auto_1fr_auto_2rem]"
: "grid-cols-[2rem_3rem_auto_1fr_auto_2rem] sm:grid-cols-[2rem_3.5rem_auto_1fr_auto_2rem]",
)}
>
{/* Concentration — hidden in PF2e mode */}
{!isPf2e && (
<button
type="button"
onClick={() => toggleConcentration(id)}
@@ -536,6 +553,7 @@ export function CombatantRow({
>
<Brain size={16} />
</button>
)}
{/* Initiative */}
<div className="rounded-md bg-muted/30 px-1">

View File

@@ -260,6 +260,8 @@ Acceptance scenarios:
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.
7. **Given** the game system is Pathfinder 2e, **When** viewing any combatant row (hovered or not), **Then** the Brain icon is not shown — even if `isConcentrating` is true.
8. **Given** a combatant has `isConcentrating` true and the game system is PF2e, **When** the user switches to a D&D system, **Then** the Brain icon appears with active styling (concentration state was preserved).
**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.
@@ -268,6 +270,7 @@ 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.
4. **Given** the game system is Pathfinder 2e and a combatant has `isConcentrating` true, **When** viewing the encounter tracker, **Then** no purple left border accent is shown on that row.
**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.
@@ -277,6 +280,7 @@ Acceptance scenarios:
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.
5. **Given** the game system is Pathfinder 2e and a combatant has `isConcentrating` true, **When** the combatant takes damage, **Then** no pulse/flash animation occurs.
**Story CC-8 — Game System Setting (P2)**
As a DM who runs games across D&D 5e, 5.5e, and Pathfinder 2e, I want to choose which game system the tracker uses so that conditions, bestiary search, stat block layout, and initiative calculation all match the game I am running.
@@ -373,6 +377,8 @@ Acceptance scenarios:
- **FR-108**: The following PF2e valued conditions MUST have maximum values enforced: dying (max 4), doomed (max 3), wounded (max 3), slowed (max 3). All other valued conditions have no enforced maximum.
- **FR-109**: When a PF2e valued condition is at its maximum value, the `[+]` increment button in the condition picker counter MUST be disabled (visually dimmed and non-interactive).
- **FR-110**: Maximum value enforcement MUST only apply when the Pathfinder 2e game system is active. D&D conditions are unaffected.
- **FR-111**: When Pathfinder 2e is the active game system, the concentration UI (Brain icon toggle, purple left border accent, damage pulse animation) MUST be hidden entirely. The Brain icon MUST NOT be shown on hover or at rest, and the concentration toggle MUST NOT be interactive.
- **FR-112**: Switching the game system MUST NOT clear or modify `isConcentrating` state on any combatant. The state MUST be preserved in storage and restored to the UI when switching back to a D&D game system.
### Edge Cases
@@ -390,6 +396,7 @@ Acceptance scenarios:
- PF2e valued condition at value 0 is treated as removed — it MUST NOT appear on the row.
- Dying, doomed, wounded, and slowed have enforced maximum values in PF2e (4, 3, 3, 3 respectively). The `[+]` button is disabled at the cap. The dynamic dying cap based on doomed value (dying max = 4 doomed) is not enforced — only the static maximum applies.
- Persistent damage is excluded from the PF2e MVP condition set. It can be added as a follow-up feature.
- When PF2e is active, concentration state (`isConcentrating`) is preserved in storage but the entire concentration UI is hidden. Switching back to D&D restores Brain icons, purple borders, and pulse behavior without data loss.
---