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>
192 lines
12 KiB
Markdown
192 lines
12 KiB
Markdown
# 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
|
|
|
|
- [x] T001 Add CSS transition utility for panel slide animation (`transition: translate 200ms ease-out`) in `apps/web/src/index.css`
|
|
- [x] 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
|
|
|
|
- [x] 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.
|
|
- [x] 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
|
|
|
|
- [x] 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`
|
|
- [x] 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.
|
|
- [x] 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`.
|
|
- [x] 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.
|
|
- [x] 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).
|
|
- [x] 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
|
|
|
|
- [x] 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).
|
|
- [x] 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.
|
|
- [x] 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.
|
|
- [x] 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`.
|
|
- [x] 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
|
|
|
|
- [x] 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.
|
|
- [x] 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
|
|
|
|
- [x] 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.
|
|
- [x] 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.
|
|
- [x] T020 Run `pnpm check` (full quality gate: audit + knip + biome + typecheck + test/coverage + jscpd) and fix any issues
|
|
- [x] 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
|
|
|
|
- [x] 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.
|
|
- [x] 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.
|
|
- [x] 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).
|
|
- [x] 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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
|