Files
initiative/specs/035-statblock-fold-pin/tasks.md
Lukas 460c65bf49 Implement stat block panel fold/unfold and pin-to-second-panel
Replace the close button and heading with fold/unfold controls that
collapse the panel to a slim right-edge tab showing the creature name
vertically, and add a pin button (xl+ viewports with creature loaded)
that opens the creature in a second left-side panel for simultaneous
reference. Fold state is respected on turn change. 19 acceptance tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:18:15 +01:00

12 KiB

Tasks: Stat Block Panel Fold/Unfold and Pin

Input: Design documents from /specs/035-statblock-fold-pin/ Prerequisites: plan.md (required), spec.md (required for user stories), research.md, data-model.md

Tests: Acceptance scenario tests in Phase 7 (apps/web/src/__tests__/stat-block-fold-pin.test.tsx).

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, US3)
  • Include exact file paths in descriptions

Phase 1: Setup (Shared Infrastructure)

Purpose: CSS utilities needed by all user stories

  • T001 Add CSS transition utility for panel slide animation (transition: translate 200ms ease-out) in apps/web/src/index.css
  • T002 [P] Add CSS utility for vertical text (writing-mode: vertical-rl) in apps/web/src/index.css

Phase 2: Foundational (Blocking Prerequisites)

Purpose: Refactor StatBlockPanel props interface to support role-based rendering before any user story work

CRITICAL: No user story work can begin until this phase is complete

  • T003 Update StatBlockPanelProps interface in apps/web/src/components/stat-block-panel.tsx: add panelRole ("browse" | "pinned"), isFolded (boolean), onToggleFold (callback), onPin (callback), onUnpin (callback), showPinButton (boolean), side ("left" | "right"), onDismiss (callback for mobile backdrop). Remove onClose prop.
  • T004 Update App.tsx to pass the new props to StatBlockPanel: set panelRole="browse", side="right", wire onDismiss to clear selectedCreatureId, add isRightPanelFolded state (default false), wire onToggleFold. File: apps/web/src/App.tsx

Checkpoint: App compiles and existing panel behavior works with new prop interface (fold/pin not yet functional)


Phase 3: User Story 1 - Fold and Unfold Stat Block Panel (Priority: P1) MVP

Goal: Replace close button with fold/unfold toggle; panel collapses to slim right-edge tab with creature name and smooth animation

Independent Test: Open a stat block, fold it, verify slim tab with creature name appears on right edge, unfold it, verify full panel returns with same creature

Implementation for User Story 1

  • T005 [US1] Remove "Stat Block" heading text (panelTitle / <span> element) from both desktop and mobile header sections in apps/web/src/components/stat-block-panel.tsx
  • T006 [US1] Replace the close button (X icon) with a fold toggle button (use PanelRightClose / PanelRightOpen Lucide icons or similar chevron) in the desktop header of apps/web/src/components/stat-block-panel.tsx. Wire to onToggleFold prop.
  • T007 [US1] Implement the folded tab view in the desktop branch of apps/web/src/components/stat-block-panel.tsx: when isFolded is true, render a 40px-wide clickable strip anchored to the right edge showing the creature name vertically (using the vertical text CSS utility from T002). Clicking the tab calls onToggleFold.
  • T008 [US1] Add the CSS slide transition to the desktop panel container in apps/web/src/components/stat-block-panel.tsx: apply the transition utility (from T001), toggle between translate-x-0 (expanded) and translate-x-[calc(100%-40px)] (folded) based on isFolded prop. The folded tab must remain visible at the viewport edge.
  • T009 [US1] Update mobile drawer behavior in apps/web/src/components/stat-block-panel.tsx: replace X close button with fold icon matching desktop. Backdrop click calls onDismiss (full dismiss). Fold toggle on mobile also calls onDismiss (since mobile has no folded tab state — fold = dismiss on mobile).
  • T010 [US1] Update auto-show logic in apps/web/src/App.tsx: when active combatant changes and selectedCreatureId is set, also set isRightPanelFolded = false to auto-unfold the panel on turn change.

Checkpoint: Panel folds to slim tab on desktop, unfolds on click, no close button, no heading, mobile drawer preserved with fold-as-dismiss, auto-unfold on turn change works


Phase 4: User Story 2 - Pin Creature to Second Panel (Priority: P2)

Goal: Pin button copies current creature to a left-side panel; right panel stays active for browsing; pin hidden on small screens

Independent Test: Open a stat block on xl+ viewport, click pin, verify left panel appears with same creature, select different combatant, verify right panel updates while left stays, click unpin, verify left panel removed

Implementation for User Story 2

  • T011 [US2] Add pinnedCreatureId state and derived pinnedCreature (via getCreature) in apps/web/src/App.tsx. Add isWideDesktop state using matchMedia("(min-width: 1280px)") with change listener (same pattern as existing isDesktop in stat-block-panel.tsx).
  • T012 [US2] Wire pin/unpin callbacks in apps/web/src/App.tsx: onPin sets pinnedCreatureId = selectedCreatureId, onUnpin sets pinnedCreatureId = null. Pass showPinButton = isWideDesktop to browse panel.
  • T013 [US2] Render the pinned StatBlockPanel in apps/web/src/App.tsx: conditionally render a second <StatBlockPanel> when pinnedCreatureId is set and isWideDesktop is true, with panelRole="pinned", side="left", creature data from pinnedCreature, and onUnpin callback.
  • T014 [US2] Implement pin and unpin button rendering in apps/web/src/components/stat-block-panel.tsx: when panelRole="browse" and showPinButton is true, render a pin icon button (use Pin Lucide icon) in the header that calls onPin. When panelRole="pinned", render an unpin icon button (use PinOff Lucide icon) in the header that calls onUnpin.
  • T015 [US2] Update panel positioning in apps/web/src/components/stat-block-panel.tsx: use the side prop to apply left-0 border-r for pinned panel vs right-0 border-l for browse panel in the desktop layout classes.

Checkpoint: Pin button visible on xl+ screens, clicking it creates left panel with same creature, right panel browses independently, unpin removes left panel, pin button hidden below 1280px


Phase 5: User Story 3 - Fold Behavior with Pinned Panel (Priority: P3)

Goal: Right panel folds independently while pinned panel remains visible; unfolding restores last browsed creature

Independent Test: Pin a creature, fold right panel, verify pinned panel stays visible and right panel shows folded tab, unfold right panel, verify it shows last browsed creature

Implementation for User Story 3

  • T016 [US3] Verify fold/unfold independence in apps/web/src/App.tsx: ensure isRightPanelFolded state only affects the browse panel and does not hide or modify the pinned panel. The pinned panel has no fold state — it is always expanded when present.
  • T017 [US3] Verify fold preserves selectedCreatureId in apps/web/src/App.tsx: when isRightPanelFolded is toggled to true, selectedCreatureId must remain unchanged so unfolding restores the same creature.

Checkpoint: Both panels operate independently — fold right panel while pinned panel stays, unfold shows last creature


Phase 6: Polish & Cross-Cutting Concerns

Purpose: Edge cases, cleanup, and quality gate

  • T018 Handle viewport resize edge case in apps/web/src/App.tsx: when isWideDesktop changes from true to false and pinnedCreatureId is set, the pinned panel should stop rendering (handled by conditional render). When resized back to wide, pinned panel reappears with same creature.
  • T019 Verify bulk import mode works with fold/unfold in apps/web/src/components/stat-block-panel.tsx: bulkImportMode panels use the same fold toggle behavior. Folded tab shows "Bulk Import" as creature name fallback.
  • T020 Run pnpm check (full quality gate: audit + knip + biome + typecheck + test/coverage + jscpd) and fix any issues
  • T021 Verify pinned creature removal edge case in apps/web/src/App.tsx: pinnedCreatureId is not cleared on combatant removal — data resolved via getCreature independently.

Phase 7: Acceptance Tests

Purpose: Map spec acceptance scenarios to automated tests

  • T022 [P] [US1] Test fold/unfold behavior in apps/web/src/__tests__/stat-block-fold-pin.test.tsx: 7 tests covering fold button, no close button, no heading, folded tab with creature name, toggle callbacks, translate classes.
  • T023 [P] [US1] Test mobile behavior in apps/web/src/__tests__/stat-block-fold-pin.test.tsx: 3 tests covering fold button on mobile, backdrop dismiss, pinned panel not rendered on mobile.
  • T024 [P] [US2] Test pin/unpin behavior in apps/web/src/__tests__/stat-block-fold-pin.test.tsx: 7 tests covering pin/unpin buttons, callbacks, panel positioning (left/right).
  • T025 [P] [US3] Test fold independence with pinned panel in apps/web/src/__tests__/stat-block-fold-pin.test.tsx: 2 tests verifying pinned panel has no fold button and is always expanded.

Dependencies & Execution Order

Phase Dependencies

  • Setup (Phase 1): No dependencies — can start immediately
  • Foundational (Phase 2): Depends on Phase 1 (CSS utilities) — BLOCKS all user stories
  • User Story 1 (Phase 3): Depends on Phase 2 completion
  • User Story 2 (Phase 4): Depends on Phase 2 completion. Can run in parallel with US1 but integrates more cleanly after US1.
  • User Story 3 (Phase 5): Depends on US1 and US2 being complete (verifies their interaction)
  • Polish (Phase 6): Depends on all user stories being complete
  • Acceptance Tests (Phase 7): Depends on all user stories being complete. All test tasks [P] can run in parallel.

User Story Dependencies

  • User Story 1 (P1): Can start after Foundational (Phase 2) — no dependencies on other stories
  • User Story 2 (P2): Can start after Foundational (Phase 2) — recommended after US1 for cleaner integration
  • User Story 3 (P3): Depends on US1 + US2 (verifies combined behavior)

Within Each User Story

  • Props/state wiring before UI rendering
  • CSS utilities before components that use them
  • Core implementation before edge cases

Parallel Opportunities

  • T001 and T002 can run in parallel (different CSS utilities, same file but independent additions)
  • T005 and T006 modify different sections of stat-block-panel.tsx header — can be done together
  • T011 and T014 modify different files (App.tsx vs stat-block-panel.tsx) — can run in parallel

Parallel Example: User Story 1

# T005 + T006 can be done in one pass (both modify header in stat-block-panel.tsx):
Task: "Remove Stat Block heading and replace close with fold toggle in apps/web/src/components/stat-block-panel.tsx"

# T007 + T008 are sequential (T007 creates the tab, T008 adds animation to it)

Parallel Example: User Story 2

# These modify different files and can run in parallel:
Task: "T011 — Add pinnedCreatureId state + isWideDesktop in apps/web/src/App.tsx"
Task: "T014 — Add pin/unpin buttons in apps/web/src/components/stat-block-panel.tsx"

Implementation Strategy

MVP First (User Story 1 Only)

  1. Complete Phase 1: CSS utilities (T001-T002)
  2. Complete Phase 2: Props refactor (T003-T004)
  3. Complete Phase 3: Fold/unfold (T005-T010)
  4. STOP and VALIDATE: Test fold/unfold independently on desktop and mobile
  5. Deploy/demo if ready — panel folds and unfolds, no close button, no heading

Incremental Delivery

  1. Setup + Foundational → App compiles with new prop interface
  2. Add User Story 1 → Test fold/unfold → Deploy/Demo (MVP!)
  3. Add User Story 2 → Test pin/unpin → Deploy/Demo
  4. Add User Story 3 → Verify combined behavior → Deploy/Demo
  5. Polish → Run quality gate
  6. Acceptance Tests → Verify all scenarios → Commit

Notes

  • [P] tasks = different files, no dependencies
  • [Story] label maps task to specific user story for traceability
  • Each user story should be independently completable and testable
  • Commit after each phase or logical group of tasks
  • Stop at any checkpoint to validate story independently
  • US3 is lightweight verification — most behavior should already work from US1 + US2 wiring