Files
initiative/specs/020-fix-zero-hp-opacity/plan.md

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/webpackages/applicationpackages/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)

  1. Remove opacity-50 from the outer row container.
  2. Add an unconscious conditional 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
  3. The HpAdjustPopover and ConditionPicker are 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.