babipanote·
#Claude Code#바이브 코딩#Flutter#GentleDo#비개발자#1인개발#buildlog

비개발자가 Claude Code로 Flutter 앱을 만든다는 것 — GentleDo 개발기

Flutter를 한 줄도 안 써 본 비개발자가 Claude Code와 함께 GentleDo를 App Store에 올리기까지. 기술 스택 선택, DB 마이그레이션, 의존성 정리, 테스트 158개. AI가 할 수 있는 것과 할 수 없는 것의 경계를 정직하게 기록.

읽는 시간 8

시리즈 위치#10 GentleDo라는 이름이 생기기까지 — Keelry를 버린 날#11 (현재)#12 App Store 심사 2일 통과기

Flutter를 한 줄도 써 본 적 없던 내가, 2026년 4월 App Store에 GentleDo를 올렸다. 모든 코드는 Claude Code와 함께 썼다. 바이브 코딩이라고 부른다.

기술 스택 — 왜 Flutter + Drift + Riverpod인가

결정은 세 가지 제약에서 출발했다. (1) 혼자 만든다 (2) 서버 운영 비용은 0원에 수렴해야 한다 (3) iOS와 Android를 한 번에 올리고 싶다.

레이어선택이유
FrameworkFlutter 3.x크로스플랫폼 1개 코드베이스. SwiftUI × Jetpack Compose 병렬 학습은 1인에게 불가능
LanguageDart 3.x (null-safe)정적 타입 + null 안정성 — AI 페어 프로그래밍과 잘 맞는다
Local DBDrift 2.x (SQLite ORM)서버 없음. Phase 1은 전부 기기 로컬
StateRiverpod 2.xProvider보다 컴파일 타임 안전성 높음. 테스트 가능성 우위
NavigationGoRouter선언형 라우팅. URL 스킴 확장 용이
Notificationsflutter_local_notifications + flutter_timezone서버 푸시 불필요. 타임존 버그가 많았다
Widgethome_widgetiOS WidgetKit + Android AppWidget 공통 API

가장 중요한 결정은 Phase 1에서 서버를 절대 넣지 않기였다. Supabase든 Firebase든, 로컬 가설("에너지 체크인 한 날이 태스크 완료율이 높다")을 검증하기 전에 인프라를 쌓는 건 완벽한 시스템 함정이라는 걸 이전 프로젝트에서 배웠다.

바이브 코딩의 현실 — 내가 한 일, AI가 한 일

Claude Code가 혼자 다 해주는 게 아니다. 내가 한 일과 AI가 한 일의 경계는 이렇게 나뉘었다.

내가 한 일

  • 프로젝트 컨텍스트 문서화 (CLAUDE.md, docs/PROJECT_BRIEF.md, docs/CONVENTIONS.md)
  • 한 커밋당 3~5개 파일만 바꾸는 규칙 강제
  • 실기기 테스트, 디자인 결정, 카피라이팅
  • "이건 지금 하지 마세요" 리스트 유지 (Supabase, AI API, Material Icons 등)
  • 가설 정의와 검증 지표 설정

Claude Code가 한 일

  • Dart 코드 실제 작성
  • Drift 스키마 마이그레이션 코드
  • Riverpod provider 설계
  • 반복 태스크 스케줄링 로직
  • 테스트 케이스 작성
  • 리팩토링 제안

핵심은 CLAUDE.md였다. 규칙을 명문화하지 않으면 AI는 매번 조금씩 다른 스타일로 짠다. "Red 사용 금지", "UI 문자열 하드코딩 금지(ARB 경유)", "Material Icons 금지(lucide_icons만)", "스트릭 카운터 절대 금지". 이 네 줄이 수백 번의 "그거 말고" 소통을 절약했다.

DB 마이그레이션 — schemaVersion 3까지의 흔적

로컬 DB를 쓰면 마이그레이션에서 반드시 한 번은 피를 본다. GentleDo는 schemaVersion = 3까지 왔다.

버전변경이유
v1초기 스키마태스크 + 카테고리 + 컨디션 체크
v2momentumScore 리네임 (구 keelryScore)브랜드 변경(#10 참고)
v3start_time, notify_enabled, notify_at_start, notify_minutes_before 추가시작일/마감일 분리 + 알림 오프셋

실기기에서 v2 → v3 마이그레이션이 TestFlight 빌드에서 한 번 실패했다. ALTER TABLE 순서가 dev 기기에선 통과했는데 일부 테스터 기기에선 컬럼 기본값이 NULL로 들어가면서 크래시. Claude Code에게 "기본값을 명시적으로 넣어달라"고 두 번 지시한 뒤에야 안정화됐다.

교훈: 마이그레이션은 dev 기기 1대가 아니라 최소 3대 기기에서 검증해야 한다.

의존성 방향 정리 — 4월 중순의 대규모 리팩토링

초반에는 features/today에서 features/tasks를 직접 import했다. 편리했지만 탭이 4개가 되니 서로가 서로를 import하기 시작했다. 순환 참조는 없었지만 어디서 뭐가 쓰이는지 더 이상 머리에 그려지지 않았다.

결국 단방향 의존성 규칙을 세웠다.

app → features → shared → data → core
  • features 간 직접 import는 today(허브)만 허용
  • shared는 features를 import하지 않는다
  • data는 core만 import한다

이 리팩토링에 1주일을 썼다. commit 473bfbc18ee6aa로 마무리. 결과적으로 App Store 심사 전 대형 파일이 쪼개졌고, 새 기능 추가가 훨씬 빨라졌다.

테스트 75 → 158개로 늘린 이유

초반엔 테스트 75개였다. 출시 1주 전, 158개로 두 배 넘게 늘렸다. 이유는 단순하다.

실기기에서 예상 못 한 크래시가 계속 터졌다.

  • 반복 태스크 복구(Android 재부팅 후 알림 재예약) 경로
  • 서브태스크 체크 시 부모 카드 상태 동기화
  • 컨디션 체크 스킵했을 때 에너지 필터 fallback
  • 다크모드 + 낮은 명도 컬러 조합의 대비

QC agent를 별도로 돌려서 리포지토리, 프로바이더, 헬퍼 커버리지를 끌어올렸다(commit 263610a). 테스트가 2배 늘자 심사 제출 빌드(build 8)에서 처음으로 내가 두려워하지 않고 "업로드" 버튼을 눌렀다.

한계 — AI가 못하는 것

정직하게 쓴다. 바이브 코딩으로 풀 수 없었던 것들이다.

  • 실기기 디버깅 — path_provider_foundation 2.6.0의 FFI 크래시는 심사 제출 직전에 발견됐다. Claude에게 "이 크래시 로그 해결해줘"라고 던져도 원인을 특정하지 못했다. dependency_overrides로 2.4.4 고정이라는 해결책은 결국 내가 패키지 이슈 트래커를 뒤져서 찾았다(#12에서 상세히)
  • Xcode 설정의 체크박스 하나 — TestFlight 암호화 선언, iPad 타깃 제거, capabilities, provisioning profile. 여기서 하루를 잃었다
  • 디자인 감각 — "이 카드가 좀 무거워 보인다" 같은 피드백은 텍스트로 표현하기 어렵다. 결국 내가 Figma에서 직접 조정한 뒤 값을 옮겼다
  • 사용자 공감 — 테스터의 "이건 왜 이렇게 되어 있어요?" 질문에 답하는 건 사람의 일이다

다음 이야기

다음 글 #12에서는 App Store 심사 2주간의 실전 기록을 쓴다. path_provider 크래시를 어떻게 잡았는지, iPad 스크린샷 요구를 어떻게 회피했는지, 그리고 제출 후 2일 만에 승인된 순간까지.

GentleDo가 궁금하면 지금 받아볼 수 있다 — App Store (iOS).


CTA — 다운로드 (iOS): apps.apple.com/us/app/gentledo/id6761796867 · GentleLab 랜딩: lsy860224.github.io/gentlelab.github.io

관련 글