Implement the 027-ui-polish feature that adds six combatant row improvements (inline conditions, row-click stat block, hover-only remove button, AC shield shape, expanded concentration click target, larger d20 icon) plus top bar redesign with icon-only StepBack/StepForward navigation buttons, dark-themed scrollbars, and multiple UX fixes including stat block panel stability during initiative rolls and mobile touch safety for hidden buttons

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-10 19:00:49 +01:00
parent d5f7b6ee36
commit f029c1a85b
14 changed files with 811 additions and 120 deletions

129
specs/027-ui-polish/plan.md Normal file
View File

@@ -0,0 +1,129 @@
# Implementation Plan: Combatant Row UI Polish
**Branch**: `027-ui-polish` | **Date**: 2026-03-10 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `/specs/027-ui-polish/spec.md`
## Summary
Six presentational improvements to the combatant row: move conditions inline after the creature name, make the row clickable for stat blocks (removing the BookOpen icon), hide the remove button until hover, display AC inside a shield shape, expand the concentration click target, and increase the d20 icon size. All changes are confined to the web adapter layer — no domain or application changes needed.
## Technical Context
**Language/Version**: TypeScript 5.8 (strict mode, verbatimModuleSyntax)
**Primary Dependencies**: React 19, Tailwind CSS v4, Lucide React (icons), Vite 6
**Storage**: N/A (no storage changes — purely presentational)
**Testing**: Vitest (existing domain tests must continue passing; no new component tests)
**Target Platform**: Modern browsers (desktop primary, touch secondary)
**Project Type**: Web application (single-page, local-first)
**Performance Goals**: No perceptible layout shift on hover; smooth opacity transitions
**Constraints**: Must not break any existing interactions; all changes in `apps/web/` only
**Scale/Scope**: ~1 main component file restructured, 1 new small component
## 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 whatsoever |
| II. Layered Architecture | PASS | All changes in adapter layer (apps/web). No domain or application imports added. |
| III. Agent Boundary | N/A | No agent features involved |
| IV. Clarification-First | PASS | All six changes were discussed and confirmed with user before speccing |
| V. Escalation Gates | PASS | All changes are within spec scope |
| VI. MVP Baseline Language | PASS | No permanent bans introduced |
| VII. No Gameplay Rules | PASS | No gameplay mechanics in this feature |
**Post-Phase 1 Re-check**: All gates still pass. No domain or application layer changes introduced during design.
## Project Structure
### Documentation (this feature)
```text
specs/027-ui-polish/
├── plan.md # This file
├── research.md # Phase 0 output
├── data-model.md # Phase 1 output
├── quickstart.md # Phase 1 output
├── checklists/
│ └── requirements.md # Spec quality checklist
└── tasks.md # Phase 2 output (via /speckit.tasks)
```
### Source Code (repository root)
```text
apps/web/src/components/
├── combatant-row.tsx # MODIFY — layout restructure, event handling, hover states
├── condition-tags.tsx # UNCHANGED — reused inline
├── condition-picker.tsx # MINOR MODIFY — positioning may need adjustment for inline context
├── d20-icon.tsx # UNCHANGED — sized via className prop
└── ac-shield.tsx # NEW — shield-shaped AC display component
```
**Structure Decision**: No new directories or architectural changes. One new presentational component (`ac-shield.tsx`). All modifications within existing `apps/web/src/components/` directory.
## Design Decisions
### D1: Inline Conditions Layout
The name column (`1fr`) already uses `flex items-center gap-1`. Conditions move into this flex container after the name text. The name text gets `min-w-0 truncate` to shrink when conditions need space. The `ConditionTags` component is reused as-is. The "+" button uses `opacity-0 group-hover:opacity-100` to appear only on hover.
The separate conditions row below the grid (with `ml-[calc(3rem+0.75rem)]`) is removed entirely.
### D2: Row Click Stat Block via Event Delegation
The row container gets an `onClick` handler that calls `onShowStatBlock` (for bestiary combatants). All interactive child elements add `event.stopPropagation()` to their existing click handlers so clicks on initiative, HP, AC, conditions, "+", "×", and concentration don't bubble up to the row handler.
Bestiary rows get `cursor-pointer` on the row container. Custom combatant rows (no `creatureId`) don't get the handler or cursor.
The BookOpen icon button is deleted from the name area.
### D3: Hover-Only Remove Button
The remove button gets `opacity-0 group-hover:opacity-100 focus:opacity-100` classes. The button remains in the DOM (preserving the `2rem` grid column) so no layout shift occurs. This matches the existing pattern used by the concentration button.
### D4: AC Shield Shape Component
New `AcShield` component renders:
- An inline SVG shield outline (stroke-based, `currentColor`) as background
- The AC number centered inside via absolute positioning
- Click handler passed through for editing
- Sized approximately 28×32px to fit 1-3 digit values
Replaces the current `Shield` Lucide icon + number in `AcDisplay`. The edit mode input still appears on click, positioned over/replacing the shield.
### D5: Concentration Click Target
Widen the first grid column from `1.25rem` to `2rem` and make the concentration button fill the full column with padding. The brain icon stays visually centered. The button's click area now spans from the row's left edge (after border) to the initiative column.
Grid changes from:
```
grid-cols-[1.25rem_3rem_1fr_auto_auto_2rem]
```
to:
```
grid-cols-[2rem_3rem_1fr_auto_auto_2rem]
```
### D6: D20 Icon Size
Change `className="h-5 w-5"` to `className="h-7 w-7"` on the `D20Icon` in `InitiativeDisplay`. The icon grows from 20px to 28px. The initiative column (3rem = 48px) and the button (`h-7 w-full`) accommodate this without overflow.
### D7: Dark Scrollbar Styling
Apply `scrollbar-color: var(--color-border) transparent` and `scrollbar-width: thin` on `*` in `index.css`. This gives all scrollbars (main page, stat block panel) a slim dark thumb (`#334155`) on a transparent track, matching the dark UI. Supported in Chrome 121+, Firefox 64+, Safari 18+.
### D8: Top Bar Icon Redesign
Replace the Previous/Next text+chevron buttons with icon-only `StepBack`/`StepForward` buttons using the `outline` variant with `border-foreground` (white border matching the icon color). Utility actions (d20, trash) use `ghost` variant with `text-muted-foreground` — creating a two-tier visual hierarchy: primary navigation (outlined, white) vs secondary utilities (borderless, grey).
Layout: `[StepBack]` — center info — `[d20][trash] [StepForward]`. The d20 and trash are tightly grouped (`gap-0`) with `gap-3` separating them from the Next button. Icon sizes: d20 `h-6 w-6`, trash `h-5 w-5`, step buttons `h-5 w-5`. All buttons `h-8 w-8`.
### D9: Remove "Initiative Tracker" Heading
Remove the `<header>` block containing the `<h1>Initiative Tracker</h1>` from `apps/web/src/App.tsx`. The turn navigation bar already provides context (round number, active combatant name), making the heading redundant. Removing it reclaims ~60px of vertical space for the combatant list.
## Complexity Tracking
No constitution violations to justify. All changes are straightforward adapter-layer modifications.