From ef50a77b07f876cc80a6c34ba2b21f8dee63a20b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20F=C3=B6rtsch?= Date: Thu, 5 Mar 2026 07:22:31 +0100 Subject: [PATCH] =?UTF-8?q?normalize=20biome=20config=20(lineWidth=2080),?= =?UTF-8?q?=20rename=20mise.toml=20=E2=86=92=20.mise.toml,=20add=20CLAUDE.?= =?UTF-8?q?md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- .gitignore | 4 +++ mise.toml => .mise.toml | 0 CLAUDE.md | 34 ++++++++++++++++++++++++ biome.json | 2 +- src/features/round/flip-card.tsx | 6 ++++- src/features/setup/setup-screen.test.tsx | 4 ++- src/features/setup/setup-screen.tsx | 15 ++++++++--- src/shared/components/ui/badge.tsx | 5 +++- src/shared/components/ui/input.tsx | 33 ++++++++++++----------- src/shared/stores/game-store.test.ts | 13 ++++++--- src/shared/stores/game-store.ts | 4 ++- src/test-setup.ts | 5 +++- 12 files changed, 97 insertions(+), 28 deletions(-) rename mise.toml => .mise.toml (100%) create mode 100644 CLAUDE.md diff --git a/.gitignore b/.gitignore index c63d997..f66e33a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ dist/ node_modules/ .DS_Store *.local +.mise.local.toml +.env +.env.local +.env.*.local diff --git a/mise.toml b/.mise.toml similarity index 100% rename from mise.toml rename to .mise.toml diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..d0679d8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,34 @@ +# impstr — Imposter Party Game + +Static PWA party game (like Werewolf/Mafia). No backend, no accounts — runs entirely in the browser. + +## Stack + +- **Frontend:** React 19, Vite, Tailwind CSS 4, TanStack Router, Zustand +- **Linting:** Biome (tabs, 80 chars, double quotes) + +## Project Structure + +``` +src/ +├── features/ ← game logic, UI components +├── routes/ ← TanStack Router file-based routes +└── shared/ ← shared components, utilities +``` + +## Local Development + +```bash +bun install +bun run dev +``` + +## Deployment + +```bash +./deploy.sh +``` + +Deploys to Uberspace (`serve.uber.space`): +- Static files → `/var/www/virtual/serve/html/impstr/` +- No backend, no database diff --git a/biome.json b/biome.json index 0d5cc8e..c01b2dc 100644 --- a/biome.json +++ b/biome.json @@ -5,7 +5,7 @@ }, "formatter": { "indentStyle": "tab", - "lineWidth": 100 + "lineWidth": 80 }, "linter": { "enabled": true, diff --git a/src/features/round/flip-card.tsx b/src/features/round/flip-card.tsx index b25570f..3a0d732 100644 --- a/src/features/round/flip-card.tsx +++ b/src/features/round/flip-card.tsx @@ -24,7 +24,11 @@ export function FlipCard({ word, flipped, onFlip }: FlipCardProps) { return (
{ if (e.key === "Enter" || e.key === " ") onFlip(); diff --git a/src/features/setup/setup-screen.test.tsx b/src/features/setup/setup-screen.test.tsx index 77e2641..c95913c 100644 --- a/src/features/setup/setup-screen.test.tsx +++ b/src/features/setup/setup-screen.test.tsx @@ -24,7 +24,9 @@ describe("SetupScreen", () => { it("renders the setup screen", () => { render(); expect(screen.getByText("Imposter")).toBeInTheDocument(); - expect(screen.getByPlaceholderText("Spieler hinzufügen…")).toBeInTheDocument(); + expect( + screen.getByPlaceholderText("Spieler hinzufügen…"), + ).toBeInTheDocument(); }); it("adds a player via button click", () => { diff --git a/src/features/setup/setup-screen.tsx b/src/features/setup/setup-screen.tsx index 628d72b..efd81fc 100644 --- a/src/features/setup/setup-screen.tsx +++ b/src/features/setup/setup-screen.tsx @@ -39,7 +39,9 @@ export function SetupScreen() {
Partyspiel

Imposter

-

Findet heraus, wer das geheime Wort nicht kennt!

+

+ Findet heraus, wer das geheime Wort nicht kennt! +

@@ -83,14 +85,21 @@ export function SetupScreen() { Alle löschen )} -
{!canStart && players.length > 0 && ( -

Mindestens {MIN_PLAYERS} Spieler benötigt

+

+ Mindestens {MIN_PLAYERS} Spieler benötigt +

)} ); diff --git a/src/shared/components/ui/badge.tsx b/src/shared/components/ui/badge.tsx index 07388f1..89c6f92 100644 --- a/src/shared/components/ui/badge.tsx +++ b/src/shared/components/ui/badge.tsx @@ -1,7 +1,10 @@ import { cn } from "@/shared/lib/utils"; import type { HTMLAttributes } from "react"; -export function Badge({ className, ...props }: HTMLAttributes) { +export function Badge({ + className, + ...props +}: HTMLAttributes) { return ( >( - ({ className, ...props }, ref) => { - return ( - - ); - }, -); +export const Input = forwardRef< + HTMLInputElement, + InputHTMLAttributes +>(({ className, ...props }, ref) => { + return ( + + ); +}); Input.displayName = "Input"; diff --git a/src/shared/stores/game-store.test.ts b/src/shared/stores/game-store.test.ts index d264b0a..f194f3c 100644 --- a/src/shared/stores/game-store.test.ts +++ b/src/shared/stores/game-store.test.ts @@ -27,7 +27,9 @@ describe("game-store", () => { it("adds a player and persists to localStorage", () => { getState().addPlayer("Alice"); expect(getState().players).toEqual(["Alice"]); - expect(JSON.parse(localStorage.getItem("impstr-players") ?? "[]")).toEqual(["Alice"]); + expect( + JSON.parse(localStorage.getItem("impstr-players") ?? "[]"), + ).toEqual(["Alice"]); }); it("trims whitespace", () => { @@ -58,7 +60,9 @@ describe("game-store", () => { getState().addPlayer("Bob"); getState().clearPlayers(); expect(getState().players).toEqual([]); - expect(JSON.parse(localStorage.getItem("impstr-players") ?? "[]")).toEqual([]); + expect( + JSON.parse(localStorage.getItem("impstr-players") ?? "[]"), + ).toEqual([]); }); }); @@ -76,7 +80,10 @@ describe("game-store", () => { }); it("filters out non-string entries", () => { - localStorage.setItem("impstr-players", JSON.stringify(["Alice", 42, null, "Bob"])); + localStorage.setItem( + "impstr-players", + JSON.stringify(["Alice", 42, null, "Bob"]), + ); getState().loadPlayers(); expect(getState().players).toEqual(["Alice", "Bob"]); }); diff --git a/src/shared/stores/game-store.ts b/src/shared/stores/game-store.ts index 2cb05ed..3d5cf9f 100644 --- a/src/shared/stores/game-store.ts +++ b/src/shared/stores/game-store.ts @@ -93,7 +93,9 @@ export const useGameStore = create((set, get) => ({ if (raw) { const parsed = JSON.parse(raw); if (Array.isArray(parsed)) { - set({ players: parsed.filter((p): p is string => typeof p === "string") }); + set({ + players: parsed.filter((p): p is string => typeof p === "string"), + }); } } } catch { diff --git a/src/test-setup.ts b/src/test-setup.ts index 7e1502d..2dc78c1 100644 --- a/src/test-setup.ts +++ b/src/test-setup.ts @@ -20,4 +20,7 @@ const localStorageMock: Storage = { key: (index: number) => [...store.keys()][index] ?? null, }; -Object.defineProperty(globalThis, "localStorage", { value: localStorageMock, writable: true }); +Object.defineProperty(globalThis, "localStorage", { + value: localStorageMock, + writable: true, +});