diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1d778e7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,28 @@
+# Initiative Tracker
+
+A turn-based initiative tracker for tabletop RPG encounters. Click "Next Turn" to cycle through combatants and advance rounds.
+
+## Prerequisites
+
+- Node.js 22
+- pnpm 10.6+
+
+## Getting Started
+
+```sh
+pnpm install
+pnpm --filter web dev
+```
+
+Open the URL printed in your terminal (typically `http://localhost:5173`).
+
+The app starts with a 3-combatant demo encounter (Aria, Brak, Cael). Click **Next Turn** to advance through the initiative order. When the last combatant finishes their turn, the round number increments and the cycle restarts.
+
+## Scripts
+
+| Command | Description |
+|---------|-------------|
+| `pnpm --filter web dev` | Start the dev server |
+| `pnpm --filter web build` | Production build |
+| `pnpm test` | Run all tests |
+| `pnpm check` | Full merge gate (format, lint, typecheck, test) |
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 4781b57..fc46af6 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -1,3 +1,43 @@
+import { useEncounter } from "./hooks/use-encounter";
+
export function App() {
- return
Initiative Tracker
;
+ const { encounter, events, advanceTurn } = useEncounter();
+ const activeCombatant = encounter.combatants[encounter.activeIndex];
+
+ return (
+
+
Initiative Tracker
+
+
+ Round {encounter.roundNumber} — Current: {activeCombatant.name}
+
+
+
+ {encounter.combatants.map((c, i) => (
+ -
+ {i === encounter.activeIndex ? `▶ ${c.name}` : c.name}
+
+ ))}
+
+
+
+
+ {events.length > 0 && (
+
+
Events
+
+ {events.map((e, i) => (
+ -
+ {e.type === "TurnAdvanced"
+ ? `Turn: ${e.previousCombatantId} → ${e.newCombatantId} (round ${e.roundNumber})`
+ : `Round advanced to ${e.newRoundNumber}`}
+
+ ))}
+
+
+ )}
+
+ );
}
diff --git a/apps/web/src/hooks/use-encounter.ts b/apps/web/src/hooks/use-encounter.ts
new file mode 100644
index 0000000..26170cd
--- /dev/null
+++ b/apps/web/src/hooks/use-encounter.ts
@@ -0,0 +1,47 @@
+import type { EncounterStore } from "@initiative/application";
+import { advanceTurnUseCase } from "@initiative/application";
+import type { DomainEvent, Encounter } from "@initiative/domain";
+import {
+ combatantId,
+ createEncounter,
+ isDomainError,
+} from "@initiative/domain";
+import { useCallback, useRef, useState } from "react";
+
+function createDemoEncounter(): Encounter {
+ const result = createEncounter([
+ { id: combatantId("1"), name: "Aria" },
+ { id: combatantId("2"), name: "Brak" },
+ { id: combatantId("3"), name: "Cael" },
+ ]);
+
+ if (isDomainError(result)) {
+ throw new Error(`Failed to create demo encounter: ${result.message}`);
+ }
+
+ return result;
+}
+
+export function useEncounter() {
+ const [encounter, setEncounter] = useState(createDemoEncounter);
+ const [events, setEvents] = useState([]);
+ const encounterRef = useRef(encounter);
+ encounterRef.current = encounter;
+
+ const advanceTurn = useCallback(() => {
+ const store: EncounterStore = {
+ get: () => encounterRef.current,
+ save: (e) => setEncounter(e),
+ };
+
+ const result = advanceTurnUseCase(store);
+
+ if (isDomainError(result)) {
+ return;
+ }
+
+ setEvents((prev) => [...prev, ...result]);
+ }, []);
+
+ return { encounter, events, advanceTurn } as const;
+}
diff --git a/packages/application/src/advance-turn-use-case.ts b/packages/application/src/advance-turn-use-case.ts
new file mode 100644
index 0000000..f6555ab
--- /dev/null
+++ b/packages/application/src/advance-turn-use-case.ts
@@ -0,0 +1,21 @@
+import {
+ advanceTurn,
+ type DomainError,
+ type DomainEvent,
+ isDomainError,
+} from "@initiative/domain";
+import type { EncounterStore } from "./ports.js";
+
+export function advanceTurnUseCase(
+ store: EncounterStore,
+): DomainEvent[] | DomainError {
+ const encounter = store.get();
+ const result = advanceTurn(encounter);
+
+ if (isDomainError(result)) {
+ return result;
+ }
+
+ store.save(result.encounter);
+ return result.events;
+}
diff --git a/packages/application/src/index.ts b/packages/application/src/index.ts
index cb0ff5c..530c4be 100644
--- a/packages/application/src/index.ts
+++ b/packages/application/src/index.ts
@@ -1 +1,2 @@
-export {};
+export { advanceTurnUseCase } from "./advance-turn-use-case.js";
+export type { EncounterStore } from "./ports.js";
diff --git a/packages/application/src/ports.ts b/packages/application/src/ports.ts
new file mode 100644
index 0000000..cae0f7e
--- /dev/null
+++ b/packages/application/src/ports.ts
@@ -0,0 +1,6 @@
+import type { Encounter } from "@initiative/domain";
+
+export interface EncounterStore {
+ get(): Encounter;
+ save(encounter: Encounter): void;
+}
diff --git a/specs/001-advance-turn/tasks.md b/specs/001-advance-turn/tasks.md
index 8f257eb..7650083 100644
--- a/specs/001-advance-turn/tasks.md
+++ b/specs/001-advance-turn/tasks.md
@@ -48,11 +48,11 @@
**Goal**: Wire up the application use case and minimal React UI with a "Next Turn" button.
-- [ ] T012 Define port interface in `packages/application/src/ports.ts` — `EncounterStore` port: `get(): Encounter`, `save(e: Encounter)`
-- [ ] T013 Implement `AdvanceTurnUseCase` in `packages/application/src/advance-turn-use-case.ts` — accepts `EncounterStore`, calls `advanceTurn`, saves result, returns events
-- [ ] T014 Export public API from `packages/application/src/index.ts` — re-export use case and port types
-- [ ] T015 Implement `useEncounter` hook in `apps/web/src/hooks/use-encounter.ts` — in-memory `EncounterStore` via React state, exposes encounter state + `advanceTurn` action, hardcoded 3-combatant demo
-- [ ] T016 Wire up `apps/web/src/App.tsx` — display current combatant, round number, combatant list with active indicator, "Next Turn" button, emitted events
+- [X] T012 Define port interface in `packages/application/src/ports.ts` — `EncounterStore` port: `get(): Encounter`, `save(e: Encounter)`
+- [X] T013 Implement `AdvanceTurnUseCase` in `packages/application/src/advance-turn-use-case.ts` — accepts `EncounterStore`, calls `advanceTurn`, saves result, returns events
+- [X] T014 Export public API from `packages/application/src/index.ts` — re-export use case and port types
+- [X] T015 Implement `useEncounter` hook in `apps/web/src/hooks/use-encounter.ts` — in-memory `EncounterStore` via React state, exposes encounter state + `advanceTurn` action, hardcoded 3-combatant demo
+- [X] T016 Wire up `apps/web/src/App.tsx` — display current combatant, round number, combatant list with active indicator, "Next Turn" button, emitted events
**Checkpoint (Milestone 2)**: `pnpm check` passes. `vite build` succeeds. Clicking "Next Turn" cycles combatants and increments rounds correctly.