# UI Component Contracts: UI Baseline **Feature**: 010-ui-baseline | **Date**: 2026-03-05 ## Layout Contract The encounter screen follows a single-column layout with three zones: ``` ┌─────────────────────────────────┐ │ EncounterHeader │ │ Title + Round/Turn status │ ├─────────────────────────────────┤ │ CombatantList │ │ ┌─ CombatantRow (active) ───┐ │ │ │ Init │ Name │ HP │ Actions│ │ │ └───────────────────────────┘ │ │ ┌─ CombatantRow ────────────┐ │ │ │ Init │ Name │ HP │ Actions│ │ │ └───────────────────────────┘ │ │ ... (scrollable if overflow) │ │ │ │ [EmptyState if no combatants] │ ├─────────────────────────────────┤ │ ActionBar │ │ [Name input] [Add] [Next Turn] │ └─────────────────────────────────┘ ``` ## CombatantRow Contract **Props**: - `combatant: Combatant` — domain entity - `isActive: boolean` — whether this is the active turn - `onRename: (id, newName) => void` - `onSetInitiative: (id, value) => void` - `onRemove: (id) => void` - `onSetHp: (id, maxHp) => void` - `onAdjustHp: (id, delta) => void` **Visual contract**: - Row uses consistent column widths across all combatant rows - Active row has visually distinct highlight (accent background or left border) - Name column truncates with ellipsis at max width - Remove action is an icon button (no text label) - All inputs use design system styling (no browser defaults) **Interaction contract**: - Click name → enter inline edit mode - Enter/blur in edit mode → commit change - Escape in edit mode → cancel - Initiative input is always visible (not click-to-edit), direct typing only - HP inputs are direct-entry text fields with numeric keyboard (`inputmode="numeric"`) - All numeric inputs: no browser spinners, ch-based widths (`6ch`), tabular numerals, centered text ## ActionBar Contract **Props**: - `onAddCombatant: (name: string) => void` - `onAdvanceTurn: () => void` **Visual contract**: - Visually separated from combatant list (spacing, background, or border) - Add form: text input + submit button in a row - Next Turn: distinct button, visually secondary to Add **Interaction contract**: - Enter in name input → add combatant + clear input - Empty name → no action (button may be disabled or form simply ignores) ## EmptyState Contract **Displayed when**: `encounter.combatants.length === 0` **Visual contract**: - Centered message in the combatant list area - Muted/secondary text color - Suggests adding a combatant