diff --git a/apps/web/src/hooks/__tests__/use-player-characters.test.tsx b/apps/web/src/hooks/__tests__/use-player-characters.test.tsx index c79457d..101243e 100644 --- a/apps/web/src/hooks/__tests__/use-player-characters.test.tsx +++ b/apps/web/src/hooks/__tests__/use-player-characters.test.tsx @@ -112,6 +112,49 @@ describe("usePlayerCharacters", () => { expect(result.current.characters[0].name).toBe("Vex'ahlia"); }); + it("createCharacter assigns a fresh id after rehydration from persistence", () => { + const stored = [ + { + id: playerCharacterId("pc-1"), + name: "Mikka", + ac: 12, + maxHp: 58, + color: undefined, + icon: undefined, + }, + { + id: playerCharacterId("pc-3"), + name: "Bob", + ac: 14, + maxHp: 40, + color: undefined, + icon: undefined, + }, + ]; + const adapters = createTestAdapters({ playerCharacters: stored }); + + const { result } = renderHook(() => usePlayerCharacters(), { + wrapper: ({ children }: { children: ReactNode }) => ( + {children} + ), + }); + + act(() => { + result.current.createCharacter( + "Charlie", + 13, + 25, + undefined, + undefined, + undefined, + ); + }); + + const ids = result.current.characters.map((pc) => pc.id); + expect(new Set(ids).size).toBe(ids.length); + expect(ids).toContain(playerCharacterId("pc-4")); + }); + it("deleteCharacter removes character and persists", () => { const { result } = renderHook(() => usePlayerCharacters(), { wrapper }); diff --git a/apps/web/src/hooks/use-player-characters.ts b/apps/web/src/hooks/use-player-characters.ts index 12638fa..9a747e0 100644 --- a/apps/web/src/hooks/use-player-characters.ts +++ b/apps/web/src/hooks/use-player-characters.ts @@ -9,10 +9,18 @@ import { isDomainError, playerCharacterId } from "@initiative/domain"; import { useCallback, useEffect, useRef, useState } from "react"; import { useAdapters } from "../contexts/adapter-context.js"; -let nextPcId = 0; +const PC_ID_PATTERN = /^pc-(\d+)$/; -function generatePcId(): PlayerCharacterId { - return playerCharacterId(`pc-${++nextPcId}`); +function generatePcId(existing: readonly PlayerCharacter[]): PlayerCharacterId { + let max = 0; + for (const pc of existing) { + const match = PC_ID_PATTERN.exec(pc.id); + if (match) { + const n = Number(match[1]); + if (n > max) max = n; + } + } + return playerCharacterId(`pc-${max + 1}`); } interface EditFields { @@ -55,7 +63,7 @@ export function usePlayerCharacters() { icon: string | undefined, level: number | undefined, ) => { - const id = generatePcId(); + const id = generatePcId(charactersRef.current); const result = createPlayerCharacterUseCase( makeStore(), id,