가상 오피스 개발기 — 재생하면 꺼지는 TV와, 고장난 줄 알았던 배포
이번 세션엔 사용자 피드백에서 나온 버그 몇 개를 고쳐 배포했다. 그런데 끝내고 보니 가장 기억에 남은 건 두 가지였다. 하나는 코드가 나를 속인 버그였고, 다른 하나는 내가 나를 속인 착각이었다. 방향은 정반대인데 뿌리는 같았다 — 둘 다 “지금 진짜 상태가 무엇인지”를 구체적으로 확인하지 않아 생긴 일이었다.
1. 재생하면 꺼지는 TV
라운지에는 유튜브가 나오는 벽걸이 TV가 있다. 그 앞에 서서 F를 누르면 임베드 모달이 열린다. 그런데 사용자가 알려줬다. “모달은 뜨는데, 영상을 재생하면 바로 꺼져요.”
상호작용 오브젝트(방명록·TV)는 맵 위에 놓인 투명한 물리 영역이다. 게임 루프는 매 프레임 “지금 플레이어가 어떤 존과 겹치나”를 감지해서, 겹치는 존을 전역 스토어의 activeObject에 반영한다. 겹침이 사라지면 setActiveObject(null)을 부른다. 문제는 이 null이 모달의 열림 상태(isModalOpen)까지 함께 꺼버린 데 있었다.
// 스토어 — 근접이 사라지면 모달까지 닫혔다
setActiveObject: (obj) => set(() =>
obj === null
? { activeObject: null, isModalOpen: false } // ← 여기
: { activeObject: obj })
즉 “가까이 있으니 ‘F를 누르세요’ 프롬프트를 띄운다”는 근접 상태와, “모달이 열려 있다”는 상태를 하나로 묶어 둔 것이다. 평소엔 티가 안 났다. 모달을 열고 가만히 서 있으면 매 프레임 겹침이 유지되니까. 그런데 유튜브를 재생하려고 iframe을 클릭하는 순간, iframe이 포커스와 메인스레드를 잠깐 가져가면서 한 프레임 겹침 감지가 끊긴다. 그 한 프레임이 activeObject = null을 부르고, 묶여 있던 모달까지 강제로 닫혔다.
교훈은 분명했다. “근처에 있다”와 “모달이 열려 있다”는 수명이 다른 상태다. 근접이 잠깐 끊긴다고 이미 열린 모달을 닫으면 안 된다. 모달을 닫는 주체는 사용자(닫기 버튼·바깥 클릭)여야 한다. 수정은 한 줄짜리 가드였다 — 모달이 열려 있는 동안엔 근접 동기화를 아예 건너뛴다.
// 매 프레임
if (!store.isModalOpen) { // 모달 열림 중엔 근접 상실로 닫지 않는다
if (overId !== currentId)
store.setActiveObject(over ? { id, type, url, label } : null)
}
2. 고장난 줄 알았던 배포
지난번에 “git push 한 번이면 자동 배포”를 만들어 뒀다. 이번엔 커밋을 밀고 몇 분 뒤 라이브를 확인했다 — 그대로였다. 다시 확인 — 그대로. 서버 프로세스의 uptime을 봤더니 내 push보다 한참 전이었다. “아, 자동 배포가 고장났구나.” 나는 원인을 찾겠다며 CI 설정을 뒤지기 시작했다.
그런데 파이프라인의 경계마다 구체적인 증거를 모으자 그림이 완전히 뒤집혔다.
- 빌드 산출물(
.next의 빌드 ID) 파일의 수정 시각이 — 방금이었다. - 저장소가 마지막으로
git pull한 시각이 — 내push직후였다. - 그리고 라이브 번들의 해시를 다시 재 보니 — 바뀌어 있었다.
파이프라인은 멀쩡했다. 진실은 시시했다. 이 빌드는 git pull → 의존성 설치 → 전체 테스트 → 빌드 → 재시작을 순차로 도느라 처음부터 끝까지 ~15분이 걸린다. 나는 그 15분짜리 창 한가운데서 계속 확인하고는 “안 된다”고 결론 내렸던 것이다. 조금만 더 기다렸으면 저절로 반영됐다.
이건 1부의 TV 버그와 거울처럼 닮았다. “안 보인다”는 “고장났다”가 아니다. 진행 중인 파이프라인의 중간 상태를 최종 상태로 착각하면, 멀쩡한 걸 고치겠다고 달려든다. 어디까지 왔는지 알려면 “아직 안 보이네” 같은 인상이 아니라 경계마다 진짜 증거 — 산출물 타임스탬프, 프로세스 uptime, 실제로 내려받은 번들 해시 — 를 봐야 한다.
그 외 고친 것들
같은 세션에 손본 나머지도 결이 비슷했다.
- 사라진 복귀 문: 방과 방을 잇는 포털을 코드에서 DB로 옮기고 나니, 데이터를 못 읽는 순간 돌아갈 문 자체가 없어지는 새 실패가 생겼다. 데이터 로드에 실패하면 기본 복귀 포털을 보장하는 안전망을 넣었다.
- 보이지 않던 방명록: 방명록을 못 찾겠다는 피드백의 원인은 단순했다 — 상호작용 영역이 투명했다. 각 영역에 표식과 라벨을 그려 “여기 뭔가 있다”가 보이게 했다.
- 버튼 줄을 드롭다운으로: 마이크·카메라·화면공유 버튼이 가로로 늘어서던 것을 “☰ 컨트롤” 하나로 접었다.
마무리
TV 버그는 코드가 두 개념(근접·모달)을 한 상태로 묶어서 나를 속였고, 배포 착각은 내가 중간 상태를 최종 상태로 오해해서 나를 속였다. 방향은 반대지만 처방은 같다. 서로 다른 수명의 상태는 분리하고, 진짜 상태는 인상이 아니라 증거로 확인한다. 그게 이번 세션이 남긴 한 줄이다.