Fix concentration glow clipping at container edges

Add padding to the inner combatant list container so the box-shadow glow
from the concentration-damage animation renders fully on all sides,
preventing clipping by the scrollable parent's implicit overflow-x.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-11 11:54:22 +01:00
parent 0c903bc9a5
commit 55d322a727
9 changed files with 348 additions and 1 deletions

View File

@@ -83,6 +83,8 @@ The constitution (`.specify/memory/constitution.md`) governs all feature work:
## Active Technologies
- TypeScript 5.8 (strict mode, `verbatimModuleSyntax`) + React 19, Tailwind CSS v4, Lucide React, class-variance-authority (cva) (032-inline-confirm-buttons)
- N/A (no persistence changes — confirm state is ephemeral) (032-inline-confirm-buttons)
- TypeScript 5.8, CSS (Tailwind CSS v4) + React 19, Tailwind CSS v4 (033-fix-concentration-glow-clip)
- N/A (no persistence changes) (033-fix-concentration-glow-clip)
## Recent Changes
- 032-inline-confirm-buttons: Added TypeScript 5.8 (strict mode, `verbatimModuleSyntax`) + React 19, Tailwind CSS v4, Lucide React, class-variance-authority (cva)

View File

@@ -155,7 +155,7 @@ export function App() {
{/* Scrollable area — combatant list */}
<div className="flex-1 overflow-y-auto min-h-0">
<div className="flex flex-col pb-2">
<div className="flex flex-col px-2 py-2">
{encounter.combatants.length === 0 ? (
<p className="py-12 text-center text-sm text-muted-foreground">
No combatants yet add one to get started

View File

@@ -0,0 +1,35 @@
# Specification Quality Checklist: Fix Concentration Shake Glow Clipping
**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-03-11
**Feature**: [spec.md](../spec.md)
## Content Quality
- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed
## Requirement Completeness
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified
## Feature Readiness
- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification
## Notes
- All items pass. The spec is a focused bug fix with clear acceptance criteria and well-defined scope.
- The Assumptions section mentions CSS techniques as examples of what might cause the issue, which is acceptable context without prescribing a solution.

View File

@@ -0,0 +1,3 @@
# Data Model: Fix Concentration Glow Clipping
No data model changes required. This is a CSS-only visual bug fix. The existing domain model, state transitions, and component props remain unchanged.

View File

@@ -0,0 +1,69 @@
# Implementation Plan: Fix Concentration Shake Glow Clipping
**Branch**: `033-fix-concentration-glow-clip` | **Date**: 2026-03-11 | **Spec**: [spec.md](spec.md)
**Input**: Feature specification from `/specs/033-fix-concentration-glow-clip/spec.md`
## Summary
The concentration-damage glow animation (`box-shadow`) is clipped on left and right edges because the scrollable combatant list container's `overflow-y: auto` implicitly sets `overflow-x: auto` per CSS spec, creating a clipping context. The fix adds horizontal padding to the inner container so the glow has room to render within the overflow area.
## Technical Context
**Language/Version**: TypeScript 5.8, CSS (Tailwind CSS v4)
**Primary Dependencies**: React 19, Tailwind CSS v4
**Storage**: N/A (no persistence changes)
**Testing**: Vitest (visual verification — CSS-only fix)
**Target Platform**: Web (modern browsers, 320px+ viewports)
**Project Type**: Web application
**Performance Goals**: 60fps animation, zero layout shift
**Constraints**: No horizontal scrollbar, no layout shift, mobile-compatible
**Scale/Scope**: Single CSS class change in one file
## 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 |
| II. Layered Architecture | PASS | Change is in adapter layer (web/UI) only |
| III. Clarification-First | PASS | Root cause is clear, fix is straightforward |
| IV. Escalation Gates | PASS | Fix matches spec scope exactly |
| V. MVP Baseline Language | PASS | N/A for bug fix |
| VI. No Gameplay Rules | PASS | N/A — visual fix only |
**Post-design re-check**: All gates still pass. No architectural changes introduced.
## Root Cause
1. The combatant list lives inside `<div className="flex-1 overflow-y-auto min-h-0">` (App.tsx:157)
2. CSS spec: when `overflow-y` is non-`visible`, `overflow-x` computes to `auto` (not `visible`)
3. The `concentration-glow` animation applies `box-shadow: 0 0 4px 2px #c084fc` (~6px outward spread)
4. The implicit `overflow-x: auto` clips this box-shadow on left and right edges
## Fix Approach
Add horizontal padding (`px-2`, 8px) to the inner `<div className="flex flex-col pb-2">` at App.tsx:158. This gives the box-shadow room to render within the container's content area, avoiding clipping. `box-shadow` is paint-only and does not affect layout, so no scrollbar or shift will result.
## Project Structure
### Documentation (this feature)
```text
specs/033-fix-concentration-glow-clip/
├── spec.md
├── plan.md # This file
├── research.md # Root cause analysis and fix strategy
├── data-model.md # No data model changes
├── quickstart.md # Verification steps
└── tasks.md # Task breakdown (generated by /speckit.tasks)
```
### Source Code (affected files)
```text
apps/web/src/
└── App.tsx # Line ~158: add px-2 to inner combatant list container
```
**Structure Decision**: Existing monorepo structure. Only one file in the adapter layer (apps/web) is modified. No new files, no structural changes.

View File

@@ -0,0 +1,20 @@
# Quickstart: Fix Concentration Glow Clipping
## What Changed
The scrollable combatant list container clips the `box-shadow` glow effect from the concentration-damage animation. The fix adds horizontal padding to the inner container so the glow has room to render.
## Files to Modify
1. **`apps/web/src/App.tsx`** — Add horizontal padding to the inner `<div>` inside the scrollable combatant list area (line ~158)
## How to Verify
1. Run `pnpm --filter web dev`
2. Add a combatant to an encounter
3. Toggle concentration on the combatant
4. Reduce the combatant's HP (deal damage)
5. Observe the purple glow animation — it should be fully visible on left and right edges
6. Test at mobile widths (320px+) — no horizontal scrollbar should appear
7. Add multiple concentrating combatants, deal damage to all simultaneously — each row's glow should be independently visible without overlap artifacts
8. With a long encounter list (6+ combatants), scroll so a concentrating combatant is at the top or bottom edge of the visible area, deal damage — glow should still be fully visible

View File

@@ -0,0 +1,35 @@
# Research: Fix Concentration Glow Clipping
## Root Cause Analysis
### Decision: The glow is clipped by the scrollable parent container's implicit overflow-x
**Rationale**: The combatant list is wrapped in a `<div className="flex-1 overflow-y-auto min-h-0">` (App.tsx:157). Per the CSS specification, when one overflow axis is set to a non-`visible` value (here `overflow-y: auto`), the other axis (`overflow-x`) computes to `auto` rather than remaining `visible`. This creates a clipping context that cuts off the `box-shadow` from the `concentration-glow` animation on the left and right edges.
The glow is defined as:
```css
box-shadow: 0 0 4px 2px #c084fc;
```
This extends ~6px outward from the combatant row element. The parent's implicit `overflow-x: auto` clips this outward extension.
**Alternatives considered**:
- `border-radius` on the row causing clipping — rejected; `border-radius` shapes `box-shadow` but does not clip it
- `translate` in the shake animation pushing the element off-screen — rejected; max displacement is only 3px and returns to 0
## Fix Strategy
### Decision: Add horizontal padding to the scrollable container to accommodate the glow spread
**Rationale**: The simplest fix is to add left/right padding to the scrollable container (or its inner flex-col child) so the `box-shadow` has room to render within the overflow area without being clipped. This avoids changing the overflow behavior (which is needed for vertical scrolling) and avoids restructuring the component hierarchy.
The glow spread is `4px blur + 2px spread = ~6px` outward. Adding `px-2` (8px) padding to the inner `<div className="flex flex-col pb-2">` at App.tsx:158 would provide enough space.
**Alternatives considered**:
1. **Change `overflow-y-auto` to `overflow-y-scroll` + `overflow-x-visible`** — rejected; CSS spec still forces `overflow-x` to `auto` when `overflow-y` is non-`visible`
2. **Use `outline` instead of `box-shadow`** — viable but changes the visual appearance; `outline` doesn't respect `border-radius` in all browsers
3. **Use `inset box-shadow`** — would avoid overflow clipping but changes the visual effect from an outer glow to an inner glow
4. **Negative margin + padding trick on the scrollable container** — more complex, same net effect as simple padding
### Decision: Verify no horizontal scrollbar is introduced
**Rationale**: Adding padding to the inner container should not cause a horizontal scrollbar since the content still fits within the parent. The `box-shadow` renders in the padding area but does not affect layout (box-shadow is a paint-only effect, not a layout-affecting property). No additional `overflow-x: hidden` should be needed.

View File

@@ -0,0 +1,68 @@
# Feature Specification: Fix Concentration Shake Glow Clipping
**Feature Branch**: `033-fix-concentration-glow-clip`
**Created**: 2026-03-11
**Status**: Draft
**Input**: User description: "Fix concentration shake glow clipped at container edges"
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Concentration Damage Glow Visible (Priority: P1)
As a game master viewing the encounter tracker, when a concentrating creature takes damage and the shake + glow animation plays, I see the full glow effect without any visual clipping on the left or right edges.
**Why this priority**: This is the core bug being fixed. The glow animation is the primary visual feedback for concentration damage events, and clipping undermines its purpose.
**Independent Test**: Can be tested by dealing damage to a concentrating creature and visually verifying the glow extends fully without being cut off at container boundaries.
**Acceptance Scenarios**:
1. **Given** a creature is concentrating on a spell, **When** the creature takes damage and the shake + glow animation triggers, **Then** the glow effect is fully visible on both the left and right sides throughout the entire animation duration
2. **Given** a creature is concentrating and the encounter list has its default layout, **When** damage triggers the animation, **Then** no part of the glow is clipped or hidden by overflow constraints
---
### User Story 2 - No Layout Side Effects (Priority: P1)
As a user on any device, the glow fix does not introduce horizontal scrollbars, layout shifts, or visual artifacts outside the animation area.
**Why this priority**: A fix that trades one visual bug for another (e.g., scroll jank) would be a regression. This is equally critical as the glow fix itself.
**Independent Test**: Can be tested by triggering the animation repeatedly on both desktop and mobile viewports and confirming no horizontal scrollbar appears and no elements shift position.
**Acceptance Scenarios**:
1. **Given** the encounter tracker is displayed at desktop width, **When** the shake + glow animation plays, **Then** no horizontal scrollbar appears on the page or encounter container
2. **Given** the encounter tracker is displayed at mobile width (320px - 480px), **When** the shake + glow animation plays, **Then** no layout shift occurs and the animation renders correctly within the viewport
---
### Edge Cases
- What happens when multiple concentrating creatures take damage simultaneously? The glow on each row must remain independently visible without overlap artifacts.
- What happens when the creature row is at the very top or bottom of a scrollable encounter list? The glow must still be fully visible.
- What happens on very narrow viewports (below 320px)? The glow should degrade gracefully without causing overflow.
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: The concentration damage glow effect MUST be fully visible on both left and right edges of the combatant row throughout the shake animation
- **FR-002**: The glow fix MUST NOT introduce a horizontal scrollbar on the page or any parent container
- **FR-003**: The glow fix MUST NOT cause any layout shift or repositioning of other elements during or after the animation
- **FR-004**: The shake + glow animation MUST render correctly on mobile-width screens (320px and above)
- **FR-005**: The glow effect MUST remain visually correct when multiple combatant rows animate simultaneously
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: The glow effect is visually complete (no clipping) on 100% of concentration damage events across all supported viewport widths (320px and above)
- **SC-002**: Zero horizontal scrollbar appearances caused by the glow animation at any viewport width
- **SC-003**: Zero measurable layout shift (CLS = 0) introduced by the animation fix
## Assumptions
- The existing shake animation behavior and timing are correct and should be preserved; only the glow clipping needs to be fixed.
- The glow effect uses CSS techniques (e.g., box-shadow, outline, or pseudo-elements) that may be clipped by `overflow: hidden` or similar properties on ancestor containers.
- "Mobile-width screens" refers to viewports of 320px width and above, consistent with standard mobile device support.

View File

@@ -0,0 +1,115 @@
# Tasks: Fix Concentration Shake Glow Clipping
**Input**: Design documents from `/specs/033-fix-concentration-glow-clip/`
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, quickstart.md
**Tests**: No test tasks — this is a CSS-only visual fix. Verification is done via manual inspection per quickstart.md.
**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)
- Include exact file paths in descriptions
## Phase 1: Setup
**Purpose**: No setup needed — existing project, no new dependencies or configuration changes.
*(No tasks in this phase)*
---
## Phase 2: Foundational (Blocking Prerequisites)
**Purpose**: No foundational work needed — the fix modifies existing CSS layout only.
*(No tasks in this phase)*
---
## Phase 3: User Story 1 - Concentration Damage Glow Visible (Priority: P1) MVP
**Goal**: The concentration-damage glow effect is fully visible on both left and right edges without clipping.
**Independent Test**: Deal damage to a concentrating creature and verify the purple glow extends fully on all sides without being cut off at container edges.
### Implementation for User Story 1
- [x] T001 [US1] Add horizontal padding (`px-2`) to the inner combatant list container `<div className="flex flex-col pb-2">` in `apps/web/src/App.tsx` (line ~158) so the `box-shadow` glow renders within the overflow area instead of being clipped
**Checkpoint**: At this point, the glow should be fully visible on left and right edges.
---
## Phase 4: User Story 2 - No Layout Side Effects (Priority: P1)
**Goal**: The padding fix does not introduce horizontal scrollbars, layout shifts, or visual artifacts.
**Independent Test**: Trigger the animation at desktop and mobile widths (320px+), confirm no horizontal scrollbar appears and no elements shift position.
### Implementation for User Story 2
- [x] T002 [US2] Verify at desktop and mobile viewports (320px+) that the padding added in T001 does not cause a horizontal scrollbar or layout shift in `apps/web/src/App.tsx`; also verify that multiple concentrating rows animating simultaneously display independent glows without overlap artifacts; adjust padding value if needed
**Checkpoint**: Animation renders correctly at all viewport widths with no side effects.
---
## Phase 5: Polish & Cross-Cutting Concerns
**Purpose**: Final validation and quality gates.
- [x] T003 Run `pnpm check` to ensure all quality gates pass (lint, typecheck, tests, coverage)
- [x] T004 Run quickstart.md validation steps from `specs/033-fix-concentration-glow-clip/quickstart.md`
---
## Dependencies & Execution Order
### Phase Dependencies
- **Phase 3 (US1)**: No prerequisites — single file change
- **Phase 4 (US2)**: Depends on T001 completion (verifies T001 doesn't regress)
- **Phase 5 (Polish)**: Depends on T001 and T002 completion
### User Story Dependencies
- **User Story 1 (P1)**: Independent — core fix
- **User Story 2 (P1)**: Depends on US1 — verification of no side effects
### Within Each User Story
- US1: Single task (T001)
- US2: Single verification task (T002) dependent on T001
### Parallel Opportunities
- No parallel opportunities — this is a 1-file, 1-line fix with sequential verification
---
## Implementation Strategy
### MVP First (User Story 1 Only)
1. Complete T001: Add padding to inner container
2. **STOP and VALIDATE**: Visually verify glow is no longer clipped
3. Complete T002: Verify no layout side effects
4. Complete T003-T004: Quality gates and quickstart validation
### Incremental Delivery
1. T001 → Glow fix applied
2. T002 → No regressions confirmed
3. T003-T004 → Quality gates pass, ready to merge
---
## Notes
- This is a minimal CSS-only fix: one Tailwind class addition to one file
- The root cause is CSS spec behavior: `overflow-y: auto` forces `overflow-x: auto`, clipping `box-shadow`
- `box-shadow` is paint-only and does not affect layout, so padding accommodates the glow without shifting content
- If `px-2` (8px) is insufficient for the 6px glow spread, increase to `px-3` (12px)