gwimong's blog Software Engineer

artifact-transformer 작업 일지 #24

만든 지 얼마 안 된 기능을 사용자가 두 번 뒤집었고, 뒤집힌 자리마다 버그가 하나씩 남았다


이 글은 2026-07-09 하루의 기록이다. 같은 artifact-transformer 웹 에디터를 두 개의 Agent Mode 세션(채널)에서 나란히 손봤다. 한 채널에서는 사용자가 이미 만든 기능을 폐기시키고, 잠시 후 정반대 방향으로 다시 뒤집었다. 다른 채널에서는 전날(#23)에 미해결로 남았던 다이어그램 개편이 이어졌다.

배경 — 두 채널, 같은 에디터

artifact-transformer는 31종 소프트웨어 산출물(요구사항, C4 아키텍처, 유스케이스, 시퀀스, ERD 등)을 생성하는 CLI에서 다중 사용자 협업 웹앱으로 전환 중이다. 이날은 그 웹 에디터를 두 개의 Agent Mode 세션에서 병행 작업했다.

한 세션은 아키텍처 질문(Git이냐 DB냐)에서 시작해 동시성 제어를 만들었다가 폐기하고, 다시 React 전면 전환으로 이어지는 길고 밀도 높은 흐름이었다. 다른 세션은 전날 끝나지 않았던 다이어그램-데이터 세션 재편의 나머지를 마저 처리했다.

문제

  1. Git 기반 저장을 계속 밀 것인가, 이 시점에 DB로 갈 것인가.
  2. 동시 편집 충돌 제어를 만들었는데, 왜 만든 지 얼마 안 돼 폐기 지시가 나왔나.
  3. React는 “검토 문서만” 쓰기로 했는데, 왜 같은 날 안에 전면 전환으로 뒤집혔나.
  4. 그 전환 과정에서 나온 버그 네 건은 각각 무엇이 근본원인이었나.
  5. 다른 채널에서 이어지던 다이어그램 소유권 개편은 어디까지 갔나.

결정 1 — Git을 시스템 오브 레코드로 유지, DB는 3단계로 유예

“Git 기반 저장 방식을 계속 밀 것인가, DB로 갈 것인가”라는 질문에는 하이브리드로 답했다. 이력·diff·PR·CI 검증 게이트가 이미 Git 위에 구축돼 있는데, 지금 DB로 전면 이전하면 버저닝·감사·리뷰를 처음부터 다시 구현해야 한다 — 그럴 실익이 없다는 판단이었다.

대신 DB는 3단계에서 계정·SSO·댓글·알림·검색 인덱스·ID 대장 같은 보조 레이어로만 나중에 들어온다. Git은 산출물의 진실 원본으로 남고, DB는 그 위에 얹는 부가 기능용이라는 역할 분리다.

결정 2 — 동시성 제어를 만들고, 그 자리에서 폐기했다

“핵심에 집중해서 commit&push 동시성 충돌 제어를 추가하고, Node/React 전환도 검토해달라”는 요청에 AskUserQuestion으로 방향을 확인했다. 사용자는 동시성 제어는 소프트락·3-way 자동병합·push 자동복구 세 가지 다 선택했고, React는 “검토 문서만” — 구현은 보류하기로 했다.

그대로 구현했다. server/editlock.py(TTL 300초 advisory lock, “OO님이 편집 중” 배너), server/merge.py(ETag 불일치 시 3-way 자동병합, 진짜 충돌만 409 반환), push 실패 시 pull --rebase --autostash 3회 자동재시도. 테스트 276개 통과, 동시편집 제어 설계서와 React 전환 검토서(React+Vite 프런트엔드만, FastAPI 백엔드 유지 권고)까지 문서로 남겼다.

그런데 곧바로 정정이 왔다. “편집 락 기능은 빼라. 대신 시퀀스·유스케이스 다이어그램 뷰어·에디터를 산출물 본문에 바로 내장해라.” 방금 만든 기능을 그 자리에서 폐기하는 지시였다. 세션 메모리 파일에는 “소프트락은 사용자 결정으로 제거됨 — 재도입 시 Redis 기반 고려”로 남겼다. 3-way 병합과 push 재시도는 유지됐고, 폐기된 건 편집 락 하나였다.

방금 세 가지 옵션 중 하나를 골라 만든 기능이 완성되자마자 폐기됐다. 로그에는 이유가 적혀 있지 않다 — 추측하지 않고 사실만 남긴다.

결정 3 — “검토만” 하기로 한 React가 같은 날 전면 전환이 됐다

다이어그램 캐시 버그(결정 4에서 다룸)를 잡은 직후, **“React로 전면 개편하고 UX를 대폭 개선해라”**는 지시가 나왔다. 몇 시간 전 “검토 문서만” 쓰기로 정한 것과 정반대 방향이다.

React 18 + Vite + TS로 전체를 다시 짰다. 의존성은 최소화하고 빌드된 dist/를 저장소에 커밋해서, 배포 시 Node가 없어도 되고 CDN도 쓰지 않는다. /ui가 새 React 화면을 서빙하고 /ui-legacy로 옛 화면에 폴백할 수 있게 남겼다. 스키마 폼, ETag/3-way 병합 UI, AI 어시스트, 다이어그램, 현황, ID 서비스까지 기존 기능을 전부 포팅했고 테스트는 277개로 늘었다.

이후 요청은 세부 다듬기로 이어졌다. JSON 필드를 완전 편집 가능한 폼으로 바꾸면서 form.tsx를 스키마 기반 타입 추론 + 12칼럼 반응형 너비 휴리스틱으로 재작성했고, 상호참조가 있는 필드는 REF_SOURCES 매핑(common/traceability.py 관례 반영)을 기반으로 한 RefSelect/RefChips 컴포넌트로 바꿔 9개 문서유형의 ID→이름 카탈로그를 실시간으로 끌어오게 했다.

마지막엔 레이아웃 철학 자체가 바뀌었다. 카테고리·이해관계자 섹션을 접을 수 있게 해달라는 요청 다음, **“핵심 정보가 꼭 맨 위일 필요는 없다 — 데이터의 원래 구조·흐름을 따라가라”**는 정정이 왔다. SchemaForm이 “핵심” 컬렉션을 맨 위로 끌어올리던 방식을 버리고 스키마 정의 순서 그대로 배치하도록 바꿨고, 다이어그램 카드도 파일별로 접히게(첫 카드만 펼침, 나머지는 지연 렌더링) 했다.

결정 4 — 전환 과정에서 튀어나온 근본원인 네 가지

같은 날 안에서 잡은 버그 네 건은 각자 다른 층에서 났다.

증상근본원인수정
모듈 소스를 고쳐도 OVERVIEW가 재렌더링 안 됨ensure_svg 캐시가 OVERVIEW 파일 자체의 mtime만 보고 !include로 딸린 모듈 파일은 안 봄_dep_mtime으로 include 의존성을 재귀 추적
C4 레벨1~2 OVERVIEW를 편집할 수 없음”이 다이어그램 그룹에 모듈 파일이 있나” 체크가 세션 전체 레벨로 적용돼, 레벨3(CMPDG) 모듈이 하나라도 있으면 레벨1/2까지 잠김hasModulesOfKind로 종류별(per-kind) 체크로 분리
SUOMS-CNTDG-COM-001 렌더링 실패PlantUML은 출력 파일명을 소스 파일명이 아니라 @startuml <name> 내부 이름으로 정하는데, 레거시 SUOMS 문서는 내부 이름이 파일명과 다름common/plantuml.py가 이미 쓰던 관례로 _svg_path를 통일 (레거시 데이터는 안 건드림)
다이어그램 확대·축소와 페이지 스크롤이 동시에 반응React의 onWheel은 passive라서 preventDefault()가 조용히 무시됨네이티브 non-passive wheel 리스너 + overscroll-behavior: contain / touch-action: none

네 건 다 겉으로 보이는 증상은 UI 레이어였지만, 원인은 캐시 의존성 추적, 체크 범위, 외부 도구(PlantUML)의 이름 규칙, 브라우저 API의 passive 기본값처럼 서로 다른 층에 흩어져 있었다.

React의 preventDefault()가 아무 경고 없이 무시되는 건 코드만 봐서는 안 보인다. 리스너가 passive로 등록된다는 사실을 알아야 증상과 원인이 연결된다.

결정 5 — 다른 채널: 다이어그램은 “설계” 그룹으로, 소스는 웹에서 직접 편집

같은 날 다른 Agent Mode 세션에서는 전날(#23) 7라운드에서 미해결로 남았던 다이어그램-데이터 세션 소유권 개편이 이어졌다.

소유 데이터 세션이 없는 아키텍처/C4, 데이터플로우, 상태플로우 다이어그램은 “설계” 사이드바 그룹으로 옮겼고, 메뉴 항목명도 산출물 이름 대신 목적 기반 이름(“업무 정의”, “기능 정의” 등)으로 바꿨다.

이어서 새 기능 요청이 들어왔다 — PlantUML 소스를 보기만 하던 걸 웹 UI에서 직접 편집 가능하게 해달라는 것. diagrams.py에 소스 읽기·쓰기 헬퍼를 추가하고, 기존 _resolve 경로 검증(디렉터리 traversal, 비-.puml 확장자, 존재하지 않는 파일 차단)을 그대로 재사용하는 GET/PUT /api/projects/{code}/diagrams/source 라우트를 붙였다. 다이어그램 카드에 [미리보기 | 소스 편집] 탭을 추가하고, 저장 시 mtime을 갱신해 SVG 재렌더링을 트리거했다. git 자동 커밋은 하지 않고(기존 다이어그램 처리와 동일한 방침), 새 다이어그램 파일 생성도 지원 범위 밖으로 명시했다.

그 직후 “소스 로드 실패” 버그가 보고됐다. 근본원인은 소스 읽기 라우트가 방금 추가됐는데 실행 중이던 dev 서버가 재시작 전이라 404가 났던 것이었다. 완전히 고치는 대신, 에러 메시지를 HTTP 상태코드와 “서버를 재시작하세요” 같은 힌트를 보여주는 진단적 메시지로 바꿨다 — 서버 프로세스 재시작 자체는 에이전트가 통제할 수 없는 영역이기 때문이다.

의도치 않은 함정 — Cowork 샌드박스 마운트, 또 그 문제

전날([#23])에도 나왔던 문제가 이날도 반복됐다. Cowork 샌드박스의 리눅스 마운트가 tests/test_server_api.py, assist.py, jobs.py 같은 파일을 실제 수정 이후에도 잘린 옛 스냅샷으로 보여줬다. 우회 패턴도 그대로였다 — /tmp나 $HOME에 사본을 다시 만들어 그 사본만 검증하고, 실제 파일과의 일치는 Read로 눈으로 대조했다. 세션 메모리 파일에 상시 주의사항으로 다시 기록해 뒀다.

마무리

이날은 한 프로젝트를 두 채널에서 동시에 밀어붙인 하루였다.

  • 아키텍처는 하이브리드로 — Git을 시스템 오브 레코드로 두고, DB는 3단계 보조 레이어로 유예했다.
  • 만든 기능을 그 자리에서 폐기 — 동시성 제어 세 가지 중 편집 락만 폐기되고, 대신 다이어그램을 산출물 본문에 내장하는 방향으로 갔다.
  • “검토만”이 같은 날 “전면 전환”으로 — React 18 + Vite + TS 전체 재작성, dist/ 커밋, no-CDN 원칙은 유지한 채였다.
  • 네 개의 버그, 네 개의 층 — 캐시 의존성, 체크 범위, 외부 도구 이름 규칙, 브라우저 API 기본값. 층이 다르면 원인 찾는 방법도 달라야 했다.
  • 다른 채널에서는 조용히 마무리 — 다이어그램을 “설계” 그룹으로 정리하고 소스 편집 기능을 붙였지만, 서버 재시작 문제로 검증은 다음으로 미뤄졌다.
  • 샌드박스 마운트 문제는 여전히 진행형 — 전날과 같은 우회책을 다시 썼다는 건, 아직 근본 해결이 안 됐다는 뜻이다.

다음에 남는 것:

  • 3단계 작업(SSO 인증, 리뷰 워크플로우, 알림, 추적성 매트릭스 시각화)과 그 단계로 유예된 DB 도입.
  • React 전환 “한 릴리스 후” 레거시 단일파일 SPA(server/static/index.html) 제거.
  • 다른 채널의 “소스 로드 실패” 버그를 서버 재시작 후 실제로 해결됐는지 확인.
  • Cowork 샌드박스 마운트 stale 이슈의 근본 원인을 다음 세션에서 찾아본다.

Comments

Content