2026-07-21 PulseUp 작업일지입니다. 오늘은 12건의 작업을 진행했습니다.
리모컨 운동 시작 카운트다운 상태 오판 수정
현상/기능
- 리모컨 운동 시작 시 “운동이 종료되었습니다” 안내만 표시되고 시작되지 않는 것처럼 보이는 문제 수정
- 시작 직후 ready 폴링과 실제 completed 상태 구분 검증
작업
- Electron의 6초 시작 카운트다운 동안 유지되는 `ready` 응답을 종료로 오판하던 폴링 race를 수정. 명시적인 `completed`에서만 종료 안내를 표시하고, 로컬 시작 직후의 `ready`는 `playing` 상태를 유지하도록 처리
- 카운트다운 중 ready는 종료가 아니며 completed만 종료 상태가 되는 회귀 테스트 2건을 추가. 관련 테스트 4건 및 Web TypeScript 검사 통과
- **Web** · `components/`, `components/`
- **Test** · `components/`
SINGLE Main-Training Loop 전용 실행
현상/기능
- `workout_exercises_logic.md` §9의 1Monitor Main Loop 기준으로 SINGLE 전용 파일 구성 및 실행 순서 점검
- 전반 `1,2,3,6,5,4`, 후반 `7,8,9,12,11,10`, MOVE 1~6 및 전용 모듈 라우팅 검증
작업
- `main/single/`
- 공용 MULTI Loop 모듈 사용을 제거하고 SINGLE 전용 DS→전환→전·후반 RND1~3→전환→CD 실행 모듈을 연결. 반복되는 RND 번호를 현재 연속 6MOVE 블록으로 분리하고 각 MOVE의 실제 position 영상을 순서대로 전달하도록 수정
- 전·후반 동일 RND 번호의 블록 격리, snake 순서, 실제 영상 position broadcast를 검증하는 4개 테스트를 추가. main 빌드 및 SINGLE/경계 테스트 12건 통과
- **Test** · `main/single/`
SINGLE/MULTI 공유 stateful 서비스 분리 후속
현상/기능
- API 한도로 중단된 서브에이전트 후속 — 공유 `WorkoutPlayService`/`PlaybackController` 상속 제거
- 누락된 MULTI renderer entry 복구 및 공유 루트 모듈 import 경계 강화
작업
- `main/single/`, `main/multi/`, `*-`
- mode별 play service·playback core를 독립 파일로 복제하고 execution/controller가 각각 로컬 구현만 상속하도록 연결. 루트 /는 MULTI 재export 호환 진입점만 유지
- `renderer/multi/`, , `presentation/`
- bootstrap이 MULTI entry로만 부팅하도록 복구하고 IPC는 runtime contract만 참조. single/multi가 공유 루트 play/playback를 import하지 못하도록 경계 테스트 추가
SINGLE/MULTI main 런타임 격리
현상/기능
- SINGLE/MULTI 실행 서비스와 playback controller의 import·상태 공유 제거 및 현재 presentation runtime만 선택
- 전역 BrowserWindow broadcast 제거 및 renderer ready 이전 이벤트 유실 방지
- 런타임 상태 격리, 대상 창 fan-out, 준비 전 이벤트와 MULTI 기존 동작 회귀 검증
작업
- `main/single/*`, `main/multi/*`, `main/presentation/`
- mode별 publisher·execution service·controller·circuit runner를 독립 조립하고 router가 presentationMode에 따라 현재 runtime만 위임. MULTI queue·intro·DS preload·Stress 전체 그룹 fan-out과 SINGLE 순서·단일 MOVE 정책을 각각 고정
- `main/presentation/`, `main/`, `main/`, `main/`
- mode별 창 대상 publisher 앞에 registry를 연결해 로드 전 이벤트를 순서대로 보관·flush하고 HTTP·심박 이벤트도 현재 runtime publisher로 전달
- main 테스트 22개 파일 50건 통과 및 `build:main` TypeScript 빌드 성공. SINGLE/MULTI 상호 import 금지 경계도 자동 검증
- **Test** · `main/presentation/*.`, `main/multi/`
운동 재생 Web/API payload 계약 경계
현상/기능
- SINGLE/MULTI 재생 계약을 분리하고 direct/relay 경로에서 동일 builder 사용
- relay가 Web에서 전달된 mode별 metadata를 변경하지 않는 경계 유지
- 모드별 전용 필드 격리, 공통 metadata 보존 및 API relay 불변 계약 검증
작업
- 공통 운동 metadata 계산과 모드별 계약 조립을 분리해 SINGLE은 선형 scope·방향·인트로 비활성을, MULTI는 presentation·인트로 활성을 독립적으로 명시
- 모니터 표시 설정 보강을 전용 모듈로 분리하고 metadata 객체는 그대로 유지한 채 최상위 표시 정보만 추가
- Web 3건·API 2건 통과, Web TypeScript typecheck·Vite production build와 API build 성공. 표준 Web build는 기존 `ignoreDeprecations: "6.0"`과 TypeScript 5.9.2 불일치로 중단되어 호환 CLI override로 typecheck를 별도 검증
- **Web** · `MonthProgram/components/`
- **API** · `routes/`, `routes/`
- **Test** · `MonthProgram/components/`, `tests/`
SINGLE 현재 영상 반복 재생
현상/기능
- 현재 재생 중인 영상이 한 번 종료된 뒤 멈추지 않고 운동 구간 동안 계속 반복 재생
- 시작 지점 복귀 후 재생 순서와 SINGLE 영상 풀 회귀 검증
작업
- `renderer/components/`
- Vimeo `ended` 및 `video_end_time` 도달 시 `video_start_time`으로 이동한 후 `play`를 호출하도록 반복 재생을 보강하고 중복 seek를 차단
- 반복 재생·영상 풀 집중 테스트 4건 통과
- **Test** · `renderer/components/`, `renderer/single/`
SINGLE Main-Training Stress 실행 순서·라벨
현상/기능
- SINGLE Stress가 A1 SET1→SET2→SET3 순서가 아닌 round 우선 순서로 재생
- 첫 MOVE에서 MULTI용 6개 영상 broadcast로 SINGLE 영상·라벨이 마지막 항목으로 덮이는 문제
- 숫자 position 4·5·6 라벨이 B3·B2·B1로 잘못 표시
- SINGLE Stress 순서·현재 MOVE broadcast·라벨 회귀 검증
작업
- `main/single/`
- DS→Main→CD phase 순서는 유지하면서 Main 내부는 Web 저장 순서(MOVE 고정·SET 순환)를 보존하도록 SINGLE Stress 전용 정렬 정책을 분리
- `main/single/`, `workout-modules/circuits/stress/`
- MULTI는 그룹 전체 broadcast를 유지하고 SINGLE은 현재 MOVE 하나만 전달하도록 presentation 정책을 분리
- `renderer/single/`
- 숫자 position을 화면 배치 순서 `A1..A3, B1..B3, C1..C3, D1..D3`에 직접 매핑해 Stress와 Loop 재생 순서 모두 올바른 라벨을 표시
- 관련 집중 테스트 20건 및 Electron 전체 테스트 91건 통과, main·renderer TypeScript 빌드 성공
- **Test**
SINGLE DS/CD position 라벨
현상/기능
- Dynamic Stretch·Cool Down 구간 라벨이 A1/A2 대신 DS1/CD1 형식으로 표시
작업
- `renderer/single/`
- stretchingMode 시 grid 슬롯(A1)을 round 0→DS, round 99→CD 접두어로 변환하고 `currentStretchingIndex`를 우선 적용해 DS1·CD2 형식으로 표시
SINGLE remaining 3초 경고색
현상/기능
- 영상 화면 상단 카운트가 3초부터 붉은색으로 표시
작업
- `renderer/single/`
- `single-remaining`에 remainingSeconds를 전달해 3초 이하일 때 `#e53935` 적용, 4초 이상은 흰색 유지
SINGLE DS1 시작 프리뷰
현상/기능
- SINGLE main-training stress 시작 시 DS1 없이 A1 영상부터 재생
- DS1 프리뷰 회귀 검증
작업
- `main/`, `renderer/single/`
- SINGLE play start 시 `broadcastWorkoutPlayPreview`를 호출하고 renderer에서 `workout-play-preview`를 구독해 stretching positionGroups 기준 DS1을 카운트다운 중 즉시 재생. prepare 시 타임라인 초기 인덱스를 round 0(DS) 우선으로 설정
- stretching preview payload에서 DS1·초기 타임라인 인덱스 해석 단위 테스트 추가
- **Test**
SINGLE 영상 시퀀스·프리로드·position 라벨
현상/기능
- 세로 운동 종료 후 재시작 시 3분할이 아닌 영상 1개만 표시
- A1/A2 position 라벨을 3화면 position-overlay와 동일한 스타일로 통일
- DS/CD 구간 SET/RND·MOVE가 증가하지 않고 0으로 유지
- SINGLE에서 벨소리가 나지 않음
- SINGLE 벨 회귀 검증
- DS/CD 누락, 운동영상 A1 라벨, 프리로드 로딩 반복, 상·하 썸네일 preload
- 다음 운동 전환 시 로딩 스피너가 반복 표시
- SINGLE 재생 회귀 검증
작업
- `renderer/single/`
- 종료 후 `workout-play-ready`가 session만 보낼 때 metadata·sequences·videoOrientation이 horizontal로 초기화되던 문제를 session merge 정규화와 main 재브로드캐스트 보강으로 수정
- `renderer/components/`, `renderer/single/`
- 3화면과 동일한 Arial Black 60px·검정 텍스트·투명 배경 스타일을 공통 상수로 추출하고 SINGLE position 라벨에 `position-overlay` 클래스로 적용
- round 0·99·stretchingMode일 때 헤더 카운터를 `SET/RND 0/0`, `MOVE 0/0`으로 고정 (3화면 timer-header-display SET 규칙과 동일)
- `renderer/components/`, `single/`, `single/`
- 3화면과 동일하게 remaining 3초 StartBell·session-start 카운트다운 벨·suppressCountdownBell을 적용. workout-single 카운트다운 모달 지원 추가
- exercise 3초·suppress·미지원 타입 벨 재생 규칙 단위 테스트 3건 통과
- DS·메인·CD 전체 타임라인과 stretchingMode positionGroups 매칭을 추가하고 세그먼트(Dynamic Stretch/Cool Down)·좌상단 position 라벨을 표시. 인접 썸네일 선조회·이미지 warm을 연동
- `renderer/single/`, `renderer/components/`
- preload player 승격 시 파괴된 host를 재사용하던 버그를 수정해 승격 후 next slot에 새 host를 생성하고 destroyEntry 시 host를 재생성. 스트레칭 position 해석·시퀀스 key에 position 포함
- player 승격·스트레칭 인덱스·DS/CD 타임라인·position 라벨 변환 단위 테스트 11건 통과
- **Test**
Electron SINGLE 실행 전면 분리
현상/기능
- SINGLE 실행은 현재 영상 외 다음 영상 1개만 프리로드하고 기존 MULTI 프리로드 로직과 분리
- SINGLE renderer lifecycle과 보조 모니터 심박 화면을 기존 MULTI renderer·타이머에서 분리
- SINGLE 창 구성과 queue/preload 정책을 MULTI 실행 경로에서 분리
- SINGLE player 수 제한과 presentation 격리 회귀 검증
작업
- `renderer/single/`
- 현재 player 1개와 다음 preload player 1개만 유지하는 전용 풀을 구현하고 다음 영상을 현재로 승격한 뒤 새 다음 영상 하나만 준비. 세로 이전 영상은 실제 player 대신 Vimeo 썸네일 정지 이미지로 표시
- SINGLE 창은 lifecycle IPC만 구독하는 전용 controller로 이동하고 grid queue·preload·intro 이벤트를 미구독. 두 번째 모니터를 `single-heart-rate` 창으로 분리해 일반 카운터·countdown 없이 심박 패널만 실행
- `main/single`, `main/multi`, `main/presentation`
- presentation별 창 계획 서비스와 재생 capability 정책을 추가해 SINGLE은 grid queue·DS preload 대기·intro를 차단하고 MULTI의 기존 3/5 화면 동작은 유지
- 현재+다음 최대 2개 player, preload 승격·마지막 영상·cleanup, SINGLE/MULTI 정책, 전용 심박 창 배치를 검증. Electron 전체 21개 파일 63개 테스트와 main/renderer/preload TypeScript 빌드 통과
- **Test**