Fix persistent damage tag ordering and differentiate condition icons
All checks were successful
CI / check (push) Successful in 2m39s
CI / build-image (push) Successful in 18s

- Render persistent damage tags before the "+" button, not after
- Use insertion order for conditions on the row instead of definition order
- Differentiate Undetected condition (EyeClosed/slate) from Invisible (Ghost/violet)
- Use purple for void persistent damage to distinguish from violet conditions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-04-11 13:06:31 +02:00
parent 0f640601b6
commit 064af16f95
7 changed files with 29 additions and 28 deletions

View File

@@ -618,14 +618,17 @@ export function CombatantRow({
onRemove={(conditionId) => toggleCondition(id, conditionId)}
onDecrement={(conditionId) => decrementCondition(id, conditionId)}
onOpenPicker={() => setPickerOpen((prev) => !prev)}
/>
>
{isPf2e && (
<PersistentDamageTags
entries={combatant.persistentDamage}
onRemove={(damageType) =>
removePersistentDamage(id, damageType)
}
/>
)}
</ConditionTags>
</div>
{isPf2e && (
<PersistentDamageTags
entries={combatant.persistentDamage}
onRemove={(damageType) => removePersistentDamage(id, damageType)}
/>
)}
{!!pickerOpen && (
<ConditionPicker
anchorRef={conditionAnchorRef}

View File

@@ -13,6 +13,7 @@ import {
EarOff,
Eclipse,
Eye,
EyeClosed,
EyeOff,
Flame,
FlaskConical,
@@ -57,6 +58,7 @@ export const CONDITION_ICON_MAP: Record<string, LucideIcon> = {
EarOff,
Eclipse,
Eye,
EyeClosed,
EyeOff,
Flame,
FlaskConical,
@@ -92,6 +94,7 @@ export const CONDITION_COLOR_CLASSES: Record<string, string> = {
pink: "text-pink-400",
amber: "text-amber-400",
orange: "text-orange-400",
purple: "text-purple-400",
gray: "text-gray-400",
violet: "text-violet-400",
yellow: "text-yellow-400",

View File

@@ -5,6 +5,7 @@ import {
getConditionDescription,
} from "@initiative/domain";
import { Plus } from "lucide-react";
import type { ReactNode } from "react";
import { useRulesEditionContext } from "../contexts/rules-edition-context.js";
import { cn } from "../lib/utils.js";
import {
@@ -18,6 +19,7 @@ interface ConditionTagsProps {
onRemove: (conditionId: ConditionId) => void;
onDecrement: (conditionId: ConditionId) => void;
onOpenPicker: () => void;
children?: ReactNode;
}
export function ConditionTags({
@@ -25,6 +27,7 @@ export function ConditionTags({
onRemove,
onDecrement,
onOpenPicker,
children,
}: Readonly<ConditionTagsProps>) {
const { edition } = useRulesEditionContext();
return (
@@ -69,6 +72,7 @@ export function ConditionTags({
</Tooltip>
);
})}
{children}
<button
type="button"
title="Add condition"

View File

@@ -60,13 +60,13 @@ describe("toggleCondition", () => {
]);
});
it("maintains definition order when adding conditions", () => {
it("appends new conditions to the end (insertion order)", () => {
const e = enc([makeCombatant("A", [{ id: "poisoned" }])]);
const { encounter } = success(e, "A", "blinded");
expect(encounter.combatants[0].conditions).toEqual([
{ id: "blinded" },
{ id: "poisoned" },
{ id: "blinded" },
]);
});
@@ -109,15 +109,16 @@ describe("toggleCondition", () => {
expect(encounter.combatants[0].conditions).toBeUndefined();
});
it("preserves order across all conditions", () => {
it("preserves insertion order across all conditions", () => {
const order = CONDITION_DEFINITIONS.map((d) => d.id);
// Add in reverse order
// Add in reverse order — result should be reverse order (insertion order)
const reversed = [...order].reverse();
let e = enc([makeCombatant("A")]);
for (const cond of [...order].reverse()) {
for (const cond of reversed) {
const result = success(e, "A", cond);
e = result.encounter;
}
expect(e.combatants[0].conditions).toEqual(order.map((id) => ({ id })));
expect(e.combatants[0].conditions).toEqual(reversed.map((id) => ({ id })));
});
});

View File

@@ -500,8 +500,8 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
description5e: "",
descriptionPf2e:
"Location unknown. Must pick a square to target; DC 11 flat check. Attacker is off-guard against your attacks.",
iconName: "Ghost",
color: "violet",
iconName: "EyeClosed",
color: "slate",
systems: ["pf2e"],
},
{

View File

@@ -70,7 +70,7 @@ export const PERSISTENT_DAMAGE_DEFINITIONS: readonly PersistentDamageDefinition[
color: "pink",
},
{ type: "force", label: "Force", iconName: "Orbit", color: "indigo" },
{ type: "void", label: "Void", iconName: "Eclipse", color: "slate" },
{ type: "void", label: "Void", iconName: "Eclipse", color: "purple" },
{ type: "spirit", label: "Spirit", iconName: "Wind", color: "neutral" },
{
type: "vitality",

View File

@@ -14,12 +14,6 @@ export interface ToggleConditionSuccess {
readonly events: DomainEvent[];
}
function sortByDefinitionOrder(entries: ConditionEntry[]): ConditionEntry[] {
const order = CONDITION_DEFINITIONS.map((d) => d.id);
entries.sort((a, b) => order.indexOf(a.id) - order.indexOf(b.id));
return entries;
}
function validateConditionId(conditionId: ConditionId): DomainError | null {
if (!VALID_CONDITION_IDS.has(conditionId)) {
return {
@@ -67,8 +61,7 @@ export function toggleCondition(
newConditions = filtered.length > 0 ? filtered : undefined;
event = { type: "ConditionRemoved", combatantId, condition: conditionId };
} else {
const added = sortByDefinitionOrder([...current, { id: conditionId }]);
newConditions = added;
newConditions = [...current, { id: conditionId }];
event = { type: "ConditionAdded", combatantId, condition: conditionId };
}
@@ -125,10 +118,7 @@ export function setConditionValue(
};
}
const added = sortByDefinitionOrder([
...current,
{ id: conditionId, value: clampedValue },
]);
const added = [...current, { id: conditionId, value: clampedValue }];
return {
encounter: applyConditions(encounter, combatantId, added),
events: [