마지막 업데이트 2026-07-22
roomId 보존 캐시(id-017)와 새 MediaStream 객체 할당(id-029) 패턴으로 안정화되었으나, PPI-968 시간종료 버튼(id-037)·#644 볼륨 슬라이더 재도입은 또다시 revert되어 5월 말 기준 여전히 진행 중인 영역이다.
치료사가 진행 중인 1:1 / 그룹 수업을 한 화면에서 동시 관찰하는 monitor-dashboard의 핵심 UI. 각 카드(SessionCard)는 한 명의 게스트(아동) 세션 하나를 담당한다.
apps/web/features/session/ui/session-card.tsx - 카드 전체apps/web/widgets/monitor-dashboard/* - 대시보드/그룹 카드 컨테이너apps/web/app/monitor-dashboard/[group]/* - 그룹 카드뷰 페이지 (정렬·머지 로직)apps/web/hooks/use-scheduled-sessions.ts - 폴링 + sticky 캐시apps/web/features/session/ui/session-audio-player.tsx - GainNode·MediaStream 핸들링| 그룹 | 기간 | 대표 PR | 패턴 |
|---|---|---|---|
| UI 통일 | 2/28 ~ 3/24 | #420 → #490 → #494 revert → #496 재도입 | 1차 시도 → 회귀 발견 → 버그 수정 포함 재도입 |
| 자리 안정화 | 4/29 ~ 5/19 | #578 → #606 revert → #622 재도입 | sticky 캐시 단독 → roomId 정렬 결합으로 보강 |
| 카드별 볼륨 | 5/1 ~ 5/24 | #581 / #596 → #604·#605 revert → #638 → #644·#157d revert | 4차례 revert. 트랙 교체 시 무음 회귀가 반복 |
| 시간/타이머 | 3/4 ~ 5/24 | #430 → #434 revert / #495 / #646 → #649 revert | 누적시간 정의·종료 흐름이 1:1뷰와 동기화 필요 |
| 스텝 네비게이션 | 3/26 ~ 4/27 | #462, #570 | 안정 안착. 카드뷰만의 차별 기능 |
| 발화/언어 감지 | 3/26 ~ 4/9 | #461, #480, #535(MFCC 전환) | Whisper LID → MFCC 전환과 동기화 |
| 게스트/네트워크 | 3/19 ~ 4/15 | #523, #547 | 안정 안착 |
| 이슈 스탬프 바 | 4/13 ~ 5/9 | #555 → #607 revert | 도입 후 사용 패턴 미스매치로 롤백 |
카드뷰에서 진입하는 포커스뷰와 대시보드에서 진입하는 1:1 모니터링뷰의 UI를 하나로 통합. 그룹 내 세션 전환 패널·뒤로가기·Open 버튼 동작을 모두 type=oneOnOne URL 파라미터 기반으로 일원화.
window.open(새 탭)으로 변경하여 1:1 모니터링과 동일 진입isOneOnOneSession 분기 dead code 삭제 (ActiveSessionsPanel, MinimizedSessionCard 등)ScheduledClass/Lesson에 담당 진행자(memberId/Name, groupMemberIds) 필드 추가SessionCard(1:1) 3단계 색상·초록 dot·하단 바 재설계 (확인중·담당·내보내기)LiveSessions 통합 그룹 카드 3영역 구조 (헤더+N/M+입장 / 인라인 세션 / 하단 바)broadcast-utils getRoomList가 group-monitoring-claim 피어를 그룹 룸 hosts에 포함id-038)
그룹 카드뷰 진입자(group-monitoring-claim)가 포커스뷰에서 "X님이 확인 중입니다" 오버레이를 잘못 트리거하는 회귀 발견. 단일 PR로 묶여 있어 통째로 revert.
그룹 카드뷰 진입자를 hostsSummary에 포함하지 않도록 변경하여 포커스뷰 오버레이 오작동 해결. 대시보드 "확인 중" 뱃지는 별도 채널(connected-hosts-updated)에서 받으므로 영향 없음.
그룹 카드뷰에서 다른 진행자를 내보내는 기능. id-031에서 1차 revert 후 id-049에서 reapply.
useLessonCompletionWithDeps 도입 (4개 CRUD 자동 주입)LessonCompletionModals 통합 — 1:1뷰·카드뷰 간 중복 제거CardEndMenu 컴포넌트 추가 (좌상단 종료 pill + 드롭다운)임시개방(isTempOpen)으로 1:1 운영 중인 그룹 사용자가 정상 그룹 카드로 분류되는 회귀. ScheduledSession 인터페이스에 isTempOpen 추가, routerManager.setGroupId 시그니처를 string | undefined로 확장.
카드뷰의 가장 끈질긴 회귀 영역. 같은 그룹의 카드가 게스트 socket 진입·외부 API 상태 변화에 따라 자리를 옮기는 문제가 3개월 내내 다른 형태로 재발했다.
updateSessionJoinStatus의 unshift 리오더 로직 제거. mergedRooms 배열에 scheduledStartTime 오름차 + userName 한글 보조 정렬 추가. 게스트 접속 시 카드 위치 변경 버그 1차 차단.
수업 시작 +20분 즈음 외부 PPI API가 status를 COMPLETED로 전이시키면 useScheduledSessions 폴링 응답에서 해당 세션이 누락 → mergedRooms 정렬 키가 (∞, userId UUID)로 점프하여 같은 시간 슬롯의 카드 자리가 뒤집힘.
// 1. includeCompleted=true 호출
// 2. sticky 캐시 (TTL 6분)으로 일시 누락된 비-terminal 세션 보존
// 3. terminal status는 즉시 제거
// 4. 정렬 comparator에 roomId tie-breaker, userName→userId 폴백 제거
id-001)
예상치 못한 동작 발견(폴 실패 시 화면 비는 케이스 등 추정). 단독 revert.
두 가지 회귀를 함께 잡도록 통합.
mergedRooms가 정렬 없이 Map 삽입 순서(소켓 활성 룸 먼저 → 예약만 된 룸 뒤)에 의존 → roomId 사전순 정렬 추가. roomId = `${userId}_${lessonIndex}`는 회기당 영구 불변이라 자리 결정적 고정.StickyEntry → RetainedSession, STICKY_TTL_MS → RETAINED_SESSION_TTL_MS).!response.ok / catch 분기에서 setScheduledSessions([]) 제거 — 폴 실패 한 사이클에도 화면이 비지 않도록 sticky 유지.roomId)로 잡아야 함. sticky 캐시는 보조 도구이지 단독 해결책이 아님.3개월 변천사 중 가장 많은 revert(4회)가 발생한 영역. 모두 "트랙 교체 시 게스트 음성이 무음이 되는" 동일한 회귀에서 비롯됨.
GainNode로 100% 초과 증폭 (HTML audio.volume 한계 우회)<audio>를 stream keepalive 용도로 유지forwardRef로 resumeContext 노출하여 autoplay 정책 안정성 확보슬라이더가 AI(핑퐁이)·영상까지 함께 조절되는 회귀 발견 → 게스트 마이크만 별도 guestGain 노드로 분리. volumeToGain 함수로 piecewise 매핑 도입 (0~100%는 0~1, 100~300%는 1~5 최대 5배 증폭). volume/isMuted/isActive ref render-time 갱신으로 stale prop 회피.
id-039 / id-054)
실제 운영에서 카드 청취가 OFF→ON 되는 순간이나 mic-audio 트랙 교체 후 게스트 음성이 무음이 되는 회귀 발견. 두 PR 모두 통째로 revert. id-050(MediaStream 객체 재할당 fix)도 함께 id-014에서 revert되어 5/7에만 3건의 revert가 연쇄 발생.
GainNode, MAX_GAIN=5, 게스트 전용 guestGain 분리SessionAudioPlayer가 render 시점 트랙 ID 변화를 감지해 source node를 재생성 (이중 방어). W3C 스펙상 script-mutation 경로에서 발화하지 않는 onaddtrack/onended 이벤트 의존성 제거forwardRef + resumeContext()로 청취 토글 user gesture 내에서 AudioContext 활성화 보장 (Chrome autoplay 정책 대응)MediaStream 객체 할당 + SessionAudioPlayer render time 트랙 ID 감지. 단일 PR에 모든 방어 로직을 묶어 통합 검증.같은 날 별도 PR(#644)로 또 다른 형태의 카드별 청취 볼륨 슬라이더 도입이 시도되었으나 곧바로 revert. 5월 말 기준 이 영역은 여전히 진행 중이며 #638(id-029)이 현행 안정판.
포커스뷰 "경과 시간"이 좀비 세션 duration을 포함한 endedSessionsTotal을 사용해 1129204:28 같은 비정상 값 표시. 포커스뷰·카드뷰 모두 서버 관리 sessionSummary.totalDuration을 사용하도록 통일. 카드뷰 헤더 inline 타이머 제거 및 오른쪽 큰 타이머를 "경과 시간 → 누적 시간"으로 교체.
id-024)
통일한 누적 시간 정의가 다른 화면과 어긋나는 회귀 의심. 단독 revert 후 별도 후속 PR(id-027, id-002)로 호스트 UI 누적 시간을 게스트 자동전환 판정값과 일치시키는 방향으로 천천히 재정비.
카드 진입 시 바로 해당 세션 오디오를 청취하도록 초기값을 OFF→ON. 카드 테두리 녹색 표시 + 비디오 mute 자동 해제. 안정 안착.
host-session-end-conversation 추가 (avatar-handlers.ts)HeadphonesIcon, PauseCircleIcon)InterruptModeButton에 hideIcon prop → 카드뷰/1:1뷰 레이아웃 통일goToNextStep 폴백socket.rooms.has(roomId), role 검증)id-025)
시간종료 흐름이 카드뷰·1:1뷰·게스트 측에서 모두 동작해야 하는데, 어딘가에서 회귀 발견. 통째로 revert. 5월 말 기준 미해결 영역.
STT 전사 결과에서 영어 키워드 감지 시 카드뷰 토스트 + 로그 하이라이트 표시.
LanguageMonitorStatusIndicator 추가 (Whisper/MFCC 상태 시각화)Whisper LID 전체 제거 + MFCC 패턴 시퀀스 기반으로 영어 발화 감지 재설계. 별도 회고 문서 MFCC 패턴 감지 튜닝 변천사에서 상세히 다룬다.
(#399 PPI-675) 카드뷰에 '응' 자동 발송 토글 추가 → (#455 PPI-839) 자동 발송 제거하고 수동 발송만 → (#487 PPI-828) 문구를 "응"에서 "음..."으로 변경. 사용자 피드백을 따라 정책이 점진적으로 변화.
게스트·호스트 양측의 네트워크 품질을 감지해 카드뷰에 빨강 오버레이/뱃지로 표시. 호스트 독립 계산 critical 레벨이 guestNetworkLevel에 반영되지 않던 버그도 함께 수정.
SessionHeader에 연결 상태 표시 영역(Row 2) 추가bg-slate-50 배경으로 헤더와 시각적 구분GUEST_TRANSPORT_STATE 이벤트를 카드뷰 그룹 모니터(monitor-group-{groupId})에도 relayuse-session-registration.ts. store에 저장하는 userName에서 _${lessonIndex} 접미사 제거use-session-checker.ts. resolveDisplayName이 정확히 ${userId}_${lessonIndex} 패턴인 경우만 UUID fallback 분류 (stale 데이터 방어)displayName === userId인 경우 UUID 직접 노출 차단, "아동" fallback 사용toast-container.tsx. wrapper pointer-events-auto → pointer-events-none, 각 토스트에 auto 부여dvh 기반으로 전환 — 화면 배율(80~125%)에 딱 맞게 조정clamp+dvh로 부드럽게 스케일링clamp+dvh로 통일[@media(max-height:870px)]) 패딩/여백 축소(#435) 카드뷰 음성 유저 청취 자동 활성화 제거. (#436) 카드뷰에서 포커스뷰 진입 시 청취 상태 OFF로 전환. 사용자가 동시에 같은 오디오를 두 채널에서 듣게 되는 케이스 방지.
게스트 디버그 패널(테스트 모드)에서 mic-audio(440Hz), ai-audio(880Hz) 오실레이터 테스트 톤을 mediasoup으로 전송하는 버튼. 모니터 카드뷰에 mic/ai 스트림 수신 상태를 AiAudioEqualizer로 표시.
모니터에 AI 오디오 이퀄라이저 추가 + 활동 전환 시 녹음 잘림 수정. 발화 이퀄라이저 가시성 확보.
PLC 상태 샘플링 + 기계음 판정 시 PLC 활성 구간 제외. CV/ZCR/RMS 3중 필터. getAiAudioInboundStats 콜백으로 WebRTC stats 제공. 카드뷰 기계음 감지(id-005 PPI-627)의 후속 안정화.
SessionCard 하단에 토글식 "이슈 기록하기" 스탬프 바category="web" + 빈 message로 즉시 POST /api/issue-textsvoice_recognition 라벨만 카드뷰에서 "사운드"로 오버라이드 (포커스뷰는 유지)id-042)
기능 자체는 문제없으나, 실제 사용 패턴과 미스매치(추정). 5월 말 기준 재도입 미완.
모니터 카드뷰 페이지 진입 시점에 LogRocket 녹화 시작. CS 대응·치료사 행동 분석용. PPI-910 게스트 연결 인디케이터와 함께 "사후 영상 리뷰" 인프라를 형성.
‹/› 텍스트로 교체branchOrigin → lastStep 통합. 분기/순차/스텝목록 점프 모든 케이스 전환 취소 지원lastStep이 덮어쓰이는 버그 수정 (hasMoved 체크)isReturningRef로 전환 취소 시 lastStep 갱신 루프 방지위 ⑤ 섹션 항목 참조. 1:1뷰·카드뷰 간 SSOT 합성 훅 도입.
| 원본 PR | revert SHA | 재도입 SHA | 핵심 회귀 원인 |
|---|---|---|---|
| #490 PPI-765 | id-038 |
id-018 (+버그 수정) |
그룹 카드뷰 진입자가 포커스뷰 오버레이 잘못 트리거 |
| #578 PPI-930 | id-001 |
id-017 (+roomId 정렬) |
sticky 캐시 단독으로는 자리 결정성 부족 |
| #581 볼륨 | id-054 |
id-029 |
트랙 교체 시 게스트 음성 무음 |
| #596 PPI-941 | id-039 |
id-029 (통합) |
위와 동일 — 같은 날 함께 revert |
| #602 PPI-943 | id-014 |
id-029 (흡수) |
MediaStream 객체 재할당이 단독 PR로는 회귀 |
| #430 PPI-785 | id-024 |
분산 후속 작업 | 누적 시간 정의가 다른 화면과 불일치 |
| #555 PPI-767 | id-042 |
미정 | 실제 사용 패턴 미스매치 |
| #646 PPI-968 | id-025 |
미정 (진행 중) | 시간종료 흐름 다중 진입점 회귀 |
| #644 볼륨 재도입 | id-003 |
미정 (진행 중) | #638 안정판 직후 추가 변경이 다시 revert |
MediaStream·AudioContext·GainNode 체인이 stale 상태로 남음. 해결책은 새 MediaStream 객체 할당 + render time 트랙 ID 감지로 source node 재생성. 단일 fix만 따로 PR하면 다시 깨지므로 볼륨 슬라이더 PR에 통합 검증 필요.
userName·표시명·소켓 활성 상태처럼 변할 수 있는 키를 정렬 기준으로 쓰면 게스트 입장·외부 API 응답에 따라 카드가 이동. roomId(userId_lessonIndex) 사전순 정렬이 유일한 안정 해법.
useLessonCompletionWithDeps·LessonCompletionModals처럼 합성 훅으로 모아야 PPI-968·#489 같은 작업이 안전.
hostsSummary에 누가 들어가는지(group-monitoring-claim 포함 여부) 같은 의미 변경은 포커스뷰 오버레이 등 멀리 떨어진 UI에 영향./api/scheduled-lessons의 completionStatus가 외부 PPI API 상태에 의존 → 폴링 누락·전이 시점이 UI 자리/표시를 흔들 수 있음. includeCompleted=true와 sticky 캐시 두 단계 방어 권장.onaddtrack/onended는 script-mutation 경로에서 발화하지 않음(W3C 스펙). render time 감지가 더 신뢰성 높음.id-037 revert 후 미해결. AI 마무리 멘트 단순화·체크아웃 점프 규칙 재정비 필요.#638은 안정. #644에서 다시 revert. 추가 변경이 필요한 케이스가 명확하지 않음.#555 revert 후 사용 패턴 정의가 다시 필요.MFCC 패턴 감지 알고리즘의 변천은 별도 문서 MFCC 패턴 감지 튜닝 변천사 참조.