commit 1a03b71779d52095cbd441803d152fbb5f804abb Author: gahusb Date: Sun May 24 15:31:41 2026 +0900 docs: initial spec, v0-plan, brainstorming, CLAUDE.md harness rules - docs/spec.md: 정식 spec (브랜드·시장·기술 스택·일정·Day 0 검증 결과) - docs/v0-plan.md: W1~W4 13 task 구현 plan (TDD + manual test 절차) - docs/brainstorming-raw.md: 2026-05-23 brainstorming 원본 - CLAUDE.md: 하네스 운영 규약 (컨텍스트 3단·agentic 7 구성요소·박재오 Why 정합도) - README.md: 입구 + v0 상태 표 - .gitattributes: Windows ↔ macOS LF 통일 + pbxproj 바이너리 처리 - src/, modules/ 폴더 구조 (.gitkeep) 박재오 + AI agent 협업 표준 정립. 7월 착수 → 5/24 앞당김. Co-Authored-By: Claude Opus 4.7 (1M context) diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d83ae3c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,37 @@ +# Auto detect text files and perform LF normalization (Windows ↔ macOS 호환) +* text=auto eol=lf + +# Shell / build scripts must stay LF +*.sh text eol=lf + +# Windows batch must stay CRLF +*.bat text eol=crlf +*.cmd text eol=crlf + +# iOS Xcode project file — merge conflicts are nightmare, treat as binary +*.pbxproj -text + +# Binary assets +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.pdf binary +*.zip binary +*.jar binary +*.keystore binary + +# Fonts +*.ttf binary +*.woff binary +*.woff2 binary +*.eot binary +*.otf binary + +# Media +*.mp4 binary +*.mov binary +*.webm binary +*.mp3 binary +*.wav binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dd8f66f --- /dev/null +++ b/.gitignore @@ -0,0 +1,81 @@ +# React Native / Expo +node_modules/ +.expo/ +.expo-shared/ +dist/ +web-build/ + +# iOS +ios/Pods/ +ios/build/ +ios/*.xcworkspace/xcuserdata/ +ios/*.xcodeproj/xcuserdata/ +ios/*.xcodeproj/project.xcworkspace/xcuserdata/ +*.ipa +*.dSYM.zip + +# Android +android/build/ +android/app/build/ +android/.gradle/ +android/local.properties +*.apk +*.aab +*.keystore + +# Native deps +ios/Podfile.lock # 박재오 결정: 일단 ignore. 팀 합류 시 commit으로 전환 +android/gradle/ + +# Metro +.metro-health-check* + +# Bundle artifact +*.jsbundle + +# Watchman +.watchmanconfig + +# Testing +coverage/ +.nyc_output/ + +# Build artifacts +build/ +out/ + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +expo-env.d.ts + +# Editor +.vscode/ +.idea/ +*.swp +*.swo +.DS_Store +Thumbs.db + +# Env / secrets +.env +.env.local +.env.*.local +*.pem +*.p12 +*.mobileprovision +GoogleService-Info.plist +google-services.json + +# EAS +.eas/ +eas.json.local + +# TypeScript +*.tsbuildinfo + +# 박재오 개인 메모 (위키로 가야 할 것) +TODO.local.md +NOTES.local.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e70aee2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,259 @@ +# Lapie — 하네스 엔지니어링 운영 규약 + +> 본 파일은 Lapie 워크스페이스에서 Claude Code (또는 다른 AI agent)와 협업할 때의 **하네스(harness) 운영 규약**이다. agent의 컨텍스트 입력·실행 권한·검증 조건을 명시한다. +> 출처: Karpathy "Software 3.0 / Agentic Engineering" (2026-05-13 박재오 위키 [[AI-Agent-설계-운영-원칙]] 정식화) + superpowers 스킬 표준. + +--- + +## 0. 프로젝트 정체성 (한 줄) + +**Lapie (라피)** = 정면 전신 사진의 신체 영역에 실시간 카메라로 옷을 대보는 iOS 앱. 박재오 D-3 Why "내가 부딪힌 불편함을 누군가 다시 부딪히지 않도록 푸는 것"의 **외면 분기 첫 증거**. 메인 흐름 '결'(내면 자기자각)과 동등 라인. + +- 정식 spec: `docs/spec.md` +- v0 plan (W1~W4 13 task): `docs/v0-plan.md` +- 브레인스토밍 원본: `docs/brainstorming-raw.md` +- 박재오 위키 메타: `[[사업-Lapie-피팅앱]]` (Obsidian Vault) + +--- + +## 1. 작업 지시 표준 — 컨텍스트 3단 템플릿 + +박재오 ↔ AI agent의 모든 작업 위임은 다음 3단 조건으로 해석·실행한다. 명시되지 않으면 agent가 추론하되, 모호하면 시작 전에 묻는다. + +- **[완료 조건]** 이 작업이 끝났다고 말할 수 있는 **객관적 기준**. (예: "테스트 3개 PASS + npx tsc 무에러 + git commit 1건") +- **[금지 조건]** 절대 하지 말 것 / 건드리지 말 것. (예: "기존 src/features/segmentation/ 수정 금지", "Skia 셰이더에 새 uniform 추가 금지", "사용자 결정 보류 항목 임의 실행 금지") +- **[검증 조건]** 결과를 어떻게 검증하는가. (예: "npx jest src/features/autoScale 결과 캡쳐", "iOS 실기기 시각 검증 스크린샷", "verification-before-completion 스킬 적용") + +agent가 다른 sub-agent에게 위임할 때도 같은 3단으로 지시한다. + +> 본 3단은 박재오 메인 위키 `CLAUDE.md`(Obsidian Vault)에서 정식화. Lapie는 그 인스턴스. + +--- + +## 2. agentic engineering 7 구성요소 (Lapie 매핑) + +Karpathy의 7 구성요소를 본 워크스페이스에 매핑한다. + +| # | 구성요소 | Lapie 적용 | +|---|---|---| +| 1 | **스펙** | `docs/spec.md` + `docs/v0-plan.md`가 단일 진실 소스. 코드 작성 전 반드시 plan task 번호를 명시. | +| 2 | **계획 검토** | 신규 기능 추가 전 plan 또는 spec에 한 줄 이상 반영. plan에 없는 작업은 spec 변경 이력에 우선 한 줄 추가. | +| 3 | **diff 리뷰** | 모든 commit은 박재오가 직접 또는 superpowers:requesting-code-review 스킬로 리뷰. agent autopilot 금지. | +| 4 | **테스트** | 순수 함수(autoScale·maskSplit·photoValidation 등)는 TDD 강제. UI·카메라·합성은 manual test 절차 명시(`docs/v0-plan.md` 각 task 참고). | +| 5 | **권한 제한** | agent는 `docs/`·`src/`·`modules/` 외부 디렉토리 수정 금지. git commit/push는 박재오 확인 후. CocoaPods·npm install은 의존성 추가 명시 후. | +| 6 | **실패 감지** | 실기기 manual test 실패 시 즉시 task 중단 + plan에 한 줄 보고. "should work" 표현 금지. | +| 7 | **롤백** | 각 task는 단일 commit 권장. 실패 시 `git revert ` 또는 `git reset --hard HEAD~1` (push 전에 한정). push 후엔 새 commit으로 수정. | + +--- + +## 3. 폴더 구조 + +``` +lapie/ +├ CLAUDE.md # 본 파일 — 하네스 운영 규약 +├ README.md # 입구 + 빠른 시작 +├ .gitignore +├ docs/ # 기획·설계 단일 진실 소스 +│ ├ spec.md # 정식 spec (브랜드·시장·기술·일정) +│ ├ v0-plan.md # W1~W4 13 task 구현 plan +│ └ brainstorming-raw.md # 2026-05-23 brainstorming 원본 보관 +├ src/ # React Native + TypeScript 코드 +│ ├ screens/ # 화면 단위 (Onboarding·PhotoCapture·MaskPreview·LiveFitting·Capture) +│ ├ features/ # 도메인 로직 (각 폴더는 단일 책임) +│ │ ├ segmentation/ # Selfie Segmentation 브릿지 + TS 래퍼 +│ │ ├ pose/ # Pose Detection 브릿지 + TS 래퍼 +│ │ ├ maskSplit/ # 상/하/전신 3 마스크 영역 분할 (Pose keypoint 기반) +│ │ ├ composite/ # Skia 셰이더 합성 +│ │ ├ autoScale/ # 자동 스케일 추정 (하이브리드, v0 차별화 포인트) +│ │ └ photoValidation/ # 정면사진 자세 검증 +│ ├ store/ # Zustand 전역 상태 (최소화) +│ ├ navigation/ # 화면 전환 +│ └ types/ # 공유 타입 정의 +├ modules/ # 네이티브 브릿지 (iOS Swift, Android Kotlin) +│ ├ segmentation/ios/ # SegmentationModule.swift + .m +│ └ pose/ios/ # PoseModule.swift + .m +├ ios/ # Expo prebuild 생성 (W1 Task 1) +├ android/ # Expo prebuild 생성 (v1) +├ app.json # Expo config (W1 Task 1) +├ package.json # W1 Task 1 +└ tsconfig.json # W1 Task 1 +``` + +**원칙**: +- `src/features//` 폴더는 단일 책임만 갖는다. 다른 feature 폴더 import는 최소화. 공통 타입은 `src/types/`로. +- `modules//ios/` 네이티브 코드는 해당 feature 폴더와 동일 이름으로 매칭. +- `docs/`는 사람용 단일 진실 소스. `src/`는 코드 진실 소스. 둘 다 commit. + +--- + +## 4. 기술 스택 + 빌드 명령어 + +### 스택 (v0 확정) +- **프레임워크**: React Native 0.76+ / Expo 52+ (bare workflow) / TypeScript +- **카메라**: react-native-vision-camera v4 (Frame Processor) +- **세그멘테이션**: iOS Vision (VNGeneratePersonSegmentationRequest) / Android: MediaPipe Selfie Segmentation (v1) +- **Pose**: iOS Vision (VNDetectHumanBodyPoseRequest) / Android: ML Kit Pose (v1) +- **합성**: @shopify/react-native-skia v1 (셰이더) +- **제스처**: react-native-gesture-handler (핀치·드래그) +- **상태**: zustand +- **이미지 픽커**: expo-image-picker +- **저장/공유**: expo-media-library + React Native Share API +- **빌드**: EAS Build (Windows 환경 보완) +- **테스트**: Jest + ts-jest (순수 함수만) + +### 빌드 명령어 (W1 Task 1 이후 사용) +```bash +# 의존성 설치 +npm install + +# 네이티브 prebuild (Mac/EAS) +npx expo prebuild --platform ios + +# iOS 실기기 빌드 (Mac 필요) +npx expo run:ios --device + +# EAS 클라우드 빌드 (Windows 가능) +eas build --platform ios --profile development + +# 테스트 (순수 함수) +npx jest + +# 특정 모듈만 +npx jest src/features/autoScale + +# TypeScript 체크 +npx tsc --noEmit +``` + +### Windows 환경 제약 +- 박재오 메인 환경 = Windows → iOS 네이티브 빌드 직접 불가 +- `Pre-Task 0.2`(`docs/v0-plan.md`)에서 결정: (a) Mac mini 구매 / (b) EAS Build 클라우드 / (c) Flutter 재검토 +- agent는 Windows에서 작업 시 `npx expo run:ios` 명령어 호출 금지 → 코드 작성 + Jest 테스트만, 빌드는 EAS 또는 Mac에서. + +--- + +## 5. 코드 컨벤션 + +### TypeScript +- `strict: true` 유지 (`tsconfig.json`) +- 모든 함수는 명시적 반환 타입 (특히 export 함수) +- `any` 금지. 모르겠으면 `unknown` 후 타입가드. +- 타입 정의는 인터페이스 우선, 합집합/리터럴은 타입 별칭. + +### React Native +- 함수형 컴포넌트 + hook만. class 금지. +- StyleSheet는 컴포넌트 파일 하단에 인라인 (CSS-in-JS 라이브러리 도입 X for v0). +- 화면 컴포넌트는 props로 콜백만 받고 내부에서 store 직접 구독. + +### 도메인 로직 (features/) +- 각 feature는 순수 함수 우선. 부수효과(네이티브 호출·파일 IO)는 별도 모듈로 분리. +- 예: `calculateScale.ts`(순수, TDD 가능) / `detectClothBounds.ts`(네이티브 호출, manual test). + +### TDD 적용 범위 +- ✅ TDD 강제: `features/autoScale/calculateScale.ts`, `features/maskSplit/splitMask.ts`, `features/photoValidation/validatePose.ts` +- 🟡 TDD 선택: `store/`, `navigation/` +- ❌ TDD 비적용: UI 화면, 카메라 합성, 네이티브 브릿지 (manual test로 대체) +- TDD 사이클: 실패 테스트 작성 → 실행 PASS X 확인 → 최소 구현 → 실행 PASS 확인 → commit +- superpowers:test-driven-development 스킬 적용 + +### 네이밍 +- 컴포넌트: PascalCase (`LiveFittingScreen`) +- 함수·변수: camelCase (`calculateScale`, `photoUri`) +- 타입: PascalCase (`PoseResult`, `ValidationResult`) +- 상수: UPPER_SNAKE_CASE (`MIN_SCALE`, `MAX_SCALE`) +- 파일: camelCase.ts 또는 PascalCase.tsx (컴포넌트만) + +--- + +## 6. 네이티브 모듈 가이드 + +### iOS Swift 브릿지 패턴 (modules//ios/) +1. `Module.swift` — 실제 로직 (Vision Framework 호출) +2. `Module.m` — Objective-C 매크로 (RCT_EXTERN_MODULE) +3. `src/features//index.ts` — NativeModules.Module 래핑 + 타입 정의 +4. `src/features//__tests__/.test.ts` — NativeModules mock + TS 래퍼 검증 + +> 자세한 예시는 `docs/v0-plan.md` Task 3·4 참고. + +### Vision Framework 호출 시 주의 +- 좌표계 변환: Vision은 좌하단 원점·normalized (0~1), UIKit은 좌상단 원점·픽셀 → 변환 필요 +- 신뢰도 임계 (`confidence > 0.3` 또는 `> 0.5`) 항상 적용 +- PixelBuffer → PNG 변환 시 메모리 해제 주의 (Swift ARC 자동이지만 큰 이미지 누적 주의) +- 이미지 로드는 `URL(string:)` → `Data` → `UIImage` → `CGImage` 4단 + +### 권한 (app.json) +- NSCameraUsageDescription +- NSPhotoLibraryUsageDescription +- NSPhotoLibraryAddUsageDescription + +--- + +## 7. 작업 시작/종료 체크리스트 (agent용) + +### 작업 시작 전 +- [ ] `docs/spec.md` 최신 결정사항 확인 +- [ ] `docs/v0-plan.md`에서 작업할 task 번호·Files·Steps 확인 +- [ ] 컨텍스트 3단(완료/금지/검증) 명시 (사용자 요청 없으면 agent 추론 후 확인) +- [ ] superpowers 스킬 평가 (brainstorming / writing-plans / executing-plans / TDD / verification 등) +- [ ] 현재 git 브랜치·작업 트리 상태 확인 (`git status`) + +### 작업 종료 시 +- [ ] 컨텍스트 3단 중 [검증 조건] 실제 실행 (예: `npx jest src/features/` 결과 캡쳐) +- [ ] superpowers:verification-before-completion 가이드 적용 (evidence before claims) +- [ ] commit message는 conventional commits 형식: `feat:`, `fix:`, `chore:`, `docs:`, `test:`, `refactor:` +- [ ] commit 직전 박재오 확인 — agent autopilot push 금지 +- [ ] `docs/v0-plan.md`의 해당 task step에 ✅ 체크 (또는 박재오에게 체크 요청) + +### git 룰 +- `--no-verify` 금지 (사용자 명시 X 한) +- `git reset --hard` 또는 `git push --force`는 박재오 확인 후만 +- 새 commit은 항상 만들기 (amend 금지, 사용자 명시 시만 amend) +- 브랜치 전략: `main` + feature 브랜치 (W1, W2, W3, W4 별도 브랜치도 가능) + +--- + +## 8. 위키 시스템 연동 (박재오 Obsidian Vault) + +### 단일 진실 소스 관계 +- **워크스페이스 `lapie/docs/`** = 정식 spec / plan / brainstorming 원본 +- **위키 `[[사업-Lapie-피팅앱]]`** = 박재오 메타 인덱스 (`workspace/lapie/` 포인터 역할) +- 의사결정 / 변경 이력 / 작업 로그는 양쪽에 한 줄씩 + +### 동기화 룰 +- Lapie 관련 큰 결정(spec 변경·기술 스택 변경·일정 변경)은: + 1. `docs/spec.md` 변경 이력에 한 줄 + 2. 박재오 위키 `[[사업-Lapie-피팅앱]]` 변경 이력에 한 줄 + 3. 박재오 위키 `wiki/log/YYYY-MM.md`에 한 줄 +- 코드·세부 구현·테스트 결과는 워크스페이스 내부에서만 (위키에 안 옮김) + +### Day 0 검증 결과 (2026-05-23 완료) +- 브랜드: **Lapie 확정** (Wittu·Mirree·Geola 비교 후) +- 인스타: @lapie_app 선점 완료 +- 도메인: lapie.kr + lapie.io 등록 (7월 착수 직전 박재오 직접) +- 상표: Class 9·42만 출원, Class 14·25 회피 (변리사 정식 조사 후) + +--- + +## 9. 박재오 정합성 검증대 + +신규 결정이 D-3 Why와 정합한지 항상 확인. 비정합 시 보류 + 박재오 확인. + +| Why 요소 | Lapie 적용 | +|---|---| +| "내가 부딪힌 불편함" | 박재오 본인 옷 구매 시 "어울릴까?" 고민 직접 경험 | +| "다시 부딪히지 않도록" | 동일 불편함을 가진 타인에게 도구로 제공 | +| 적용 영역 '결' (외면 분기) | 외면(체형·스타일)을 명료히 자각하기 어려움 | +| 발견 4 "함께" | 지인 공유 부가기능 = 발견 4의 시각화 | +| 발견 3 "증명" | 친구·SNS에 "어울린다" 증명받는 욕구 | + +→ 본 검증대 통과 안 하는 기능은 v0에서 제외. v1·v2 추가 기능도 동일 적용. + +--- + +## 10. 메타 정보 + +- 생성일: 2026-05-24 +- 박재오 위키: `[[사업-Lapie-피팅앱]]` ([[사업-Lapie-피팅앱-v0-plan]]는 본 워크스페이스로 이동) +- 작성 도구: Claude Code + superpowers 스킬 5종 +- 최초 brainstorming: 2026-05-23 (superpowers:brainstorming → writing-plans → dispatching-parallel-agents → verification-before-completion) +- 본 CLAUDE.md 패턴 출처: Karpathy Software 3.0 + 박재오 위키 [[AI-Agent-설계-운영-원칙]] diff --git a/README.md b/README.md new file mode 100644 index 0000000..1bf50d1 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# Lapie (라피) + +정면 전신 사진의 신체 영역에 **실시간 카메라로 옷을 대보는** iOS 앱. +박재오 [[정체성-Why-탐색|D-3 Why]]의 외면 분기 첫 증거. + +## 입구 + +- **하네스 운영 규약**: `CLAUDE.md` ← AI agent와 협업하기 전에 반드시 읽음 +- **정식 spec**: `docs/spec.md` +- **v0 구현 plan (W1~W4, 13 task)**: `docs/v0-plan.md` +- **brainstorming 원본**: `docs/brainstorming-raw.md` + +## v0 상태 (2026-05-24 기준) + +| 항목 | 상태 | +|---|---| +| Brainstorming (6단 결정) | ✅ | +| Spec 작성 | ✅ | +| v0 plan (W1~W4) | ✅ | +| Day 0 검증 (도메인·앱스토어·인스타·상표) | ✅ | +| @lapie_app 인스타 핸들 선점 | ✅ | +| lapie.kr / lapie.io 도메인 등록 | ⏸ 7월 착수 직전 | +| macOS 접근 방안 결정 | ⏸ Pre-Task 0.2 | +| 변리사 정식 상표 조사 | ⏸ 출원 직전 | +| Figma 와이어프레임 5화면 | ⏸ W1 첫 주 | +| **W1 Task 1 (Expo 세팅) 착수** | ⏸ 2026-07 예정 | + +## 기술 스택 (v0) + +React Native (Expo bare) + TypeScript + react-native-vision-camera + @shopify/react-native-skia + iOS Vision Framework (Swift 브릿지) + Zustand + Jest. + +자세한 빌드 명령어는 `CLAUDE.md` §4 참고. + +## 박재오 위키 연결 + +- 메타 페이지: `[[사업-Lapie-피팅앱]]` +- 트랙 분류: 박재오 위키 카테고리 3 (사업) +- Why 증거 라인: #3 (외면 분기) diff --git a/docs/brainstorming-raw.md b/docs/brainstorming-raw.md new file mode 100644 index 0000000..e7afd67 --- /dev/null +++ b/docs/brainstorming-raw.md @@ -0,0 +1,133 @@ +# Lapie 피팅 앱 — 브레인스토밍 브리핑 원본 + +> 출처: 2026-05-23 Claude Code 대화 (superpowers:brainstorming 스킬 사용). +> 본 raw는 정리 전 원본 흐름 보관. 정제된 위키 페이지는 [[사업-Lapie-피팅앱]] 참고. + +--- + +## 트리거 — 사용자 토로 원본 + +"인물의 정면 사진을 편집해서 그 사람의 체형에 맞게 상의나 하의 아니면 전신 형태로 뚫어두고, 그 뚫린 부분은 카메라로 설정을 변경하여 옷에 대보면 그 사람이 직접 입지 않아도 이 옷이 잘 맞는지 비교할 수 있는 어플을 만들어보려고 해. 주요 기능은 정면 사진에서 몸 체형에 맞게 빈 공간으로 만들고 거길 카메라로 연결하는게 메인 기능이야. 부가적인 기능은 현재 화면을 캡쳐하도록 해서 지인에게 공유하는 느낌으로 할 수 있는거지. 이런 가벼운 어플을 만들어보려고 하는데 기술적으로 가능한 영역부터 시작해서 내용을 분석하고. 앱 이름 네이밍. 시장 분석(타겟층 분석). 소요 시간 등을 계산하고 분석해서 브리핑해줘." + +--- + +## 1차 브리핑 (Claude → 박재오) + +### 기술 가능성 결론 +**기술적으로 100% 가능**. 핵심 기능은: +- 정면 전신 사진 입력 +- AI 세그멘테이션(iOS Vision / Android MediaPipe·MLKit)으로 사람 영역 자동 추출 +- Pose 키포인트로 상의/하의/전신 부위 분할 +- 카메라 라이브 피드를 마스크 영역에만 합성 (그린스크린 셰이더) +- 캡쳐 + SNS 공유 + +**난이도 평가**: +- 사람 실루엣 추출: ⭐ 쉬움 (OS 표준 API) +- 부위 분리: ⭐⭐ 중간 +- 카메라 합성 정렬: ⭐⭐ 중간 +- **자동 스케일 매칭**: ⭐⭐⭐⭐ 어려움 — 차별화 포인트이자 발목 잡는 곳 +- 실제 사이즈 측정: ⭐⭐⭐⭐⭐ 매우 어려움 (v2 이후, LiDAR/ARCore Depth 필요) + +### 시장 분석 핵심 +- Primary 타겟: 20~30대 여성, 온라인 쇼핑 반품률 높음, 사이즈 고민 +- Secondary: 부모님 옷 선물, 중고거래 옷 사이즈 확인, 오프라인 매장 동행자 없을 때 +- 경쟁 지형: AR 가상 피팅(의류 3D 모델 필요 → 일반 사용자 불가) / AI 의류 생성(실물 다름) / **이 앱은 "실물을 들고 대본다" — 의류 디지털화 불필요, SNS 공유성 강함** +- 비즈니스 모델: Freemium / 어필리에이트(무신사·쿠팡) / B2B(동대문 셀러) + +### 1차 네이밍 후보 Top 3 +1. OOTDoll +2. TryThis / 트라이디스 +3. 닿아봐 + +### 1차 소요시간 (박재오 가용시간 ×1.7 보정) +- v0: 4~5주 +- v1: 누적 8~12주 +- v2: 누적 15~22주 + +--- + +## 결정 흐름 (5단계 순차 대화) + +### 결정 1 — 포지셔닝 +- 선택지: 토이 vs MVP 검증 vs **부업 트랙 신설** +- 박재오 선택: **부업 트랙 신설** + +### 결정 2 — Why 정합도 ([[정체성-Why-탐색]] 검증대) +- 선택지: 내가 부딪힌 불편함 / "함께" 키워드 살리는 별개 트랙 / 토이로 보류 / Why 확장 +- 박재오 선택: **"내가 직접 부딪힌 불편함"** +- 결과: D-3 Why의 정식 증거·적용 영역으로 편입. '결' 메인 흐름의 외면 분기 + +### 결정 3 — 브랜드 소속 +- 선택지: '결' 산하 외면 트랙 / 쟁승메이드 새 사업부 / **독립 브랜드** / hedgy_75 채널 산하 +- 박재오 선택: **독립 브랜드** +- 결과: 톤 자유도 ↑, SNS 바이럴 친화, 인프라·마케팅 분리 부담 감수 + +### 결정 4 — v0 MVP 범위 +- 선택지: **MVP 검증형 (3~4주)** / 극경탕 토이형 (1~2주) / 베타 출시형 (5~6주) +- 박재오 선택: **MVP 검증형 (3~4주)** — 보정 4~6주 +- 포함: 자동 마스킹 + 3모드 + 자세 가이드 + 자동 스케일 추정 + SNS 공유 + +### 결정 5 — 기술 스택 +- 선택지: iOS 네이티브 / Flutter / **RN + Vision Camera** / 웹 프로토타입 +- 박재오 선택: **React Native + Vision Camera** +- 결과: React 경험 활용, v1 Android 동시 확장 옵션 보존, EAS Build, iOS 우선 + +### 결정 6 — 네이밍 +- 방향성: **신조어 점유형 (Linear/Toss 패턴)** +- 후보 4개: Wittu / Mirree / **Lapie** / Geola +- 박재오 선택: **Lapie (라피)** +- 의미: Lap("얹어 대다") + 부드러운 어미. 의미 점유 + 글로벌 친화 + +--- + +## design 요약 (사용자 승인) + +### 한 줄 정의 +정면 전신 사진의 신체 영역에 실시간 카메라로 옷을 대보는 iOS 앱. D-3 Why "외면의 결을 명료하게 자각하기 어려운 불편함"의 첫 증거이자 발견 4 "함께" 키워드를 시각화로 점유. + +### 핵심 사용자 여정 +정면사진 등록(자세 가이드) → 자동 마스킹(3 모드) → 카메라 실행 → 자동 스케일 + 핀치 미세조정 → 캡쳐 → SNS 공유 + +### 자동 스케일 알고리즘 (하이브리드) +``` +초기 추정: + ref_shoulder_px = 정면사진의 좌우 어깨 keypoint 픽셀거리 + ref_shoulder_cm = 사용자 입력 키 × 0.25 (인체 평균) + cam_cloth_width = 카메라에서 옷 윤곽 검출 후 최장 가로 픽셀 + 스케일 추정 = ref_shoulder_px / cam_cloth_width + +사용자 핀치 줌: + 자동 추정값을 초기 transform으로 적용 + 핀치/드래그로 ± 30% 미세조정 +``` + +### v0 일정 (7월 착수, 주 15~20h) +| 주차 | 작업 | 산출물 | +|---|---|---| +| W1 | RN 세팅 + Vision Camera PoC + Selfie Segmentation 마스크 | 마스크 오버레이 동작 | +| W2 | 정면사진 등록 + Pose keypoint + 3모드 마스크 자동 분할 | 사진 1장에서 3 마스크 | +| W3 | Skia 합성 + 자동 스케일 추정 | 카메라 합성 동작 | +| W4 | 캡쳐·저장·공유, 온보딩 3장, 폴리싱 | 박재오 사용 가능 빌드 | +| W5~6 (버퍼) | 자동 스케일 보강, 인스타 콘텐츠 5편 | 친구 5명 베타 | + +### Day 0 차단성 액션 +1. 도메인 검색 (lapie.app / lapie.kr / lapie.io) +2. 앱스토어 동명 앱 검색 +3. 인스타 핸들 검색 (@lapie / @lapie_app) +4. KIPRIS 상표 검색 (한국 + 미국) +5. Apple Developer Program 가입 ($99) — 7월 착수 직전 +6. Figma 와이어프레임 5화면 — W1 첫 주 + +--- + +## 사용자 결정 (2026-05-23 대화 끝) +"A로 가자 우선 writing-plans 작성하고 다음에 수정하면서 디벨롭하면 되니까." +→ design 승인 + wiki 페이지 작성 + raw 보관 + writing-plans 스킬 호출. + +--- + +## 메타 정보 +- 대화 도구: Claude Code (superpowers:brainstorming + writing-plans) +- 대화 일시: 2026-05-23 +- 결정 누적: 6건 (포지셔닝 + Why + 브랜드 + 범위 + 스택 + 네이밍) +- 변경 가능성: design은 초안. v0 W1 착수 전까지 자유 수정 가능 (사용자 명시) diff --git a/docs/spec.md b/docs/spec.md new file mode 100644 index 0000000..9f3330b --- /dev/null +++ b/docs/spec.md @@ -0,0 +1,270 @@ +> [!takeaway] 비즈니스/방향성 관점에서의 핵심 +> **Lapie (라피)** 는 박재오 [[정체성-Why-탐색|D-3 Why]] "내가 부딪힌 불편함을 누군가 다시 부딪히지 않도록 푸는 것"의 **외면 분기 첫 증거**다. 메인 흐름 '결'이 내면(사주·MBTI 영역)을 다룬다면 Lapie는 외면(체형·옷·스타일 자각의 어려움)을 다룬다. 발견 4 "함께" 키워드(지인 공유)를 시각화로 점유하는 **독립 SNS 친화 브랜드**. v0 = MVP 검증형 4~6주 / iOS 단독 / React Native + Vision Camera. 7월 착수 예정 (현재 5~6월은 [[프로젝트-이모추]] · [[프로젝트-무진장-AI-광고제]] · [[프로젝트-우리카드-AI숏폼-공모전]] · [[정체성-Why-탐색|'결' PMF 1단계]]가 가용시간 점유). 차별점은 "실물 옷을 들고 화면에 댄다" — 의류 디지털화 불필요, 결과물 = 실물 그대로. + +# 사업 — Lapie (라피) 피팅 앱 + +## 한 줄 정의 + +정면 전신 사진의 신체 영역에 **실시간 카메라로 옷을 대보는** iOS 앱. 사용자가 옷을 카메라 앞에 들면, 사진 속 본인이 그 옷을 입은 듯한 합성 영상이 보이고, 캡쳐해서 지인에게 공유할 수 있다. + +## 브랜드 — Lapie (라피) + +### 네이밍 근거 +- Lap("얹어 대다") + 부드러운 어미 → **"옷을 (내 몸에) 얹어본다"** 의미 점유 +- 신조어 점유형 ([[정체성-Why-탐색|D-3 Why 페이지]] 명시: Linear·Notion·Toss 패턴) +- 글로벌 발음 친화, 카테고리에 묶이지 않음 +- 박재오 변연계 선택 (Wittu / Mirree / Lapie / Geola 4개 후보 중) + +### 톤 +- 가벼운 SNS 친화 (쟁승메이드·'결' 메인 흐름과 톤 분리) +- 행위 직설형: "들고 → 댄다 → 본다 → 보낸다" +- 독립 브랜드 (쟁승메이드 산하 아님) + +## Why 정합도 — D-3 Why 검증대 통과 + +| Why 요소 | Lapie 매핑 | 정합도 | +|---|---|---| +| 내가 부딪힌 불편함 | 박재오 본인이 "이 옷이 나한테 어울릴까?" 고민을 직접 경험 | ✅ **강** (사용자 본인 확인 2026-05-23) | +| 적용 영역 '결' | 외면(체형·스타일) 자기자각의 어려움 — 내면 결과 메타 패턴 동일 | ✅ **확장형** ('결'의 외면 분기) | +| 발견 4 "함께" | 지인 공유 부가기능이 정확히 일치 | ✅ **강** | +| 발견 3 "증명" | "이 옷이 어울린다"를 친구·SNS에 증명받고 싶은 욕구 | ✅ **정합** | + +→ D-3 Why 정식 증거 라인 #3 추가 ([[프로젝트-쟁승메이드-Co]] #1 사주 / [[사업-AI음악작곡-워크플로우]] #2 음악 / **Lapie #3 외면 피팅**). + +## 메인 기능 (v0) + +### 핵심 동작 흐름 +``` +앱 열기 + ↓ +정면 전신 사진 1장 등록 (자세 가이드 오버레이 + 자동 검증) + ↓ +자동 마스킹 (MediaPipe Selfie Segmentation + Pose 17점) + ├ 상의 모드 (어깨~허리) + ├ 하의 모드 (허리~발목) + └ 전신 모드 (전체 실루엣) + ↓ +카메라 실행 → 옷을 카메라 앞에 댐 + ↓ +자동 스케일 초기 추정 + 사용자 핀치 미세조정 (하이브리드) + ↓ +캡쳐 → SNS·카톡 공유 / 갤러리 저장 +``` + +### 자동 스케일 알고리즘 (v0 차별화 포인트) +``` +초기 추정: + ref_shoulder_px = 정면사진의 좌우 어깨 keypoint 픽셀거리 + ref_shoulder_cm = 사용자 입력 키 × 0.25 (인체 평균) + cam_cloth_width = 카메라에서 옷 윤곽 검출 (Selfie + 색차 마스크) + 스케일 추정 = ref_shoulder_px / cam_cloth_width + +사용자 핀치 줌: + 자동 추정값을 초기 transform으로 적용 + 핀치/드래그로 ±30% 미세조정 +``` + +> 자동 스케일은 v0 핵심 차별화이자 발목 잡는 곳. 정확도 부족 시 "재미는 있는데 실용성 없음"으로 굳어질 위험. W3에 별도 검증 사이클 필요. + +## 기술 스택 + +| 영역 | 선택 | 근거 | +|---|---|---| +| 프레임워크 | React Native (Expo bare / EAS Build) | 박재오 React 경험 활용 + v1 Android 동시 확장 가능 | +| 카메라 | react-native-vision-camera v3 | Frame Processor로 ML 모델 호출 + 셰이더 합성 | +| 세그멘테이션 | iOS: Vision (VNGeneratePersonSegmentationRequest) / Android: MediaPipe Selfie | 무료 · on-device · 실시간 | +| Pose 키포인트 | iOS: Vision (VNDetectHumanBodyPoseRequest) / Android: ML Kit Pose | 17점 자동 | +| 합성 | react-native-skia | 마스크 + 카메라 피드 셰이더 합성 | +| 상태관리 | Zustand | 최소 전역 상태 | +| 빌드 | EAS Build (iOS) | TestFlight → 앱스토어 | +| 백엔드 (v0) | 없음 | v1부터 회원·클라우드 추가 | + +### 컴포넌트 구조 (RN) +``` +src/ +├ screens/ +│ ├ Onboarding.tsx // 첫 사용 가이드 3장 +│ ├ PhotoCapture.tsx // 정면사진 촬영/선택 + 가이드 +│ ├ MaskPreview.tsx // 자동 마스킹 결과 + 모드 선택 +│ ├ LiveFitting.tsx ⭐ // 카메라 합성 메인 화면 +│ └ Capture.tsx // 캡쳐 결과 + 공유 +├ features/ +│ ├ segmentation/ // Selfie Segmentation 브릿지 +│ ├ pose/ // MLKit/Vision Pose 브릿지 +│ ├ composite/ // Skia 셰이더 합성 +│ └ autoScale/ // 어깨너비 ↔ 옷 너비 비율 추정 +├ store/ // Zustand +└ native_modules/ + ├ ios/ // Vision API 브릿지 + └ android/ // ML Kit 브릿지 (v1) +``` + +## 시장 분석 + +### Primary 타겟 +20~30대 여성, 온라인 쇼핑(무신사·지그재그·에이블리) 빈도 높음, 반품률 높음, 사이즈/스타일 자주 고민. 구매 직전 "친구에게 사진 보내고 의견 묻는" 행동 패턴 → Lapie의 캡쳐·공유 기능이 그 행동을 흡수. + +### Secondary 타겟 +- 부모님·배우자 옷 선물 시 본인 사진 위에 대보고 싶은 사람 +- 중고거래(번개장터·당근) 옷 사이즈감 확인 +- 빨래방·드라이클리닝 전 코디 미리보기 + +### 경쟁 지형 +| 카테고리 | 예시 | Lapie와의 차이 | +|---|---|---| +| AR 가상 피팅 (3D 의류 모델 필요) | Wanna, Snap AR Try-On, Trillenium | **의류 디지털화 전제** → 일반 사용자 불가. Lapie는 실물 옷만 있으면 OK | +| 매장 키오스크 AR Mirror | 무신사 매장, 신세계 일부 | B2B, 일반 접근 불가 | +| AI 의류 생성 | Doji, AI Outfit | AI가 옷을 *생성* → 실물과 다름. Lapie는 실물 그대로 | +| **Lapie 자리** | (직접 경쟁자 거의 없음) | 실물 옷 들고 화면에 대본다 = 의류 디지털화 X, 시각적 임팩트 강함 | + +### 비즈니스 모델 후보 +1. **Freemium**: 무료 + 캡쳐 워터마크 / 프리미엄(워터마크 제거 + 워드로브 저장 + 다중 사진) +2. **어필리에이트**: 옷 인식 → 무신사·쿠팡·지그재그 유사 상품 추천 (수수료) — v2 +3. **B2B 라이선스**: 동대문 셀러·쇼핑몰 사장 모델 1장 위에 신상 합성 — v2 +4. **광고**: 트래픽 누적 후 후순위 + +### 마케팅 채널 +**앱 사용 영상 자체가 마케팅**. 시각적 임팩트가 강해 인스타 릴스 / 틱톡 / 쇼츠 친화. [[사업-hedgy75-인스타]] 카드뉴스 채널과 시너지 가능(1인 빌더 일지의 다음 빌드 콘텐츠). + +## 일정 (v0 → v2) + +### v0 — MVP 검증형 (4~6주, 7월 착수 예정) + +| 주차 | 작업 | 산출물 | +|---|---|---| +| W1 | RN 세팅 + Vision Camera PoC + Selfie Segmentation 마스크 | 마스크 오버레이 동작 | +| W2 | 정면사진 등록 + Pose keypoint + 3모드 마스크 자동 분할 | 사진 1장에서 3 마스크 | +| W3 | Skia 합성 + 자동 스케일 추정 | 카메라 합성 동작 | +| W4 | 캡쳐·저장·공유, 온보딩 3장, 폴리싱 | 박재오 사용 가능 빌드 | +| W5~6 (버퍼) | 자동 스케일 보강, 인스타 콘텐츠 5편 | 친구 5명 베타 | + +**검증 게이트**: 인스타 릴스 5~10편 + 100명 사용. 매직 넘버 = 주간 콘텐츠 발행 수 + 영상 조회수 ([[정체성-Why-탐색]] PMF 매직넘버 #7 원칙 적용). + +### v1 — 출시 (누적 10~14주) +- Android 추가 +- 회원·클라우드 저장 (NAS 활용 가능, [[자산-인프라-NAS-GPU]]) +- 인앱결제 (워터마크 제거) +- TestFlight → 앱스토어 정식 출시 +- 1차 마케팅 (쇼츠 5편) + +### v2 — 수익화 (누적 15~22주) +- 옷 인식 → 어필리에이트(무신사·쿠팡) +- 워드로브 갤러리 +- 친구 합성 (2인 사진) +- 동대문 셀러용 B2B 모듈 + +## Day 0 차단성 액션 (착수 전 필수) + +### 검증 결과 (2026-05-23, dispatching-parallel-agents 4건 병렬 실행) + +| # | 액션 | 결과 | Evidence 강도 | +|---|---|---|---| +| 1 | **도메인 검색** | 🟡 lapie.app ❌(점유, 불어 파킹) / lapie.com ❌($18,999 프리미엄) / lapie.co ❌ / **lapie.kr ✅** / **lapie.io ✅** / getlapie.com ✅ / trylapie.com ✅ | 강 (DNS NXDOMAIN + 등록 사이트 직접 확인) | +| 2 | **앱스토어 동명 앱** | 🟢 **충돌 없음**. App Store(US/KR) "Lapie"·"라피" 0건 / Google Play(Global/KR) 0건. 패션·피팅 카테고리 유사명 없음 | 강 (4채널 직접 검색) | +| 3 | **인스타 핸들** | 🟢 **@lapie_app ✅ 선점 완료 (2026-05-23 박재오 직접)** / @lapie ❌(개인 비활성) / @lapieofficial ❌(La Pie 💎 주얼리 브랜드) / @uselapie ❌(누군가 선점) / @lapie.app·@lapie_kr·@lapie_korea ✅ 추정 (보조 핸들 후보) | **확정** (가입 화면에서 직접 확인 후 등록) | +| 4 | **KIPRIS·USPTO 상표** | 🟡 **중위험**. Lapie 직접 일치 미확인 / 한글 유사 다수: 라피타(LaPitta, Class 9·10) · 라피도(Rapido, Class 25 소멸 추정) · LAP(Class 25 운영 중) / **인스타 신호 추가: La Pie 주얼리 → Class 14 추가 위험** | 약 (KIPRIS·USPTO·WIPO 자동조회 차단 → 우회 웹검색만, 변리사 정식 조사 필요) | +| 5 | Apple Developer Program 가입 ($99) | ⏸ 7월 착수 직전 | — | +| 6 | Figma 와이어프레임 5화면 | ⏸ W1 첫 주 | — | + +### 종합 판정 (2026-05-23): 🟢 **Lapie 브랜드 확정** (4건 검증 + 대안 3건 비교 후 박재오 결정) + +> 🎉 **@lapie_app 인스타 핸들 선점 완료** (2026-05-23). 가장 회수 어려운 자산 1순위 확보. 도메인(lapie.kr / lapie.io) 등록은 7월 착수 직전 박재오 직접. + +**유지 근거 (강):** +- 앱스토어 글로벌·한국 충돌 0건 = 출시 자체에는 무리 없음 +- lapie.kr + lapie.io 확보 가능 = 한국 시장 + 글로벌 테크 톤 둘 다 커버 +- @lapie_app 등 핸들 선점 가능 (추정 강함) +- Class 9(앱)·Class 42(SaaS) = 출원 가능성 영역 + +**조건·위험 (중):** +- lapie.app + lapie.com 사실상 막힘 → 글로벌 메인 도메인 톤 약화 (lapie.io로 보완) +- Class 14(주얼리, La Pie 인스타) + Class 25(의류, 라피타·라피도·LAP) **브랜드 컨퓨전 위험** → 의류 카테고리 확장 제한 또는 변리사 검토 후 출원 +- KIPRIS·USPTO 자동조회 차단으로 상표 결과는 **공개 검색 한도** — 정식 출원 전 변리사 유사상표 조사 필수 +- @uselapie 누군가 선점 = 작명 시도 흔적 → **빠른 선점 권장** + +### 즉시 권장 액션 → 진행 상태 + +1. ⏸ **lapie.kr 등록** (가비아·후이즈, 약 2만/년) — 7월 착수 직전 박재오 직접 +2. ⏸ **lapie.io 등록** (Namecheap 등, 약 5만/년) — 7월 착수 직전 박재오 직접 +3. ✅ **@lapie_app 인스타 선점 완료** (2026-05-23 박재오 직접) — 가장 회수 어려운 자산 확보 +4. ⏸ Class 9·42 출원은 7월 착수 후 변리사 검토 → 진행 +5. 🚫 Class 14·25 의류·주얼리 카테고리 출원·확장 회피 (또는 변리사 정식 검토) + +### 대안 후보 4건 비교 (2026-05-23 추가 검증 완료) + +박재오 요청으로 Wittu·Mirree·Geola 3개 대안에도 동일 4건 검증(도메인·앱스토어·인스타·상표) 실행. dispatching-parallel-agents 스킬로 브랜드별 1 agent 병렬 dispatch. + +| 영역 | **Lapie 🟡** | Wittu 🟡 | Mirree 🔴 | Geola 🔴 | +|---|---|---|---|---| +| 메인 .com | ❌ $18,999 | ❌ 2026-06 만료 갱신가능성 | ❌ 선점 | ❌ **28년 보유** | +| .kr / .io | ✅ 확정 / ✅ 확정 | ⚠ 미확인 | ⚠ 미확인 | ✅ 추정 / ✅ 추정 | +| 앱스토어 | 🟢 4채널 0건 | 🟢 0건 + 컨셉충돌 | 🟢 0건 + ASO noise | 🟡 Geolah + GOELIA 인접 | +| 인스타 _app 가능 | ✅ 추정 | ✅ 추정 | ✅ 추정 (단 @mirree 선점) | ⚠ 핸들 불명 | +| 상표 핵심 위험 | Class 14·25 (La Pie 주얼리 + 라피타·라피도·LAP) | **Class 25 (위트·WITT·WIT·위벳유 다중)** | Class 9·42 (**미리캔버스**) + 25 (Art by Mirree) | Class 25 (**(주)지올 G-ALL** + **GOELIA**) + 9 (Geolah) | +| 한국어 SEO | 중립 | "위트" 검색 노이즈 | **🔴 "미리"=부사+미리캔버스** | 발음 모호 | +| 글로벌 SEO | 중립 | 중립 | **🔴 Art by Mirree 호주** | **🔴 GOELIA 중국 패션 앱** | + +**판정 결과**: +- 🟡 **Lapie (1순위 유지)** — .kr+.io 가용 확정 우위, 상표 위험 비슷한 수준이나 "위트"보다 "라피"가 한국어 일반 어휘 점유 약함 +- 🟡 **Wittu (후보 #2)** — Lapie 변리사 조사 🔴 판정 시 대체 후보 +- 🔴 **Mirree (폐기)** — mirree.com 선점 + @mirree 선점 + Art by Mirree 호주 아티스트 + 미리캔버스 한국 SEO 점유 + "미리" 부사 = 4중 회피 사유 +- 🔴 **Geola (폐기)** — geola.com 28년 보유 + GOELIA 글로벌 패션 앱 + (주)지올 G-ALL + 영문 발음 모호성 = 4중 회피 사유 + +**최종 결정 (2026-05-23)**: **Lapie 유지** + 즉시 권장 액션 3건 + Class 9·42 출원만 + 변리사 정식 조사. Lapie 🔴 판정 시 Wittu 진입, 그 외(Mirree·Geola)는 후보군에서 제외. + +## 위험 요소 + +1. **자동 스케일 UX 실패** — 정확도 부족 시 "재미는 있는데 실용성 없음". W3에 별도 검증 사이클 필수. +2. **iOS 단독 출시의 한국시장 함정** — Android 비중 70%대. v1에서 Android 필수. +3. **사진 촬영 진입장벽** — 정면 전신·배경 단순·조명 일정 = 사용자 직접 촬영 어려움. **가이드 오버레이 + 자세 자동 검증** 필수. +4. **개인정보·사진 데이터 정책** — 앱스토어 심사에서 명시 필요. on-device 처리로 유리. +5. **선점 우위 약함** — 컨셉 모방 쉬움. 6개월 내 시장 점유 + 캐릭터 IP / 커뮤니티로 해자. + +## 운영·트랙 통합 + +| 통합 지점 | 관계 | +|---|---| +| [[정체성-Why-탐색]] | D-3 Why 증거 라인 #3 (외면 분기). PMF 매직넘버 원칙 동일 적용 | +| [[정체성-미션과-방향성]] | 1인 비즈니스 파이프라인 트랙 추가 | +| [[사업-부업-파이프라인]] | 트랙 신설 (블로그 폐기 자리 승계 또는 6번째 트랙) | +| [[사업-hedgy75-인스타]] | 1인 빌더 일지 빌드 대상으로 카드뉴스 5편 노출 (마케팅 시너지) | +| [[자산-인프라-NAS-GPU]] | v1 클라우드 저장 시 Synology 활용 | +| [[기술-스택-역량]] | RN + iOS Swift 신규 스택 학습 영역 | + +## 다음 액션 + +- ✅ Day 0 차단성 액션 1~4번 검증 완료 (2026-05-23) — Lapie 1순위 유지 확정 +- ✅ @lapie_app 인스타 선점 완료 (2026-05-23 박재오 직접) +- ✅ writing-plans 스킬로 W1~W4 구체 구현 plan 작성 완료 (2026-05-23) — [[사업-Lapie-피팅앱-v0-plan]] 참고 +- ⏸ lapie.kr + lapie.io 도메인 등록 — 7월 착수 직전 +- ⏸ Pre-Task 0.2 macOS 접근 방안 결정 (Mac mini 구매 / EAS 클라우드 / Flutter 재검토) +- ⏸ 변리사 정식 상표 조사 (Class 9·42 출원 전) — 7월 착수 후 +- ⏸ 7월 착수 직전 design + plan 재검토 (5~6월 공모전 트랙 마무리 후) +- ⏸ Figma 와이어프레임 5화면 (W1 첫 주) + +## 관련 링크 / 교차참조 + +- [[사업-Lapie-피팅앱-v0-plan]] — v0 4~6주 구체 구현 plan (13 task / TDD 가능 부분 단위 테스트 / UI·카메라·합성 manual test). 양방향 +- [[정체성-Why-탐색]] — D-3 Why 검증대 통과. 외면의 결 분기. 양방향. +- [[정체성-미션과-방향성]] — 1인 비즈니스 트랙 신설 +- [[사업-부업-파이프라인]] — 트랙 추가 +- [[사업-쟁승메이드]] — 메인 브랜드와 분리 (독립 브랜드 결정) +- [[사업-hedgy75-인스타]] — 마케팅 채널 시너지 + 빌더 일지 콘텐츠 +- [[프로젝트-쟁승메이드-Co]] — Why 증거 #1 (사주 카탈로그). Lapie는 #3 +- [[사업-AI음악작곡-워크플로우]] — Why 증거 #2 (음악). Lapie는 #3 +- [[자산-인프라-NAS-GPU]] — v1 클라우드 저장 인프라 +- [[기술-스택-역량]] — RN + iOS Swift 신규 학습 +- [[프로젝트-이모추]], [[프로젝트-무진장-AI-광고제]], [[프로젝트-우리카드-AI숏폼-공모전]] — 5~6월 가용시간 점유 트랙 (Lapie 7월 착수 근거) +- [[../CLAUDE|CLAUDE.md]] — 컨텍스트 3단 템플릿 + +## 출처 / Raw + +- `raw/2026-05-23-Lapie-피팅앱-브리핑-원본.md` — 2026-05-23 Claude Code 대화 원본 (브레인스토밍 흐름 + 결정 6건 + design) + +## 변경 이력 + +- 2026-05-23: 페이지 신설. superpowers:brainstorming 스킬로 6단 결정(포지셔닝·Why 정합·브랜드·범위·스택·네이밍) 완료 후 design 승인. RN + Vision Camera 스택, 독립 브랜드 Lapie, v0 4~6주 7월 착수. D-3 Why 증거 라인 #3 편입. +- 2026-05-23: superpowers:writing-plans 스킬로 [[사업-Lapie-피팅앱-v0-plan]] 작성 완료. 13 task(Pre 2 + W1~W4 9 + W5~6 2) / Windows에서 RN iOS 빌드 불가 이슈 Pre-Task 0.2에 명시(mac 확보 / EAS 클라우드 / Flutter 재검토 3 옵션). 사용자 명시: 7월 착수 직전 재수정 가능. +- 2026-05-23: **Day 0 차단성 액션 1~4번 검증 완료** (superpowers:dispatching-parallel-agents 스킬로 4건 병렬 실행). 종합 판정 🟡 Lapie 유지 가능 but 조건부. 즉시 권장: lapie.kr + lapie.io + @lapie_app 선점, Class 9·42 출원만 가능 영역, Class 14·25 회피. 위험 신호 2건: (1) @lapieofficial = La Pie 주얼리 브랜드 → Class 14 추가 위험, (2) @uselapie 누군가 선점 = 작명 시도 흔적. 상표 결과는 KIPRIS/USPTO 자동조회 차단으로 공개 검색 한도 — 변리사 정식 조사 필수. +- 2026-05-23: **대안 3건(Wittu·Mirree·Geola) 동일 4건 검증 완료** (dispatching-parallel-agents 브랜드별 1 agent × 3 병렬). 결과: Wittu 🟡 / Mirree 🔴 / Geola 🔴. **Lapie 1순위 유지 확정** — 도메인 .kr+.io 가용 확정 우위 + 상표 위험에서 "위트(WIT/WITT)"보다 "라피"가 한국어 일반 어휘 점유 약함. 후보 #2 = Wittu (Lapie 변리사 🔴 시 대체). Mirree·Geola는 폐기(.com 선점 + 글로벌 패션 앱 인접 충돌 + 한국어 SEO/발음 모호성). spec 페이지의 "대안 후보" 섹션을 4×4 비교 매트릭스로 전면 갱신. +- 2026-05-23: 🟢 **Lapie 브랜드 확정 + @lapie_app 인스타 선점 완료** (박재오 직접). 종합 판정 🟡 조건부 → 🟢 확정. 가장 회수 어려운 자산 1순위 확보. 도메인(lapie.kr/lapie.io) 등록은 7월 착수 직전으로 미룸. Day 0 표 인스타 항목 ✅ 갱신, 즉시 권장 액션 #3 ✅, 다음 액션 섹션 진행 상태 반영. diff --git a/docs/v0-plan.md b/docs/v0-plan.md new file mode 100644 index 0000000..93a7c27 --- /dev/null +++ b/docs/v0-plan.md @@ -0,0 +1,1468 @@ +> [!takeaway] 비즈니스/방향성 관점에서의 핵심 +> [[사업-Lapie-피팅앱]] design의 **v0 4~6주 구체 구현 계획**. 7월 착수 직전 (6월 말 [[프로젝트-우리카드-AI숏폼-공모전]] 마감 후) 재수정 가능. 박재오는 RN 신규 영역이므로 모든 task는 bite-sized + TDD 가능 부분은 TDD / UI·카메라·합성은 manual test 절차 명시. 핵심 차별화 = **Task 9 자동 스케일** = v0 W3 발목 잡힐 가능성 가장 큰 곳. + +# 사업 — Lapie 피팅앱 v0 구현 계획 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** iOS 단독 RN 앱 v0 빌드 — 정면 전신 사진 등록 → 자동 마스킹(상/하/전신 3모드) → 카메라 라이브 합성(자동스케일+핀치) → 캡쳐·SNS 공유까지. 박재오 본인+아내+친구 5명이 사용 가능한 수준. + +**Architecture:** Expo bare workflow 위 React Native + TypeScript. 카메라·세그멘테이션·합성은 react-native-vision-camera + react-native-skia로 처리. iOS Vision Framework 직접 호출 부분은 Swift 네이티브 모듈로 분리. 상태는 Zustand로 최소화. + +**Tech Stack:** React Native (0.76+) / Expo (52+, bare) / TypeScript / react-native-vision-camera (v4) / react-native-skia (v1) / @shopify/react-native-skia / react-native-worklets-core / Vision Framework (Swift bridge) / Zustand / Jest (유닛) / Detox (선택, v1으로 미룸) + +**전제 조건:** +- 박재오 가용시간: 주 15~20h. 7월 1주 착수 가정. +- macOS + Xcode 15+ 필요 (현재 박재오 메인은 Windows이므로 Xcode 접근 방안 별도 확인 — 5번 액션 참고). +- Apple Developer Program 가입 ($99/yr). +- 워크스페이스: `C:\Users\jaeoh\Desktop\workspace\lapie\` 신설 (또는 macOS 경로). + +**중요:** 박재오 Windows 환경에서 RN iOS 빌드 불가. 다음 중 선택: +- (a) macOS 기기 확보 (중고 Mac mini M1 ~50만) — Day 0 결정 항목 +- (b) Expo EAS Build 클라우드 빌드 ($29/월) — Mac 없이 가능, 단 iOS 네이티브 모듈 디버깅 어려움 +- (c) 처음부터 RN 포기, **Flutter or 웹 PWA** 재검토 → design 재논의 필요 + +→ **이 plan은 (a) 또는 (b) 가정**. 7월 착수 전 결정 필요. + +--- + +## Pre-Tasks: Day 0 차단성 액션 (착수 전, 비코딩) + +### Task 0.1: 도메인·앱스토어·인스타·상표 검증 + +**Files:** 없음 (외부 검색) + +- [ ] **Step 1: 도메인 가용성 확인** + - `lapie.app` / `lapie.kr` / `lapie.io` Namecheap·Cafe24에서 검색 + - 결과 기록: [[사업-Lapie-피팅앱]] "Day 0 차단성 액션" 표에 ✅/❌ 표시 + - 모두 사용 불가 시 대안: Wittu / Mirree / Geola 순으로 재검증 + +- [ ] **Step 2: 앱스토어 동명 앱 검색** + - App Store (iOS) + Google Play (Android) 검색: "Lapie", "라피" + - 동명 패션·피팅 앱 존재 시 → 네이밍 재검토 + +- [ ] **Step 3: 인스타 핸들 확보** + - `@lapie` / `@lapie_app` / `@lapie_official` 검색 + - 가능한 핸들 1개 즉시 선점 등록 (이메일·비밀번호만 필요, 정식 운영은 W4 직전) + +- [ ] **Step 4: 상표 검색 (KIPRIS + USPTO)** + - KIPRIS (http://www.kipris.or.kr) "Lapie" 검색 — 의류·앱 류 + - USPTO (https://tmsearch.uspto.gov) "Lapie" 검색 + - 선출원 발견 시 → 네이밍 재검토 + +- [ ] **Step 5: 결과를 wiki에 반영** + - [[사업-Lapie-피팅앱]] "Day 0 차단성 액션" 표에 검증 결과 갱신 + - 충돌 발견 시 본 plan 머리의 brand name을 새 후보로 변경 + +### Task 0.2: 개발 환경 결정 + +- [ ] **Step 1: macOS 접근 방안 결정** + - (a) 중고 Mac mini M1 구매 (~50만) + - (b) EAS Build 클라우드 ($29/월 = 7월~10월 약 12만) + - (c) Flutter or PWA 재논의 (design 변경 필요) + - 결정 → [[사업-Lapie-피팅앱]] 변경 이력에 한 줄 기록 + +- [ ] **Step 2: Apple Developer Program 가입** + - https://developer.apple.com/programs/ $99/yr + - 7월 착수 직전 가입 (그 전에 가입하면 1년 카운트가 일찍 시작됨) + +--- + +## Workspace Setup + +### Task 1: RN + Expo 프로젝트 초기화 + +**Files:** +- Create: `C:/Users/jaeoh/Desktop/workspace/lapie/` (또는 mac 경로) +- Create: `lapie/package.json` (Expo CLI가 생성) +- Create: `lapie/app.json` +- Create: `lapie/tsconfig.json` + +- [ ] **Step 1: Expo 프로젝트 생성** + +```bash +cd C:/Users/jaeoh/Desktop/workspace/ +npx create-expo-app@latest lapie --template blank-typescript +cd lapie +``` + +Expected: `lapie/` 디렉터리에 `App.tsx`, `package.json`, `tsconfig.json` 생성. + +- [ ] **Step 2: Bare workflow로 전환 (네이티브 모듈 필요)** + +```bash +npx expo prebuild --platform ios +``` + +Expected: `lapie/ios/` 디렉터리 생성, CocoaPods install. (Windows에서는 prebuild 실패 가능 — mac 환경 또는 EAS에서 실행). + +- [ ] **Step 3: 필수 의존성 설치** + +```bash +npm install react-native-vision-camera @shopify/react-native-skia react-native-worklets-core zustand react-native-safe-area-context +npm install --save-dev jest @types/jest ts-jest +``` + +- [ ] **Step 4: 권한 설정 (`app.json`)** + +```json +{ + "expo": { + "name": "Lapie", + "slug": "lapie", + "ios": { + "bundleIdentifier": "com.lapie.app", + "infoPlist": { + "NSCameraUsageDescription": "옷을 카메라에 비춰 합성 미리보기에 사용합니다.", + "NSPhotoLibraryUsageDescription": "정면 사진을 등록하고 캡쳐를 저장하기 위해 사용합니다.", + "NSPhotoLibraryAddUsageDescription": "합성 결과 사진을 저장하기 위해 사용합니다." + } + } + } +} +``` + +- [ ] **Step 5: 첫 빌드 확인** + +```bash +npx expo run:ios +``` + +Expected: iOS 시뮬레이터에 기본 화면 표시. + +- [ ] **Step 6: Git 초기화 + 첫 커밋** + +```bash +cd lapie +git init +git add . +git commit -m "chore: initial Expo + RN + TS setup with vision-camera/skia deps" +``` + +--- + +## W1 — 카메라 + 세그멘테이션 PoC + +### Task 2: Vision Camera 카메라 화면 + 권한 + +**Files:** +- Create: `lapie/src/screens/CameraTestScreen.tsx` +- Modify: `lapie/App.tsx` + +- [ ] **Step 1: 카메라 권한 hook 작성** + +`lapie/src/screens/CameraTestScreen.tsx`: +```tsx +import { useEffect } from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import { Camera, useCameraDevice, useCameraPermission } from 'react-native-vision-camera'; + +export function CameraTestScreen() { + const { hasPermission, requestPermission } = useCameraPermission(); + const device = useCameraDevice('back'); + + useEffect(() => { + if (!hasPermission) requestPermission(); + }, [hasPermission, requestPermission]); + + if (!hasPermission) { + return 카메라 권한이 필요합니다; + } + if (!device) { + return 카메라 디바이스 없음; + } + + return ( + + ); +} + +const styles = StyleSheet.create({ + center: { flex: 1, justifyContent: 'center', alignItems: 'center' }, +}); +``` + +- [ ] **Step 2: App.tsx에서 호출** + +`lapie/App.tsx`: +```tsx +import { CameraTestScreen } from './src/screens/CameraTestScreen'; +export default function App() { + return ; +} +``` + +- [ ] **Step 3: 실기기 빌드 + 검증** (시뮬레이터는 카메라 없음) + +```bash +npx expo run:ios --device +``` + +Expected: 실기기에서 후방 카메라 실시간 프리뷰 표시. + +- [ ] **Step 4: 커밋** + +```bash +git add src/screens/CameraTestScreen.tsx App.tsx +git commit -m "feat: vision-camera live preview with permission flow" +``` + +### Task 3: iOS Vision Selfie Segmentation 네이티브 브릿지 + +**Files:** +- Create: `lapie/modules/segmentation/ios/SegmentationModule.swift` +- Create: `lapie/modules/segmentation/ios/SegmentationModule.m` +- Create: `lapie/src/features/segmentation/index.ts` +- Create: `lapie/src/features/segmentation/segmentImage.ts` +- Test: `lapie/src/features/segmentation/__tests__/segmentation.test.ts` + +> Vision Framework의 `VNGeneratePersonSegmentationRequest`를 RN으로 노출. 입력: 이미지 URI, 출력: 마스크 PNG URI. + +- [ ] **Step 1: Swift 모듈 작성** + +`lapie/modules/segmentation/ios/SegmentationModule.swift`: +```swift +import Foundation +import Vision +import UIKit + +@objc(SegmentationModule) +class SegmentationModule: NSObject { + + @objc static func requiresMainQueueSetup() -> Bool { false } + + @objc(segmentPerson:resolver:rejecter:) + func segmentPerson(_ imageUri: String, + resolver: @escaping RCTPromiseResolveBlock, + rejecter: @escaping RCTPromiseRejectBlock) { + guard let url = URL(string: imageUri), + let imageData = try? Data(contentsOf: url), + let uiImage = UIImage(data: imageData), + let cgImage = uiImage.cgImage else { + rejecter("E_IMG", "Image load failed", nil) + return + } + + let request = VNGeneratePersonSegmentationRequest() + request.qualityLevel = .accurate + request.outputPixelFormat = kCVPixelFormatType_OneComponent8 + + let handler = VNImageRequestHandler(cgImage: cgImage, options: [:]) + do { + try handler.perform([request]) + guard let result = request.results?.first else { + rejecter("E_NO_RESULT", "No segmentation result", nil); return + } + // PixelBuffer → PNG 파일로 저장 + let maskUri = try saveMaskPNG(result.pixelBuffer, originalSize: uiImage.size) + resolver(maskUri) + } catch { + rejecter("E_SEG", error.localizedDescription, error) + } + } + + private func saveMaskPNG(_ pixelBuffer: CVPixelBuffer, originalSize: CGSize) throws -> String { + let ciImage = CIImage(cvPixelBuffer: pixelBuffer) + let scaleX = originalSize.width / ciImage.extent.width + let scaleY = originalSize.height / ciImage.extent.height + let scaled = ciImage.transformed(by: CGAffineTransform(scaleX: scaleX, y: scaleY)) + let context = CIContext() + guard let cgImage = context.createCGImage(scaled, from: scaled.extent) else { + throw NSError(domain: "Lapie", code: 1) + } + let uiImage = UIImage(cgImage: cgImage) + let pngData = uiImage.pngData()! + let path = NSTemporaryDirectory() + "mask_\(UUID().uuidString).png" + try pngData.write(to: URL(fileURLWithPath: path)) + return "file://" + path + } +} +``` + +- [ ] **Step 2: Objective-C 브릿지** + +`lapie/modules/segmentation/ios/SegmentationModule.m`: +```objc +#import + +@interface RCT_EXTERN_MODULE(SegmentationModule, NSObject) +RCT_EXTERN_METHOD(segmentPerson:(NSString *)imageUri + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +@end +``` + +- [ ] **Step 3: TS 래퍼** + +`lapie/src/features/segmentation/index.ts`: +```ts +import { NativeModules } from 'react-native'; + +const { SegmentationModule } = NativeModules; + +export async function segmentPerson(imageUri: string): Promise { + if (!SegmentationModule?.segmentPerson) { + throw new Error('SegmentationModule not linked'); + } + return SegmentationModule.segmentPerson(imageUri); +} +``` + +- [ ] **Step 4: 유닛 테스트 (모킹)** + +`lapie/src/features/segmentation/__tests__/segmentation.test.ts`: +```ts +import { segmentPerson } from '..'; + +jest.mock('react-native', () => ({ + NativeModules: { + SegmentationModule: { + segmentPerson: jest.fn(async (uri: string) => `file://mask_for_${uri}.png`), + }, + }, +})); + +describe('segmentPerson', () => { + it('returns mask URI for input image', async () => { + const result = await segmentPerson('file://input.jpg'); + expect(result).toBe('file://mask_for_file://input.jpg.png'); + }); +}); +``` + +- [ ] **Step 5: 테스트 실행 (실패 → 통과)** + +```bash +npx jest src/features/segmentation +``` + +Expected: 1 passed (모킹된 호출 검증). + +- [ ] **Step 6: 실기기 manual test** + - Photo Library에서 정면사진 1장 선택 → `segmentPerson(uri)` 호출 + - 반환된 mask URI를 ``로 표시 + - 시각 검증: 사람 영역이 흰색, 배경이 검은색인 마스크 확인 + +- [ ] **Step 7: CocoaPods 설치 + 커밋** + +```bash +cd ios && pod install && cd .. +git add modules/segmentation src/features/segmentation +git commit -m "feat: iOS Vision person segmentation native bridge" +``` + +--- + +## W2 — 정면사진 등록 + Pose 키포인트 + 마스크 분할 + +### Task 4: iOS Vision Pose Detection 네이티브 브릿지 + +**Files:** +- Create: `lapie/modules/pose/ios/PoseModule.swift` +- Create: `lapie/modules/pose/ios/PoseModule.m` +- Create: `lapie/src/features/pose/index.ts` +- Test: `lapie/src/features/pose/__tests__/pose.test.ts` + +- [ ] **Step 1: Swift 모듈 작성 (17개 keypoint 반환)** + +`lapie/modules/pose/ios/PoseModule.swift`: +```swift +import Foundation +import Vision +import UIKit + +@objc(PoseModule) +class PoseModule: NSObject { + + @objc static func requiresMainQueueSetup() -> Bool { false } + + @objc(detectPose:resolver:rejecter:) + func detectPose(_ imageUri: String, + resolver: @escaping RCTPromiseResolveBlock, + rejecter: @escaping RCTPromiseRejectBlock) { + guard let url = URL(string: imageUri), + let data = try? Data(contentsOf: url), + let uiImage = UIImage(data: data), + let cgImage = uiImage.cgImage else { + rejecter("E_IMG", "Image load failed", nil); return + } + + let request = VNDetectHumanBodyPoseRequest() + let handler = VNImageRequestHandler(cgImage: cgImage, options: [:]) + do { + try handler.perform([request]) + guard let observation = request.results?.first else { + rejecter("E_NO_POSE", "No pose detected", nil); return + } + let points = try observation.recognizedPoints(.all) + let imageW = CGFloat(cgImage.width) + let imageH = CGFloat(cgImage.height) + var result: [String: [String: Any]] = [:] + for (jointName, point) in points where point.confidence > 0.3 { + // Vision 좌표: 좌하단 원점, normalized (0~1). UIKit으로 변환. + result[jointName.rawValue.rawValue] = [ + "x": point.location.x * imageW, + "y": (1 - point.location.y) * imageH, + "confidence": point.confidence, + ] + } + resolver(result) + } catch { + rejecter("E_POSE", error.localizedDescription, error) + } + } +} +``` + +- [ ] **Step 2: Obj-C 브릿지** + +`lapie/modules/pose/ios/PoseModule.m`: +```objc +#import + +@interface RCT_EXTERN_MODULE(PoseModule, NSObject) +RCT_EXTERN_METHOD(detectPose:(NSString *)imageUri + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +@end +``` + +- [ ] **Step 3: TS 래퍼 + 타입** + +`lapie/src/features/pose/index.ts`: +```ts +import { NativeModules } from 'react-native'; + +const { PoseModule } = NativeModules; + +export interface Keypoint { + x: number; + y: number; + confidence: number; +} + +export type Joint = + | 'left_shoulder_joint' | 'right_shoulder_joint' + | 'left_hip_joint' | 'right_hip_joint' + | 'left_ankle_joint' | 'right_ankle_joint' + | 'neck_1_joint' | 'root'; + +export type PoseResult = Partial>; + +export async function detectPose(imageUri: string): Promise { + if (!PoseModule?.detectPose) throw new Error('PoseModule not linked'); + return PoseModule.detectPose(imageUri); +} +``` + +- [ ] **Step 4: 유닛 테스트 (모킹)** + +`lapie/src/features/pose/__tests__/pose.test.ts`: +```ts +import { detectPose } from '..'; + +jest.mock('react-native', () => ({ + NativeModules: { + PoseModule: { + detectPose: jest.fn(async () => ({ + left_shoulder_joint: { x: 100, y: 200, confidence: 0.9 }, + right_shoulder_joint: { x: 300, y: 200, confidence: 0.9 }, + })), + }, + }, +})); + +describe('detectPose', () => { + it('returns keypoint map', async () => { + const result = await detectPose('file://photo.jpg'); + expect(result.left_shoulder_joint?.x).toBe(100); + expect(result.right_shoulder_joint?.x).toBe(300); + }); +}); +``` + +- [ ] **Step 5: 실기기 manual test** + - 정면사진 1장 입력 → 어깨·골반·발목 keypoint가 사진 위 정확한 위치에 표시되는지 시각 검증 + - 임시 화면 `PoseDebugScreen.tsx`에 keypoint를 점으로 overlay + +- [ ] **Step 6: 커밋** + +```bash +git add modules/pose src/features/pose +git commit -m "feat: iOS Vision human body pose native bridge (17 keypoints)" +``` + +### Task 5: 정면사진 등록 화면 + 자세 가이드 오버레이 + +**Files:** +- Create: `lapie/src/screens/PhotoCaptureScreen.tsx` +- Create: `lapie/src/features/photoValidation/validatePose.ts` +- Test: `lapie/src/features/photoValidation/__tests__/validatePose.test.ts` + +- [ ] **Step 1: 자세 검증 함수 (TDD)** + +`lapie/src/features/photoValidation/__tests__/validatePose.test.ts`: +```ts +import { validatePose } from '../validatePose'; + +describe('validatePose', () => { + it('passes when all required joints present with high confidence', () => { + const pose = { + left_shoulder_joint: { x: 100, y: 200, confidence: 0.9 }, + right_shoulder_joint: { x: 300, y: 200, confidence: 0.9 }, + left_hip_joint: { x: 120, y: 400, confidence: 0.85 }, + right_hip_joint: { x: 280, y: 400, confidence: 0.85 }, + left_ankle_joint: { x: 130, y: 700, confidence: 0.7 }, + right_ankle_joint: { x: 270, y: 700, confidence: 0.7 }, + }; + const result = validatePose(pose); + expect(result.score).toBeGreaterThanOrEqual(80); + expect(result.passed).toBe(true); + }); + + it('fails when shoulders missing', () => { + const pose = { + left_hip_joint: { x: 120, y: 400, confidence: 0.85 }, + right_hip_joint: { x: 280, y: 400, confidence: 0.85 }, + }; + const result = validatePose(pose); + expect(result.passed).toBe(false); + expect(result.reasons).toContain('어깨가 보이지 않습니다'); + }); + + it('fails when body tilted (shoulders y diff > 30px)', () => { + const pose = { + left_shoulder_joint: { x: 100, y: 200, confidence: 0.9 }, + right_shoulder_joint: { x: 300, y: 250, confidence: 0.9 }, + left_hip_joint: { x: 120, y: 400, confidence: 0.85 }, + right_hip_joint: { x: 280, y: 400, confidence: 0.85 }, + left_ankle_joint: { x: 130, y: 700, confidence: 0.7 }, + right_ankle_joint: { x: 270, y: 700, confidence: 0.7 }, + }; + const result = validatePose(pose); + expect(result.passed).toBe(false); + expect(result.reasons).toContain('몸이 기울어져 있습니다'); + }); +}); +``` + +- [ ] **Step 2: 실행 → 실패 확인** + +```bash +npx jest src/features/photoValidation +``` + +Expected: FAIL — validatePose not defined. + +- [ ] **Step 3: 구현** + +`lapie/src/features/photoValidation/validatePose.ts`: +```ts +import type { PoseResult } from '../pose'; + +export interface ValidationResult { + score: number; + passed: boolean; + reasons: string[]; +} + +const REQUIRED = [ + 'left_shoulder_joint', 'right_shoulder_joint', + 'left_hip_joint', 'right_hip_joint', + 'left_ankle_joint', 'right_ankle_joint', +] as const; + +export function validatePose(pose: PoseResult): ValidationResult { + const reasons: string[] = []; + + for (const joint of REQUIRED) { + if (!pose[joint] || pose[joint]!.confidence < 0.5) { + const label = joint.includes('shoulder') ? '어깨' + : joint.includes('hip') ? '골반' + : '발목'; + const msg = `${label}이 보이지 않습니다`; + if (!reasons.includes(msg)) reasons.push(msg); + } + } + + if (pose.left_shoulder_joint && pose.right_shoulder_joint) { + const yDiff = Math.abs(pose.left_shoulder_joint.y - pose.right_shoulder_joint.y); + if (yDiff > 30) reasons.push('몸이 기울어져 있습니다'); + } + + const score = Math.max(0, 100 - reasons.length * 25); + return { score, passed: score >= 80, reasons }; +} +``` + +- [ ] **Step 4: 테스트 통과 확인** + +```bash +npx jest src/features/photoValidation +``` + +Expected: 3 passed. + +- [ ] **Step 5: 사진 등록 화면 (UI)** + +`lapie/src/screens/PhotoCaptureScreen.tsx`: +```tsx +import { useState } from 'react'; +import { View, Text, Button, Image, StyleSheet } from 'react-native'; +import * as ImagePicker from 'expo-image-picker'; +import { detectPose } from '../features/pose'; +import { validatePose, ValidationResult } from '../features/photoValidation/validatePose'; + +export function PhotoCaptureScreen({ onPhotoReady }: { onPhotoReady: (uri: string) => void }) { + const [photoUri, setPhotoUri] = useState(null); + const [validation, setValidation] = useState(null); + + async function pickAndValidate() { + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ImagePicker.MediaTypeOptions.Images, + allowsEditing: false, + quality: 0.8, + }); + if (result.canceled) return; + const uri = result.assets[0].uri; + setPhotoUri(uri); + const pose = await detectPose(uri); + const v = validatePose(pose); + setValidation(v); + } + + return ( + + 정면 전신 사진 + + • 어깨·골반·발목이 모두 보이도록{'\n'} + • 정면을 응시{'\n'} + • 단색 배경 권장 + +