Joyuno's Dev Blog

2026-08-03 (월) 개발일지

dev-blog

오늘은 여러 프로젝트를 동시에 진행하면서 각각 다른 성격의 문제들을 마주했다. 하네스의 로컬 테스트 환경 정리, Claude Code 설정 파일 스키마 버그, USB 초기화, 그리고 Lepons 워크플로우 패널 구현까지 — 각 이슈마다 사고의 방향이 어떻게 틀렸는지, 그리고 왜 그렇게 수정했는지가 핵심이었다.

하네스 플러그인 — 로컬 경로 설치와 HANDOFF 문서화

처음엔 하네스 플러그인을 마켓플레이스에 바로 푸시하려고 했다. 하지만 현재 상태를 보니 로컬에서만 테스트되고 있었고, 마켓플레이스가 아직 v0.3.1 버전을 잡고 있었다. 이게 문제였다.

로컬 개발 중인 플러그인을 마켓플레이스 버전과 섞어서 테스트하면 어느 것이 동작하는지 알 수 없다.

로컬 경로로 플러그인을 직접 등록하는 방식으로 바꿨다. ~/Downloads/하네스/riff 경로를 마켓플레이스에 추가하고 joyuno-riff 버전을 설치하니 현재 개발 버전이 정확히 로드됐다.

더 중요한 건 다음 개발자를 위한 맥락 보존이었다. 지금까지의 모든 개발 세션을 한 파일에 정리해야 했는데, 단순 작업 목록이 아니라 왜 이렇게 설계했는지를 담아야 했다. HANDOFF.md를 리포 루트에 작성했다:

  • 현재 상태: main 병합 완료(d1b563f), 미푸시 39커밋, push 대기 이유
  • v1.0 정의: 결정 표 + 핵심 메커니즘 (커밋 앵커·canvas-lint·verdict 게이트)
  • 다음 단계: 어디서 이어야 하는지

이 문서 하나로 다음 개발자가 전체 맥락을 복원할 수 있게 구성했다.

Claude Code 설정 파일 스키마 버그 — 두 가지 구조 오류

~/.claude/settings.json을 읽어보니 hook 항목 2개가 잘못된 구조를 갖고 있었다.

처음엔 경로 문제라고 생각했다. 스크립트가 실제로 존재하는지 확인해봤는데 둘 다 executable 이었다. 그럼 뭐가 문제일까?

ls -la ~/hook-script-1.sh  # 존재함, 실행 가능
ls -la ~/hook-script-2.sh  # 존재함, 실행 가능

진짜 문제는 경로가 아니라 JSON 스키마 형태였다. 두 hook 항목이 command 필드를 직접 entry 레벨에 놓고 있었다. 올바른 구조는 각 hook이 hooks 배열 안에 객체로 들어가야 한다는 뜻이었다.

// ❌ 잘못된 형태
{
  "hooks": [
    { "command": "~/hook-script-1.sh" }  // command가 entry 자체에
  ]
}

// ✅ 올바른 형태
{
  "hooks": [
    { "command": "~/hook-script-1.sh", "on": "event" }
  ]
}

파일을 직접 수정해서 스키마를 정렬했다. 이제 Claude Code가 설정을 올바르게 파싱할 수 있다.

USB 부팅 이미지 초기화 — “완전히 깨끗하게”의 함정

사용자가 부팅 USB를 만드는 데 사용했던 USB 메모리를 초기 상태로 돌리고 싶다고 했다. 처음엔 디스크를 통째로 clean 해서 모든 파티션을 날려야 한다고 생각했다.

현재 상태를 확인해봤다:

  • 디스크 1번: VendorCo ProductCode, 7.5GB, USB
  • 파티션: 1개 (드라이브 D:), NTFS, 라벨 없음
  • 여유공간: 7.5GB / 7.5GB (완전히 비어있음)

여기서 전환점이 생겼다. 부팅 USB의 흔한 후유증 두 가지를 확인해봤다:

흔한 증상 현재 상태
용량이 줄어 보임 (8GB→몇백MB) ✅ 전체 7.5GB 그대로
숨은 부트 파티션 여러 개 ✅ 파티션 딱 1개

사실 지금도 실용적으로는 이미 멀쩡했다. 파일 레벨에서도 잔존 데이터가 없었다. D:\ 안에는 System Volume Information(NTFS 자동 생성 폴더) 하나뿐이었다.

디스크를 깔끔하게 다시 포맷하는 게 “초기 상태”라는 사용자의 의도를 만족하지만, 현재 상태는 이미 부팅 이미지 흔적이 없었다. diskpart 스크립트를 준비했지만 — 사실은 필요 없었다.

Lepons 워크플로우 패널 — 여러 라운드의 구현과 검증

이 부분이 가장 복잡했다. 워크플로우 패널을 Lepons 앱에 추가하는 작업이었는데, 단순 UI 추가가 아니라 터미널 분할, 드래그 리사이즈, 레이아웃 재구성을 포함했다.

Task 1: 파인 트리 데이터 모델

처음엔 기존 터미널 구조에 파인 트리를 얹으려고 했다. 하지만 #terminal 요소가 hidden 속성과 display: flex 규칙을 동시에 받으면서 레이아웃이 꼬였다.

UA 스타일시트의 [hidden] { display: none; } 이 명시적 display: flex 와 충돌하고 있었다.

fit.fit() 호출 시 숨겨진 터미널에서 레이아웃을 계산하려고 하니 무의미한 계산만 반복됐다. 가드를 추가했다:

if (activeKey && terminalPanel.hidden === false) {
  fit.fit();
}

이제 보이는 터미널에서만 리사이징이 동작한다.

Task 2~4: 분할 버튼, 드래그 리사이즈

분할 버튼이 크럼바에 있어야 하는데, 초기에는 버튼이 setCrumb() 호출에 의해 매번 지워졌다. setCrumb()이 crumb.replaceChildren()을 호출해서 전체 내용을 덮어쓰고 있었다.

해법은 구조를 바꾸는 것이었다. 분할 버튼(#openWorkflow, #workflowBadge)을 #crumb 내 구조적 형제로 두고, setCrumb()은 #crumbText 자식만 갱신하도록 재포인팅했다. 이제 버튼이 살아남는다.

드래그 리사이즈에서는 또 다른 문제가 있었다. apply() 함수가 매 프레임 renderPanes()로 전체 트리를 다시 그리고 있었고, 드래그 중인 핸들 요소 자신이 파괴되고 있었다.

드래그 중엔 형제 엘리먼트의 스타일만 직접 바꾸고, 드롭 시점에만 전체 리렌더링해야 한다.

리사이즈 로직을 분리했다:

  • 드래그 중: left / top 스타일만 갱신
  • 드롭 후: renderPanes() 호출

Task 5: 크럼바 버그 수정과 최종 검증

워크플로우 패널이 열렸을 때 터미널이 축소되지 않는 버그가 있었다. 터미널의 CSS에 min-width: 0 이 없어서 flex 아이템이 기본값 auto 로 축소를 거부하고 있었다.

#terminal {
  flex: 1;
  min-width: 0;  /* 이 줄이 필수 */
}

이 한 줄로 터미널이 1036px에서 736px로 축소되면서 300px 패널이 들어갈 공간이 생겼다.

검증: Playwright 로 실제 브라우저에서 레이아웃을 측정했다. 패널 너비 300px, 오른쪽 끝 1280px — 뷰포트 안에 완전히 들어간다.

마지막으로 전체 브랜치 코드 리뷰를 돌렸다. 타입체크 clean, 123/123 테스트 통과, 빌드 성공. 두 계획 모두 main 에 fast-forward merge 됐고 codex/m0-probes 브랜치는 삭제했다.

오늘의 한줄

사고의 전환점을 찾는 게 핵심이다. 처음 가설이 틀렸을 때 (“경로 문제다”, “디스크를 완전히 초기화해야 한다”, “전체 트리를 매번 리렌더링해야 한다”), 그 다음 단계에서 뭘 다시 봤는지가 문제 해결의 속도를 결정한다.