CR Editing Feature - Implementation Documentation

Overview

This document describes the CR (Challenge Rating) editing feature implementation based on PR #454, adapted to work with the new MonsterSpecification-based architecture.

Background

The CR editing feature allows users to change the Challenge Rating of a monster and automatically switch to the appropriate variant if needed. For example, if a monster template has three variants (Soldier CR 1, Veteran CR 3, General CR 12), selecting CR 5 will automatically switch to the Veteran variant at CR 5.

Key Design Decision: MonsterSpecification as Single Source of Truth

The previous PR #454 attempted to synchronize CR across multiple places (MonsterCard.currentCr, MonsterStatblock.cr, specification) which led to race conditions and complex state management. The new implementation uses MonsterSpecification as the single source of truth, eliminating this complexity.

Architecture

Component Hierarchy

MonsterBuilder (owns canonical specification)
  ↓
MonsterCard (receives specification as prop)
  ├─ MonsterInfo (displays CR with edit button)
  └─ CrEditDialog (modal for CR selection)

Data Flow

1. User clicks edit button → MonsterInfo emits 'cr-edit-requested'
2. MonsterCard opens CrEditDialog with current specification
3. User selects CR → Dialog calls API to fetch new specification
4. Dialog emits 'specification-updated' with new specification
5. MonsterCard emits 'specification-changed' to parent
6. MonsterBuilder updates canonical specification
7. Specification change propagates down → MonsterStatblock updates

Implementation Details

CrEditDialog Component

Location: foe_foundry_ui/src/components/CrEditDialog.ts

Public API:

// Input Properties
@property({ type: Object })
specification: MonsterSpecification | null;

@property({ type: Array })
relatedMonsters: RelatedMonster[];

@property({ type: Boolean, reflect: true })
open: boolean;

// Output Events
// 'specification-updated' - CustomEvent with detail: { specification: MonsterSpecification }
// 'dialog-closed' - emitted when dialog closes without selection

See full documentation in the source file.

Testing

Test File: tests/components/cr-edit-dialog.test.ts

Coverage: 13 tests, all passing

References