3.9 KiB
Implementation Plan: Fix Zero-HP Opacity
Branch: 020-fix-zero-hp-opacity | Date: 2026-03-06 | Spec: spec.md
Input: Feature specification from /specs/020-fix-zero-hp-opacity/spec.md
Summary
Fix a CSS bug where opacity-50 on the combatant row container cascades to absolutely-positioned popover and dropdown children, making the HP adjust popover and condition picker unusable for unconscious (0 HP) combatants. The fix removes the container-level opacity and applies dimming to individual leaf elements instead, so overlay components render at full opacity.
Technical Context
Language/Version: TypeScript 5.8 (strict mode, verbatimModuleSyntax)
Primary Dependencies: React 19, Tailwind CSS v4, Lucide React (icons)
Storage: N/A (no storage changes)
Testing: Vitest (existing test suite)
Target Platform: Web browser (Vite 6 dev server)
Project Type: Web application (monorepo: apps/web + packages/domain + packages/application)
Performance Goals: N/A (visual fix only)
Constraints: Must pass pnpm check merge gate
Scale/Scope: Single file change (combatant-row.tsx)
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. Fix is adapter-layer only. |
| II. Layered Architecture | PASS | Change is entirely in apps/web (adapter layer). No cross-layer imports added. |
| III. Agent Boundary | N/A | No agent layer involvement. |
| IV. Clarification-First | PASS | Bug is clearly defined; no ambiguous decisions. |
| V. Escalation Gates | PASS | Fix is within spec scope. |
| VI. MVP Baseline Language | PASS | No scope language changes. |
| VII. No Gameplay Rules | PASS | No gameplay logic involved. |
Post-Phase 1 re-check: All gates still pass. The fix is a single-file CSS class restructuring in the adapter layer with no architectural implications.
Project Structure
Documentation (this feature)
specs/020-fix-zero-hp-opacity/
├── plan.md # This file
├── research.md # Root cause analysis and fix approach
├── data-model.md # No data model changes (documented)
├── quickstart.md # Verification steps
└── tasks.md # Phase 2 output (/speckit.tasks command)
Source Code (repository root)
apps/web/src/components/
└── combatant-row.tsx # Single file to modify
Structure Decision: This is a bug fix touching a single component file. No new files, modules, or structural changes needed. The existing monorepo structure (apps/web → packages/application → packages/domain) is unchanged.
Fix Approach
Current (broken)
opacity-50 is applied to the outermost row <div> when status === "unconscious" (line 312). This creates a CSS stacking context that dims all descendants, including the absolutely-positioned HpAdjustPopover and ConditionPicker.
Proposed (fixed)
- Remove
opacity-50from the outer row container. - Add an
unconsciousconditional class to each leaf/display element within the row:- Concentration button
- Initiative input
- Name display (
EditableName) - AC display (
AcDisplay) - HP value button (inside
ClickableHp) - HP separator slash
- Max HP display (
MaxHpDisplay) - Condition tags (
ConditionTags) - Remove button
- The
HpAdjustPopoverandConditionPickerare not wrapped in any dimmed element and render at full opacity.
Implementation detail: Apply opacity-50 to the inner grid container and the conditions container while ensuring popovers (HpAdjustPopover, ConditionPicker) are rendered as siblings outside those dimmed wrappers, not as children. This is the simplest approach — it avoids scattering opacity classes across every leaf element.