diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 25ef71d..f1d5717 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -55,11 +55,10 @@ function useActionBarAnimation(combatantCount: number) { const empty = combatantCount === 0; const risingClass = rising ? " animate-rise-to-center" : ""; const settlingClass = settling ? " animate-settle-to-bottom" : ""; - const topBarClass = settling - ? " animate-slide-down-in" - : topBarExiting - ? " absolute inset-x-0 top-0 z-10 px-4 animate-slide-up-out" - : ""; + const exitingClass = topBarExiting + ? " absolute inset-x-0 top-0 z-10 px-4 animate-slide-up-out" + : ""; + const topBarClass = settling ? " animate-slide-down-in" : exitingClass; const showTopBar = !empty || topBarExiting; return { @@ -194,7 +193,7 @@ export function App() { block: "nearest", behavior: "smooth", }); - }, [encounter.activeIndex]); + }, []); // Auto-show stat block for the active combatant when turn changes, // but only when the viewport is wide enough to show it alongside the tracker. @@ -203,7 +202,7 @@ export function App() { useEffect(() => { if (prevActiveIndexRef.current === encounter.activeIndex) return; prevActiveIndexRef.current = encounter.activeIndex; - if (!window.matchMedia("(min-width: 1024px)").matches) return; + if (!globalThis.matchMedia("(min-width: 1024px)").matches) return; const active = encounter.combatants[encounter.activeIndex]; if (!active?.creatureId || !isLoaded) return; sidePanel.showCreature(active.creatureId as CreatureId); @@ -216,8 +215,8 @@ export function App() { return (
-
- {actionBarAnim.showTopBar && ( +
+ {!!actionBarAnim.showTopBar && (
+
{/* Scrollable area — combatant list */} -
+
{encounter.combatants.map((c, i) => ( {/* Pinned Stat Block Panel (left) */} - {sidePanel.pinnedCreatureId && sidePanel.isWideDesktop && ( + {!!sidePanel.pinnedCreatureId && sidePanel.isWideDesktop && ( { const parentHandler = vi.fn(); render( // biome-ignore lint/a11y/noStaticElementInteractions: test wrapper + // biome-ignore lint/a11y/noNoninteractiveElementInteractions: test wrapper
} diff --git a/apps/web/src/__tests__/stat-block-collapse-pin.test.tsx b/apps/web/src/__tests__/stat-block-collapse-pin.test.tsx index 5608abe..84311c7 100644 --- a/apps/web/src/__tests__/stat-block-collapse-pin.test.tsx +++ b/apps/web/src/__tests__/stat-block-collapse-pin.test.tsx @@ -6,6 +6,9 @@ import { cleanup, fireEvent, render, screen } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { StatBlockPanel } from "../components/stat-block-panel"; +const CLOSE_REGEX = /close/i; +const COLLAPSE_REGEX = /collapse/i; + const CREATURE_ID = "srd:goblin" as CreatureId; const CREATURE: Creature = { id: CREATURE_ID, @@ -26,7 +29,7 @@ const CREATURE: Creature = { }; function mockMatchMedia(matches: boolean) { - Object.defineProperty(window, "matchMedia", { + Object.defineProperty(globalThis, "matchMedia", { writable: true, value: vi.fn().mockImplementation((query: string) => ({ matches, @@ -92,7 +95,7 @@ describe("Stat Block Panel Collapse/Expand and Pin", () => { screen.getByRole("button", { name: "Collapse stat block panel" }), ).toBeInTheDocument(); expect( - screen.queryByRole("button", { name: /close/i }), + screen.queryByRole("button", { name: CLOSE_REGEX }), ).not.toBeInTheDocument(); }); @@ -247,7 +250,7 @@ describe("Stat Block Panel Collapse/Expand and Pin", () => { it("pinned panel has no collapse button", () => { renderPanel({ panelRole: "pinned", side: "left" }); expect( - screen.queryByRole("button", { name: /collapse/i }), + screen.queryByRole("button", { name: COLLAPSE_REGEX }), ).not.toBeInTheDocument(); }); diff --git a/apps/web/src/adapters/bestiary-adapter.ts b/apps/web/src/adapters/bestiary-adapter.ts index 03a0e35..ba2a5b8 100644 --- a/apps/web/src/adapters/bestiary-adapter.ts +++ b/apps/web/src/adapters/bestiary-adapter.ts @@ -9,6 +9,8 @@ import type { import { creatureId, proficiencyBonus } from "@initiative/domain"; import { stripTags } from "./strip-tags.js"; +const LEADING_DIGITS_REGEX = /^(\d+)/; + // --- Raw 5etools types (minimal, for parsing) --- interface RawMonster { @@ -168,7 +170,7 @@ function extractAc(ac: RawMonster["ac"]): { } if ("special" in first) { // Variable AC (e.g. spell summons) — parse leading number if possible - const match = first.special.match(/^(\d+)/); + const match = first.special.match(LEADING_DIGITS_REGEX); return { value: match ? Number(match[1]) : 0, source: first.special, diff --git a/apps/web/src/adapters/strip-tags.ts b/apps/web/src/adapters/strip-tags.ts index 9aae1ed..3ab37f9 100644 --- a/apps/web/src/adapters/strip-tags.ts +++ b/apps/web/src/adapters/strip-tags.ts @@ -53,7 +53,7 @@ export function stripTags(text: string): string { // {@atkr type} → mapped attack roll text result = result.replace(/\{@atkr\s+([^}]+)\}/g, (_, type: string) => { - return ATKR_MAP[type.trim()] ?? `Attack Roll:`; + return ATKR_MAP[type.trim()] ?? "Attack Roll:"; }); // {@actSave ability} → "Ability saving throw" diff --git a/apps/web/src/components/ac-shield.tsx b/apps/web/src/components/ac-shield.tsx index e276c41..4d8a8d4 100644 --- a/apps/web/src/components/ac-shield.tsx +++ b/apps/web/src/components/ac-shield.tsx @@ -12,7 +12,7 @@ export function AcShield({ value, onClick, className }: AcShieldProps) { type="button" onClick={onClick} className={cn( - "relative inline-flex items-center justify-center text-sm tabular-nums text-muted-foreground transition-colors hover:text-hover-neutral", + "relative inline-flex items-center justify-center text-muted-foreground text-sm tabular-nums transition-colors hover:text-hover-neutral", className, )} style={{ width: 28, height: 32 }} @@ -29,8 +29,8 @@ export function AcShield({ value, onClick, className }: AcShieldProps) { > - - {value !== undefined ? value : "\u2014"} + + {value == null ? "\u2014" : String(value)} ); diff --git a/apps/web/src/components/action-bar.tsx b/apps/web/src/components/action-bar.tsx index 8aeea6d..3ec7afb 100644 --- a/apps/web/src/components/action-bar.tsx +++ b/apps/web/src/components/action-bar.tsx @@ -85,20 +85,20 @@ function AddModeSuggestions({
{pcMatches.length > 0 && ( <> -
+
Players
    @@ -113,18 +113,18 @@ function AddModeSuggestions({
  • @@ -144,19 +144,18 @@ function AddModeSuggestions({
  • @@ -578,7 +577,7 @@ export function ActionBar({ {!browseMode && nameInput.length >= 2 && !hasSuggestions && ( )} - {showRollAllInitiative && onRollAllInitiative && ( + {showRollAllInitiative && !!onRollAllInitiative && ( @@ -54,7 +55,7 @@ export function BulkImportPrompt({ return (
    -
    +
    Loading sources... {processed}/{importState.total}
    @@ -74,23 +75,20 @@ export function BulkImportPrompt({ return (
    -

    +

    Import All Sources

    -

    +

    Load stat block data for all {totalSources} sources at once.

    -