diff --git a/.specify/scripts/bash/check-prerequisites.sh b/.specify/scripts/bash/check-prerequisites.sh index 98e387c..b888085 100755 --- a/.specify/scripts/bash/check-prerequisites.sh +++ b/.specify/scripts/bash/check-prerequisites.sh @@ -122,6 +122,10 @@ fi # Build list of available documents docs=() +# Include required docs that passed validation above +[[ -f "$FEATURE_SPEC" ]] && docs+=("spec.md") +[[ -f "$IMPL_PLAN" ]] && docs+=("plan.md") + # Always check these optional docs [[ -f "$RESEARCH" ]] && docs+=("research.md") [[ -f "$DATA_MODEL" ]] && docs+=("data-model.md") diff --git a/CLAUDE.md b/CLAUDE.md index 4e3ba2d..e7c5b28 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -72,6 +72,8 @@ The constitution (`.specify/memory/constitution.md`) governs all feature work: - N/A (no storage changes — existing localStorage persistence unchanged) (019-combatant-row-declutter) - TypeScript 5.8 (strict mode, verbatimModuleSyntax) + React 19, Tailwind CSS v4, Lucide React (icons) (020-fix-zero-hp-opacity) - Browser localStorage (existing adapter, extended for creatureId) (021-bestiary-statblock) +- TypeScript 5.8 (strict mode, verbatimModuleSyntax) + React 19, Tailwind CSS v4 + React 19, Tailwind CSS v4, Vite 6 (022-fixed-layout-bars) +- N/A (no storage changes -- purely presentational) (022-fixed-layout-bars) ## Recent Changes - 003-remove-combatant: Added TypeScript 5.x (strict mode, verbatimModuleSyntax) + React 19, Vite diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index fdd0631..621e1b1 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,5 +1,5 @@ import type { Creature } from "@initiative/domain"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { ActionBar } from "./components/action-bar"; import { CombatantRow } from "./components/combatant-row"; import { StatBlockPanel } from "./components/stat-block-panel"; @@ -64,6 +64,15 @@ export function App() { [isLoaded, search], ); + // Auto-scroll to the active combatant when the turn changes + const activeRowRef = useRef(null); + useEffect(() => { + activeRowRef.current?.scrollIntoView({ + 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 useEffect(() => { @@ -77,62 +86,68 @@ export function App() { }, [encounter.activeIndex, encounter.combatants, getCreature, isLoaded]); return ( -
-
- {/* Header */} -
-

- Initiative Tracker -

-
- - {/* Turn Navigation */} - - - {/* Combatant List */} -
- {encounter.combatants.length === 0 ? ( -

- No combatants yet — add one to get started -

- ) : ( - encounter.combatants.map((c, i) => ( - handleCombatantStatBlock(c.creatureId as string) - : undefined - } - /> - )) - )} +
+
+ {/* Turn Navigation — fixed at top */} +
+
- {/* Action Bar */} - + {/* Scrollable area — combatant list */} +
+
+

+ Initiative Tracker +

+
+ +
+ {encounter.combatants.length === 0 ? ( +

+ No combatants yet — add one to get started +

+ ) : ( + encounter.combatants.map((c, i) => ( + handleCombatantStatBlock(c.creatureId as string) + : undefined + } + /> + )) + )} +
+
+ + {/* Action Bar — fixed at bottom */} +
+ +
{/* Stat Block Panel */} diff --git a/apps/web/src/components/combatant-row.tsx b/apps/web/src/components/combatant-row.tsx index e3fe52b..93af558 100644 --- a/apps/web/src/components/combatant-row.tsx +++ b/apps/web/src/components/combatant-row.tsx @@ -4,7 +4,7 @@ import { deriveHpStatus, } from "@initiative/domain"; import { BookOpen, Brain, Shield, X } from "lucide-react"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { type Ref, useCallback, useEffect, useRef, useState } from "react"; import { cn } from "../lib/utils"; import { ConditionPicker } from "./condition-picker"; import { ConditionTags } from "./condition-tags"; @@ -267,6 +267,7 @@ function AcDisplay({ } export function CombatantRow({ + ref, combatant, isActive, onRename, @@ -278,7 +279,7 @@ export function CombatantRow({ onToggleCondition, onToggleConcentration, onShowStatBlock, -}: CombatantRowProps) { +}: CombatantRowProps & { ref?: Ref }) { const { id, name, initiative, maxHp, currentHp } = combatant; const status = deriveHpStatus(currentHp, maxHp); const dimmed = status === "unconscious"; @@ -313,6 +314,7 @@ export function CombatantRow({ return (