Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@
// the player in its `SessionRoom` and broadcasts `SEAT_CONFIRMED` to all
// sockets in the room. We track the seated set here so the UI can render
// a peer-presence indicator and confirm the handshake landed.
const [seatedPlayers, setSeatedPlayers] = useState<

Check failure on line 250 in src/App.tsx

View workflow job for this annotation

GitHub Actions / lint + typecheck + test

'seatedPlayers' is assigned a value but never used
Record<string, string /* character_slot */>
>({});

Expand Down Expand Up @@ -475,7 +475,15 @@
// names; mutant_wasteland uses class+name). The DB primary key
// for the seat is (slug, player_id), so the slot is mostly
// descriptive metadata that flows back via SEAT_CONFIRMED.
//
// The server emits `character.model_dump()` which nests the
// name under `core.name` (Python pydantic model). The flat
// `name` / `character_name` fallbacks are kept for any
// historical or alternative emission paths but the canonical
// location is `core.name`.
const charCore = charData?.core as Record<string, unknown> | undefined;
const charNameForSeat =
(charCore?.name as string | undefined) ??
(charData?.name as string | undefined) ??
(charData?.character_name as string | undefined);
if (charNameForSeat && sendRef.current) {
Expand Down Expand Up @@ -1127,7 +1135,7 @@
});
}
}
}, [readyState, connected, send]);

Check warning on line 1138 in src/App.tsx

View workflow job for this annotation

GitHub Actions / lint + typecheck + test

React Hook useEffect has a missing dependency: 'displayName'. Either include it or remove the dependency array

// If connection fails, clear saved session so we don't loop
useEffect(() => {
Expand Down
11 changes: 10 additions & 1 deletion src/components/CharacterCreation/CharacterCreation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,19 @@ export function CharacterCreation({ scene, loading, onRespond }: CharacterCreati
// turn. "Waiting for the narrator..." is true regardless of which
// chargen step we're between. Genre packs can override via
// ``scene.loading_text``.
//
// The heartbeat dot is the "system is alive" cue — playtest 2026-04-24
// flagged the all-text spinner as indistinguishable from a crash. Pulse
// matches the in-game MultiplayerTurnBanner idiom (emerald, w-2 h-2).
return (
<div data-testid="character-creation">
<div data-testid="creation-loading" role="status"
className="flex items-center justify-center min-h-[200px]">
className="flex items-center justify-center gap-2 min-h-[200px]">
<span
data-testid="chargen-heartbeat-dot"
aria-hidden="true"
className="inline-block w-2 h-2 rounded-full shrink-0 bg-emerald-500 animate-pulse"
/>
<p className="text-sm italic text-muted-foreground/50 animate-pulse">
{scene?.loading_text ?? "Waiting for the narrator..."}
</p>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { CharacterCreation } from "../CharacterCreation";

describe("CharacterCreation loading view", () => {
it("renders the heartbeat dot alongside the wait copy", () => {
render(
<CharacterCreation scene={null} loading={true} onRespond={() => {}} />,
);
const loading = screen.getByTestId("creation-loading");
expect(loading).toBeInTheDocument();
expect(loading).toHaveTextContent(/waiting for the narrator/i);

const dot = screen.getByTestId("chargen-heartbeat-dot");
expect(dot).toBeInTheDocument();
expect(dot.className).toMatch(/animate-pulse/);
expect(dot.className).toMatch(/bg-emerald-500/);
});

it("uses the genre-pack scene loading_text when provided", () => {
render(
<CharacterCreation
scene={
{
scene_index: 1,
total_scenes: 4,
loading_text: "The bones are casting...",
} as never
}
loading={true}
onRespond={() => {}}
/>,
);
expect(screen.getByTestId("creation-loading")).toHaveTextContent(
/the bones are casting/i,
);
// Heartbeat must still render even with custom copy.
expect(screen.getByTestId("chargen-heartbeat-dot")).toBeInTheDocument();
});
});
Loading