---
name: apps-in-toss
description: >
  앱인토스(Apps in Toss) 미니앱을 React Native(Granite 프레임워크)로 개발할 때 사용하세요.
  다음 상황에서 이 스킬을 활성화하세요:
  - 앱인토스 RN 미니앱 프로젝트 생성/스캐폴딩, Granite 프레임워크 설정
  - granite.config.ts 작성, appName/권한/브랜드 설정
  - 파일 기반 라우팅으로 새 화면 추가, 딥링크 intoss:// 스킴 처리
  - TDS React Native 컴포넌트(AmountTop, Asset, Button, Txt 등) 사용
  - 카메라, 위치, 연락처, 사진첩 등 네이티브 권한 연동
  - 인앱 결제(IAP), 인앱 광고(리워드/전면), 토스 로그인/인증 구현
  - 앱 빌드(.ait 번들), 샌드박스 연결, 로컬 개발 서버 세팅
  - adb 포트 포워딩, iOS 실기기 연결, Metro 서버 트러블슈팅
---

# 앱인토스 React Native 스킬

---
 
## 📋 프로젝트 컨텍스트 — 토스 캘린더 미니앱
 
이 스킬에는 **숙소 관리용 토스 캘린더 미니앱** 전체 설계 문서가 포함되어 있어요.
아래 파일들을 상황에 맞게 읽어 코드를 생성하세요.
 
### 파일 목록 및 언제 읽을지
 
| 파일 | 내용 | 읽어야 할 때 |
|---|---|---|
| `references/project/TASK-LIST.md` | 25개 순차 태스크 + 진행 상태 | **항상 먼저 읽기** — 다음 할 일 확인 |
| `references/project/PRD.md` | 기능 명세, 권한 체계, 데이터 모델 | "무엇을 만들어야 하나" 가 궁금할 때 |
| `references/project/DEV-DESIGN.md` | 코드 설계, 훅 구조, 파일 구조 | 실제 코드를 작성할 때 |
| `references/project/UI-UX.md` | 화면 명세, 와이어프레임, UX 원칙 | UI 컴포넌트를 만들 때 |
 
### 순차 태스크 처리 프로토콜
 
사용자가 "다음 태스크", "다음 할 일", "T01 해줘" 등을 요청하면:
 
```
Step 1 — TASK-LIST.md 읽기
  references/project/TASK-LIST.md 를 열어
  [ ] 상태인 첫 번째 태스크를 찾는다.
 
Step 2 — 해당 태스크의 [참고] 파일 섹션만 읽기
  태스크에 명시된 파일과 섹션만 열어 읽는다.
  (전체 파일을 다 읽지 말 것 — 토큰 낭비)
  예) T05의 참고: DEV-DESIGN.md → "4.2 핵심 훅 useRoomAuth" 섹션만
 
Step 3 — 코드 생성
  MCP로 TDS/SDK 스펙 확인 후 코드 작성.
  산출물 파일명을 정확히 명시.
 
Step 4 — 완료 안내
  "✅ T05 완료. TASK-LIST.md의 T05를 [x]로 표시하고
   다음은 T06 (authService)입니다." 형식으로 안내.
```
 
### 태스크 간 의존 관계 (반드시 지킬 것)
 
```
T01 (스캐폴딩) → T02 (타입) → T05 (useRoomAuth) → T06 (authService) → T08 (join 화면)
                              ↓
                            T07 (index 화면)
 
T03 (Firebase) → T10 (Firestore) → T11 (useSchedules) → T13 (캘린더 메인)
                                 → T15 (useChecklist) → T17 (체크리스트 화면)
                                 → T18 (useLedger)    → T19 (정산 화면)
```
 
선행 태스크가 완료되지 않았으면 해당 태스크를 진행하지 않고
"T05는 T02, T03이 완료된 후 진행할 수 있어요." 라고 안내하세요.
 
---

## Goal

Granite 프레임워크 기반의 앱인토스 미니앱 코드를 정확하게 생성하고,
일반 RN/Expo 패턴을 앱인토스 전용 패턴으로 대체하는 것.

---

---

## ⚡ 코드 생성 전 필수 프로토콜 (환각 방지)

TDS 컴포넌트 Props 또는 SDK 함수를 포함한 코드를 작성하기 전에
**반드시 아래 순서로 문서를 먼저 확인하세요**. 기억에 의존한 코드 생성은 금지입니다.

```
Step 1 — MCP tools로 실시간 검색 (항상 최신, 1순위)

  [앱인토스 SDK / 프레임워크 / 라우팅]
    search_docs("검색어")          → 관련 문서 목록 + ID 반환
    get_doc(id)                    → 해당 문서 전체 내용 조회

  [TDS React Native 컴포넌트 Props]
    search_tds_rn_docs("검색어")   → 관련 TDS RN 문서 목록 + ID 반환
    get_tds_rn_doc(id)             → 해당 컴포넌트 전체 Props/예제 조회

  [예제 코드]
    list_examples()                → 전체 예제 목록 조회
    get_example(id)                → 특정 예제 전체 코드 조회

  사용 패턴:
    search_docs("camera") → get_doc("카메라-관련-ID")
    search_tds_rn_docs("AmountTop") → get_tds_rn_doc("amounttop-ID")

Step 2 — 로컬 문서 (MCP 실패 시 fallback)
  references/llms.txt          → SDK/프레임워크 API 링크
  references/tds-react-native.txt   → TDS RN 컴포넌트 전체

Step 3 - 로컬 문서 (Step 2 - 로컬 문서 조회 실패 시)
  references/llms-full.txt          → SDK/프레임워크 API 전체 코드

Step 4 — 모른다고 명시 (셋 다 실패 시)
  코드 생성 중단 후:
  "⚠️ [컴포넌트/API명]의 정확한 스펙은 문서 확인이 필요합니다.
   → https://tossmini-docs.toss.im/tds-react-native/"
```

### 특히 주의할 타입들

| 항목 | 왜 위험한가 |
|---|---|
| `AmountTop` `topPadding` | `number`가 아닌 `0 \| 64 \| 80` union만 허용 |
| `AmountTop` `bottomPadding` | `0 \| 16 \| 24` union만 허용 |
| `AmountTop` `horizontalPadding` | `0 \| 24` union만 허용 |
| `Asset.frameShape` | 임의 문자열 금지, 열거값만 허용 |
| `permissions` 배열 | `'microphone'` 등 미지원 권한 넣으면 런타임 에러 |
| SDK 반환값 필드명 | `result.token` vs `result.accessToken` 등 — 반드시 확인 |


## 핵심 원칙 (반드시 지킬 것)

1. `npm create granite-app` 방식만 사용 — Expo, RN CLI 절대 금지
2. `pages/` 파일 구조 = 딥링크 스킴 — 별도 라우터 설정 없음
3. UI는 `@toss/tds-react-native` 우선 — 기본 RN 컴포넌트 대체
4. 네이티브 기능은 앱인토스 SDK — 서드파티 RN 라이브러리 대체
5. SDK 2.x만 — 1.x API 언급 금지

---

## 패턴 A — 프로젝트 초기 설정

### 스캐폴딩 + 패키지 설치

```bash
npm create granite-app
# → 앱 이름: my-toss-app (kebab-case)
# → 도구: biome 권장

cd my-toss-app && npm install
npm install @apps-in-toss/framework
npm install @toss/tds-react-native

npx ait init
# → 프레임워크: React Native
# → appName: my-toss-app (콘솔 등록명과 반드시 동일)
```

### granite.config.ts 완성 예시

```typescript
import { appsInToss } from '@apps-in-toss/framework/plugins';
import { defineConfig } from '@granite-js/react-native/config';

export default defineConfig({
  appName: 'my-toss-app',
  plugins: [
    appsInToss({
      brand: {
        displayName: '내 앱 이름',
        primaryColor: '#3182F6',
        icon: 'https://...콘솔에서복사한아이콘URL...',
      },
      permissions: [
        // 사용하는 권한만: 'camera' | 'album' | 'location' | 'contacts' | 'clipboard'
      ],
    }),
  ],
});
```

---

## 패턴 B — 화면(페이지) 작성

### 기본 페이지 구조

```typescript
// pages/home.tsx → intoss://my-toss-app/home
import { createRoute } from '@granite-js/react-native';
import { View, StyleSheet } from 'react-native';
import { Txt, Button } from '@toss/tds-react-native';

export const Route = createRoute('/home', {
  component: HomePage,
});

function HomePage() {
  const navigation = Route.useNavigation();
  return (
    <View style={styles.container}>
      <Txt typography="t1">환영합니다</Txt>
      <Button size="large" onPress={() => navigation.navigate('/detail')}>
        상세 보기
      </Button>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 24 },
});
```

### 동적 라우트 + 파라미터

```typescript
// pages/item/[id].tsx → intoss://my-toss-app/item/:id
import { createRoute } from '@granite-js/react-native';

export const Route = createRoute('/item/:id', {
  component: ItemDetailPage,
});

function ItemDetailPage() {
  const { id } = Route.useParams();
  return ( /* id를 사용하는 UI */ );
}
```

### 화면 이동 패턴

```typescript
navigation.navigate('/detail');            // 기본 이동
navigation.navigate('/item/' + itemId);    // 파라미터 포함
navigation.goBack();                       // 뒤로 가기
navigation.reset({ index: 0, routes: [{ name: '/' }] }); // 스택 초기화
```

---

## 패턴 C — TDS 컴포넌트 (검수 필수)

```typescript
import { AmountTop, Asset, Button, Txt } from '@toss/tds-react-native';

// 금액 표시
<AmountTop subTitle="보낼 금액" title="50,000원" />
<AmountTop
  subTitle={<AmountTop.SubTitle onPress={handleChange}>토스뱅크</AmountTop.SubTitle>}
  title="1,234,567원"
  button={<Button size="medium" onPress={handleRefresh}>새로고침</Button>}
/>

// 이미지 / 아이콘 / Lottie
<Asset.Image source={{ uri: '...' }} frameShape={Asset.frameShape.SquareLarge} />
<Asset.Icon name="heart-line" color="blue" />
<Asset.Lottie src="https://static.toss.im/lotties-common/alarm-spot.json" loop
  frameShape={Asset.frameShape.CleanW60} />
```

> ⚠️ TDS는 샌드박스 앱에서만 테스트 가능. 로컬 브라우저에서는 동작하지 않음.

---

## 패턴 D — 네이티브 기능 (SDK)

```typescript
import { camera, album, location, clipboard, haptic } from '@apps-in-toss/framework';

// 카메라 촬영 (permissions: ['camera'] 필요)
const photo = await camera.takePhoto();

// 사진첩 선택 (permissions: ['album'] 필요)
const photos = await album.pickPhotos({ maxCount: 5 });

// 위치 일회성 (permissions: ['location'] 필요)
const pos = await location.getCurrentPosition();

// 위치 실시간 추적
const watchId = location.watchPosition((pos) => { /* ... */ });
location.clearWatch(watchId);

// 클립보드 (permissions: ['clipboard'] 필요)
await clipboard.setString('복사할 텍스트');

// 햅틱
haptic.impact('medium');
haptic.notification('success'); // 'success' | 'error' | 'warning'
```

---

## 패턴 E — 수익화

```typescript
import { TossLogin, InAppPurchase, InAppAd } from '@apps-in-toss/framework';

// 토스 로그인
const result = await TossLogin.login();
if (result.success) { const { accessToken, userId } = result; }

// 인앱 결제
const products = await InAppPurchase.getProducts(['product_id']);
const purchase = await InAppPurchase.purchase('product_id');

// 리워드 광고
const ad = await InAppAd.showRewardedAd({ adUnitId: 'your-ad-unit-id' });
if (ad.rewarded) { /* 보상 지급 */ }

// 전면 광고
await InAppAd.showInterstitialAd({ adUnitId: 'your-ad-unit-id' });
```

---

## 패턴 F — 공유

```typescript
import { share } from '@apps-in-toss/framework';
await share.shareText('공유할 텍스트');
await share.shareUrl('https://...');
```

---

## Few-Shot — ❌ 금지 vs ✅ 올바른 패턴

```typescript
// ❌ 절대 금지: React Navigation + RN 기본 컴포넌트
import { useNavigation } from '@react-navigation/native'; // ❌
import { Text, TouchableOpacity } from 'react-native';    // ❌
const nav = useNavigation();
nav.navigate('Detail');  // ❌

// ✅ 올바른 방법: Granite 라우팅 + TDS
import { createRoute } from '@granite-js/react-native';   // ✅
import { Txt, Button } from '@toss/tds-react-native';      // ✅
const navigation = Route.useNavigation();
navigation.navigate('/detail');  // ✅
```

```typescript
// ❌ 절대 금지: 서드파티 네이티브 라이브러리
import Geolocation from '@react-native-community/geolocation'; // ❌
import { RNCamera } from 'react-native-camera';                // ❌

// ✅ 올바른 방법: 앱인토스 SDK
import { location, camera } from '@apps-in-toss/framework';    // ✅
```

---

## 트러블슈팅 가이드

| 에러 / 상황 | 원인 | 해결 |
|---|---|---|
| `플러그인 옵션이 올바르지 않습니다` | icon 필드 문제 | `icon: ''` 임시 처리 |
| `too many open files` | node_modules 오염 | `rm -rf node_modules && npm install` |
| Android 연결 안 됨 | adb 포트 미설정 | `adb reverse tcp:8081 tcp:8081` + `tcp:5173 tcp:5173` |
| iOS 실기기 연결 안 됨 | 네트워크/권한 | 동일 와이파이 + 로컬 네트워크 권한 허용 |
| `잠시 문제가 생겼어요` | 서버 연결 끊김 | adb 재연결 또는 `npm run dev` 재시작 |
| TDS 컴포넌트 안 보임 | 브라우저 미지원 | 샌드박스 앱에서 테스트 |
| 네트워크 인스펙터 먹통 | 다중 인스턴스 | 앱 종료 → 서버 재시작 → 앱 재시작 |
| SDK 1.x 업로드 불가 | 구버전 SDK | SDK 2.x 마이그레이션 필수 |
| PC웹 Not Found (8081) | 정상 동작 | 샌드박스 앱으로만 접근 가능 |

---

## 참고 문서 (우선순위 순)

| 순위 | 방법 | MCP Tool | 용도 |
|---|---|---|---|
| 1 | **MCP 실시간** | `search_docs` + `get_doc` | 앱인토스 SDK/프레임워크 API |
| 1 | **MCP 실시간** | `search_tds_rn_docs` + `get_tds_rn_doc` | TDS RN 컴포넌트 Props |
| 1 | **MCP 실시간** | `list_examples` + `get_example` | 예제 코드 전체 조회 |
| 2 | **로컬 fallback** | `references/llms.txt` | MCP 실패 시 SDK 문서 |
| 2 | **로컬 fallback** | `references/tds-react-native.txt` | MCP 실패 시 TDS 문서 |
| 3 | **로컬 fallback** | `references/llms-full.txt` | 로컬 fallback 실패 시 SDK 전체 상세문서 |
| 4 | **환경 세팅** | `references/setup.md` | 트러블슈팅 |

