12 KiB
Tasks: Combatant Row UI Polish
Input: Design documents from /specs/027-ui-polish/
Prerequisites: plan.md (required), spec.md (required for user stories), research.md, data-model.md
Tests: No new tests required — this feature is purely presentational. Existing domain tests must continue passing.
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
Purpose: No new project setup needed — all infrastructure exists. This phase is empty.
Phase 2: Foundational (Layout Restructure)
Purpose: Restructure the combatant row grid layout to support inline conditions. This MUST be complete before individual user stories can be implemented, since it changes the core grid structure.
⚠️ CRITICAL: US1 restructures the grid and name column — all subsequent stories build on this new layout.
- T001 Move
ConditionTagsand the "+" condition button from the separate conditions row (thedivwithml-[calc(3rem+0.75rem)]) into the name column's flex container inapps/web/src/components/combatant-row.tsx. Place them after the name text inside the1frgrid cell. Addflex-wrapto the container so conditions wrap gracefully. Applymin-w-0andtruncateon the name text so it truncates when conditions need space. Do NOT useshrink-0— the name must be allowed to shrink to make room for inline conditions. Remove the entire separate conditions rowdivbelow the main grid. - T002 Make the "+" condition button hover-only by adding
opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacityclasses inapps/web/src/components/combatant-row.tsx. When no conditions exist, the "+" should be the only inline element after the name and should only appear on hover. When conditions exist, the "+" appears after the last condition icon on hover. - T003 Verify the
ConditionPickerdropdown positioning still works correctly from its new inline position inapps/web/src/components/condition-picker.tsx. The existing flip logic (checkingrect.bottom > window.innerHeight) should still work since positioning isabsoluterelative to the picker's parent. Adjust if the new inline context changes the reference point.
Checkpoint: Conditions render inline after creature name. "+" appears on hover only. No second row below combatants. All condition add/remove interactions work as before.
Phase 3: User Story 2 — Row Click Opens Stat Block (Priority: P1)
Goal: Make the entire combatant row clickable to open the stat block, removing the dedicated BookOpen icon.
Independent Test: Click on the creature name or empty row space for a bestiary combatant — stat block panel opens. Click on interactive elements (HP, AC, initiative, conditions) — their own actions fire, stat block does not open.
- T004 [US2] Remove the BookOpen icon button from the name area in the
EditableNamesection ofapps/web/src/components/combatant-row.tsx. Remove theBookOpenimport fromlucide-reactif no longer used elsewhere in the file. - T005 [US2] Add an
onClickhandler to the row containerdivinapps/web/src/components/combatant-row.tsxthat callsonShowStatBlock?.()when the combatant has acreatureId. Addcursor-pointerto the row container for bestiary combatants. Confirm that the parent component'sonShowStatBlockhandler implements toggle behavior (clicking the same row again closes the stat block panel, per spec edge case). - T006 [US2] Add
event.stopPropagation()to all interactive child element click handlers inapps/web/src/components/combatant-row.tsx— specifically: concentration button, initiative display (d20 button, edit click, display click), condition icon buttons, "+" condition button, AC display click, HP display click, and remove button. This prevents row-level stat block opening when interacting with these elements.
Checkpoint: Clicking non-interactive row areas opens stat block for bestiary combatants. BookOpen icon is gone. All existing interactions (HP, AC, initiative, conditions, concentration, remove) still work without triggering stat block.
Phase 4: User Story 3 — Hover-Only Remove Button (Priority: P2)
Goal: Hide the × remove button by default, showing it only on row hover.
Independent Test: Hover over a combatant row — × appears. Move mouse away — × disappears. Row layout does not shift.
- T007 [US3] Add
opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacityclasses to the remove button (X icon) inapps/web/src/components/combatant-row.tsx. Keep the button in the DOM (preserving the2remgrid column) so no layout shift occurs. This follows the same pattern used by the concentration button.
Checkpoint: Remove button hidden by default, visible on hover, no layout shift. Removal still works.
Phase 5: User Story 4 — AC Shield Shape (Priority: P2)
Goal: Display the AC number inside a shield-shaped outline instead of beside a shield icon.
Independent Test: View any combatant with AC set — number appears inside a shield-shaped SVG outline. Click to edit AC — editing works as before.
- T008 [P] [US4] Create new
AcShieldcomponent inapps/web/src/components/ac-shield.tsx. Render an inline SVG shield outline (stroke-based,currentColor, approximately 28×32px viewBox) with a centered<span>overlay for the AC number using relative/absolute positioning. Acceptvalue(number | undefined),onClickcallback, andclassNameprops. Show a placeholder (e.g., "—") when no value is set. - T009 [US4] Replace the
AcDisplaysub-component's non-edit rendering inapps/web/src/components/combatant-row.tsxto use the newAcShieldcomponent instead of the LucideShieldicon + number. Keep the edit-mode input behavior unchanged — when clicked, the shield is replaced by the existing input field. Remove theShieldimport fromlucide-reactif no longer used elsewhere in the file.
Checkpoint: AC displays inside shield shape. Edit mode still works. Single-digit, double-digit, and triple-digit values render centered.
Phase 6: User Story 5 — Expanded Concentration Click Target (Priority: P3)
Goal: Widen the concentration button's clickable area to fill the gutter between the left border and initiative column.
Independent Test: Click in the space left of the initiative number — concentration toggles. Brain icon stays visually centered.
- T010 [US5] Widen the first grid column from
1.25remto2remin the grid templategrid-cols-[1.25rem_3rem_1fr_auto_auto_2rem]inapps/web/src/components/combatant-row.tsx. Make the concentration button fill the full column width (w-full h-full) while keeping the brain icon visually centered. Verify the concentration shake/glow animation still works with the wider button.
Checkpoint: Concentration clickable across full left gutter. Brain icon centered. Animation intact.
Phase 7: User Story 6 — Larger D20 Icon (Priority: P3)
Goal: Increase the d20 roll-initiative icon size for better recognizability.
Independent Test: View a bestiary combatant without initiative — d20 icon is visibly larger and recognizable as a die.
- T011 [P] [US6] Change the D20Icon className from
h-5 w-5toh-7 w-7in theInitiativeDisplaysub-component withinapps/web/src/components/combatant-row.tsx. Verify the icon fits within the 3rem initiative column without overflow.
Checkpoint: D20 icon at 28px is recognizable. No overflow. Roll interaction works.
Phase 8: Polish & Cross-Cutting Concerns
- T012 Run
pnpm checkto verify all existing tests pass, linting is clean, types check, and no unused exports are flagged by Knip in the project root - T013 Visual verification: check all combatant states (active turn, concentrating, unconscious/dimmed, bloodied, no conditions, many conditions, no AC, no initiative, custom combatant without creatureId) render correctly with the new layout
- T014 Verify touch device behavior: "+" condition button and "×" remove button are accessible via tap/focus even without hover
- T015 [P] Style browser scrollbars to match dark UI by adding
scrollbar-colorandscrollbar-widthproperties inapps/web/src/index.css - T016 [P] Redesign top bar buttons: replace Previous/Next text+chevron with StepBack/StepForward outline icon buttons (
border-foreground, white), keep d20/trash as ghost buttons (grey), group d20+trash tightly with spacing before Next inapps/web/src/components/turn-navigation.tsx - T017 [P] Remove the "Initiative Tracker"
<header>heading fromapps/web/src/App.tsxto maximize vertical combatant space
Dependencies & Execution Order
Phase Dependencies
- Foundational (Phase 2): No prerequisites — restructures the core layout
- US2 Row Click (Phase 3): Depends on Phase 2 (BookOpen removal and row click both affect the name area)
- US3 Hover Remove (Phase 4): Depends on Phase 2 (needs stable grid layout)
- US4 AC Shield (Phase 5): Independent — can run in parallel with Phase 3/4
- US5 Concentration (Phase 6): Depends on Phase 2 (modifies the same grid template)
- US6 D20 Size (Phase 7): Independent — can run in parallel with any phase after Phase 2
- Polish (Phase 8): Depends on all previous phases
User Story Dependencies
- US1 (Inline Conditions): Foundational — extracted to Phase 2 since all stories depend on it
- US2 (Row Click Stat Block): Depends on US1 layout restructure (Phase 2)
- US3 (Hover Remove): Depends on US1 layout restructure (Phase 2)
- US4 (AC Shield): Independent — new component + replacement in AcDisplay
- US5 (Concentration Target): Depends on US1 grid template change (Phase 2)
- US6 (D20 Size): Independent — single className change
Within Each User Story
- Implementation tasks are sequential within each story
- Stories are independently testable at each checkpoint
Parallel Opportunities
After Phase 2 (Foundational) completes:
- T007 (US3), T008 (US4), T010 (US5), and T011 (US6) can all start in parallel
- T004-T006 (US2) can run in parallel with T008 (US4) and T011 (US6)
Parallel Example: After Phase 2
# These can all launch in parallel after foundational layout restructure:
Task: "T007 [US3] Hover-only remove button in combatant-row.tsx"
Task: "T008 [US4] Create AcShield component in ac-shield.tsx"
Task: "T011 [US6] Increase D20Icon size in combatant-row.tsx"
# Then sequentially:
Task: "T009 [US4] Integrate AcShield into AcDisplay in combatant-row.tsx" (after T008)
Task: "T010 [US5] Widen concentration grid column in combatant-row.tsx" (after T007 or independently)
Implementation Strategy
MVP First (Phase 2 + User Story 2)
- Complete Phase 2: Layout restructure (inline conditions, hover-only "+")
- Complete Phase 3: Row click stat block + remove BookOpen icon
- STOP and VALIDATE: Conditions inline, row click works, no regressions
- This alone delivers the biggest visual improvement
Incremental Delivery
- Phase 2 → Inline conditions (biggest layout change)
- Phase 3 → Row click stat block (biggest UX improvement)
- Phase 4 → Hover-only remove (visual cleanup)
- Phase 5 → AC shield shape (visual polish)
- Phase 6+7 → Concentration target + D20 size (minor refinements)
- Phase 8 → Polish and verify all states
Notes
- All tasks modify
apps/web/src/components/only — no domain or application layer changes - Only one new file created:
apps/web/src/components/ac-shield.tsx - The main file
combatant-row.tsxis modified by most tasks — be careful with parallel edits to this file - Existing domain tests must pass after all changes (
pnpm check) - Commit after each phase or logical group for clean history