[코인충 마이그레이션] with Fable 5 + superpowers
코인충은 2021년도에 만든 워낙 오래된 프로젝트이다. 스택도 그 당시로서는 최선이었으나 지금보면 완전한 레거시. 특히 SSR을 직접 구현했던게 내 개인적으로 공부는 됐지만 지금은 손도 대기 끔찍한 레거시여서 마이그레이션을 해야지 해야지 생각만 하고 있다가, Fable 5가 풀린 상황에서 superpowers를 통해 계획을 세우고 Sonnet 5를 subagent로 쓰면서 --dangerously-skip-permissions 하에 알아서 끝까지 작업을 완료하도록 했다. 결과적으로 마이그레이션이 상당히 맘에 드는 수준으로 이뤄져서 정말 놀라웠다.
Nuxt 기반으로 거의 코드를 죄다 갈아엎은 수준이지만, 사용자 입장에선 바뀐 것을 전혀 인지하지 못할 수준으로 잘 이식됐다.
대상: coinsect_frontend (Vue 3 Composition + Vuex + axios + 자체 구현 SSR/WAS) → coinsect_nuxt (Nuxt 4 + TanStack Query + Pinia + headless composable) 기간: 2026-07-02 ~ 2026-07-06 (커밋 기준) 결과: 계획 20개 태스크 전부 완료, 34 커밋, 테스트 164/164 green, 실서비스(coinsect.io) 대비 SEO 패리티 검증, 프로덕션 빌드 확인
주말이 껴 있어서 7월 2일부터 6일로 결과가 나오기는 했는데 사실상 하루만에 완료.
스택 전환
| 구 (coinsect_frontend) | 신 (coinsect_nuxt) | |
|---|---|---|
| 프레임워크 | Vue 3 + Vue CLI | Nuxt 4 |
| 상태관리 | Vuex | Pinia (UI/실시간 상태) |
| 서버 데이터 | Vuex action + 수제 캐시(setTimeout) | TanStack Query (SSR hydration, staleTime) |
| HTTP | axios | $fetch (Nuxt 내장) |
| SSR | 자체 Express WAS + renderToString + window.INITIAL_DATA |
Nitro |
| 웹소켓 | 컴포넌트마다 새 커넥션, 재연결 없음 | 거래소별 공유 싱글턴 + ref-count + 자동 재연결 |
| 런타임 의존성 | axios 포함 다수 | 7개뿐: nuxt, vue, vue-router, @tanstack/vue-query, pinia, @pinia/nuxt, dayjs |
의존성 최소화를 명시적으로 요구했다 — VueUse 같은 유틸 라이브러리도 배제하고 debounce, 이벤트 리스너, 드래그, 웹소켓 재연결까지 전부 직접 구현하도록 시켰다.
프로세스: 4단계 (이 문단은 Claude Code의 입장)
1단계 — 코드 파악 (병렬 탐색)
계획을 세우기 전에 구 레포를 이해해야 했다. 핵심 파일(라우터, 스토어, axios 모듈, App.vue)은 직접 정독하고, 나머지는 세 개의 탐색 에이전트를 병렬로 띄웠다:
- 앱 인프라(모달/토스트/툴팁 시스템, 이벤트 버스, 레이아웃 셸)
- 라우트/기능 인벤토리(26개 라우트 전부, 챗 서브시스템, 웹소켓 훅, 커뮤니티)
- SCSS 아키텍처(전역 유틸리티 클래스 의존도, 이식 난이도)
이 조사에서 나온 핵심 판단들이 이후 전체 설계를 결정했다:
- 모달 시스템(
await modal.confirm()한 줄로 끝나는 promise-through-store 패턴)은 보존할 가치가 있는 좋은 설계로 판정 — Pinia 위에 같은 인체공학으로 재구현. - SCSS는 이미
@use기반,::v-deep0건, 테마도 100% 네이티브 CSS 변수 — 거의 그대로 이식 가능. Tailwind 전환은 견적만 수 주 규모로 나와서 기각. - 실제 인증 라우트가 0개(스캐폴딩만 존재)라는 것도 이때 발견 — 이식 대상에서 제외.
2단계 — 계획 수립 (superpowers:writing-plans)
조사 결과를 바탕으로 20개 태스크짜리 구현 계획을 작성했다. 각 태스크는 파일 경로, 인터페이스 시그니처, (인프라 태스크는) 완성 코드, 보존해야 할 동작 명세, TDD 스텝까지 포함한 자기완결적 브리프였다 — 신선한 컨텍스트의 구현자가 원본을 참고하며 재작성할 수 있도록.
Global Constraints에 "의존성 최소화", "Tailwind 안 함", "죽은 코드 이식 안 함(FoolsDayPrank, TwitterTimeline, 인증 스캐폴딩)" 같은 프로젝트 전역 규칙을 명시해, 이후 모든 태스크가 암묵적으로 따르게 했다.
3단계 — 구현 (superpowers:subagent-driven-development)
태스크마다 완전히 새 컨텍스트의 Sonnet 서브에이전트를 디스패치했다. 각 에이전트는:
- 브리프를 읽고 원본(구 레포) 코드를 직접 읽어가며 구현
- 테스트 작성/실행, 실라이브 API(
api.coinsect.io)에 대고 SSR 검증 - 자체 리뷰 후 보고 (구현 내용, 검증 증거, 이탈/우려사항)
- 커밋은 하지 않고 스테이징까지만 — 컨트롤러(Fable 5, 이 세션)가 diff를 확인하고 커밋
컨트롤러는 매 태스크마다: 보고 검토 → (필요시) 진행 레저(progress.md)와 계획서 갱신 → 다음 브리프 추출 → 다음 에이전트 디스패치, 를 반복했다. 복잡도가 높은 태스크(모달 시스템, 커뮤니티 게시판, 웹소켓 레이어, 웹챗) 4곳에는 별도의 스팟 리뷰 에이전트를 추가로 붙여 구현자와 다른 시각으로 검증했다.
숫자로 보는 결과
- 계획 태스크: 20/20 완료
- 커밋: 34개 (기능 20 + 리뷰 수정 4 + 사용자 QA 후속 수정 10)
- 테스트: 164/164 green (vitest)
- 런타임 의존성: 7개 (VueUse/axios/ofetch 등 전부 배제, 직접 구현)
- 번들 크기: 클라이언트 JS+CSS 약 1MB,
.output전체 gzip 991KB - SDD 산출물: 브리프+리포트 40개 파일, 총 1,958줄 — 태스크별 근거 기록
왜 이 방식이 통했나 (요약)
- 코드를 먼저 이해시키고(병렬 탐색), 계획을 문서로 못박은 뒤(writing-plans), 신선한 컨텍스트로 하나씩 구현시키고(subagent-driven-development), 리뷰로 걸러내고(스팟 리뷰), 사람이 실사용으로 마무리하는 — 각 단계가 서로 다른 종류의 실수를 잡아낸다. 자동화된 리뷰는 웹소켓 레이스나 CSS 회귀를 못 잡았지만, 실사용 QA가 잡았다. 반대로 KIMP 산술 정밀도나 CDN 검증처럼 사람이 놓치기 쉬운 것은 리뷰 단계가 잡았다.
- "구현자는 스테이징까지, 컨트롤러가 커밋" 규칙 덕분에 모든 변경이 커밋 전에 한 번 더 사람(컨트롤러) 눈을 거쳤다.
- 의존성 최소화라는 제약이 오히려 "이 라이브러리가 뭘 하는지 몰라도 됨"이 아니라 "우리가 뭘 만들고 있는지 정확히 알아야 함"을 강제해서, 버그의 원인 파악이 항상 우리 코드 안에서 끝났다(서드파티 블랙박스 없이).