Implement the 010-ui-baseline feature that establishes a modern UI using Tailwind CSS v4 and shadcn/ui-style components for the encounter screen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-05 18:36:39 +01:00
parent 8185fde0e8
commit 1c40bf7889
20 changed files with 1533 additions and 273 deletions

View File

@@ -0,0 +1,36 @@
# Specification Quality Checklist: UI Baseline
**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-03-05
**Feature**: [spec.md](../spec.md)
## Content Quality
- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed
## Requirement Completeness
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified
## Feature Readiness
- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification
## Notes
- Technology choices (Tailwind, shadcn/ui) are mentioned only in the Assumptions section as adapter-layer decisions, not in requirements or success criteria.
- All 11 functional requirements are testable through visual inspection of the rendered UI.
- No clarification markers needed — the feature description was detailed and scope is well-bounded (UI-only, no domain changes).

View File

@@ -0,0 +1,78 @@
# 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

View File

@@ -0,0 +1,75 @@
# Data Model: UI Baseline
**Feature**: 010-ui-baseline | **Date**: 2026-03-05
## Domain Entities (UNCHANGED)
This feature makes no domain changes. The existing domain types are consumed as-is by the UI layer.
### Combatant (read-only from UI perspective)
| Field | Type | Notes |
|-------|------|-------|
| id | CombatantId (branded string) | Unique identifier |
| name | string | Display name, editable inline |
| initiative | number \| undefined | Sort order, editable inline |
| maxHp | number \| undefined | Maximum hit points |
| currentHp | number \| undefined | Current hit points (present when maxHp is set) |
### Encounter (read-only from UI perspective)
| Field | Type | Notes |
|-------|------|-------|
| combatants | readonly Combatant[] | Sorted by initiative descending |
| activeIndex | number | Index of current turn's combatant |
| roundNumber | number | Current round counter |
## UI Component Model (NEW)
These are adapter-layer visual components — not domain entities.
### CombatantRow
Renders a single combatant as a structured row with consistent column alignment.
| Column | Content | Behavior |
|--------|---------|----------|
| Initiative | Number or placeholder | Text input with `inputmode="numeric"`, no spinners, direct typing only |
| Name | Combatant name | Inline editable, click-to-edit. Truncates with ellipsis |
| HP | currentHp / maxHp | Text inputs with `inputmode="numeric"`, no spinners, direct entry for both values |
| Actions | Remove icon button | Compact icon (X or trash), tooltip on hover |
**Numeric field styling**: All numeric inputs use `type="text"` with `inputmode="numeric"` to suppress browser spinners while showing the numeric keyboard on mobile. Initiative uses `6ch` width (12 digit values typical). HP fields use `7ch` width (supports up to 4-digit values like 1000). All use tabular numerals (`font-variant-numeric: tabular-nums`) for column alignment and centered text.
**Visual states**:
- Default row
- Active row (highlighted background/border for current turn)
- Editing state (inline input replaces display text)
### ActionBar
Groups primary encounter controls.
| Element | Type | Behavior |
|---------|------|----------|
| Name input | Text input | Enter combatant name |
| Add button | Primary button | Adds combatant to encounter |
| Next Turn button | Secondary/outline button | Advances to next combatant |
### EncounterHeader
Displays encounter status.
| Element | Content |
|---------|---------|
| Title | "Initiative Tracker" |
| Status | Round number and active combatant name |
### EmptyState
Displayed when combatants list is empty.
| Element | Content |
|---------|---------|
| Message | "No combatants yet" or similar |
| Hint | Prompt to add first combatant |

View File

@@ -0,0 +1,75 @@
# Implementation Plan: UI Baseline
**Branch**: `010-ui-baseline` | **Date**: 2026-03-05 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `/specs/010-ui-baseline/spec.md`
## Summary
Establish a modern UI baseline for the encounter screen by integrating Tailwind CSS v4 and shadcn/ui into the existing Vite + React 19 web app. Replace all unstyled HTML with a consistent design system: structured combatant rows with aligned columns, active turn highlight, grouped action bar, icon remove buttons, and consistent typography. No domain or application layer changes — all work is in the `apps/web` adapter layer.
## Technical Context
**Language/Version**: TypeScript 5.8 (strict mode, verbatimModuleSyntax)
**Primary Dependencies**: React 19, Vite 6, Tailwind CSS v4, shadcn/ui, Lucide React (icons)
**Storage**: N/A (no storage changes — localStorage persistence unchanged)
**Testing**: Vitest (existing layer boundary tests must pass; no new visual tests in MVP baseline)
**Target Platform**: Modern browsers (desktop-first, not broken on mobile)
**Project Type**: Web application (monorepo: apps/web + packages/domain + packages/application)
**Performance Goals**: No perceptible rendering delay; encounter screen usable during live tabletop play
**Constraints**: UI-only changes; domain and application layers untouched
**Scale/Scope**: Single screen (encounter tracker), ~6 component areas to restyle
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
| Principle | Status | Notes |
|-----------|--------|-------|
| I. Deterministic Domain Core | PASS | No domain changes |
| II. Layered Architecture | PASS | All changes in adapter layer (apps/web) only |
| III. Agent Boundary | N/A | No agent features involved |
| IV. Clarification-First | PASS | Spec is fully specified, no ambiguities |
| V. Escalation Gates | PASS | Feature stays within spec scope |
| VI. MVP Baseline Language | PASS | Dark theme, full responsive noted as "not in MVP baseline" |
| VII. No Gameplay Rules | PASS | No gameplay logic involved |
## Project Structure
### Documentation (this feature)
```text
specs/010-ui-baseline/
├── plan.md # This file
├── research.md # Phase 0: Tailwind v4 + shadcn/ui setup research
├── data-model.md # Phase 1: UI component model (no domain changes)
├── quickstart.md # Phase 1: Developer quickstart
├── contracts/ # Phase 1: UI component contracts
│ └── ui-components.md
└── tasks.md # Phase 2 output (created by /speckit.tasks)
```
### Source Code (repository root)
```text
apps/web/
├── src/
│ ├── main.tsx # Add global CSS import
│ ├── index.css # NEW: Tailwind directives + CSS variables
│ ├── lib/
│ │ └── utils.ts # NEW: cn() helper (clsx + twMerge)
│ ├── components/
│ │ └── ui/ # NEW: shadcn/ui primitives (Button, Input, Card, etc.)
│ ├── App.tsx # Refactored: use new components + Tailwind classes
│ └── hooks/
│ └── use-encounter.ts # UNCHANGED
└── package.json # Updated: new dependencies
packages/domain/ # UNCHANGED
packages/application/ # UNCHANGED
```
**Structure Decision**: Follows existing monorepo layout. New UI components live in `apps/web/src/components/ui/` following shadcn/ui convention. No new packages or layers introduced.
## Complexity Tracking
No constitution violations. No complexity justifications needed.

View File

@@ -0,0 +1,62 @@
# Quickstart: UI Baseline
**Feature**: 010-ui-baseline | **Date**: 2026-03-05
## Prerequisites
- Node.js 18+
- pnpm 10.6+
## Setup
```bash
# Install dependencies (from repo root)
pnpm install
# Start dev server
pnpm --filter web dev
# → http://localhost:5173
```
## New Dependencies (to be added)
```bash
# Tailwind CSS v4 with Vite plugin
pnpm --filter web add tailwindcss @tailwindcss/vite
# shadcn/ui utilities
pnpm --filter web add clsx tailwind-merge class-variance-authority
# Radix UI primitives (used by shadcn/ui components)
pnpm --filter web add @radix-ui/react-slot @radix-ui/react-tooltip
# Icons
pnpm --filter web add lucide-react
```
## Key Files
| File | Purpose |
|------|---------|
| `apps/web/src/index.css` | Tailwind directives + CSS custom properties |
| `apps/web/src/lib/utils.ts` | `cn()` class merge utility |
| `apps/web/src/components/ui/` | shadcn/ui primitives (Button, Input, etc.) |
| `apps/web/src/components/combatant-row.tsx` | Combatant row component |
| `apps/web/src/App.tsx` | Main layout (header, list, action bar) |
| `apps/web/vite.config.ts` | Updated with Tailwind plugin |
## Quality Gate
```bash
# Must pass before commit
pnpm check
```
This runs: knip → format → lint → typecheck → test
## Design System
- **Styling**: Tailwind CSS v4 utility classes
- **Components**: shadcn/ui (copied into project, not a package dependency)
- **Icons**: Lucide React
- **Class merging**: `cn()` from `lib/utils.ts` (clsx + tailwind-merge)

View File

@@ -0,0 +1,72 @@
# Research: UI Baseline
**Feature**: 010-ui-baseline | **Date**: 2026-03-05
## R1: Tailwind CSS Version Choice
**Decision**: Use Tailwind CSS v4 (latest stable)
**Rationale**: Tailwind v4 uses a CSS-first configuration approach with `@import "tailwindcss"` and CSS-based theme customization via `@theme`. This simplifies setup — no `tailwind.config.ts` is strictly required for basic usage. Vite has first-class support via `@tailwindcss/vite` plugin.
**Alternatives considered**:
- Tailwind v3: Mature but requires JS config file and PostCSS plugin. v4 is stable and recommended for new projects.
## R2: shadcn/ui Integration Approach
**Decision**: Use shadcn/ui CLI to scaffold components into `apps/web/src/components/ui/`
**Rationale**: shadcn/ui is not a package dependency — it copies component source code into the project. This gives full control over styling and avoids version lock-in. Components use Tailwind classes + Radix UI primitives.
**Alternatives considered**:
- Radix UI directly: More low-level, requires writing all styles manually. shadcn/ui provides pre-styled Tailwind components.
- Material UI / Chakra UI: Heavier runtime, opinionated styling that conflicts with Tailwind approach.
## R3: Tailwind v4 + Vite Setup
**Decision**: Use `@tailwindcss/vite` plugin instead of PostCSS
**Rationale**: Tailwind v4 provides a dedicated Vite plugin that is faster than the PostCSS approach. Setup is:
1. Install `tailwindcss @tailwindcss/vite`
2. Add plugin to `vite.config.ts`
3. Create `index.css` with `@import "tailwindcss"`
4. Import `index.css` in `main.tsx`
**Alternatives considered**:
- PostCSS plugin: Works but slower; Vite plugin is recommended for Vite projects.
## R4: Icon Library
**Decision**: Use Lucide React for icons (remove button, etc.)
**Rationale**: Lucide is the default icon set for shadcn/ui. Tree-shakeable, consistent style, and already expected by shadcn/ui component templates.
**Alternatives considered**:
- Heroicons: Good quality but not the shadcn/ui default.
- Inline SVG: Too manual for maintenance.
## R5: CSS Utility Helper (cn function)
**Decision**: Use `clsx` + `tailwind-merge` via a `cn()` utility function
**Rationale**: Standard shadcn/ui pattern. `clsx` handles conditional classes, `tailwind-merge` deduplicates conflicting Tailwind classes. The `cn()` helper is placed in `lib/utils.ts`.
**Alternatives considered**:
- `clsx` alone: Doesn't deduplicate conflicting Tailwind classes (e.g., `p-2 p-4`).
## R6: Component Decomposition
**Decision**: Extract App.tsx into focused components while keeping them in a single file or minimal files
**Rationale**: The current App.tsx (~280 lines) has inline components (EditableName, MaxHpInput, CurrentHpInput). For the UI baseline, we'll restructure into:
- `App.tsx` — layout shell (header, combatant list, action bar)
- `components/combatant-row.tsx` — single combatant row with all controls
- `components/ui/` — shadcn/ui primitives (Button, Input, Card)
This keeps the change focused while establishing a scalable component structure.
**Alternatives considered**:
- Keep everything in App.tsx: Gets unwieldy with Tailwind classes added.
- Full atomic decomposition: Over-engineered for current scope.
## R7: verbatimModuleSyntax Compatibility
**Decision**: shadcn/ui components work with `verbatimModuleSyntax` since they use standard ESM imports
**Rationale**: shadcn/ui generates standard TypeScript files with explicit type imports. The `cn` utility and Radix imports use value imports. No special handling needed.
## R8: Biome 2.0 Compatibility
**Decision**: No conflicts expected; Tailwind class strings are just strings
**Rationale**: Biome formats/lints TypeScript and JSX. Tailwind classes in `className` props are plain strings, which Biome ignores content-wise. The shadcn/ui generated code follows standard formatting conventions. May need to run `pnpm format` after generating components.
## R9: Knip Compatibility
**Decision**: May need to configure Knip to recognize shadcn/ui component exports
**Rationale**: shadcn/ui components are copied into the project. If not all are immediately used, Knip may flag them as unused. Solution: only install the shadcn/ui components we actually need (Button, Input, Card/container).

View File

@@ -0,0 +1,140 @@
# Feature Specification: UI Baseline
**Feature Branch**: `010-ui-baseline`
**Created**: 2026-03-05
**Status**: Draft
**Input**: User description: "Establish a modern UI baseline for the encounter screen using Tailwind + shadcn/ui."
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Structured Combatant Layout (Priority: P1)
As a game master viewing the encounter screen, I see each combatant displayed as a structured row with initiative, name, HP, and actions aligned in consistent columns, so I can quickly scan the battlefield state.
**Why this priority**: The core value of a UI baseline is replacing the unstyled list with a consistent, scannable layout. Every other visual improvement builds on this.
**Independent Test**: Can be fully tested by adding 3+ combatants and verifying that all data fields (initiative, name, HP, actions) are visually aligned in a grid/table-like layout.
**Acceptance Scenarios**:
1. **Given** an encounter with multiple combatants, **When** the screen loads, **Then** each combatant is displayed as a row with initiative, name, HP, and actions in consistent columns.
2. **Given** combatants with varying name lengths, **When** displayed, **Then** columns remain aligned and do not shift or overlap.
3. **Given** a combatant with no initiative or HP set, **When** displayed, **Then** placeholder or empty state is shown in the appropriate column without breaking alignment.
---
### User Story 2 - Active Combatant Highlight (Priority: P1)
As a game master during combat, I see the active combatant clearly highlighted so I can instantly identify whose turn it is without reading text markers.
**Why this priority**: Identifying the active turn is the primary interaction during live play. A clear visual highlight is essential for usability.
**Independent Test**: Can be tested by advancing turns and verifying the active combatant has a distinct visual treatment (background color, border, or similar).
**Acceptance Scenarios**:
1. **Given** an encounter in progress, **When** viewing the combatant list, **Then** the active combatant's row has a visually distinct highlight (e.g., accent background, left border indicator).
2. **Given** the turn advances, **When** a new combatant becomes active, **Then** the highlight moves to the new active combatant and is removed from the previous one.
---
### User Story 3 - Grouped Action Bar (Priority: P2)
As a game master, I see primary encounter controls (add combatant, next turn) grouped in a clearly defined action bar, so controls are easy to find and visually separated from the combatant list.
**Why this priority**: Grouping controls improves discoverability and reduces visual clutter, but is less critical than the combatant layout itself.
**Independent Test**: Can be tested by verifying that the "Add Combatant" form and "Next Turn" button are visually grouped in a distinct bar area, separated from the combatant list.
**Acceptance Scenarios**:
1. **Given** the encounter screen, **When** viewing controls, **Then** the "Add Combatant" input and "Next Turn" button are grouped in a visually distinct action bar.
2. **Given** the action bar, **When** inspecting layout, **Then** it is clearly separated from the combatant list by spacing, background, or border.
---
### User Story 4 - Inline Editing with Consistent Styling (Priority: P2)
As a game master, I can click on a combatant's name to edit it inline, and all editable controls (including initiative and HP inputs) match the overall visual style of the application.
**Why this priority**: Inline editing already exists functionally. This story ensures the edit states are visually consistent with the new design system rather than appearing as unstyled browser defaults.
**Independent Test**: Can be tested by clicking a combatant name to enter edit mode and verifying the input field matches the application's visual style.
**Acceptance Scenarios**:
1. **Given** a combatant row, **When** I click the name, **Then** it transitions to an inline text input styled consistently with the design system.
2. **Given** a combatant row, **When** I edit initiative or HP values, **Then** the number inputs are styled consistently with the design system.
---
### User Story 5 - Remove Action as Icon Button (Priority: P3)
As a game master, I see the remove action as a small icon button rather than a full text button, so it takes up less space and reduces visual noise.
**Why this priority**: This is a refinement that improves information density. The remove action is infrequently used, so a compact icon button is appropriate.
**Independent Test**: Can be tested by verifying each combatant row has a small icon-sized remove button (e.g., trash or X icon) instead of a text "Remove" button.
**Acceptance Scenarios**:
1. **Given** a combatant row, **When** viewing the actions column, **Then** the remove action is displayed as a small icon button (not a text label).
2. **Given** the icon button, **When** hovering, **Then** a tooltip or visual feedback indicates the action is "Remove".
---
### User Story 6 - Consistent Typography and Spacing (Priority: P2)
As a game master, the encounter screen uses consistent font sizes, weights, and spacing throughout, creating a cohesive and professional appearance.
**Why this priority**: Typography and spacing consistency is foundational to a "modern UI baseline" and affects the perceived quality of every other element.
**Independent Test**: Can be tested by verifying that heading sizes, body text, input text, and spacing follow a consistent scale across the entire screen.
**Acceptance Scenarios**:
1. **Given** the encounter screen, **When** inspecting typography, **Then** headings, labels, and body text use a consistent type scale.
2. **Given** the encounter screen, **When** inspecting spacing, **Then** padding and margins follow a consistent spacing scale.
---
### Edge Cases
- What happens when no combatants exist? The screen should display an empty state message (e.g., "No combatants yet") rather than a blank area.
- What happens with very long combatant names? Names should truncate with ellipsis rather than breaking the layout.
- What happens on narrow viewports? The layout should remain usable, though a fully responsive mobile design is not in the MVP baseline for this feature.
- What happens with 20+ combatants? The list should scroll without the action bar or header disappearing.
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: System MUST display combatants in a structured row layout with columns for initiative, name, HP (current/max), and actions. All numeric fields use text inputs with `inputmode="numeric"` (no browser spinners), ch-based widths, tabular numerals, and centered text.
- **FR-002**: System MUST visually highlight the active combatant's row with a distinct background, border, or accent treatment.
- **FR-003**: System MUST group the "Add Combatant" form and "Next Turn" button in a visually distinct action bar area.
- **FR-004**: System MUST display the remove action as a compact icon button (not a text label) on each combatant row.
- **FR-005**: System MUST style all form inputs (text fields, number inputs) consistently using the design system's components.
- **FR-006**: System MUST apply consistent typography (font family, sizes, weights) and spacing (margins, padding) from a defined scale.
- **FR-007**: System MUST show a round/turn status indicator (current round number and active combatant name) in a header or status area.
- **FR-008**: System MUST display an empty state message when no combatants are present.
- **FR-009**: System MUST truncate long combatant names with ellipsis rather than breaking the layout.
- **FR-010**: System MUST NOT introduce any domain logic changes; all changes are confined to the adapter/UI layer.
- **FR-011**: System MUST NOT display domain events in the main UI layout.
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: All combatant data fields (initiative, name, HP, actions) are visually aligned in consistent columns across all combatant rows.
- **SC-002**: The active combatant is identifiable within 1 second of glancing at the screen, without reading text labels.
- **SC-003**: A new user can locate the "Add Combatant" and "Next Turn" controls within 3 seconds of viewing the screen.
- **SC-004**: All interactive elements (buttons, inputs) are styled consistently with no browser-default styled controls visible.
- **SC-005**: The encounter screen presents a cohesive visual appearance with no mismatched fonts, inconsistent spacing, or unstyled elements.
## Assumptions
- Tailwind CSS and shadcn/ui will be used as the design system and component library. These are adapter-layer technology choices.
- The existing functional behavior (inline editing, HP controls, add/remove/advance) is preserved exactly; only visual presentation changes.
- A dark theme or theme toggle is not included in the MVP baseline for this feature.
- Full mobile/responsive optimization is not included in the MVP baseline for this feature, though the layout should not be broken on smaller screens.
- Domain events will be removed from the main UI display (they were a development aid, not a user-facing feature).

View File

@@ -0,0 +1,207 @@
# Tasks: UI Baseline
**Input**: Design documents from `/specs/010-ui-baseline/`
**Prerequisites**: plan.md (required), spec.md (required), research.md, data-model.md, contracts/ui-components.md
**Tests**: No new tests requested in spec. Existing tests (layer boundary checks) must continue to pass.
**Organization**: Tasks grouped by user story. US1+US2 are combined (both P1, same component).
## Format: `[ID] [P?] [Story] Description`
- **[P]**: Can run in parallel (different files, no dependencies)
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
- Include exact file paths in descriptions
## Phase 1: Setup (Shared Infrastructure)
**Purpose**: Install Tailwind CSS v4, shadcn/ui utilities, and configure the build pipeline
- [x] T001 Install `tailwindcss` and `@tailwindcss/vite` as devDependencies and add the Tailwind Vite plugin to `apps/web/vite.config.ts`
- [x] T002 [P] Create `apps/web/src/index.css` with `@import "tailwindcss"` directive and `@theme` block defining CSS custom properties for the design system (colors, radii, fonts)
- [x] T003 [P] Install `clsx` and `tailwind-merge` as dependencies in `apps/web` and create the `cn()` utility in `apps/web/src/lib/utils.ts`
- [x] T004 Import `./index.css` in `apps/web/src/main.tsx`
- [x] T005 [P] Install `lucide-react` as a dependency in `apps/web`
---
## Phase 2: Foundational (Blocking Prerequisites)
**Purpose**: Create shadcn/ui-style primitive components that all user stories depend on
**Warning**: No user story work can begin until this phase is complete
- [x] T006 [P] Install `class-variance-authority` as a dependency in `apps/web` and create Button component in `apps/web/src/components/ui/button.tsx` following shadcn/ui pattern (variant props: default, outline, ghost, icon; size props: default, sm, icon) using `cn()` and `class-variance-authority`
- [x] T007 [P] Create Input component in `apps/web/src/components/ui/input.tsx` following shadcn/ui pattern (styled text/number input replacing browser defaults) using `cn()`
**Note**: T008 merged into T006 (install CVA + create Button in one task).
**Checkpoint**: Foundation ready — shadcn/ui primitives available, Tailwind active
---
## Phase 3: User Story 1 + 2 — Structured Layout + Active Highlight (Priority: P1) MVP
**Goal**: Replace the unstyled `<ul>/<li>` combatant list with a structured row layout (initiative | name | HP | actions columns) and visually highlight the active combatant's row
**Independent Test**: Add 3+ combatants, verify columns are aligned. Set initiative and HP on some. Advance turns and confirm the active row has a distinct highlight that moves correctly.
### Implementation
- [x] T009 [US1] Extract `CombatantRow` component to `apps/web/src/components/combatant-row.tsx` — accepts `combatant`, `isActive`, and action callbacks per the UI contract. Render a grid/flex row with four columns: initiative (number input), name (click-to-edit text), HP (current/max with +/- buttons), actions (remove button placeholder). Apply active row highlight (accent left border + subtle background) when `isActive` is true. Move `EditableName`, `MaxHpInput`, and `CurrentHpInput` inline components into this file.
- [x] T010 [US1] Refactor `apps/web/src/App.tsx` to use `CombatantRow` — replace the `<ul>` list with a styled container. Add encounter header section showing title ("Initiative Tracker") and round/turn status. Remove domain events display section entirely (FR-011). Keep `useEncounter` hook usage and all callbacks wired through.
**Checkpoint**: Combatants display in aligned columns with active highlight. All existing functionality preserved.
---
## Phase 4: User Story 3 — Grouped Action Bar (Priority: P2)
**Goal**: Group the "Add Combatant" form and "Next Turn" button into a visually distinct action bar separated from the combatant list
**Independent Test**: Verify controls are grouped in a distinct bar area with visual separation (background, border, or spacing) from the combatant list.
### Implementation
- [x] T011 [US3] Extract `ActionBar` component to `apps/web/src/components/action-bar.tsx` — accepts `onAddCombatant` and `onAdvanceTurn` callbacks. Render a styled bar with the add-combatant form (Input + Button) and Next Turn button (outline/secondary variant). Apply visual separation from the combatant list.
- [x] T012 [US3] Update `apps/web/src/App.tsx` to use `ActionBar` component — replace inline form and button with the extracted component.
**Checkpoint**: Action bar is visually grouped and separated from combatant list.
---
## Phase 5: User Story 4 — Inline Editing with Consistent Styling (Priority: P2)
**Goal**: Ensure all inline edit states (name, initiative, HP inputs) use the design system Input component instead of unstyled browser defaults
**Independent Test**: Click a combatant name to edit — the input should match the design system style. Verify initiative and HP number inputs are also styled consistently.
### Implementation
- [x] T013 [US4] Update `EditableName`, `MaxHpInput`, `CurrentHpInput`, and initiative input in `apps/web/src/components/combatant-row.tsx` to use the shadcn/ui-style `Input` component from `components/ui/input.tsx`. Ensure edit-mode inputs match display-mode styling for seamless transitions.
**Checkpoint**: All form inputs across the encounter screen use consistent design system styling.
---
## Phase 6: User Story 5 — Remove Action as Icon Button (Priority: P3)
**Goal**: Replace the text "Remove" button with a compact icon button using a Lucide icon
**Independent Test**: Each combatant row shows a small icon button (X or Trash2) instead of a text "Remove" button. Hovering shows tooltip feedback.
### Implementation
- [x] T014 [US5] Replace the remove `<button>` in `apps/web/src/components/combatant-row.tsx` with a `Button` (ghost/icon variant) wrapping a Lucide `X` or `Trash2` icon. Add `title` attribute for hover tooltip ("Remove combatant").
**Checkpoint**: Remove action is a compact icon button with hover feedback.
---
## Phase 7: User Story 6 — Consistent Typography and Spacing (Priority: P2)
**Goal**: Apply a consistent type scale and spacing scale across the entire encounter screen
**Independent Test**: Inspect the screen — headings, body text, labels, and inputs use a consistent font family, size scale, and spacing rhythm with no arbitrary values.
### Implementation
- [x] T015 [US6] Review and refine typography and spacing across all components — ensure `apps/web/src/index.css` theme defines a consistent type scale (heading, body, label sizes) and spacing tokens. Update `apps/web/src/components/combatant-row.tsx`, `apps/web/src/components/action-bar.tsx`, and `apps/web/src/App.tsx` to use consistent Tailwind spacing/typography utilities (no arbitrary pixel values).
**Checkpoint**: The entire encounter screen has cohesive typography and spacing.
---
## Phase 8: Polish & Cross-Cutting Concerns
**Purpose**: Edge cases, cleanup, and quality gate validation
- [x] T016 Add empty state message in `apps/web/src/App.tsx` — when `encounter.combatants.length === 0`, display a centered muted message ("No combatants yet — add one to get started") instead of an empty list area
- [x] T017 Add long name truncation with CSS `text-overflow: ellipsis` on the name column in `apps/web/src/components/combatant-row.tsx` and make the combatant list area scrollable when it overflows (sticky header + action bar)
- [x] T018 Run `pnpm check` (knip + format + lint + typecheck + test) and fix all issues — ensure no unused imports from shadcn/ui, Biome formatting passes, TypeScript compiles, and layer boundary tests pass
---
## Dependencies & Execution Order
### Phase Dependencies
- **Setup (Phase 1)**: No dependencies — start immediately
- **Foundational (Phase 2)**: Depends on T001 (Tailwind installed) and T003 (cn() available)
- **US1+US2 (Phase 3)**: Depends on Phase 2 (Button, Input primitives available)
- **US3 (Phase 4)**: Depends on Phase 3 (App.tsx refactored with layout shell)
- **US4 (Phase 5)**: Depends on Phase 3 (CombatantRow exists with inline edit components)
- **US5 (Phase 6)**: Depends on Phase 3 (CombatantRow exists) + T005 (lucide-react installed)
- **US6 (Phase 7)**: Depends on Phases 3-6 (all components exist to audit typography)
- **Polish (Phase 8)**: Depends on all user stories complete
### User Story Dependencies
- **US1+US2 (P1)**: Can start after Phase 2 — no other story dependencies
- **US3 (P2)**: Depends on US1+US2 (App.tsx layout shell must exist)
- **US4 (P2)**: Depends on US1+US2 (CombatantRow must exist)
- **US5 (P3)**: Depends on US1+US2 (CombatantRow must exist)
- **US6 (P2)**: Depends on US3, US4, US5 (all components must exist to audit)
### Within Each User Story
- Component extraction before integration with App.tsx
- Structural changes before styling refinements
### Parallel Opportunities
- T002, T003, T005 can run in parallel (different files, no dependencies)
- T006, T007 can run in parallel (different files)
- US3 (T011-T012) and US4 (T013) and US5 (T014) can run in parallel after Phase 3 (different files)
---
## Parallel Example: Phase 1 Setup
```bash
# These three tasks touch different files and can run simultaneously:
Task T002: "Create index.css with Tailwind directives in apps/web/src/index.css"
Task T003: "Create cn() utility in apps/web/src/lib/utils.ts"
Task T005: "Install lucide-react in apps/web"
```
## Parallel Example: Phase 2 Foundational
```bash
# These tasks create independent component files:
Task T006: "Install CVA + create Button in apps/web/src/components/ui/button.tsx"
Task T007: "Create Input in apps/web/src/components/ui/input.tsx"
```
---
## Implementation Strategy
### MVP First (US1+US2 Only)
1. Complete Phase 1: Setup (Tailwind + utilities)
2. Complete Phase 2: Foundational (Button, Input primitives)
3. Complete Phase 3: US1+US2 (structured layout + active highlight)
4. **STOP and VALIDATE**: Combatants display in aligned columns, active turn highlighted
5. This alone delivers the core visual upgrade
### Incremental Delivery
1. Setup + Foundational -> Build pipeline ready
2. US1+US2 -> Structured layout with active highlight (MVP!)
3. US3 -> Grouped action bar
4. US4 -> Styled inline editing
5. US5 -> Icon remove button
6. US6 -> Typography/spacing audit
7. Polish -> Edge cases, quality gate
8. Each phase adds visual polish without breaking previous work
---
## Notes
- All changes are in `apps/web/` only — domain and application packages are untouched (FR-010)
- Domain events display is removed in T010 (FR-011)
- No new test files created — existing Vitest layer boundary tests must pass (T018)
- shadcn/ui components are hand-written following the pattern (not CLI-generated) to ensure Biome/Knip compatibility
- Run `pnpm format` after each phase to keep Biome happy