Files
initiative/specs/022-fixed-layout-bars/plan.md

5.5 KiB

Implementation Plan: Fixed Layout Bars

Branch: 022-fixed-layout-bars | Date: 2026-03-09 | Spec: 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)

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)

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.