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>
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) inapps/web/src/index.css - T002 [P] Add CSS utility for vertical text (
writing-mode: vertical-rl) inapps/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
StatBlockPanelPropsinterface inapps/web/src/components/stat-block-panel.tsx: addpanelRole("browse" | "pinned"),isFolded(boolean),onToggleFold(callback),onPin(callback),onUnpin(callback),showPinButton(boolean),side("left" | "right"),onDismiss(callback for mobile backdrop). RemoveonCloseprop. - T004 Update
App.tsxto pass the new props to StatBlockPanel: setpanelRole="browse",side="right", wireonDismissto clearselectedCreatureId, addisRightPanelFoldedstate (defaultfalse), wireonToggleFold. 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 inapps/web/src/components/stat-block-panel.tsx - T006 [US1] Replace the close button (X icon) with a fold toggle button (use
PanelRightClose/PanelRightOpenLucide icons or similar chevron) in the desktop header ofapps/web/src/components/stat-block-panel.tsx. Wire toonToggleFoldprop. - T007 [US1] Implement the folded tab view in the desktop branch of
apps/web/src/components/stat-block-panel.tsx: whenisFoldedis 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 callsonToggleFold. - 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 betweentranslate-x-0(expanded) andtranslate-x-[calc(100%-40px)](folded) based onisFoldedprop. 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 callsonDismiss(full dismiss). Fold toggle on mobile also callsonDismiss(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 andselectedCreatureIdis set, also setisRightPanelFolded = falseto 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
pinnedCreatureIdstate and derivedpinnedCreature(viagetCreature) inapps/web/src/App.tsx. AddisWideDesktopstate usingmatchMedia("(min-width: 1280px)")with change listener (same pattern as existingisDesktopin stat-block-panel.tsx). - T012 [US2] Wire pin/unpin callbacks in
apps/web/src/App.tsx:onPinsetspinnedCreatureId = selectedCreatureId,onUnpinsetspinnedCreatureId = null. PassshowPinButton = isWideDesktopto browse panel. - T013 [US2] Render the pinned StatBlockPanel in
apps/web/src/App.tsx: conditionally render a second<StatBlockPanel>whenpinnedCreatureIdis set andisWideDesktopis true, withpanelRole="pinned",side="left", creature data frompinnedCreature, andonUnpincallback. - T014 [US2] Implement pin and unpin button rendering in
apps/web/src/components/stat-block-panel.tsx: whenpanelRole="browse"andshowPinButtonis true, render a pin icon button (usePinLucide icon) in the header that callsonPin. WhenpanelRole="pinned", render an unpin icon button (usePinOffLucide icon) in the header that callsonUnpin. - T015 [US2] Update panel positioning in
apps/web/src/components/stat-block-panel.tsx: use thesideprop to applyleft-0 border-rfor pinned panel vsright-0 border-lfor 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: ensureisRightPanelFoldedstate 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
selectedCreatureIdinapps/web/src/App.tsx: whenisRightPanelFoldedis toggled to true,selectedCreatureIdmust 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: whenisWideDesktopchanges from true to false andpinnedCreatureIdis 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:bulkImportModepanels 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:pinnedCreatureIdis not cleared on combatant removal — data resolved viagetCreatureindependently.
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)
- Complete Phase 1: CSS utilities (T001-T002)
- Complete Phase 2: Props refactor (T003-T004)
- Complete Phase 3: Fold/unfold (T005-T010)
- STOP and VALIDATE: Test fold/unfold independently on desktop and mobile
- Deploy/demo if ready — panel folds and unfolds, no close button, no heading
Incremental Delivery
- Setup + Foundational → App compiles with new prop interface
- Add User Story 1 → Test fold/unfold → Deploy/Demo (MVP!)
- Add User Story 2 → Test pin/unpin → Deploy/Demo
- Add User Story 3 → Verify combined behavior → Deploy/Demo
- Polish → Run quality gate
- 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