-
Notifications
You must be signed in to change notification settings - Fork 2
[FIX] 타이머 화면의 음소거 아이콘 미반영 버그 및 NormalTimer 레이아웃 문제를 수정 #442
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
010b836
e61046e
fa32442
2acd567
650f3a3
65b2beb
677f8df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,24 +28,29 @@ get_current_branch() { | |
|
|
||
| # For non-git repos, try to find the latest feature directory | ||
| local repo_root=$(get_repo_root) | ||
| local specs_dir="$repo_root/specs/feat" | ||
| local specs_base="$repo_root/specs" | ||
|
|
||
| if [[ -d "$specs_dir" ]]; then | ||
| if [[ -d "$specs_base" ]]; then | ||
| local latest_feature="" | ||
| local highest=0 | ||
|
|
||
| for dir in "$specs_dir"/*; do | ||
| if [[ -d "$dir" ]]; then | ||
| local dirname=$(basename "$dir") | ||
| if [[ "$dirname" =~ ^([0-9]+)- ]]; then | ||
| local number=${BASH_REMATCH[1]} | ||
| number=$((10#$number)) | ||
| if [[ "$number" -gt "$highest" ]]; then | ||
| highest=$number | ||
| latest_feature=$dirname | ||
| for type_dir in "$specs_base"/*/; do | ||
| [[ -d "$type_dir" ]] || continue | ||
| local type_name | ||
| type_name="$(basename "$type_dir")" | ||
| for dir in "$type_dir"*/; do | ||
| if [[ -d "$dir" ]]; then | ||
| local dirname=$(basename "$dir") | ||
| if [[ "$dirname" =~ ^([0-9]+)- ]]; then | ||
| local number=${BASH_REMATCH[1]} | ||
| number=$((10#$number)) | ||
| if [[ "$number" -gt "$highest" ]]; then | ||
| highest=$number | ||
| latest_feature="${type_name}/#${dirname}" | ||
| fi | ||
| fi | ||
| fi | ||
| fi | ||
| done | ||
| done | ||
|
|
||
| if [[ -n "$latest_feature" ]]; then | ||
|
|
@@ -107,29 +112,41 @@ check_feature_branch() { | |
| return 1 | ||
| } | ||
|
|
||
| get_feature_dir() { echo "$1/specs/feat/$2"; } | ||
| get_feature_dir() { | ||
| local repo_root="$1" | ||
| local branch_name="$2" | ||
| find_feature_dir_by_prefix "$repo_root" "$branch_name" | ||
| } | ||
|
|
||
| # Find feature directory by issue number or numeric prefix | ||
| # Supports: feat/#96-social-login → specs/feat/096-* | ||
| # 096-social-login → specs/feat/096-* | ||
| # Supports: fix/#441-slug → specs/fix/441-* | ||
| # feat/#96-social-login → specs/feat/096-* | ||
| # 096-social-login → specs/feat/096-* (legacy) | ||
| find_feature_dir_by_prefix() { | ||
| local repo_root="$1" | ||
| local branch_name="$2" | ||
| local specs_dir="$repo_root/specs/feat" | ||
|
|
||
| # Extract type prefix from branch name (e.g., fix/#441-slug → fix, feat/#96-slug → feat) | ||
| local type_prefix="feat" # default for legacy branches | ||
| if [[ "$branch_name" =~ ^([a-z]+)/#[0-9]+ ]]; then | ||
| type_prefix="${BASH_REMATCH[1]}" | ||
| fi | ||
|
Comment on lines
+130
to
+133
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
|
||
| local specs_dir="$repo_root/specs/$type_prefix" | ||
|
|
||
| # Extract issue number from branch name | ||
| local issue_num=$(extract_issue_number "$branch_name") | ||
|
|
||
| if [[ -z "$issue_num" ]]; then | ||
| # If no issue number found, fall back to exact match under specs/feat/ | ||
| # If no issue number found, fall back to exact match under specs/{type}/ | ||
| echo "$specs_dir/$branch_name" | ||
| return | ||
| fi | ||
|
|
||
| # Zero-pad to 3 digits for matching | ||
| local padded=$(printf "%03d" "$((10#$issue_num))") | ||
|
|
||
| # Search for directories in specs/feat/ that start with this prefix | ||
| # Search for directories in specs/{type}/ that start with this prefix | ||
| local matches=() | ||
| if [[ -d "$specs_dir" ]]; then | ||
| for dir in "$specs_dir"/"$padded"-*; do | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # Specification Quality Checklist: 음소거 아이콘 헤더 반영 및 타이머 화면 레이아웃 개선 | ||
|
|
||
| **Purpose**: Validate specification completeness and quality before proceeding to planning | ||
| **Created**: 2026-04-04 | ||
| **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 | ||
|
|
||
| - 음소거 아이콘(FR-001~002)과 레이아웃(FR-003~009)은 독립적으로 구현/테스트 가능 | ||
| - 볼륨 0 = 음소거 동작 정책은 명확화를 통해 확정됨 (세션 2026-04-04) | ||
| - 두 줄 레이아웃 정렬 방식(중앙)은 명확화를 통해 확정됨 (세션 2026-04-04) | ||
| - 타이머 설정 화면은 이번 수정 범위에서 제외됨 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| # Data Model: 음소거 아이콘 헤더 반영 및 타이머 화면 레이아웃 개선 | ||
|
|
||
| > 이 픽스는 순수 UI 레이어 수정이므로 새로운 타입/엔티티 추가 없음. | ||
| > 기존 타입을 그대로 활용. | ||
|
|
||
| ## 관련 기존 타입 | ||
|
|
||
| ### `TimerPageLogics` (src/page/TimerPage/hooks/useTimerPageState.ts) | ||
|
|
||
| ```ts | ||
| export interface TimerPageLogics { | ||
| // ... 기존 필드 ... | ||
| volume: number; // 0~10 정수. 0이면 음소거 상태 | ||
| setVolume: (value: number) => void; | ||
| isVolumeBarOpen: boolean; | ||
| toggleVolumeBar: () => void; | ||
| volumeRef: React.RefObject<HTMLDivElement>; | ||
| } | ||
| ``` | ||
|
|
||
| **`isMuted` 파생 로직**: `const isMuted = volume === 0;` | ||
| - `TimerPage.tsx` 내에서 파생하여 사용 | ||
| - `TimerPageLogics` 인터페이스 변경 없음 | ||
|
|
||
| ### `NormalTimerProps` (src/page/TimerPage/components/NormalTimer.tsx) | ||
|
|
||
| ```ts | ||
| interface NormalTimerProps { | ||
| normalTimerInstance: NormalTimerInstance; | ||
| isAdditionalTimerAvailable: boolean; | ||
| item: TimeBoxInfo; // item.speaker: string | null | ||
| teamName: string | null; | ||
| } | ||
| ``` | ||
|
|
||
| **레이아웃 로직 변경**: | ||
| - `teamName` → 첫 번째 줄 (truncate 적용) | ||
| - `item.speaker` → 두 번째 줄 (완전 표시) | ||
| - 두 줄 모두 중앙 정렬 | ||
|
|
||
| ## 변경 영향 범위 | ||
|
|
||
| | 파일 | 변경 종류 | 설명 | | ||
| |---|---|---| | ||
| | `src/page/TimerPage/TimerPage.tsx` | 수정 | volume === 0 분기로 음소거 아이콘 조건부 렌더링 | | ||
| | `src/page/TimerPage/components/NormalTimer.tsx` | 수정 | 두 줄 레이아웃 + h1 text-center 추가 | | ||
| | `src/components/icons/` | 변경 없음 | DTVolumeMuted 미생성, react-icons 사용 | | ||
| | `src/page/TimerPage/hooks/useTimerPageState.ts` | 변경 없음 | 인터페이스 그대로 유지 | | ||
| | `src/apis/` | 변경 없음 | API 호출 없음 | |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| # Implementation Plan: 음소거 아이콘 헤더 반영 및 타이머 화면 레이아웃 개선 | ||
|
|
||
| **Branch**: `fix/#441-mute-timer-layout-fix` | **Date**: 2026-04-04 | **Spec**: [spec.md](./spec.md) | ||
| **Input**: Feature specification from `/specs/fix/441-mute-timer-layout-fix/spec.md` | ||
|
|
||
| ## Summary | ||
|
|
||
| 헤더의 볼륨 버튼 아이콘이 음소거 상태를 반영하지 않는 버그와 | ||
| `NormalTimer`에서 팀명과 토론자 정보가 한 줄로 압축되는 레이아웃 버그를 수정한다. | ||
| 순수 UI 레이어 변경(2개 파일 수정)으로, API/상태관리 변경 없음. | ||
|
|
||
| ## Technical Context | ||
|
|
||
| **Language/Version**: TypeScript 5.7 (strict mode) | ||
| **Primary Dependencies**: React 18, Tailwind CSS 3, react-icons 5, react-i18next | ||
| **Storage**: N/A (localStorage 볼륨 값 - 기존 로직 유지) | ||
| **Testing**: Vitest + @testing-library/react + @testing-library/user-event + MSW | ||
| **Target Platform**: Web (Chrome/Safari/Firefox) | ||
| **Project Type**: Web SPA (Vite + React Router v7) | ||
| **Performance Goals**: 즉각적인 아이콘 상태 변경 (React 상태 업데이트 기반, 별도 성능 목표 없음) | ||
| **Constraints**: 기존 TimerPageLogics 인터페이스 변경 없음, API 호출 없음 | ||
| **Scale/Scope**: TimerPage 1개 + NormalTimer 1개 컴포넌트 수정 | ||
|
|
||
| ## Constitution Check | ||
|
|
||
| ✅ **레이어드 폴더 구조**: 수정 대상 파일이 기존 구조(`page/TimerPage/`, `page/TimerPage/components/`) 내 위치 | ||
| ✅ **코드 스타일**: function declaration 유지, camelCase/PascalCase 준수 | ||
| ✅ **TDD**: 테스트 파일 먼저 작성 후 구현 예정 | ||
| ✅ **i18n**: 기존 `t()` 사용 패턴 유지, 하드코딩 텍스트 없음 | ||
| ✅ **API 레이어 패턴**: API 변경 없음 | ||
| ✅ **순환 의존성**: 없음 | ||
|
|
||
| **Constitution 위반 사항**: 없음 | ||
|
|
||
| ## Project Structure | ||
|
|
||
| ### Documentation (this feature) | ||
|
|
||
| ```text | ||
| specs/fix/441-mute-timer-layout-fix/ | ||
| ├── plan.md # 이 파일 | ||
| ├── research.md # Phase 0 output ✅ | ||
| ├── data-model.md # Phase 1 output ✅ | ||
| ├── test-contracts/ | ||
| │ ├── NormalTimer.md # Phase 1 output ✅ | ||
| │ └── TimerPage-mute-icon.md # Phase 1 output ✅ | ||
| └── tasks.md # Phase 2 output (/speckits:tasks 로 생성) | ||
| ``` | ||
|
|
||
| ### Source Code (수정 대상) | ||
|
|
||
| ```text | ||
| src/ | ||
| ├── page/TimerPage/ | ||
| │ ├── TimerPage.tsx # 수정: 음소거 시 다른 아이콘 표시 | ||
| │ ├── TimerPage.test.tsx # 신규: 헤더 음소거 아이콘 TDD 테스트 | ||
| │ └── components/ | ||
| │ ├── NormalTimer.tsx # 수정: 두 줄 레이아웃 + text-center | ||
| │ └── NormalTimer.test.tsx # 신규: 레이아웃 TDD 테스트 | ||
| ``` | ||
|
|
||
| ## Architecture Decision Table | ||
|
|
||
| | 결정 | 고려한 옵션 | 선택 | 근거 | 프로젝트 구조 영향 | 테스트 용이성 | | ||
| |---|---|---|---|---|---| | ||
| | 음소거 아이콘 소스 | ① react-icons `RiVolumeMuteFill` ② 새 DTVolumeMuted 아이콘 | react-icons | 전체화면 토글 패턴과 일관성, 새 파일 불필요 | 변경 없음 | 높음 (클래스명/aria로 검증) | | ||
| | isMuted 파생 위치 | ① `TimerPage` 내 `volume === 0` ② `useTimerPageState` 인터페이스에 추가 | TimerPage 내 파생 | 파생값 추가 노출 불필요, 인터페이스 변경 최소화 | 변경 없음 | 높음 | | ||
| | 두 줄 레이아웃 방식 | ① flex-col + 개별 `<p>` ② `<br/>` 구분 | flex-col + 개별 `<p>` | 시맨틱, 각 줄 독립 스타일 적용 용이 | 없음 | 높음 (별도 요소로 쿼리) | | ||
| | 팀명 말줄임 | ① truncate를 팀명 줄에만 | truncate 팀명 줄만 | 토론자 줄은 항상 완전 표시 (FR-004) | 없음 | 높음 | | ||
| | 순서명 정렬 | ① text-center 추가 ② 현행 유지 | text-center 추가 | 한/영 모두 일관 중앙 정렬 보장 | 없음 | 높음 | | ||
|
|
||
| ## TDD Implementation Order | ||
|
|
||
| ### RED 단계 (테스트 먼저 작성) | ||
|
|
||
| **우선순위 1: NormalTimer 컴포넌트 (component 레이어)** | ||
|
|
||
| ``` | ||
| NormalTimer.test.tsx 작성 (모두 RED): | ||
| 1. 팀명과 토론자가 각각 독립된 DOM 요소로 분리되는지 | ||
| 2. 팀명 줄에 truncate 클래스가 있는지 | ||
| 3. 토론자 줄에 truncate 클래스가 없는지 | ||
| 4. 팀명만 있을 때 토론자 요소가 미렌더링되는지 | ||
| 5. 팀명, 토론자 모두 없을 때 아이콘 영역 미렌더링되는지 | ||
| 6. h1(순서명)에 text-center 클래스가 있는지 | ||
| ``` | ||
|
|
||
| **우선순위 2: TimerPage 페이지 (page 레이어)** | ||
|
|
||
| ``` | ||
| TimerPage.test.tsx 작성 (모두 RED): | ||
| 1. volume > 0일 때 음소거 아이콘이 없는지 | ||
| 2. volume === 0일 때 음소거 아이콘이 표시되는지 | ||
| 3. 볼륨을 0으로 변경하면 헤더 아이콘이 즉시 음소거로 변경되는지 | ||
| ``` | ||
|
|
||
| ### GREEN 단계 (최소 구현) | ||
|
|
||
| ``` | ||
| NormalTimer.tsx 수정: | ||
| - flex-row → flex-col 변경 | ||
| - 팀명 p 태그 분리 + truncate 유지 | ||
| - 토론자 p 태그 분리 | ||
| - h1에 text-center 추가 | ||
|
|
||
| TimerPage.tsx 수정: | ||
| - volume === 0 조건으로 RiVolumeMuteFill / DTVolume 분기 | ||
| ``` | ||
|
|
||
| ### REFACTOR 단계 | ||
|
|
||
| ``` | ||
| - 불필요한 className 정리 | ||
| - 테스트 가독성 개선 | ||
| ``` | ||
|
|
||
| ## Complexity Tracking | ||
|
|
||
| > Constitution 위반 없음 — 이 섹션은 해당 없음 |
Uh oh!
There was an error while loading. Please reload this page.