2026-09-15 (화) 개발일지
오늘은 옵시디언 정리 작업에서 부딪힌 문제들을 정리했다. 핵심은 두 가지였다. 하나는 자식이 있는 트리 노드가 기본적으로 펼쳐져야 하는데 특정 조건에서 접힌 채로 렌더링되는 문제였고, 다른 하나는 자동이력서지원 프로젝트에서 버전 관리와 파일 복사 방식의 혼선이 빚은 문제였다. 둘 다 겉으로 보기엔 단순해 보였지만, 파고들수록 원인이 다른 곳에 있었다.
옵시디언 트리 뷰 — 자식 노드가 기본적으로 펼쳐지지 않는 문제
사이드바의 트리에서 자식이 있는 노드를 클릭하면 하위 트리가 펼쳐져야 하는데, 특정 노드에서만 하위 항목이 보이지 않았다. 처음엔 isExpanded 상태 관리가 꼬인 줄 알고 useState 초기값을 추적했다. defaultExpandedKeys 같은 prop이 있나 싶어 컴포넌트 인터페이스를 뒤졌지만, 이 트리는 완전히 수제로 만든 거라 그런 게 없었다.
로그를 찍어보니 렌더링 시점에 모든 노드가 isExpanded: false 로 시작하고 있었다. 여기서 첫 가설을 세웠다. “아, 부모 노드의 expanded 상태를 자식에게 전파하는 로직이 빠졌구나.” 그래서 재귀 렌더링 함수(renderTreeNodes)를 열어 상태 전파 코드를 확인했다. 그런데 문제는 거기가 아니었다. 상태 전파는 정상적으로 동작하고 있었고, 클릭하면 하위 트리가 잘 열렸다.
그제서야 진짜 원인을 의심하기 시작했다. 초기 렌더링에서 expandedKeys를 계산하는 부분을 다시 봤다. 트리 데이터를 가져온 뒤 루트부터 순회하면서 자식이 있는 노드를 전부 펼치는 로직이 있었는데, 이게 데이터 로딩이 끝나기 전에 실행되고 있었다. mockData.ts에서 데이터를 불러오는 타이밍과 트리 초기화 타이밍이 어긋나서, 데이터가 비어 있는 상태에서 “자식이 있는 노드” 판별이 전부 false로 나온 거였다.
해법은 두 가지였다. 하나는 데이터 로딩 완료 후에 expandedKeys를 다시 계산하는 것이고, 다른 하나는 초기 렌더링을 데이터 로딩 이후로 미루는 것이다. 전자는 useEffect 의존성 배열에 데이터 상태를 추가하는 방식이라 간단했지만, 로딩 중에 트리가 잠깐 빈 상태로 보이는 문제가 남았다. 후자는 로딩 스피너를 추가해야 해서 작업량이 늘었다. 결국 전자를 택했다. 로딩 중 빈 트리는 어차피 1~2초 안에 채워지고, 지금 문제는 “데이터가 있는데도 접혀 있는” 상태였으니까.
수정 파일은 OrgUserPickerModal.tsx와 mockData.ts였다. mockData.ts에서 자식이 있는 노드 3개(sc_d, sc_e, sc_f)를 확인했고, OrgUserPickerModal.tsx의 useEffect에서 데이터 로딩 후 expandedKeys를 재계산하도록 바꿨다. 검증은 브라우저에서 해당 노드들을 클릭 없이도 하위 트리가 펼쳐져 보이는지 확인하는 것으로 마쳤다. 정상 동작했다.
한 가지 특이한 점은 루트 행에 화살표가 없다는 것이었다. 처음엔 이게 버그인 줄 알았는데, 알고 보니 루트는 데이터로 그린 게 아니라 손으로 박아넣은 JSX였다. 그래서 확장 버튼을 그릴 조건(hasChildren && depth > 0)에서 depth가 0이라 자연스럽게 화살표가 빠진 거였다. 확장 버튼 자리에 <span className="inline-block w-4 shrink-0" />로 빈칸 16px를 넣어 정렬만 맞췄다. 자동으로 채워진 게 아니라, 의도적으로 빈칸을 넣은 것이다.
자동이력서지원 — 파일 복사 방식과 버전 관리의 혼선
이건 다른 프로젝트에서 겪은 문제다. 배포용 .app 파일을 폐쇄망으로 옮기는 작업이었는데, pom.xml의 <version>이 0.1.0으로 고정되어 있어서 빌드할 때마다 파일 이름이 같았다. 같은 경로에 복사하면 이전 파일이 조용히 덮어써지고, 폐쇄망에 이미 옮겨 둔 사본과 이름·버전이 동일해 어느 쪽이 최신인지 구분되지 않았다.
처음엔 단순히 파일 이름에 타임스탬프를 붙이는 스크립트를 만들면 해결될 거라 생각했다. cp 명령 대신 mv로 파일을 옮기면서 이름에 날짜를 추가하는 방식이었다. 그런데 잠깐, mv는 이름 바꾸기도 겸한다. 같은 폴더 안에서 옮기면 개명이 되는 거라, “복사”와 “이동”의 의미를 정확히 구분해야 했다. 이 부분을 정리하면서 문서(build.md)에 명령어 의미를 표로 정리해 넣었다.
| 명령 | 뜻 | 하는 일 |
|---|---|---|
cp |
copy | 파일 복사. 원본이 남는다 |
mv |
move | 파일 이동. 이름 바꾸기도 mv 다 (같은 폴더 안에서 옮기면 개명) |
하지만 더 근본적인 문제는 버전이 0.1.0으로 고정되어 있다는 점이었다. 파일명에 타임스탬프를 붙여도 버전 정보가 안 바뀌면, “이 파일이 어떤 변경사항을 담고 있는지”는 알 수 없었다. 그래서 pom.xml의 버전을 빌드 시점에 동적으로 바꾸는 방법을 검토했다. Maven의 -Drevision 플래그를 쓰는 방식인데, 이건 CI 환경변수 주입이 필요해서 폐쇄망에서는 적용하기 까다로웠다.
결국 택한 건 빌드 스크립트에서 버전을 명시적으로 관리하는 방식이었다. build.md에 절차를 정리하고, 빌드 전에 버전을 올리는 단계를 추가했다. 수동으로 버전을 올리는 게 완전한 해결은 아니지만, 최소한 “같은 이름의 파일이 덮어써져서 어느 쪽이 최신인지 모르는” 문제는 없앴다.
검증은 빌드 후 생성된 .app 파일의 이름에 버전이 제대로 들어갔는지 확인하는 것으로 했다. 그리고 폐쇄망으로 옮기기 전에 파일명을 명시적으로 변경하는 과정을 문서에 추가했다.
오늘의 한줄
트리 노드가 펼쳐지지 않는 문제는 상태 관리 버그가 아니라 데이터 로딩 타이밍 문제였고, 파일 복사 문제는 cp/mv의 의미 혼선이 아니라 버전 정보의 부재가 근본 원인이었다. 겉으로 보이는 증상 뒤에 다른 원인이 숨어 있는 경우가 많다.