Implement the 022-fixed-layout-bars feature that pins turn navigation to the top and add-creature bar to the bottom of the encounter tracker with only the combatant list scrolling between them, and auto-scrolls to the active combatant on turn change
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<HTMLDivElement>(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,24 +86,26 @@ export function App() {
|
||||
}, [encounter.activeIndex, encounter.combatants, getCreature, isLoaded]);
|
||||
|
||||
return (
|
||||
<div className="h-screen overflow-y-auto">
|
||||
<div className="mx-auto flex w-full max-w-2xl flex-col gap-6 px-4 py-8">
|
||||
{/* Header */}
|
||||
<header className="space-y-1">
|
||||
<h1 className="text-2xl font-bold tracking-tight">
|
||||
Initiative Tracker
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
{/* Turn Navigation */}
|
||||
<div className="flex h-screen flex-col">
|
||||
<div className="mx-auto flex w-full max-w-2xl flex-1 flex-col gap-6 px-4 min-h-0">
|
||||
{/* Turn Navigation — fixed at top */}
|
||||
<div className="shrink-0 pt-8">
|
||||
<TurnNavigation
|
||||
encounter={encounter}
|
||||
onAdvanceTurn={advanceTurn}
|
||||
onRetreatTurn={retreatTurn}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Combatant List */}
|
||||
<div className="flex flex-1 flex-col gap-1">
|
||||
{/* Scrollable area — combatant list */}
|
||||
<div className="flex-1 overflow-y-auto min-h-0">
|
||||
<header className="space-y-1 mb-6">
|
||||
<h1 className="text-2xl font-bold tracking-tight">
|
||||
Initiative Tracker
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<div className="flex flex-col gap-1 pb-2">
|
||||
{encounter.combatants.length === 0 ? (
|
||||
<p className="py-12 text-center text-sm text-muted-foreground">
|
||||
No combatants yet — add one to get started
|
||||
@@ -103,6 +114,7 @@ export function App() {
|
||||
encounter.combatants.map((c, i) => (
|
||||
<CombatantRow
|
||||
key={c.id}
|
||||
ref={i === encounter.activeIndex ? activeRowRef : null}
|
||||
combatant={c}
|
||||
isActive={i === encounter.activeIndex}
|
||||
onRename={editCombatant}
|
||||
@@ -122,8 +134,10 @@ export function App() {
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Bar */}
|
||||
{/* Action Bar — fixed at bottom */}
|
||||
<div className="shrink-0 pb-8">
|
||||
<ActionBar
|
||||
onAddCombatant={addCombatant}
|
||||
onAddFromBestiary={handleAddFromBestiary}
|
||||
@@ -134,6 +148,7 @@ export function App() {
|
||||
onShowStatBlock={handleShowStatBlock}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stat Block Panel */}
|
||||
<StatBlockPanel
|
||||
|
||||
@@ -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<HTMLDivElement> }) {
|
||||
const { id, name, initiative, maxHp, currentHp } = combatant;
|
||||
const status = deriveHpStatus(currentHp, maxHp);
|
||||
const dimmed = status === "unconscious";
|
||||
@@ -313,6 +314,7 @@ export function CombatantRow({
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"group rounded-md px-3 py-2 transition-colors",
|
||||
isActive
|
||||
|
||||
35
specs/022-fixed-layout-bars/checklists/requirements.md
Normal file
35
specs/022-fixed-layout-bars/checklists/requirements.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Specification Quality Checklist: Fixed Layout Bars
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2026-03-09
|
||||
**Feature**: [spec.md](../spec.md)
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [x] No implementation details (languages, frameworks, APIs)
|
||||
- [x] Focused on user value and business needs
|
||||
- [x] Written for non-technical stakeholders
|
||||
- [x] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||
- [x] Requirements are testable and unambiguous
|
||||
- [x] Success criteria are measurable
|
||||
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||
- [x] All acceptance scenarios are defined
|
||||
- [x] Edge cases are identified
|
||||
- [x] Scope is clearly bounded
|
||||
- [x] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [x] All functional requirements have clear acceptance criteria
|
||||
- [x] User scenarios cover primary flows
|
||||
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [x] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- All items pass. Spec is ready for `/speckit.clarify` or `/speckit.plan`.
|
||||
- This is a purely presentational/layout change with no domain or data implications.
|
||||
112
specs/022-fixed-layout-bars/plan.md
Normal file
112
specs/022-fixed-layout-bars/plan.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Implementation Plan: Fixed Layout Bars
|
||||
|
||||
**Branch**: `022-fixed-layout-bars` | **Date**: 2026-03-09 | **Spec**: [spec.md](./spec.md)
|
||||
**Input**: Feature specification from `/specs/022-fixed-layout-bars/spec.md`
|
||||
|
||||
## Summary
|
||||
|
||||
Pin the turn navigation bar to the top and the add-creature action bar to the bottom of the encounter tracker, making only the combatant list scrollable between them. This is achieved by converting the current single-scroll page layout into a CSS flexbox three-zone layout within the existing `h-screen` container.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: TypeScript 5.8 (strict mode, verbatimModuleSyntax) + React 19, Tailwind CSS v4
|
||||
**Primary Dependencies**: React 19, Tailwind CSS v4, Vite 6
|
||||
**Storage**: N/A (no storage changes -- purely presentational)
|
||||
**Testing**: Vitest (existing tests must continue to pass; no new domain/application tests needed)
|
||||
**Target Platform**: Web browser (desktop and tablet)
|
||||
**Project Type**: Web application (single-page app)
|
||||
**Performance Goals**: No performance impact -- CSS-only layout change
|
||||
**Constraints**: Must not break existing autocomplete dropdown, stat block panel, or responsive behavior
|
||||
**Scale/Scope**: Single file change (App.tsx layout restructure), possible minor adjustments to ActionBar
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
| Principle | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| I. Deterministic Domain Core | PASS | No domain changes |
|
||||
| II. Layered Architecture | PASS | Change is purely in the adapter/UI layer |
|
||||
| III. Agent Boundary | N/A | No agent features involved |
|
||||
| IV. Clarification-First | PASS | Spec assumptions documented; no ambiguity remains |
|
||||
| V. Escalation Gates | PASS | Implementation stays within spec scope |
|
||||
| VI. MVP Baseline Language | PASS | No scope exclusions needed |
|
||||
| VII. No Gameplay Rules | PASS | No gameplay logic involved |
|
||||
| Merge Gate | PASS | `pnpm check` will be run before commit |
|
||||
|
||||
**Post-design re-check**: All gates still pass. No data model, contracts, or domain changes introduced.
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/022-fixed-layout-bars/
|
||||
├── plan.md # This file
|
||||
├── research.md # Layout strategy research
|
||||
├── quickstart.md # Development quickstart
|
||||
├── spec.md # Feature specification
|
||||
└── checklists/
|
||||
└── requirements.md # Spec quality checklist
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
|
||||
```text
|
||||
apps/web/src/
|
||||
├── App.tsx # PRIMARY: Layout restructure (flex zones), auto-scroll on turn change
|
||||
└── components/
|
||||
├── combatant-row.tsx # Minor: accept ref prop for auto-scroll
|
||||
├── turn-navigation.tsx # No changes expected (flex-shrink-0 applied by parent)
|
||||
└── action-bar.tsx # Minor: verify dropdown not clipped by new layout
|
||||
```
|
||||
|
||||
**Structure Decision**: No new files or directories. The change restructures the existing layout in App.tsx by wrapping the three sections (TurnNavigation, combatant list, ActionBar) in a flex column where top and bottom are non-shrinkable and the middle is the sole scrollable area.
|
||||
|
||||
## Design
|
||||
|
||||
### Current Layout (App.tsx)
|
||||
|
||||
```
|
||||
div.h-screen.overflow-y-auto ← entire page scrolls
|
||||
div.mx-auto.max-w-2xl.flex-col.gap-6
|
||||
h1 "Initiative Tracker"
|
||||
TurnNavigation ← scrolls with page
|
||||
combatant list ← scrolls with page
|
||||
ActionBar ← scrolls with page
|
||||
StatBlockPanel (sibling, fixed)
|
||||
```
|
||||
|
||||
### Target Layout (App.tsx)
|
||||
|
||||
```
|
||||
div.h-screen.flex.flex-col ← no overflow on root
|
||||
div.mx-auto.max-w-2xl.flex-1.flex-col.min-h-0
|
||||
TurnNavigation (flex-shrink-0) ← fixed at top
|
||||
div.flex-1.overflow-y-auto ← only this scrolls
|
||||
h1 "Initiative Tracker"
|
||||
combatant list
|
||||
ActionBar (flex-shrink-0) ← fixed at bottom
|
||||
StatBlockPanel (sibling, fixed) ← unchanged
|
||||
```
|
||||
|
||||
### Key CSS Changes
|
||||
|
||||
1. **Root container**: Remove `overflow-y-auto`, add `flex flex-col`
|
||||
2. **Inner wrapper**: Add `flex-1 min-h-0` (min-h-0 is critical for flex children to shrink below content size)
|
||||
3. **TurnNavigation wrapper**: `flex-shrink-0` to prevent compression
|
||||
4. **Scrollable area**: New wrapper `div` with `flex-1 overflow-y-auto` containing the heading and combatant list
|
||||
5. **ActionBar wrapper**: `flex-shrink-0` to prevent compression
|
||||
6. **Padding**: Move `py-8` from the outer wrapper to the scrollable inner area; top/bottom bars get their own padding
|
||||
|
||||
### Auto-Scroll on Turn Change
|
||||
|
||||
When `encounter.activeIndex` changes, the active combatant's row scrolls into view using `scrollIntoView({ block: "nearest", behavior: "smooth" })`. This only scrolls if the row is off-screen (`block: "nearest"`), so no jarring jumps occur when the active row is already visible. Implemented via a `ref` on the active `CombatantRow` and a `useEffect` keyed on `activeIndex`.
|
||||
|
||||
### Autocomplete Dropdown
|
||||
|
||||
The ActionBar's suggestion dropdown uses `absolute bottom-full z-50`. Since ActionBar will be a flex-shrink-0 child at the bottom of the flex container (not inside the overflow-y-auto area), the dropdown will render upward overlaying the scrollable area. The `z-50` ensures it stays above combatant rows. The dropdown's parent container has `position: relative`, so no clipping from `overflow-y-auto` occurs (the dropdown is outside that container).
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
No constitution violations. No complexity justifications needed.
|
||||
29
specs/022-fixed-layout-bars/quickstart.md
Normal file
29
specs/022-fixed-layout-bars/quickstart.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Quickstart: 022-fixed-layout-bars
|
||||
|
||||
## What This Feature Does
|
||||
|
||||
Converts the encounter tracker from a single scrollable page to a three-zone layout:
|
||||
- **Fixed top bar**: Turn navigation (Previous / Round X / Next)
|
||||
- **Scrollable middle**: Combatant list
|
||||
- **Fixed bottom bar**: Add-creature action bar
|
||||
|
||||
## Key File
|
||||
|
||||
The only file that needs structural changes:
|
||||
|
||||
- `apps/web/src/App.tsx` — Main layout container
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
pnpm --filter web dev # Start dev server
|
||||
pnpm check # Run all checks before committing
|
||||
```
|
||||
|
||||
## Testing Approach
|
||||
|
||||
1. Add 15+ combatants to create overflow
|
||||
2. Scroll the combatant list — top and bottom bars must stay fixed
|
||||
3. Verify autocomplete suggestions appear above the bottom bar
|
||||
4. Test with stat block panel open on desktop
|
||||
5. Resize viewport to verify responsive behavior
|
||||
33
specs/022-fixed-layout-bars/research.md
Normal file
33
specs/022-fixed-layout-bars/research.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Research: Fixed Layout Bars
|
||||
|
||||
## R-001: Layout Strategy — Flexbox vs Fixed Positioning
|
||||
|
||||
**Decision**: Use CSS flexbox with `flex-shrink-0` on fixed bars and `flex-1 overflow-y-auto` on the scrollable middle section.
|
||||
|
||||
**Rationale**: The current layout already uses a flex column (`flex flex-col`) inside a `h-screen` container. Converting from a single scrollable page to a three-zone layout (fixed top, scrollable middle, fixed bottom) requires only removing `overflow-y-auto` from the root and adding it to the combatant list wrapper. This avoids `position: fixed` which would require manual offset calculations and could conflict with the existing StatBlockPanel fixed positioning.
|
||||
|
||||
**Alternatives considered**:
|
||||
- `position: sticky` on top/bottom bars: Would require the scroll container to be the viewport, and `sticky` bottom is unreliable across browsers.
|
||||
- `position: fixed` on both bars with padding offsets: More complex, requires measuring bar heights to offset content, and creates z-index/stacking context issues with the StatBlockPanel.
|
||||
|
||||
## R-002: Autocomplete Dropdown Behavior
|
||||
|
||||
**Decision**: The ActionBar suggestion dropdown (already `z-50`, `absolute`, `bottom-full`) will render upward from the fixed bottom bar. Since the dropdown uses absolute positioning within the ActionBar's relative container, it will naturally overlay the scrollable combatant list above it.
|
||||
|
||||
**Rationale**: The dropdown already positions itself above the input with `bottom-full`. With the ActionBar fixed at the bottom, the dropdown will appear overlaying the combatant list, which is the expected behavior. The `z-50` ensures it renders above combatant rows. No changes needed to the dropdown positioning.
|
||||
|
||||
**Alternatives considered**: Portal-based rendering — unnecessary complexity given current positioning already works.
|
||||
|
||||
## R-003: "Initiative Tracker" Heading Placement
|
||||
|
||||
**Decision**: The heading scrolls away with the combatant list (placed inside the scrollable area) or is removed from the visible area. Per the spec assumption, only the turn navigation bar and add-creature bar need to be fixed.
|
||||
|
||||
**Rationale**: Keeping the heading fixed would consume additional vertical space on small viewports. The heading is not a frequently-needed control.
|
||||
|
||||
**Alternatives considered**: Including heading in the fixed top area — rejected to maximize combatant list space.
|
||||
|
||||
## R-004: StatBlockPanel Interaction
|
||||
|
||||
**Decision**: No changes to StatBlockPanel. It uses `fixed top-0 right-0 bottom-0` on desktop and renders as a sibling outside the main content wrapper. The main content area already uses `max-w-2xl` which leaves room for the 400px panel.
|
||||
|
||||
**Rationale**: StatBlockPanel is positioned independently of the main content flow and will not be affected by changing the inner layout from single-scroll to flex zones.
|
||||
75
specs/022-fixed-layout-bars/spec.md
Normal file
75
specs/022-fixed-layout-bars/spec.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Feature Specification: Fixed Layout Bars
|
||||
|
||||
**Feature Branch**: `022-fixed-layout-bars`
|
||||
**Created**: 2026-03-09
|
||||
**Status**: Draft
|
||||
**Input**: User description: "I would like to have the 'add creature' bar as a fixed bottom bar and the bar where we have the current round and next/previous buttons as a top bar. Those should never scroll out of the screen and we should only scroll the combatants in between."
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - Fixed Navigation and Add-Creature Bars (Priority: P1)
|
||||
|
||||
As a DM managing a large encounter with many combatants, I want the round/turn navigation bar pinned to the top of the screen and the add-creature bar pinned to the bottom, so that I can always advance turns and add new creatures without scrolling away from the action.
|
||||
|
||||
**Why this priority**: This is the core of the feature. Without fixed positioning of both bars, the user must scroll to reach essential controls during combat, which interrupts the flow of play.
|
||||
|
||||
**Independent Test**: Can be fully tested by adding enough combatants to cause scrolling, then verifying both bars remain visible while scrolling through the combatant list.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** an encounter with enough combatants to overflow the viewport, **When** the user scrolls through the combatant list, **Then** the turn navigation bar (Previous / Round X / Next) remains fixed at the top of the encounter area and never scrolls out of view.
|
||||
2. **Given** an encounter with enough combatants to overflow the viewport, **When** the user scrolls through the combatant list, **Then** the add-creature bar remains fixed at the bottom of the encounter area and never scrolls out of view.
|
||||
3. **Given** an encounter with enough combatants to overflow the viewport, **When** the user scrolls, **Then** only the combatant list between the two bars scrolls.
|
||||
4. **Given** an encounter with few combatants that fit within the viewport, **When** the user views the tracker, **Then** the layout still displays the navigation bar at top and add-creature bar at bottom with the combatant list in between (no unnecessary empty space or visual artifacts).
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - Consistent Behavior Across Viewports (Priority: P2)
|
||||
|
||||
As a DM using different devices (desktop, tablet), I want the fixed-bar layout to work consistently regardless of screen size, so that the experience is reliable on any device I use at the table.
|
||||
|
||||
**Why this priority**: The tracker is used on various devices during play sessions. The fixed layout must adapt to different viewport sizes without breaking.
|
||||
|
||||
**Independent Test**: Can be tested by resizing the browser window to various sizes and verifying the bars stay fixed and the combatant list remains scrollable.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** any viewport width, **When** the encounter tracker is displayed, **Then** the top navigation bar and bottom add-creature bar remain fixed and the combatant list scrolls independently.
|
||||
2. **Given** a viewport where the stat block side panel is visible (wide desktop), **When** the user scrolls the combatant list, **Then** the fixed bars and scrollable list behave correctly alongside the stat block panel.
|
||||
|
||||
---
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- What happens when there are zero combatants? The top and bottom bars should still be visible with the empty state between them.
|
||||
- What happens when there is exactly one combatant? No scrolling needed; layout should not look broken.
|
||||
- What happens when the autocomplete/suggestion dropdown appears above the add-creature input? It should still be accessible and not clipped by the scrollable area.
|
||||
- What happens when the viewport is very short (e.g., 400px tall)? The combatant list area should still be scrollable, even if only a small portion is visible.
|
||||
- What happens when the active combatant is scrolled off-screen and the turn changes? The list auto-scrolls to bring the newly active combatant into view smoothly.
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: The turn navigation bar (Previous button, round/turn display, Next button) MUST remain fixed at the top of the encounter tracker area and never scroll out of view.
|
||||
- **FR-002**: The add-creature bar (name input, add button, bestiary search toggle) MUST remain fixed at the bottom of the encounter tracker area and never scroll out of view.
|
||||
- **FR-003**: The combatant list MUST be the only scrollable region between the fixed top and bottom bars.
|
||||
- **FR-004**: The scrollable combatant list MUST automatically allow scrolling (touch, wheel, keyboard) when combatants overflow the available vertical space.
|
||||
- **FR-005**: The autocomplete suggestion dropdown from the add-creature bar MUST remain fully visible and interactive, not clipped by the scroll container.
|
||||
- **FR-006**: The layout MUST preserve existing visual styling, spacing, and responsiveness of all components (turn navigation, combatant rows, action bar, stat block panel).
|
||||
- **FR-007**: When the active turn changes (via Next/Previous), the active combatant's row MUST automatically scroll into view if it is not currently visible in the scrollable area.
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: With 20+ combatants in an encounter, the turn navigation bar and add-creature bar remain visible at all scroll positions without any user action beyond normal scrolling.
|
||||
- **SC-002**: Users can add a new creature and navigate turns at any point during scrolling without needing to scroll to a specific position first.
|
||||
- **SC-003**: The layout renders correctly on viewports from 360px to 2560px wide and 400px to 1440px tall without visual breakage.
|
||||
- **SC-004**: Existing functionality (autocomplete suggestions, bestiary search, stat block panel, combatant interactions) continues to work identically after the layout change.
|
||||
|
||||
## Assumptions
|
||||
|
||||
- The "Initiative Tracker" heading can scroll away with the combatant list or be repositioned as needed -- the key fixed elements are only the turn navigation bar and the add-creature bar.
|
||||
- The stat block side panel (fixed right sidebar on desktop) remains independent of this layout change and continues to function as-is.
|
||||
- No new data entities or state changes are needed -- this is a purely presentational/layout change.
|
||||
100
specs/022-fixed-layout-bars/tasks.md
Normal file
100
specs/022-fixed-layout-bars/tasks.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Tasks: Fixed Layout Bars
|
||||
|
||||
**Input**: Design documents from `/specs/022-fixed-layout-bars/`
|
||||
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md
|
||||
|
||||
**Tests**: No test tasks included (not requested in spec). Existing tests must continue to pass.
|
||||
|
||||
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
|
||||
|
||||
## Format: `[ID] [P?] [Story] Description`
|
||||
|
||||
- **[P]**: Can run in parallel (different files, no dependencies)
|
||||
- **[Story]**: Which user story this task belongs to (e.g., US1, US2)
|
||||
- Include exact file paths in descriptions
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: User Story 1 - Fixed Navigation and Add-Creature Bars (Priority: P1) MVP
|
||||
|
||||
**Goal**: Pin TurnNavigation to top and ActionBar to bottom of the encounter area, with only the combatant list scrolling between them.
|
||||
|
||||
**Independent Test**: Add 15+ combatants so the list overflows the viewport. Scroll the combatant list and verify both bars remain visible. Also verify the layout looks correct with 0 and 1 combatants.
|
||||
|
||||
### Implementation for User Story 1
|
||||
|
||||
- [x] T001 [US1] Restructure the main layout container from single-scroll to three-zone flexbox layout in `apps/web/src/App.tsx`: remove `overflow-y-auto` from root div, add `flex flex-col` to root, add `flex-1 min-h-0` to inner wrapper, wrap TurnNavigation with `flex-shrink-0`, create a new scrollable `div` with `flex-1 overflow-y-auto` around the heading and combatant list, wrap ActionBar with `flex-shrink-0`, and adjust padding so `py-8` moves to the scrollable inner area while top/bottom bars get appropriate spacing
|
||||
- [x] T002 [US1] Verify the autocomplete suggestion dropdown from ActionBar remains fully visible and interactive above the fixed bottom bar — confirm `z-50` and `absolute bottom-full` positioning in `apps/web/src/components/action-bar.tsx` is not clipped by the new scroll container
|
||||
- [x] T003 [US1] Run `pnpm check` to verify all existing tests pass, linting is clean, and no regressions are introduced
|
||||
|
||||
**Checkpoint**: Turn navigation and add-creature bars are fixed. Combatant list scrolls independently. Autocomplete works. All checks pass.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: User Story 2 - Consistent Behavior Across Viewports (Priority: P2)
|
||||
|
||||
**Goal**: Ensure the fixed-bar layout works correctly across all viewport sizes and alongside the stat block side panel.
|
||||
|
||||
**Independent Test**: Resize the browser from 360px to 2560px wide and from 400px to 1440px tall. Verify bars stay fixed and combatant list scrolls at every size. Open the stat block panel on desktop and confirm no layout conflicts.
|
||||
|
||||
### Implementation for User Story 2
|
||||
|
||||
- [x] T004 [US2] Test and fix responsive layout issues in `apps/web/src/App.tsx` for narrow viewports (360px wide), very short viewports (400px tall), and wide viewports with the stat block panel open (1024px+ with 400px panel). Pass criteria: both bars visible, no horizontal overflow, combatant list scrollable (touch, wheel, and keyboard) at each tested size
|
||||
- [x] T005 [US2] Run `pnpm check` to verify no regressions after responsive fixes
|
||||
|
||||
**Checkpoint**: Layout works correctly across all target viewport sizes. All checks pass.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Polish & Cross-Cutting Concerns
|
||||
|
||||
- [x] T006 Verify edge cases
|
||||
- [x] T008 [US1] Add auto-scroll to active combatant on turn change: add `ref` prop to `CombatantRow` (`apps/web/src/components/combatant-row.tsx`), attach ref to active row in `App.tsx`, add `useEffect` keyed on `activeIndex` calling `scrollIntoView({ block: "nearest", behavior: "smooth" })`: zero combatants (empty state visible between bars), one combatant (no scroll needed, no visual artifacts), and very short viewport (combatant area still scrollable)
|
||||
- [x] T007 Final `pnpm check` to confirm merge readiness
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Execution Order
|
||||
|
||||
### Phase Dependencies
|
||||
|
||||
- **Phase 1 (US1)**: No prerequisites — can start immediately
|
||||
- **Phase 2 (US2)**: Depends on Phase 1 (T001 must be complete for responsive testing)
|
||||
- **Phase 3 (Polish)**: Depends on Phase 2
|
||||
|
||||
### Within User Story 1
|
||||
|
||||
- T001 (layout restructure) → T002 (autocomplete verification) → T003 (check gate)
|
||||
- T001 is the core change; T002 and T003 are verification steps
|
||||
|
||||
### Within User Story 2
|
||||
|
||||
- T004 (responsive fixes) → T005 (check gate)
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### MVP First (User Story 1 Only)
|
||||
|
||||
1. Complete T001: Restructure App.tsx layout
|
||||
2. Complete T002: Verify autocomplete dropdown
|
||||
3. Complete T003: Run pnpm check
|
||||
4. **STOP and VALIDATE**: Fixed bars work with scrollable combatant list
|
||||
5. This alone delivers the core feature value
|
||||
|
||||
### Incremental Delivery
|
||||
|
||||
1. US1 → Fixed layout works on typical desktop → Deploy/Demo (MVP!)
|
||||
2. US2 → Responsive edge cases fixed → Deploy/Demo
|
||||
3. Polish → Edge cases verified, merge-ready
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- This is a CSS-only layout change — no domain, application, or data model changes
|
||||
- Primary change is in a single file: `apps/web/src/App.tsx`
|
||||
- ActionBar (`action-bar.tsx`) may need minor verification but likely no code changes
|
||||
- StatBlockPanel is independent (fixed positioning as sibling) and should not be affected
|
||||
- The "Initiative Tracker" heading moves inside the scrollable area per spec assumption
|
||||
Reference in New Issue
Block a user