Skip to content

Latest commit

 

History

History
212 lines (164 loc) · 15.6 KB

File metadata and controls

212 lines (164 loc) · 15.6 KB

Unplan 코드베이스 개요

AI 스마트 스케줄러 앱(Unplan)의 전체 구조·구현 현황·인프라를 한 문서로 정리한 온보딩 문서입니다. 기준일: 2026-07-06 · 처음 합류하거나 전체 그림이 필요할 때 이 문서부터 읽으세요.

← 문서 목록 · 코어 컨벤션 · 확장 컨벤션 · 개발 체크리스트


1. 한눈에 보기

항목 내용
제품 Unplan — AI 스마트 스케줄러 (모바일)
플랫폼 iOS / Android (Expo, React Native)
언어 TypeScript (strict)
프레임워크 Expo SDK 56, React Native 0.85, React 19
라우팅 Expo Router v56 (파일 기반, src/app)
상태관리 Zustand + Immer (클라이언트) · TanStack React Query (서버)
네트워크 Axios + Orval (OpenAPI → 타입/훅 자동생성)
저장소 expo-secure-store (민감정보) · react-native-mmkv (일반)
인증 카카오 / 구글 소셜 로그인 (Apple 미구현)
빌드/배포 EAS Build + EAS Update(OTA), GitHub Actions CI/CD
디자인 Figma 기반 디자인 토큰 + primitive-first 컴포넌트 시스템
코드 규모 src 기준 약 5,800 LOC (TS/TSX)

현재 단계: 인증·온보딩 플로우는 동작하는 수준으로 구현됨. 메인 탭 화면(홈/일정/설정)은 아직 플레이스홀더. 백엔드 연동은 인증·온보딩이 실제 연결되어 있고, schedule은 domains/schedule/api boundary가 준비됐지만 기존 mock/store 기반 화면을 서버 query로 전환하는 작업은 후속 범위입니다.


2. 기술 스택 상세

런타임 / 핵심

  • Expo SDK 56, React Native 0.85.3, React 19.2 — Expo의 CNG(Continuous Native Generation)/prebuild 워크플로우 사용. ios/·android/ 디렉터리는 git에 커밋하지 않고 빌드 시 생성.
  • expo-router 56src/app을 라우트 루트로 사용(app.config.jsexpo-router 플러그인 root: 'src/app'). typedRoutes 실험 기능 활성화.
  • expo-updates — OTA 업데이트(채널 기반).

상태 / 데이터

  • zustand 5 + immer 11 — 전역 클라이언트 상태. create() + produce() 패턴(미들웨어 없이 수동 immer).
  • @tanstack/react-query 5 — 서버 상태. _layout.tsx에서 QueryClient 전역 설정(retry 2, staleTime 5분).
  • axios 1.16src/lib/api/client.ts 단일 인스턴스. 요청 인터셉터로 Bearer 토큰 자동 주입.
  • orval 7 — OpenAPI 스펙 → React Query 훅 + 타입 자동생성(npm run api:generate).
  • react-hook-form 7 + zod 4 + @hookform/resolvers — 폼/검증(설치됨, 본격 사용 전).

UI / 기타

  • react-native-reanimated 4 + react-native-worklets + gesture-handler — 애니메이션/제스처.
  • react-native-svg — 아이콘/그래픽.
  • @sun-typeface/suit — SUIT 한글 폰트(7 weight, _layout.tsx에서 로드).
  • date-fns 4 — 날짜 처리(한국어 로케일).
  • react-native-mmkv 4 — 빠른 KV 저장소.

3. 디렉터리 구조

src/
├── app/              # route/layout only (thin layer, 화면 로직 금지)
│   ├── _layout.tsx       # 루트: 폰트 로드, SDK 초기화, Provider, Stack
│   ├── index.tsx         # 스플래시 → 세션/온보딩 판단 후 분기 진입점
│   ├── (auth)/           # 로그인
│   ├── onboarding/        # recovery → sleep → activity → transport
│   └── (tabs)/           # 홈 · 일정 · 설정
├── screens/          # 라우트가 렌더링하는 실제 화면 구현
│   ├── auth/ home/ schedule/ settings/ onboarding/
├── components/
│   ├── ui/               # 전역 base primitive
│   ├── domain/           # 여러 feature에서 재사용되는 도메인 표현
│   └── features/         # 화면명/플로우명 기준 조합 컴포넌트
├── domains/          # UI 없는 도메인 로직 레이어
│   ├── auth/
│   ├── onboarding/
│   └── schedule/
├── lib/              # 앱 전역 인프라 / 크로스컷팅
│   ├── api/              # client, mutator, endpoints(생성), model(생성)
│   ├── auth/             # google-sdk, kakao-sdk, token-storage
│   ├── storage/          # secure-storage, mmkv-storage
│   ├── device/           # device-id
│   ├── i18n/             # 간이 t() 번역기
│   └── utils/            # date 등
├── hooks/            # 앱 전역 훅 (use-auth)
├── constants/        # config, 디자인 토큰(colors/typography/spacing/theme)
└── translations/     # ko.ts (번역 키-값)

구조 원칙(AGENTS.md / core.md): app은 route/layout만, 화면은 screens, 재사용 primitive는 components/ui, 도메인 표현은 components/domain, 화면/플로우 조합은 components/features/<screen-or-flow>, 도메인 로직은 domains/<domain>, 인프라는 src/lib. card는 최상위 domain이 아니라 schedule 도메인의 하위 개념이며, pin/queue card UI flow는 feature/screen 이름으로만 남긴다. 라우트 파일은 export { XScreen as default } from '@/screens/...' 형태의 1줄 재노출을 우선한다.

목표 도메인 목록은 문서에 유지하되 실제 repo에는 현재 코드가 있는 폴더만 둔다. onboarding 도메인은 최초 설정 저장과 이후 선호값 수정(setting-onboarding API)을 담당하며, 온보딩 UI 플로우는 screens/onboarding, app/onboarding에 둔다.


4. 앱 부팅 & 네비게이션 흐름

  1. app/_layout.tsx — SUIT 폰트 로드, configureGoogleAuthSDK() / initializeKakaoAuthSDK() 호출, useOnboardingStore.hydrateOnboarding() + useAuthStore.hydrateSession() 실행. 폰트 로드 전까지 null 렌더.
  2. app/index.tsxSplashScreen 표시 → 세션 hydration 완료 후 분기:
    • 미인증 → /login
    • 인증 O, 온보딩 미완료 → /onboarding/recovery
    • 인증 O, 온보딩 완료 → /(tabs)
    • hasNavigatedRef로 중복 네비게이션 방지.
  3. 로그인(screens/auth/login-screen.tsx) — 카카오/구글 로그인 핸들러. 성공 시 온보딩 완료 여부에 따라 /(tabs) 또는 /onboarding/recovery로 이동. Apple 로그인은 비활성(handleUnavailableLogin).
  4. 온보딩recovery → sleep → activity → transport 순서. 각 단계는 push, 마지막 transport에서 completeOnboarding() 호출 후 /(tabs)replace.

5. 도메인별 구현 현황

5.1 인증 (auth) — ✅ 동작

  • lib/auth/kakao-sdk.ts / google-sdk.ts — SDK 지연 초기화 + 설정 누락 시 graceful 경고/전용 에러 클래스(KakaoSDKConfigError, GoogleSDKConfigError).
  • domains/auth/social-login.tsloginWithKakao() / loginWithGoogle(). 카카오톡 미설치 시 카카오계정 로그인 폴백, 취소/네트워크/SDK/설정/알수없음으로 에러 정규화(SocialLoginError).
  • domains/auth/api/client.tssubmitSocialLogin()/auth/{provider}로 토큰 전송.
  • domains/auth/api/mapper.ts — 생성 응답 DTO를 AuthSession으로 변환.
  • domains/auth/use-auth-store.tssetSession / hydrateSession / logout. 토큰은 SecureStore에 저장.
  • hooks/use-auth.ts — store 셀렉터 묶음.
  • lib/device/device-id.ts — UUID 디바이스 ID 생성·영속(SecureStore), 폴백 포함.

5.2 온보딩 (onboarding) — ✅ API 연동

  • domains/onboarding/use-onboarding-store.ts — 회복 수단, 목표 수면시간, 집중/졸림/수면 시간대, 이동수단 선택과 제출 상태 관리. 서버 저장 성공 후 완료 플래그를 MMKV에 저장.
  • domains/onboarding/api/client.ts — 최초 온보딩 단일 저장 API를 호출.
  • domains/onboarding/api/mapper.ts — 프론트 온보딩 모델을 생성 요청 DTO로 변환.
  • domains/onboarding/sleep-condition.ts — 수면 분(minute)을 risk/lack/good/excess로 분류(Figma 스펙 반영).

5.3 메인 탭 (home / schedule / settings) — 🟡 진행 중

  • 홈·일정은 플레이스홀더이며, 설정에는 로그아웃과 개발용 온보딩 초기화 기능이 연결됨.
  • domains/schedule/api/client.tsschedule-crud generated endpoint를 감싸 일별/주별/월별/상세 조회와 생성/수정/삭제를 domain ViewModel로 반환.
  • domains/schedule/api/mapper.ts — schedule generated DTO/request와 프론트 schedule model 사이의 변환을 담당.
  • domains/schedule/api/query-keys.ts / queries.ts / mutations.ts — TanStack Query key와 hook 경계. 화면의 기존 mock/store 데이터 전환은 별도 PR에서 진행.

6. API 레이어

  • 클라이언트: lib/api/client.tsConfig.apiUrl 기반 axios. 요청 인터셉터가 SecureStore의 access token을 Bearer로 주입. 응답 인터셉터에 401 refresh-token 재시도 TODO(미구현).
  • Orval 자동생성: orval.config.tsOPENAPI_SPEC_URL에서 스펙을 받아 src/lib/api/endpoints(태그별 React Query 훅)와 src/lib/api/model(타입)을 생성. 커스텀 mutator(orval-mutator.ts)로 공용 axios 인스턴스 재사용. 두 디렉터리는 ESLint ignore 대상.
  • 생성된 태그: auth-controller, daily-memo, onboarding, setting-onboarding, schedule-crud, test-controller.
  • 인증·온보딩·schedule은 각 src/domains/<domain>/api/client.ts의 domain API wrapper를 통해 generated endpoint를 사용함. screen/components는 generated endpoint/model을 직접 참조하지 않음.

7. 디자인 시스템

  • 토큰: constants/colors.ts, typography.ts, spacing.ts, theme.ts. constants/index.tstheme.ts에서 재노출(colors, typography, spacing, radius, 타입). 화면/컴포넌트는 토큰만 참조 → 디자인 변경 시 값만 교체.
  • 폰트: SUIT(7 weight). fontFamilyWeight 맵으로 weight별 패밀리 관리.
  • 컴포넌트(components/ui): Typography, Button, Input/TextField, Card, Chip/ChipGroup, Tag, Header(다수 변형), Footer, GNB, BottomCTA, BottomSheet(여러 종류), Modal(여러 종류), Calendar, ProgressBar/Segment, TimeStepper, ConditionCard, RecommendCard, ScreenLayout, StatusBar, ViewModeButton, AppIcon, Icon, BrandLogo 등 30+개.
  • 규칙(primitive-first): 새 UI는 기존 primitive를 먼저 기반으로 구현. primitive가 부족하면 도메인 컴포넌트에서 중복 구현하지 말고 primitive를 확장. components/ui만 배럴 export(index.ts) 허용.
  • ⚠️ 현재 다수 primitive가 화면에서 미사용(Figma 기반 선구현). §9 참고.

8. 인프라 / 빌드 / 배포

환경변수 (.env.local, .env.example)

용도
EXPO_PUBLIC_API_URL API 베이스 URL
OPENAPI_SPEC_URL Orval 입력(Swagger JSON)
EXPO_PUBLIC_KAKAO_NATIVE_APP_KEY 카카오 Native App Key
EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID / _IOS_CLIENT_ID / _IOS_URL_SCHEME 구글 OAuth
EXPO_PUBLIC_APP_ENV development / staging / production

Admin Key·Client Secret 등 비밀값은 앱 번들에 절대 포함하지 않음(.env.example에 명시).

네이티브 설정

  • app.config.js — 앱명 Unplan, slug unplan, bundle/package com.unplan.app, scheme unplan. Expo plugins: 카카오 maven 레포(커스텀 플러그인), 구글 modular headers(커스텀 플러그인), expo-router, secure-store, kakao core, google-signin. runtimeVersion.policy: appVersion, EAS projectId 포함.
  • 커스텀 플러그인(plugins/)with-kakao-maven-repository.js, with-google-modular-headers.js(iOS Pod 설정).

EAS (eas.json)

  • 프로필: development, development-simulator, preview(internal, preview 채널), production(autoIncrement, production 채널). appVersionSource: remote.

CI/CD (.github/workflows)

  • ci.yml — PR/푸시(develop·main)에서 quality 잡(type-check, lint, test --if-present). develop 푸시 시 변경 경로를 감지(detect-preview-changes)하여:
    • 네이티브 변경 없음 → OTA 업데이트(eas update --channel preview)
    • 네이티브 변경 있음(또는 수동 강제) → Android preview 빌드 + iOS 시뮬레이터 빌드
  • release.ymlv*.*.* 태그 푸시 시 eas build --profile production --platform all.

코드 품질 게이트

  • ESLint(flat config) + Prettier + Husky(pre-commit) + lint-staged. 커밋 시 eslint --fix --max-warnings 0 + prettier --write.
  • 스크립트: type-check, lint, lint:fix, format, api:generate.

9. 코드 리뷰 요약 (확장성 · 유지보수 · 불필요 코드)

잘 되어 있는 점

  • 책임이 명확히 분리된 구조(app=라우트 / screens / components / domains / lib)와 thin route layer.
  • 인증 인프라가 견고: SecureStore(민감)·MMKV(일반) 분리, SDK 지연초기화 가드, 소셜 로그인 에러 정규화, 디바이스 ID 영속.
  • 타입 안전성(strict TS, 경로 alias) + 자동화(Orval, ESLint/Prettier/Husky) + 정교한 CI(OTA/네이티브 빌드 분기).
  • 디자인 토큰 + primitive-first 컨벤션으로 디자인 변경에 강함.

정리하면 좋은 점 (불필요 / 중복)

  1. 미사용 UI primitive 다수Input, Modal, BottomSheet, Calendar, GNB, ConditionCard, RecommendCard, ProgressBar, ProgressSegment, StatusBar, TimeStepper, ViewModeButton, ChipGroup, AppIcon, Tag 등이 화면에서 전혀 사용되지 않음. 디자인 선반영 의도라면 OK지만, 화면 구현이 임박하지 않았다면 드리프트/유지보수 부담. 살릴 것·뺄 것을 트래킹 권장.
  2. Orval 생성 훅 활용 범위 제한 — 인증·온보딩은 수기 래퍼에서 생성 함수를 사용하고, schedule은 domain API boundary와 TanStack Query hook이 준비됨. 다만 schedule 화면은 아직 mock/store 기반이라 서버 query 전환은 후속 작업임.
  3. 타입 중복/데드코드domains/auth/model.tsSchedule, ApiError 인터페이스가 미사용. ApiResponse는 생성 모델과 개념 중복.
  4. 미구현 TODO — axios 401 refresh 재시도. 백엔드 계약 확정 시 처리 필요.
  5. 테스트 부재 — CI는 npm test --if-present를 돌리지만 테스트 프레임워크/스크립트가 전혀 없음(devDeps에도 없음). 검증 공백.
  6. 네이밍 잔재package.jsonname: scheduler-app, MMKV id scheduler-app-storage가 리브랜딩된 Unplan과 불일치.
  7. 빈 디렉터리 마커src/lib/hooks/.gitkeep(빈 폴더), src/lib/i18n/.gitkeep·src/lib/auth/.gitkeep(이미 파일 있어 불필요).
  8. i18n 한계 — 단일 로케일 + 보간/복수형 없는 간이 t(). 현 단계엔 충분하나 확장 시 한계.

위 항목은 동작을 막는 버그가 아니라 정리/결정이 필요한 부채입니다. 우선순위: API 소스 일원화(2) → 데드코드 제거(3,6,7) → 테스트 도입(5) → 미사용 컴포넌트 정책(1).


10. 다음 단계 제안

  • 백엔드 API 계약 확정 후: schedule 화면의 mock/store 기반 데이터를 서버 query로 단계적 전환하고, 나머지 도메인의 Orval 연동과 401 refresh 구현.
  • 홈/일정/설정 화면을 선구현된 primitive로 실제 구현.
  • 테스트 환경(예: Jest + RNTL) 도입 및 CI에 연결.
  • 데드코드/네이밍 정리 + 미사용 컴포넌트 정책 결정.

문서 자동 점검: 본 문서는 2026-07-06 기준 코드 스냅샷을 반영합니다. 구조·구현이 바뀌면 함께 갱신하세요.