Skip to content

[FEAT] 청중용 라이브 토론 공유 페이지 개선#464

Open
i-meant-to-be wants to merge 13 commits into
developfrom
feat/#460
Open

[FEAT] 청중용 라이브 토론 공유 페이지 개선#464
i-meant-to-be wants to merge 13 commits into
developfrom
feat/#460

Conversation

@i-meant-to-be

@i-meant-to-be i-meant-to-be commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

🚩 연관 이슈

closed #460

📝 작업 내용

사회자 화면의 토론 진행 상태와 테이블 정보를 청중 화면에 함께 전달하여, 일반 발언과 자유토론 타이머를 실제 토론 구성에 맞게 볼 수 있도록 개선했습니다.

1. 청중용 테이블 조회 API 연동

청중 화면이 소켓의 남은 시간뿐 아니라 토론 주제, 테이블 이름, 팀과 발언자, 순서별 시간 설정까지 조회하여 완전한 화면을 구성합니다.

기존 청중 화면은 실시간 소켓 메시지만 받을 수 있어 “몇 초가 남았는지”는 알 수 있었지만, 그 시간이 어떤 발언 순서와 팀에 해당하는지는 표현할 수 없었습니다. 새로 제공된 청중용 테이블 조회 API를 AudienceSharePage에 연결하여 이 제약을 해소했습니다.

  • useGetDebateTableDataForShare로 테이블 정보와 전체 타이머 순서를 조회합니다.
  • API 조회가 성공한 뒤 청중 소켓 구독을 시작하도록 useAudienceSocketenabled 옵션을 추가했습니다.
  • 소켓 payload의 sequence와 API의 table[sequence]를 결합해 현재 순서의 실제 설정을 표시합니다.
  • 테이블 이름과 토론 주제를 헤더에 표시하고, 빈 값에는 기존 대체 문구를 사용합니다.
  • 잘못된 방 ID, API 조회 실패, 소켓 오류, 유효하지 않은 타이머 설정을 로딩·오류·재시도 화면으로 구분했습니다.

2. 일반 발언 타이머 화면 완성

일반 발언에서는 현재 순서의 성격과 참여자를 한눈에 확인하면서 남은 시간과 진행도를 함께 볼 수 있습니다.

단순히 남은 시간만 표시하던 화면을 실제 토론 테이블 설정에 맞게 확장했습니다.

  • 발언 유형(speechType)을 제목으로 표시합니다.
  • 찬성·반대 발언에서는 팀 이름과 토론자 이름을 함께 표시하고, 중립 순서에서는 참여자 영역을 숨깁니다.
  • MM:SS 형식의 고정폭 타이머와 초과 시간의 음수 표시를 지원합니다.
  • 전체 발언 시간 대비 경과 비율을 TimerProgressBar로 표시합니다.
  • 찬성은 파란색, 반대는 빨간색, 중립은 무채색으로 구분합니다.

3. 자유토론 타이머의 팀별 시간 관리

자유토론에서는 찬성·반대 팀의 시간을 각각 보존하고, 사회자의 재생·정지·팀 전환·초기화·순서 이동을 청중 화면에서도 같은 규칙으로 반영합니다.

자유토론은 팀 전체 시간과 1회당 발언 시간이 함께 움직이므로 일반 카운트다운만으로는 정확한 상태를 표현할 수 없습니다. 이를 위해 팀별 규칙을 담당하는 useAudienceTimeBasedCountdown을 별도로 추가했습니다.

  • PROS/CONS의 전체 시간과 현재 발언 시간을 독립적으로 보존합니다.
  • 현재 발언 중인 팀의 전체 시간과 현재 발언 시간을 같은 경과 시간만큼 감소시킵니다.
  • 두 시간 중 하나가 00:00에 도달하면 해당 팀의 카운트다운을 함께 정지합니다.
  • STOP 후에는 표시값을 유지하고, PLAY 시 유지한 값에서 다시 시작합니다.
  • TEAM_SWITCH 시 이전 팀의 시간을 보존하고 새 팀의 현재 발언 시간을 남은 전체 시간에 맞춰 초기화합니다.
  • RESET은 현재 팀을, BEFORE/NEXT는 이동한 순서의 양 팀을 API 설정값으로 다시 초기화합니다.
  • timePerSpeaking === null인 순서는 팀 전체 시간을 한 번에 사용할 수 있는 모드로 처리합니다.

저수준 시간 감소와 포맷팅은 기존 useAudienceCountdown이 계속 담당하며, 자유토론 훅은 네 개의 카운트다운을 조합하고 소켓 이벤트 규칙을 조정하는 역할만 담당합니다. syncKey, minimumTime, 실행 상태 전환 옵션을 추가하여 일반 타이머의 초과 시간 동작은 유지하면서 자유토론 시간만 0초에서 제한했습니다.

4. 자유토론 전용 2열 UI 구성

청중은 왼쪽에서 찬성 팀, 오른쪽에서 반대 팀의 전체·현재 시간을 동시에 비교할 수 있습니다.

AudienceTimeBasedTimer는 두 팀의 배치만 담당하고, 팀 하나의 세부 표현은 재사용 가능한 AudienceTimeBasedTimerDisplay로 분리했습니다.

  • timePerSpeaking === null이면 팀 이름, 전체 잔여 시간, 진행도를 표시합니다.
  • 1회당 발언 시간이 있으면 전체 시간 배지·전체 잔여 시간과 현재 시간 배지·현재 발언 잔여 시간을 함께 표시합니다.
  • 현재 발언 팀에는 aria-current="step"과 스크린 리더용 “현재 발언 중” 상태를 제공합니다.
  • 발언 중이 아닌 팀은 화면 전체를 흐리게 만들지 않고, 제목·타이머·배지·프로그레스 바를 무채색으로 직접 표시합니다.
  • 음수 시간은 00:00, 진행도는 0~100 범위로 제한합니다.

5. 사회자 화면의 소켓 잔여 시간 수정

사회자 화면이 자유토론 설정에 맞는 잔여 시간을 전송하여 청중 화면과 실제 타이머가 어긋나지 않도록 수정했습니다.

기존에는 자유토론 소켓 payload가 항상 현재 발언 타이머를 사용해, 1회당 발언 시간이 없는 순서에서 잘못된 값이 전송될 수 있었습니다.

  • getRemainingTimeForShare 순수 함수를 추가해 전송할 시간을 한 곳에서 결정합니다.
  • 일반 발언은 normalTimer.timer를 사용합니다.
  • 자유토론에 1회당 발언 시간이 있으면 선택 팀의 speakingTimer, 없으면 totalTimer를 사용합니다.
  • FINISHED는 명시적으로 null payload를 보내고, 유효한 잔여 시간이 없으면 타이머 이벤트를 발행하지 않습니다.
  • 라이브 공유 기능의 임시 비활성 플래그를 제거하여 로그인한 사용자가 기능을 사용할 수 있도록 했습니다.

6. 공통 진행도와 접근성 개선

일반 발언과 자유토론이 같은 진행도 표현과 접근성 규칙을 공유하도록 정리했습니다.

  • Framer Motion 기반 TimerProgressBar를 추가했습니다.
  • 진행도를 0~100으로 정규화하고 role="progressbar", aria-valuemin, aria-valuemax, aria-valuenow를 제공합니다.
  • 실행 중에는 진행도가 자연스럽게 움직이고, 정지·초기화 상태에서는 즉시 반영합니다.
  • 시간은 시각적으로 정렬된 숫자와 별도의 접근 가능한 aria-label을 함께 제공합니다.

7. 소켓 상태 정규화와 회귀 테스트 보강

반복되는 실시간 이벤트와 타이머 종류 전환까지 자동 테스트로 고정하여 이후 변경에서 동작이 깨지는 것을 방지합니다.

useAudienceShareState가 소켓 메시지를 화면에서 바로 사용할 수 있는 상태로 정규화하도록 확장했습니다.

  • 자유토론 상태에 sequence, eventType, revision을 보존합니다.
  • 동일한 값의 RESET이 연속으로 들어와도 각각 새로운 초기화로 인식합니다.
  • TEAM_SWITCH 전후의 실행 상태를 유지하고, BEFORE/NEXT의 실제 이동 대상 인덱스를 보정합니다.
  • 일반 발언과 자유토론 사이를 이동할 때 API의 대상 boxType을 기준으로 표시 상태를 전환합니다.
  • API 오류, 소켓 연결, 카운트다운 경계값, UI 분기, 팀 전환, 순서 이동, 소켓 전송 시간에 대한 Vitest 테스트를 추가·보강했습니다.

🧪 확인 사항

정적 검사와 프로덕션 빌드, 타이머·상태·UI 단위 테스트를 통해 주요 경로를 확인했습니다.

  • Prettier 포매팅
  • npm run lint — ESLint, Stylelint, TypeScript 검사
  • npm run build — Vite 프로덕션 빌드
  • 일반 발언 및 자유토론 관련 Vitest
  • 소켓 상태와 잔여 시간 선택 로직 Vitest

🏞️ 스크린샷 (선택)

일반 발언 타이머 (찬성)

스크린샷_13-7-2026_163227_localhost

일반 발언 타이머 (반대)

image

일반 발언 타이머 (중립)

스크린샷_13-7-2026_163238_localhost

자유토론 — 1회당 발언 시간 없음

스크린샷_13-7-2026_163310_localhost

자유토론 — 1회당 발언 시간 있음

스크린샷_13-7-2026_163213_localhost

🗣️ 리뷰 요구사항 (선택)

반드시 청중용 테이블을 로그인 세션과 분리된 다음 환경 중 하나에서 열어 주세요.

  • Chrome: 시크릿 모드(Incognito)
  • Edge: InPrivate 모드
  • Safari: 개인정보 보호 브라우징(Private Browsing)

그 상태에서 본인의 일반 브라우저 환경에는 사회자용 타이머를 열고, 아래 동작을 직접 실행해 청중 화면이 같은 상태를 표시하는지 확인 부탁드립니다.

  1. 일반 발언에서 재생, 정지, 초기화, 이전·다음 순서 이동
  2. 일반 발언의 발언 유형, 팀 이름, 토론자, 남은 시간과 진행도 표시
  3. 자유토론에서 재생, 정지, PROS/CONS 팀 전환과 초기화
  4. 자유토론의 팀별 전체 시간이 전환 후에도 보존되는지 확인
  5. 1회당 발언 시간이 있는 순서와 없는 순서에서 각각 올바른 시간이 감소하는지 확인
  6. 활성 팀은 진영 색상, 비활성 팀은 무채색으로 표시되는지 확인
  7. 토론 종료 시 청중 화면이 종료 상태로 전환되는지 확인

특히 사회자 화면의 자유토론 타이머와 청중 화면의 “전체 시간/현재 시간”이 팀 전환과 RESET 전후에 동일하게 움직이는지 중점적으로 리뷰 부탁드립니다.

Summary by CodeRabbit

  • 새 기능

    • 공유 화면에서 일반 타이머와 시간 기반 타이머를 더욱 정확하게 표시합니다.
    • 팀별 전체 시간과 현재 발언 시간을 구분해 보여줍니다.
    • 진행률 표시줄에 팀별 색상과 현재 발언 상태를 반영합니다.
    • 시간 초과 시 경과 시간을 명확히 표시합니다.
  • 개선 사항

    • 로딩, 연결 중, 서버 오류 상태를 일관된 화면으로 안내합니다.
    • 잘못된 타이머 정보로 인한 비정상 표시를 방지합니다.
    • 타이머 진행 및 팀 전환 시 남은 시간이 안정적으로 동기화됩니다.
    • 화면 접근성을 위한 타이머 및 진행률 안내를 강화했습니다.

@i-meant-to-be
i-meant-to-be requested a review from useon July 13, 2026 07:43
@i-meant-to-be i-meant-to-be self-assigned this Jul 13, 2026
@i-meant-to-be i-meant-to-be added the feat 기능 개발 label Jul 13, 2026
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@i-meant-to-be, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 51 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 64d7a3d9-772a-44c9-aa0a-2bca3f7a9f3d

📥 Commits

Reviewing files that changed from the base of the PR and between 2c4e9a7 and 222086b.

📒 Files selected for processing (2)
  • src/page/AudienceSharePage/components/AudienceTimeBasedTimerDisplay.test.tsx
  • src/page/AudienceSharePage/components/AudienceTimeBasedTimerDisplay.tsx

Walkthrough

청중용 공유 페이지에 진행 바와 시간 기반 타이머 표시를 추가하고, 카운트다운·소켓·이벤트 상태 전이와 로딩/오류 렌더링을 확장했다. TimerPage는 공유 이벤트의 잔여 시간 계산과 FINISHED 발행 흐름을 정리했다.

Changes

타이머 UI와 표시 컴포넌트

Layer / File(s) Summary
진행 바와 일반 타이머 표시
src/components/TimerProgressBar/*, src/page/AudienceSharePage/components/AudienceNormalTimer.*
진행률 클램핑, 팀별 색상, 애니메이션, 접근성 속성, 음수 시간 표시와 일반 타이머의 팀·발화자 정보를 구현했다.
시간 기반 타이머 표시
src/page/AudienceSharePage/components/AudienceTimeBasedTimer*
팀별 전체 시간과 현재 발언 시간을 표시하고, 현재 팀 상태·팀명 폴백·진행률·접근성 속성을 처리하도록 분리했다.

카운트다운과 이벤트 상태 전이

Layer / File(s) Summary
일반 카운트다운 확장
src/page/AudienceSharePage/hooks/useAudienceCountdown*
최소 시간, 음수 시간, 실행 상태 변경 시 리셋 여부와 동기화 키를 지원하도록 카운트다운을 변경했다.
시간 기반 카운트다운
src/page/AudienceSharePage/hooks/useAudienceTimeBasedCountdown*
양 팀의 전체 잔여 시간과 현재 발언 시간을 관리하고 PLAY, STOP, RESET, TEAM_SWITCH 전이를 처리한다.
공유 표시 상태 전이
src/page/AudienceSharePage/hooks/useAudienceShareState*
소켓 이벤트에 따라 sequence, eventType, revision, 팀, 실행 상태 및 타이머 시간을 계산하며 테이블 기반 BEFORE/NEXT 이동을 반영한다.

청중 공유 페이지 통합

Layer / File(s) Summary
소켓 활성화와 페이지 렌더링
src/hooks/sockets/*, src/page/AudienceSharePage/AudienceSharePage.*
쿼리·소켓 활성화 상태, 타임박스 유효성, 로딩·오류 화면, 일반·시간 기반 타이머 렌더링 및 헤더 표시 조건을 통합했다.

공유 이벤트 잔여 시간 발행

Layer / File(s) Summary
공유 잔여 시간 계산과 이벤트 발행
src/page/TimerPage/*
타이머 유형과 현재 팀에 따른 공유 잔여 시간 계산을 헬퍼로 분리하고, FINISHED 및 null 잔여 시간 처리 흐름을 변경했다.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested labels: design

Suggested reviewers: useon, jaeml06

Poem

깡충 rabbit, 타이머 길을 닦았네
팀별 시간도 반짝반짝 달리네
소켓 문은 필요할 때 열리고
진행 바는 알맞게 차오르고
“찰칵!” 이벤트도 질서 있게 간다네 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 청중용 라이브 토론 공유 페이지 개선이라는 핵심 변경을 간결하게 잘 요약합니다.
Linked Issues check ✅ Passed #460의 API 기반 청중용 공유 페이지 개선 목표와 소켓, 타이머, UI, 테스트 변경이 전반적으로 일치합니다.
Out of Scope Changes check ✅ Passed 요약된 변경들은 대부분 청중 공유 페이지와 연관되어 있고, TimerPage 수정도 공유 타이머 전송 보완으로 범위 안입니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#460

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

🚀 Preview 배포 완료!

환경 URL
Preview 열기
API Dev 환경

PR이 닫히면 자동으로 정리됩니다.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements and refactors the audience share page (AudienceSharePage) and its associated timers (AudienceNormalTimer, AudienceTimeBasedTimerDisplay) to fetch debate table data and synchronize normal and time-based countdowns via sockets. It also introduces the useAudienceTimeBasedCountdown hook and updates socket-related hooks and pages to handle state transitions, accompanied by comprehensive unit tests. The review feedback suggests initializing useMotionValue with the current progress in TimerProgressBar to prevent initial animation glitches, and recommends adding defensive nullish checks before calling .trim() on potentially null or undefined fields (such as teamName, name, and agenda) to avoid runtime errors.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

className,
}: TimerProgressBarProps) {
const normalizedProgress = clamp(0, 100, progress);
const progressMotionValue = useMotionValue(0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

페이지가 처음 렌더링되거나 새로고침될 때, 타이머가 이미 진행 중인 상태(예: 80%)라면 진행률 바가 0%부터 80%까지 애니메이션되는 시각적 어색함이 발생할 수 있습니다. useMotionValuenormalizedProgress로 초기화하면 첫 렌더링 시에는 애니메이션 없이 현재 진행률을 즉시 표시하고, 이후 변경 사항에 대해서만 부드럽게 애니메이션되도록 개선할 수 있습니다.

Suggested change
const progressMotionValue = useMotionValue(0);
const progressMotionValue = useMotionValue(normalizedProgress);

Comment on lines +38 to +41
const teamLabel =
teamName.trim() === ''
? t('팀명 없음')
: t('{{team}} 팀', { team: t(teamName) });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

API 응답 데이터에 teamName이 누락되거나 null 또는 undefined로 전달될 경우, teamName.trim() 호출 시 런타임 에러(TypeError)가 발생할 수 있습니다. 안전한 실행을 위해 teamName에 대한 nullish 체크를 추가하는 방어적 프로그래밍을 적용하는 것이 좋습니다.

Suggested change
const teamLabel =
teamName.trim() === ''
? t('팀명 없음')
: t('{{team}} 팀', { team: t(teamName) });
const teamLabel =
!teamName || teamName.trim() === ''
? t('팀명 없음')
: t('{{team}} 팀', { team: t(teamName) });

Comment on lines +59 to +62
const teamLabel =
teamName.trim() === ''
? t('팀명 없음')
: t('{{team}} 팀', { team: t(teamName) });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

API 응답 데이터에 teamName이 누락되거나 null 또는 undefined로 전달될 경우, teamName.trim() 호출 시 런타임 에러(TypeError)가 발생할 수 있습니다. 안전한 실행을 위해 teamName에 대한 nullish 체크를 추가하는 방어적 프로그래밍을 적용하는 것이 좋습니다.

Suggested change
const teamLabel =
teamName.trim() === ''
? t('팀명 없음')
: t('{{team}} 팀', { team: t(teamName) });
const teamLabel =
!teamName || teamName.trim() === ''
? t('팀명 없음')
: t('{{team}} 팀', { team: t(teamName) });

Comment on lines +329 to +343
name={
debateTableQuery.data.info.name.trim() === ''
? t('테이블 이름 없음')
: t(debateTableQuery.data.info.name)
}
/>
</DefaultLayout.Header.Left>
<DefaultLayout.Header.Center>
<HeaderTitle
title={
debateTableQuery.data.info.agenda.trim() === ''
? t('주제 없음')
: t(debateTableQuery.data.info.agenda)
}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

API 응답 데이터의 name 또는 agenda가 누락되거나 null 또는 undefined로 전달될 경우, .trim() 호출 시 런타임 에러가 발생할 수 있습니다. 안전한 실행을 위해 nullish 체크를 추가하는 방어적 프로그래밍을 적용하는 것이 좋습니다.

Suggested change
name={
debateTableQuery.data.info.name.trim() === ''
? t('테이블 이름 없음')
: t(debateTableQuery.data.info.name)
}
/>
</DefaultLayout.Header.Left>
<DefaultLayout.Header.Center>
<HeaderTitle
title={
debateTableQuery.data.info.agenda.trim() === ''
? t('주제 없음')
: t(debateTableQuery.data.info.agenda)
}
/>
name={
!debateTableQuery.data.info.name || debateTableQuery.data.info.name.trim() === ''
? t('테이블 이름 없음')
: t(debateTableQuery.data.info.name)
}
/>
</DefaultLayout.Header.Left>
<DefaultLayout.Header.Center>
<HeaderTitle
title={
!debateTableQuery.data.info.agenda || debateTableQuery.data.info.agenda.trim() === ''
? t('주제 없음')
: t(debateTableQuery.data.info.agenda)
}
/>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/sockets/useAudienceSocket.ts`:
- Around line 84-86: Update the enabled-state handling in useAudienceSocket so
transitioning enabled from true to false also clears latestMessage when
unsubscribing. Preserve the existing subscription behavior for enabled states
and ensure consumers cannot read the previous event while the subscription is
disabled.

In `@src/page/AudienceSharePage/AudienceSharePage.test.tsx`:
- Line 14: Replace the direct mock of useGetDebateTableDataForShare in
AudienceSharePage tests with MSW request handlers. Configure handlers covering
successful, failed, and delayed API responses so the tests exercise the real
query hook and page integration state transitions; remove the hook mock and
update affected test setup and assertions accordingly.

In `@src/page/AudienceSharePage/AudienceSharePage.tsx`:
- Around line 192-199: Update the hasInvalidNormalTimeBox ||
hasInvalidTimeBasedTimeBox branch in AudienceSharePage so validation failures
display a configuration-error message instead of the server-connection failure
text. Preserve the existing ErrorContent and handleReload behavior, and use the
repository’s Korean-string i18n key convention for the new message.

In `@src/page/AudienceSharePage/components/AudienceTimeBasedTimerDisplay.tsx`:
- Around line 79-83: AudienceTimeBasedTimerDisplay의 현재 발언 상태 안내 조건을 수정해,
isCurrentTeam뿐 아니라 타이머가 실행 중인 상태일 때만 `현재 발언 중`을 렌더링하세요. 일시 정지 상태에서는 해당 스크린 리더
안내가 표시되지 않도록 기존 실행 상태 변수를 함께 확인하고, 그 외 렌더링 동작은 유지하세요.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49d1a801-6e78-4761-b5d5-755910e6006d

📥 Commits

Reviewing files that changed from the base of the PR and between 7cf90bf and 2c4e9a7.

📒 Files selected for processing (21)
  • src/components/TimerProgressBar/TimerProgressBar.test.tsx
  • src/components/TimerProgressBar/TimerProgressBar.tsx
  • src/hooks/sockets/useAudienceSocket.test.ts
  • src/hooks/sockets/useAudienceSocket.ts
  • src/page/AudienceSharePage/AudienceSharePage.test.tsx
  • src/page/AudienceSharePage/AudienceSharePage.tsx
  • src/page/AudienceSharePage/components/AudienceNormalTimer.test.tsx
  • src/page/AudienceSharePage/components/AudienceNormalTimer.tsx
  • src/page/AudienceSharePage/components/AudienceTimeBasedTimer.test.tsx
  • src/page/AudienceSharePage/components/AudienceTimeBasedTimer.tsx
  • src/page/AudienceSharePage/components/AudienceTimeBasedTimerDisplay.test.tsx
  • src/page/AudienceSharePage/components/AudienceTimeBasedTimerDisplay.tsx
  • src/page/AudienceSharePage/hooks/useAudienceCountdown.test.ts
  • src/page/AudienceSharePage/hooks/useAudienceCountdown.ts
  • src/page/AudienceSharePage/hooks/useAudienceShareState.test.ts
  • src/page/AudienceSharePage/hooks/useAudienceShareState.ts
  • src/page/AudienceSharePage/hooks/useAudienceTimeBasedCountdown.test.ts
  • src/page/AudienceSharePage/hooks/useAudienceTimeBasedCountdown.ts
  • src/page/TimerPage/TimerPage.tsx
  • src/page/TimerPage/getRemainingTimeForShare.test.ts
  • src/page/TimerPage/getRemainingTimeForShare.ts

Comment on lines +84 to +86
if (!enabled) {
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

구독을 비활성화할 때 이전 메시지도 초기화하세요.

enabledtrue에서 false로 바뀌면 구독은 해제되지만 latestMessage는 남아, 소비자가 비활성 기간에 직전 이벤트를 현재 메시지로 읽을 수 있습니다.

수정 예시
     if (!enabled) {
+      resetMessage();
       return;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!enabled) {
return;
}
if (!enabled) {
resetMessage();
return;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/sockets/useAudienceSocket.ts` around lines 84 - 86, Update the
enabled-state handling in useAudienceSocket so transitioning enabled from true
to false also clears latestMessage when unsubscribing. Preserve the existing
subscription behavior for enabled states and ensure consumers cannot read the
previous event while the subscription is disabled.


// 모의 (Mock)
vi.mock('./hooks/useAudienceShareState');
vi.mock('../../hooks/query/useGetDebateTableDataForShare');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

API 쿼리 훅 직접 모킹을 MSW 핸들러로 교체하세요.

현재 방식은 실제 요청·쿼리 상태 연동을 우회합니다. 성공, 실패, 지연 응답을 MSW 핸들러로 구성해 페이지 통합 경로를 검증해야 합니다.

As per coding guidelines, “Use MSW (Mock Service Worker) for API mocking in tests, minimize other mocks” 규칙을 적용해야 합니다. <coding_guidelines>

Also applies to: 79-89

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/page/AudienceSharePage/AudienceSharePage.test.tsx` at line 14, Replace
the direct mock of useGetDebateTableDataForShare in AudienceSharePage tests with
MSW request handlers. Configure handlers covering successful, failed, and
delayed API responses so the tests exercise the real query hook and page
integration state transitions; remove the hook mock and update affected test
setup and assertions accordingly.

Source: Coding guidelines

Comment on lines +192 to 199
if (hasInvalidNormalTimeBox || hasInvalidTimeBasedTimeBox) {
return (
<ErrorContent
message={t('서버 연결에 실패했어요.')}
onReload={handleReload}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

잘못된 타임박스를 소켓 연결 장애로 표시하지 마세요.

이 분기는 API 시간 설정 검증 실패이므로 “서버 연결에 실패했어요.”는 원인을 잘못 안내합니다. 설정 오류임을 명확히 표시해 주세요.

수정 예시
         <ErrorContent
-          message={t('서버 연결에 실패했어요.')}
+          message={t('토론 시간 설정을 확인할 수 없어요.')}
           onReload={handleReload}
         />

Based on learnings, 이 저장소의 TSX i18n 키는 한국어 문자열 규칙을 유지했습니다. <retrieved_learnings>

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (hasInvalidNormalTimeBox || hasInvalidTimeBasedTimeBox) {
return (
<ErrorContent
message={t('서버 연결에 실패했어요.')}
onReload={handleReload}
/>
);
}
if (hasInvalidNormalTimeBox || hasInvalidTimeBasedTimeBox) {
return (
<ErrorContent
message={t('토론 시간 설정을 확인할 수 없어요.')}
onReload={handleReload}
/>
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/page/AudienceSharePage/AudienceSharePage.tsx` around lines 192 - 199,
Update the hasInvalidNormalTimeBox || hasInvalidTimeBasedTimeBox branch in
AudienceSharePage so validation failures display a configuration-error message
instead of the server-connection failure text. Preserve the existing
ErrorContent and handleReload behavior, and use the repository’s Korean-string
i18n key convention for the new message.

Source: Learnings

Comment on lines +79 to +83
{isCurrentTeam ? (
<span className="sr-only" data-testid={`${teamId}-speaking-status`}>
{t('현재 발언 중')}
</span>
) : null}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

정지 상태를 “현재 발언 중”으로 안내하지 마세요.

현재 팀이지만 일시 정지된 경우에도 스크린 리더가 현재 발언 중으로 안내합니다. 실행 상태도 함께 확인해야 합니다.

수정안
-      {isCurrentTeam ? (
+      {isCurrentTeam && isRunning ? (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{isCurrentTeam ? (
<span className="sr-only" data-testid={`${teamId}-speaking-status`}>
{t('현재 발언 중')}
</span>
) : null}
{isCurrentTeam && isRunning ? (
<span className="sr-only" data-testid={`${teamId}-speaking-status`}>
{t('현재 발언 중')}
</span>
) : null}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/page/AudienceSharePage/components/AudienceTimeBasedTimerDisplay.tsx`
around lines 79 - 83, AudienceTimeBasedTimerDisplay의 현재 발언 상태 안내 조건을 수정해,
isCurrentTeam뿐 아니라 타이머가 실행 중인 상태일 때만 `현재 발언 중`을 렌더링하세요. 일시 정지 상태에서는 해당 스크린 리더
안내가 표시되지 않도록 기존 실행 상태 변수를 함께 확인하고, 그 외 렌더링 동작은 유지하세요.

@useon useon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

숀 !!! 리뷰가 늦어 죄송합니다 ㅜㅜ .. 이번 경우에는 전체적인 공유 페이지 개선이라 코드가 많이 바뀌어 어쩔 수 없었을 것 같아요 👍😊 다음에는 쪼매 작은 단위로 올려주시면 리뷰를 더 빨리할 수 있을 것 같아요!!! 몇 가지 코멘트 남겼으니 편하게 확인해 주세요!! 라이브 공유 관련 구현 감사해요 ~!!

* @returns {Error | null} returns.error - 가장 최근에 발생한 소켓 오류입니다.
*/
export default function useAudienceSocket(roomId: number) {
interface UseAudienceSocketOptions {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인터페이스 이름이 use로 시작이 되어서 살짝 훅 같이 헷갈리는 것 같아요 !
AudienceSocketOptions 요런 이름은 어떨까요?

roomId: number,
options: UseAudienceSocketOptions = {},
) {
const { enabled = true } = options;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재는 AudienceSharePage에서 API 조회 성공 이후에만 소켓을 붙이기 위해 enabled를 넘기고 있는데, 훅 자체의 기본값은 true라서 옵션을 빼먹으면 바로 구독이 시작됩니다. 의도한 설계라면 괜찮지만, 청중 소켓처럼 roomId와 초기 데이터 준비 순서가 중요한 훅이라면 기본값을 명시적으로 둘지, 호출부에서 항상 enabled를 넘기게 할지 정해두면 더 예측 가능할 것 같습니다 !!

return (
<span
className={clsx(
'grid w-[5ch] grid-cols-[2ch_1ch_2ch] items-center justify-center gap-x-[0.33ch] font-bold tabular-nums',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 곳에서만 ch 단위를 쓰신 이유가 있으실까요 ??

onReload: () => void;
}

function LoadingContent() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금 AudienceSharePage가 API 로딩/에러 처리뿐 아니라 소켓 이벤트 해석, NEXT/BEFORE/RESET/TEAM_SWITCH에 따른 sequence 계산, timeBox 유효성 검증, 일반/자유토론 타이머 분기까지 한 번에 처리하고 있습니다. 그래서 가독성이 떨어지고 복잡도가 높아진 것 같아요.

소켓 이벤트를 받아 청중 화면 상태를 만드는 로직을 별도 도메인 함수로 분리하는 방법은 어떨까요? 예를 들면 WAITING | NORMAL_TIMER | TIME_BASED_TIMER | FINISHED | ERROR 같은 화면 상태를 먼저 만들고, 페이지는 그 결과만 렌더링하는 ..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 청중용 라이브 토론 공유 페이지 개선

2 participants