diff --git a/README.md b/README.md
index bd0117a..e8cd5fe 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,7 @@ EXPO_PUBLIC_MOCK_API_FAIL_ENDPOINTS=playlist.detail npm run web
- [비개발자용 Codex 개발 가이드](docs/codex/NON_DEVELOPER_CODEX_GUIDE.md)
- [Codex 요청 프롬프트 모음](docs/codex/CODEX_PROMPTS.md)
- [UI 피드백 루프 운영 문서](docs/codex/UI_FEEDBACK_LOOP.md)
+- [개발용 테스트 매니저](docs/codex/TEST_MANAGER.md)
- [PR 전용 개발 흐름](docs/codex/PR_ONLY_WORKFLOW.md)
- [Mock Server 안내](src/mock-server/README.md)
diff --git a/docs/README.md b/docs/README.md
index 6088f0a..bfed5ad 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -18,6 +18,7 @@ Soundlog 문서는 목적별로 관리합니다. 공모전 기획, RN 프론트
- [비개발자용 Codex 개발 가이드](codex/NON_DEVELOPER_CODEX_GUIDE.md): Codex에게 개발을 맡기는 방식
- [Codex 요청 프롬프트 모음](codex/CODEX_PROMPTS.md): 바로 복사해서 쓸 수 있는 요청문
- [UI 피드백 루프 운영 문서](codex/UI_FEEDBACK_LOOP.md): 자연어 UI 수정 요청을 계획-리뷰-구현-리뷰 루프로 처리하는 방식
+- [개발용 테스트 매니저](codex/TEST_MANAGER.md): 페이지 이동, mock API, seed 데이터 검수를 위한 개발 도구
- [PR 전용 개발 흐름](codex/PR_ONLY_WORKFLOW.md): `main` 직접 push를 막고 PR로만 병합하는 방식
## Mock API
diff --git a/docs/codex/TEST_MANAGER.md b/docs/codex/TEST_MANAGER.md
new file mode 100644
index 0000000..80859d8
--- /dev/null
+++ b/docs/codex/TEST_MANAGER.md
@@ -0,0 +1,72 @@
+# Soundlog 테스트 매니저 구현 계획
+
+## 목적
+
+개발 모드에서 비개발자와 팀원이 앱의 주요 페이지, 조건문, 로딩/실패 상태, 로컬 데이터 상태를 직접 조작하며 검수할 수 있게 한다.
+
+## 검수 결과
+
+- 온보딩 완료 후 홈 필터 기본값이 뒤집혀 있다.
+ - 현재: `preferredMoods[0]` -> 상단 필터, `travelStyles[0]` -> 무드 필터
+ - 기대: 상단 필터는 추천 범위이므로 `전체`, 무드 필터는 `preferredMoods[0]`
+- Mock API 실패/지연은 환경변수로만 설정되어 앱 실행 중 상태 전환 테스트가 어렵다.
+- Moment Log, Recap, Library, Player, Session 상태를 한 화면에서 빠르게 seed/clear할 방법이 없다.
+- 화면 이동 테스트가 탭/카드 탐색에 의존해 QA 속도가 느리다.
+
+## 구현 범위
+
+1. `DevTestManager` 플로팅 버튼
+ - `__DEV__`에서만 렌더링한다.
+ - 루트 provider 하위에 붙여 모든 화면에서 접근 가능하게 한다.
+ - 좌측 하단 기본 위치를 사용하고 드래그 이동을 지원한다.
+ - 탭바와 미니 플레이어를 완전히 대체하지 않도록 작은 원형 버튼으로 시작한다.
+
+2. 테스트 매니저 패널
+ - 페이지 이동: 홈, 온보딩, 플레이리스트 상세, Recap 리스트, Recap 공유, 보관함, 마이, 카메라
+ - 추천 조건: 홈 상단 필터, 무드 필터, 여행 모드
+ - 위치/세션: 서울/부산 위치 seed, 위치 거부/불가 상태, 여행 시작/종료/리셋
+ - 데이터 seed: 샘플 Moment Log, 보관함 좋아요/저장, 현재 재생곡
+ - 데이터 clear: Moment Log, 보관함, 추천 이벤트, 플레이어
+ - Mock API: 정상, 느림, 전체 실패, endpoint별 실패
+
+3. Runtime Mock API 상태
+ - `src/store/devToolsStore.ts`에 mock delay/fail 상태를 저장한다.
+ - `src/mock-server/delay.ts`가 환경변수보다 runtime 설정을 우선 참고한다.
+ - Query cache를 invalidate해서 변경 상태가 즉시 반영되게 한다.
+ - 실패/지연 설정은 개발 세션용 상태로 두고 영속 저장하지 않는다.
+
+4. 미완성/기획 불일치 보정
+ - 온보딩 완료 시 상단 필터는 `전체`, 무드 필터는 첫 선호 무드로 설정한다.
+ - 홈 필터와 무드 필터 개념을 계속 분리한다.
+
+## 주요 파일
+
+- `src/components/dev/DevTestManager.tsx`
+- `src/store/devToolsStore.ts`
+- `src/mock-server/delay.ts`
+- `src/providers/AppProviders.tsx`
+- `src/store/playerStore.ts`
+- `src/store/libraryStore.ts`
+- `src/store/momentLogStore.ts`
+- `src/components/onboarding/OnboardingScreen.tsx`
+- `docs/codex/TEST_MANAGER.md`
+
+## 검증
+
+- TypeScript: `tsc --noEmit`
+- diff whitespace: `git diff --check`
+- Runtime hook: mock API 정상/느림/실패 전환
+- UI: 웹에서 플로팅 버튼이 이동 가능하고 패널이 열린다.
+
+## 위험과 대응
+
+- 테스트 매니저가 실제 배포 UI에 노출될 위험: `__DEV__` guard로 제한한다.
+- 패널이 화면을 가릴 위험: Modal 패널로 열고 닫기 버튼을 제공한다.
+- Mock 실패 상태가 남아 검수를 방해할 위험: `정상` 버튼으로 모든 runtime mock 설정을 초기화한다.
+- Store 경계를 깨뜨릴 위험: seed/clear는 store action을 통해 수행하고 컴포넌트에서 배열을 직접 변형하지 않는다.
+- Query 결과가 이전 상태로 남을 위험: mock 상태 변경 후 React Query cache를 invalidate한다.
+
+## 계획 리뷰 반영
+
+- Claude CLI 호출은 응답 없이 장시간 대기해 중단했고, `soundlog-ui-reviewer` 체크리스트로 자체 리뷰를 수행했다.
+- 리뷰 결과 runtime mock 변경 후 cache invalidation, `__DEV__` guard, store action 경계, 온보딩 필터 불일치 수정이 필요하다고 판단했다.
diff --git a/package-lock.json b/package-lock.json
index 0fb2694..412d5d8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,19 +11,19 @@
"@expo/vector-icons": "^15.1.1",
"@react-native-async-storage/async-storage": "2.2.0",
"@tanstack/react-query": "^5.100.14",
- "expo": "~56.0.4",
+ "expo": "~56.0.8",
"expo-camera": "~56.0.7",
"expo-constants": "~56.0.15",
- "expo-dev-client": "~56.0.15",
+ "expo-dev-client": "~56.0.18",
"expo-file-system": "~56.0.7",
"expo-font": "~56.0.5",
"expo-image": "~56.0.9",
"expo-linear-gradient": "~56.0.4",
- "expo-linking": "~56.0.11",
- "expo-location": "~56.0.13",
+ "expo-linking": "~56.0.13",
+ "expo-location": "~56.0.15",
"expo-media-library": "~56.0.6",
- "expo-router": "^56.2.6",
- "expo-sharing": "~56.0.13",
+ "expo-router": "~56.2.8",
+ "expo-sharing": "~56.0.15",
"expo-status-bar": "~56.0.4",
"nativewind": "^4.2.4",
"react": "19.2.3",
@@ -32,7 +32,7 @@
"react-native-reanimated": "4.3.1",
"react-native-safe-area-context": "~5.7.0",
"react-native-screens": "4.25.2",
- "react-native-view-shot": "4.0.3",
+ "react-native-view-shot": "5.1.0",
"react-native-web": "^0.21.2",
"react-native-worklets": "0.8.3",
"zustand": "^5.0.13"
@@ -1347,9 +1347,9 @@
"license": "MIT"
},
"node_modules/@expo/fingerprint": {
- "version": "0.19.2",
- "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.19.2.tgz",
- "integrity": "sha512-+/cBrRHiHmldvT8ZPrrHobAOMTUTzOq6Qpr1YLSoIg0J9hbEkJOg9vUvpxiLNWSQY0eKtVTvMO03EIdPC2aQdQ==",
+ "version": "0.19.3",
+ "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.19.3.tgz",
+ "integrity": "sha512-w9Au2IVrtc0Ct+WRa05DVHGNHXYq6VyPZWuFP+5x055OeZ5q6k5Yg+aJ1gfShmjdOhDftRcsvmWmTdTZlWaTZw==",
"license": "MIT",
"dependencies": {
"@expo/env": "^2.3.0",
@@ -1384,12 +1384,12 @@
}
},
"node_modules/@expo/inline-modules": {
- "version": "0.0.9",
- "resolved": "https://registry.npmjs.org/@expo/inline-modules/-/inline-modules-0.0.9.tgz",
- "integrity": "sha512-otMUXI4mOjytbe9OQ3i5X4SV0LP1GpzqLdr9+rdxUc1b0FjdvbTM/GkcbrwY4pU0fGSK0qFqX+jgSieyi+XbUA==",
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/@expo/inline-modules/-/inline-modules-0.0.10.tgz",
+ "integrity": "sha512-DKEfq877UTAmL/gOT5aFhlLNDg/EVmTSca7JQMKDGR6mjFSAcrmQf4GJNsi6ztiaqj6cTnIfoSz0hAYdnr6RJQ==",
"license": "MIT",
"dependencies": {
- "@expo/config-plugins": "~56.0.7"
+ "@expo/config-plugins": "~56.0.8"
}
},
"node_modules/@expo/json-file": {
@@ -1403,12 +1403,12 @@
}
},
"node_modules/@expo/local-build-cache-provider": {
- "version": "56.0.7",
- "resolved": "https://registry.npmjs.org/@expo/local-build-cache-provider/-/local-build-cache-provider-56.0.7.tgz",
- "integrity": "sha512-GedmHPUQeLKbRZNzxZ4ZiN7NKQw65MSOMMnIqJnbXySZYYeBWg5TMuCzafE0Pt0Tsd2vmp2F7OPpsgAFGFoaBw==",
+ "version": "56.0.8",
+ "resolved": "https://registry.npmjs.org/@expo/local-build-cache-provider/-/local-build-cache-provider-56.0.8.tgz",
+ "integrity": "sha512-UsuXwpNi57MNhzZ3be4XThc8xW6nzk3Wu37s1+2qcfZGeJcMLKDFfwO6n8YXeIiGlCsOi0Ee1rsTdgjrKt/YJQ==",
"license": "MIT",
"dependencies": {
- "@expo/config": "~56.0.8",
+ "@expo/config": "~56.0.9",
"chalk": "^4.1.2"
}
},
@@ -1451,6 +1451,73 @@
"metro-transform-worker": "0.84.4"
}
},
+ "node_modules/@expo/metro-config": {
+ "version": "56.0.13",
+ "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-56.0.13.tgz",
+ "integrity": "sha512-OPyNYiex/6Ms8zT2POdIZsLhcAZYk7O+yJvpz5uG/4QRA7aiESfCy1I+0YHewMlR4P1YQeyxIrfTurs6m9xfZA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.20.0",
+ "@babel/core": "^7.20.0",
+ "@babel/generator": "^7.20.5",
+ "@expo/config": "~56.0.9",
+ "@expo/env": "~2.3.0",
+ "@expo/json-file": "~10.2.0",
+ "@expo/metro": "~56.0.0",
+ "@expo/require-utils": "^56.1.3",
+ "@expo/spawn-async": "^1.8.0",
+ "@jridgewell/gen-mapping": "^0.3.13",
+ "@jridgewell/remapping": "^2.3.5",
+ "@jridgewell/sourcemap-codec": "^1.5.5",
+ "browserslist": "^4.25.0",
+ "chalk": "^4.1.0",
+ "debug": "^4.3.2",
+ "getenv": "^2.0.0",
+ "glob": "^13.0.0",
+ "hermes-parser": "^0.33.3",
+ "jsc-safe-url": "^0.2.4",
+ "lightningcss": "^1.30.1",
+ "msgpackr": "^2.0.1",
+ "picomatch": "^4.0.4",
+ "postcss": "^8.5.14",
+ "resolve-from": "^5.0.0"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ },
+ "peerDependenciesMeta": {
+ "expo": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@expo/metro-config/node_modules/hermes-estree": {
+ "version": "0.33.3",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz",
+ "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==",
+ "license": "MIT"
+ },
+ "node_modules/@expo/metro-config/node_modules/hermes-parser": {
+ "version": "0.33.3",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz",
+ "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==",
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.33.3"
+ }
+ },
+ "node_modules/@expo/metro-config/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/@expo/metro-file-map": {
"version": "56.0.3",
"resolved": "https://registry.npmjs.org/@expo/metro-file-map/-/metro-file-map-56.0.3.tgz",
@@ -1465,6 +1532,31 @@
"walker": "^1.0.8"
}
},
+ "node_modules/@expo/metro-runtime": {
+ "version": "56.0.13",
+ "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-56.0.13.tgz",
+ "integrity": "sha512-aMaFa/RPYm2iQoyYOB5q8AxDmWvf4E2yFbZ6rmBIQWaIPDdixGVUlLQeV8DlDAfZ/j+pNYO7l5M+774WbgkTgg==",
+ "license": "MIT",
+ "dependencies": {
+ "@expo/log-box": "^56.0.12",
+ "anser": "^1.4.9",
+ "pretty-format": "^29.7.0",
+ "stacktrace-parser": "^0.1.10",
+ "whatwg-fetch": "^3.0.0"
+ },
+ "peerDependencies": {
+ "@expo/log-box": "^56.0.12",
+ "expo": "*",
+ "react": "*",
+ "react-dom": "*",
+ "react-native": "*"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@expo/osascript": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.6.0.tgz",
@@ -1478,9 +1570,9 @@
}
},
"node_modules/@expo/package-manager": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.12.0.tgz",
- "integrity": "sha512-SWr6093nwBjn94cvElsYZNUnhvs+XtUatUz3h0vAn0IbaWG0B6l/V5ZfOBptX/xq6rMpFG5ibIf/eckLSXw8Gg==",
+ "version": "1.12.1",
+ "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.12.1.tgz",
+ "integrity": "sha512-fQLiFAcFRWF53mtuLK32SUJQ1ahhrTcBZPZPedYTiUT5ha5FF+UO6bPtCc0Y/hgj0/m3HCGBAuSHjbg2kI9oPQ==",
"license": "MIT",
"dependencies": {
"@expo/json-file": "^10.2.0",
@@ -1503,19 +1595,19 @@
}
},
"node_modules/@expo/prebuild-config": {
- "version": "56.0.12",
- "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-56.0.12.tgz",
- "integrity": "sha512-cMI1EwpVhVaZQ92VtkRGpyvBV/iC06NMBwi+p69mwvoQTJKqswgCwYK7txFH5KaavKMmYMUaZ1twiC7jd/jDRQ==",
+ "version": "56.0.14",
+ "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-56.0.14.tgz",
+ "integrity": "sha512-JHdMqR7Mf5ApLC50ZwTL0Z86ezrHOMYwoSHcWT6Pha/+1TcC+/J+i7vjhP06wGXQ2Kvjt74p/3mKg2Pd12KjhQ==",
"license": "MIT",
"dependencies": {
- "@expo/config": "~56.0.8",
- "@expo/config-plugins": "~56.0.7",
+ "@expo/config": "~56.0.9",
+ "@expo/config-plugins": "~56.0.8",
"@expo/config-types": "^56.0.5",
- "@expo/image-utils": "^0.10.0",
+ "@expo/image-utils": "^0.10.1",
"@expo/json-file": "^10.2.0",
"@react-native/normalize-colors": "0.85.3",
"debug": "^4.3.1",
- "expo-modules-autolinking": "~56.0.11",
+ "expo-modules-autolinking": "~56.0.14",
"resolve-from": "^5.0.0",
"semver": "^7.6.0"
}
@@ -1569,6 +1661,39 @@
"integrity": "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==",
"license": "MIT"
},
+ "node_modules/@expo/ui": {
+ "version": "56.0.15",
+ "resolved": "https://registry.npmjs.org/@expo/ui/-/ui-56.0.15.tgz",
+ "integrity": "sha512-PFZBzztQGCp2bRFP8wIOb5ntP2ORH2GdQkJMSJcDOd4NldoWMe1pFqv7PdthjNlaaTHHTTHK+RsQrz+M6z6isw==",
+ "license": "MIT",
+ "dependencies": {
+ "sf-symbols-typescript": "^2.1.0",
+ "vaul": "^1.1.2"
+ },
+ "peerDependencies": {
+ "@babel/core": "*",
+ "expo": "*",
+ "react": "*",
+ "react-dom": "*",
+ "react-native": "*",
+ "react-native-reanimated": "*",
+ "react-native-worklets": "*"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ },
+ "react-native-reanimated": {
+ "optional": true
+ },
+ "react-native-worklets": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@expo/vector-icons": {
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-15.1.1.tgz",
@@ -1694,9 +1819,9 @@
}
},
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz",
- "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==",
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.4.tgz",
+ "integrity": "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==",
"cpu": [
"arm64"
],
@@ -1707,9 +1832,9 @@
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz",
- "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==",
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.4.tgz",
+ "integrity": "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==",
"cpu": [
"x64"
],
@@ -1720,9 +1845,9 @@
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz",
- "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==",
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.4.tgz",
+ "integrity": "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==",
"cpu": [
"arm"
],
@@ -1733,9 +1858,9 @@
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz",
- "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==",
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.4.tgz",
+ "integrity": "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==",
"cpu": [
"arm64"
],
@@ -1746,9 +1871,9 @@
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz",
- "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==",
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.4.tgz",
+ "integrity": "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==",
"cpu": [
"x64"
],
@@ -1759,9 +1884,9 @@
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz",
- "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==",
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.4.tgz",
+ "integrity": "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==",
"cpu": [
"x64"
],
@@ -3168,9 +3293,9 @@
}
},
"node_modules/babel-preset-expo": {
- "version": "56.0.12",
- "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-56.0.12.tgz",
- "integrity": "sha512-8sOIpdzMXgx81CcCF4wwAQC8xo9akFgy32pciVjHo/4tphLOXez7wfqv5p9StgyMLQPEF4qhXG2Rkbz1QAgu2A==",
+ "version": "56.0.14",
+ "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-56.0.14.tgz",
+ "integrity": "sha512-+JKVMYf3HajO3tPRA9DlKd/VhZOPTHyTzUo2yZajfMAoQ3l5VEdGVxm2MzX4DXMNKXwsC8GOeTRx7CrO/5dBDA==",
"license": "MIT",
"dependencies": {
"@babel/generator": "^7.20.5",
@@ -3219,7 +3344,7 @@
"peerDependencies": {
"@babel/runtime": "^7.20.0",
"expo": "*",
- "expo-widgets": "^56.0.14",
+ "expo-widgets": "^56.0.16",
"react-refresh": ">=0.14.0 <1.0.0"
},
"peerDependenciesMeta": {
@@ -4049,31 +4174,31 @@
}
},
"node_modules/expo": {
- "version": "56.0.4",
- "resolved": "https://registry.npmjs.org/expo/-/expo-56.0.4.tgz",
- "integrity": "sha512-ZwoOkOTwITJrFQRRO5tUsBp6NlddvjWSs3ADb+zOu1UIQWBCk9dmwmSrdFxu0P+hYnU2hk5k/Y6xq6DPLNSKzg==",
+ "version": "56.0.8",
+ "resolved": "https://registry.npmjs.org/expo/-/expo-56.0.8.tgz",
+ "integrity": "sha512-GzQi5450yrCk5JRSlm0epsmtURBErh0wS77uWLZImFdnPICuX912MaRWooR+Q1Sw/7aQjp9F+KXH+dvrqGxpeQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.20.0",
- "@expo/cli": "^56.1.11",
+ "@expo/cli": "^56.1.13",
"@expo/config": "~56.0.9",
"@expo/config-plugins": "~56.0.8",
"@expo/devtools": "~56.0.2",
"@expo/dom-webview": "~56.0.5",
- "@expo/fingerprint": "^0.19.2",
- "@expo/local-build-cache-provider": "^56.0.7",
+ "@expo/fingerprint": "^0.19.3",
+ "@expo/local-build-cache-provider": "^56.0.8",
"@expo/log-box": "^56.0.12",
"@expo/metro": "~56.0.0",
- "@expo/metro-config": "~56.0.12",
+ "@expo/metro-config": "~56.0.13",
"@ungap/structured-clone": "^1.3.0",
- "babel-preset-expo": "~56.0.12",
- "expo-asset": "~56.0.14",
- "expo-constants": "~56.0.15",
+ "babel-preset-expo": "~56.0.14",
+ "expo-asset": "~56.0.15",
+ "expo-constants": "~56.0.16",
"expo-file-system": "~56.0.7",
"expo-font": "~56.0.5",
"expo-keep-awake": "~56.0.3",
- "expo-modules-autolinking": "~56.0.12",
- "expo-modules-core": "~56.0.12",
+ "expo-modules-autolinking": "~56.0.14",
+ "expo-modules-core": "~56.0.14",
"pretty-format": "^29.7.0",
"react-refresh": "^0.14.2",
"whatwg-url-minimum": "^0.1.2"
@@ -4110,6 +4235,21 @@
}
}
},
+ "node_modules/expo-asset": {
+ "version": "56.0.15",
+ "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-56.0.15.tgz",
+ "integrity": "sha512-BHGi2IAOPQTcOelkUdcz1WIknfCTRjkcpYHX1azjMwgYenrVC+J5qcqJGaC8eUOWLCRtkRJWGnmFQRYtLU1nUQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@expo/image-utils": "^0.10.1",
+ "expo-constants": "~56.0.16"
+ },
+ "peerDependencies": {
+ "expo": "*",
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/expo-camera": {
"version": "56.0.7",
"resolved": "https://registry.npmjs.org/expo-camera/-/expo-camera-56.0.7.tgz",
@@ -4131,9 +4271,9 @@
}
},
"node_modules/expo-constants": {
- "version": "56.0.15",
- "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-56.0.15.tgz",
- "integrity": "sha512-7187sd55swLX+CM0noAV5LreEgkUaDG/zEXy9quonfzKpJxy8zJAszp9S++xOx/FcqBVEEEcQhE8tKTujwL8fg==",
+ "version": "56.0.16",
+ "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-56.0.16.tgz",
+ "integrity": "sha512-6tsiN+gmTUPp/atyA+uY9Tg8VOdXdmb4s/3TVGolfn6A/oCAraw1pcPZX5XllyD+xUguxB6eBSFAT8494hZVMA==",
"license": "MIT",
"dependencies": {
"@expo/env": "~2.3.0"
@@ -4144,13 +4284,13 @@
}
},
"node_modules/expo-dev-client": {
- "version": "56.0.15",
- "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-56.0.15.tgz",
- "integrity": "sha512-YJBO0xMv0CRhVhZu4NPWoR0zS/nyhbjpBiEhEd4SOD/mcmW1I1ncURLn8Ej63yJjuCGL6pFQLkCskAak1OJyuA==",
+ "version": "56.0.18",
+ "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-56.0.18.tgz",
+ "integrity": "sha512-pTfDcYTOvrs4vCgAaM+vP2OEO93oGkczgGpTAzCY7ZTIvtPhpekJURHBxfOnKvfn97IF3Hk+8J9tMozsNDj0Gw==",
"license": "MIT",
"dependencies": {
- "expo-dev-launcher": "~56.0.15",
- "expo-dev-menu": "~56.0.14",
+ "expo-dev-launcher": "~56.0.18",
+ "expo-dev-menu": "~56.0.16",
"expo-dev-menu-interface": "~56.0.0",
"expo-manifests": "~56.0.4",
"expo-updates-interface": "~56.0.1"
@@ -4160,13 +4300,13 @@
}
},
"node_modules/expo-dev-launcher": {
- "version": "56.0.15",
- "resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-56.0.15.tgz",
- "integrity": "sha512-KVG8haacJiYHu7wLJiDYQbKM0CqFBqf0BJ9YvWWBhxOZjNOtwspVIsKS4idiIOeQsFCRb2Axt8svQ+opvuvE6A==",
+ "version": "56.0.18",
+ "resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-56.0.18.tgz",
+ "integrity": "sha512-7acFJlkAbp3cMz7Uy787todMR/3A/Row2EOPD21RRoetvzJe4DTm9s7RwJ8PDtyNyued9rooD4+Q6rD8ijpTgw==",
"license": "MIT",
"dependencies": {
"@expo/schema-utils": "^56.0.0",
- "expo-dev-menu": "~56.0.14",
+ "expo-dev-menu": "~56.0.16",
"expo-manifests": "~56.0.4"
},
"peerDependencies": {
@@ -4175,9 +4315,9 @@
}
},
"node_modules/expo-dev-menu": {
- "version": "56.0.14",
- "resolved": "https://registry.npmjs.org/expo-dev-menu/-/expo-dev-menu-56.0.14.tgz",
- "integrity": "sha512-4dx14nedjWSCdpPKj74IGIfuM5nd2ePMpD3vNraq+srsZzWfNMh9gLFwcXtfQIpgXkHavO5178bJ3VCJVsnNsg==",
+ "version": "56.0.16",
+ "resolved": "https://registry.npmjs.org/expo-dev-menu/-/expo-dev-menu-56.0.16.tgz",
+ "integrity": "sha512-aVgoe+YGhrQnpwiB5BRI7G+uQnGHMUij32bBnEVdc6eJrVZCStxQlV9NeFbbXxrDhLJt6OSqbCHbLR+XToWUUA==",
"license": "MIT",
"dependencies": {
"expo-dev-menu-interface": "~56.0.0"
@@ -4269,12 +4409,12 @@
}
},
"node_modules/expo-linking": {
- "version": "56.0.11",
- "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-56.0.11.tgz",
- "integrity": "sha512-MEPgML2mqm2Y8rP6zTleOpCmYiFyfQfNSOBpDIb7CYpbDQleStugvceKsEsL4v8C0Dl5u7e8KkkrbqmgpOOIBw==",
+ "version": "56.0.13",
+ "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-56.0.13.tgz",
+ "integrity": "sha512-38YrpTh6xdiDxmYSDIUffDqev1hIcEggw2fZ3IZhNp2DVLF1xvqsbO6hJD1fuBKN8P34B3Ggc9Yy26fkqdfCOA==",
"license": "MIT",
"dependencies": {
- "expo-constants": "~56.0.14",
+ "expo-constants": "~56.0.16",
"invariant": "^2.2.4"
},
"peerDependencies": {
@@ -4283,9 +4423,9 @@
}
},
"node_modules/expo-location": {
- "version": "56.0.13",
- "resolved": "https://registry.npmjs.org/expo-location/-/expo-location-56.0.13.tgz",
- "integrity": "sha512-MUHG1IXoQIhi4oadyOG9UtrbVLPeaicOT0ARrQ+X5Tt7+6KAHVbg4TIwLiv4oqpdNh+R0XiHdNXOe4oPQrs8eg==",
+ "version": "56.0.15",
+ "resolved": "https://registry.npmjs.org/expo-location/-/expo-location-56.0.15.tgz",
+ "integrity": "sha512-CM5+1untDxsuN0NIgsBS9cRel5xh8UXstQS6KtQw/run5PiArqCl51cnTuG+aqjYgE+9gweSG70PI6A1Ax1XTA==",
"license": "MIT",
"dependencies": {
"@expo/image-utils": "^0.10.1"
@@ -4317,9 +4457,9 @@
}
},
"node_modules/expo-modules-autolinking": {
- "version": "56.0.12",
- "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-56.0.12.tgz",
- "integrity": "sha512-Sn/LiLSL4as/YOGoatsuRQYS7rxAQHK2oYbFMT/I3iIXHLSeb0DQeuAIytVQ9ypWWck9s2krH2T6NeztyftnaA==",
+ "version": "56.0.14",
+ "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-56.0.14.tgz",
+ "integrity": "sha512-9ugtZkheNPYDkW4DZopY1rH2BCbUICaafUEPxRgbLDR5UNRF5K3cdHMIMEt8pxZPq2+eX4wCm+6pbSvdY/DPHg==",
"license": "MIT",
"dependencies": {
"@expo/require-utils": "^56.1.3",
@@ -4332,9 +4472,9 @@
}
},
"node_modules/expo-modules-core": {
- "version": "56.0.12",
- "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-56.0.12.tgz",
- "integrity": "sha512-2Rf+FBU2EXe27km3m066xHu4kuUSpNT35nzk98fFxIV8B2Ah+FHub2rvAznEcGAUlDArVA2S/6+pMlHWijbicQ==",
+ "version": "56.0.14",
+ "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-56.0.14.tgz",
+ "integrity": "sha512-dl1TlYRm1k7xk9QeAyDoMfFE2p6rNyzHUcH5ArcGwUzO8YKku+Z2tQ8+kG7zLe3OhfMoJcFR/czrFy7vGSVI6w==",
"license": "MIT",
"dependencies": {
"@expo/expo-modules-macros-plugin": "~0.0.9",
@@ -4362,15 +4502,15 @@
}
},
"node_modules/expo-router": {
- "version": "56.2.6",
- "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-56.2.6.tgz",
- "integrity": "sha512-KouVa/E2zQc1aALWSd5eZjbsLKldgozQ546p+bgAR2nGPRTO4WpMRKNIxjB89We1G4RwWpxQ5vgTU1WZ+FpqOg==",
+ "version": "56.2.8",
+ "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-56.2.8.tgz",
+ "integrity": "sha512-l387I/ddPY/5SS+Rfpp1SrRV9gBKevxtPuZod7igMjR6L674QrxEwGiAILRq6AKCSbrP2I0ufKj7e5xz8JqA4Q==",
"license": "MIT",
"dependencies": {
"@expo/log-box": "^56.0.12",
- "@expo/metro-runtime": "^56.0.12",
+ "@expo/metro-runtime": "^56.0.13",
"@expo/schema-utils": "^56.0.0",
- "@expo/ui": "^56.0.13",
+ "@expo/ui": "^56.0.15",
"@radix-ui/react-slot": "^1.2.0",
"@radix-ui/react-tabs": "^1.1.12",
"@react-native-masked-view/masked-view": "^0.3.2",
@@ -4398,11 +4538,11 @@
},
"peerDependencies": {
"@expo/log-box": "^56.0.12",
- "@expo/metro-runtime": "^56.0.12",
+ "@expo/metro-runtime": "^56.0.13",
"@testing-library/react-native": ">= 13.2.0",
"expo": "*",
- "expo-constants": "^56.0.15",
- "expo-linking": "^56.0.11",
+ "expo-constants": "^56.0.16",
+ "expo-linking": "^56.0.13",
"react": "*",
"react-dom": "*",
"react-native": "*",
@@ -4434,64 +4574,6 @@
}
}
},
- "node_modules/expo-router/node_modules/@expo/metro-runtime": {
- "version": "56.0.12",
- "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-56.0.12.tgz",
- "integrity": "sha512-7fWsZfIq+Kn6ilr5lx1YNQGJjukmvwnrl91cTRASdQIKXQoXF7AXRAU0CrDjA+dNMZ6UWDK3l8wpQjk7CA1Z/A==",
- "license": "MIT",
- "dependencies": {
- "@expo/log-box": "^56.0.12",
- "anser": "^1.4.9",
- "pretty-format": "^29.7.0",
- "stacktrace-parser": "^0.1.10",
- "whatwg-fetch": "^3.0.0"
- },
- "peerDependencies": {
- "@expo/log-box": "^56.0.12",
- "expo": "*",
- "react": "*",
- "react-dom": "*",
- "react-native": "*"
- },
- "peerDependenciesMeta": {
- "react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/expo-router/node_modules/@expo/ui": {
- "version": "56.0.13",
- "resolved": "https://registry.npmjs.org/@expo/ui/-/ui-56.0.13.tgz",
- "integrity": "sha512-Dx05pO3lo8lzWp0hgvJ011j/a5DD2BwHXtr08hdiRUc03KrWQJ3QzdbqPqNayrr+Usc2COC+bOkmPNX7N0k0+w==",
- "license": "MIT",
- "dependencies": {
- "sf-symbols-typescript": "^2.1.0",
- "vaul": "^1.1.2"
- },
- "peerDependencies": {
- "@babel/core": "*",
- "expo": "*",
- "react": "*",
- "react-dom": "*",
- "react-native": "*",
- "react-native-reanimated": "*",
- "react-native-worklets": "*"
- },
- "peerDependenciesMeta": {
- "@babel/core": {
- "optional": true
- },
- "react-dom": {
- "optional": true
- },
- "react-native-reanimated": {
- "optional": true
- },
- "react-native-worklets": {
- "optional": true
- }
- }
- },
"node_modules/expo-router/node_modules/@radix-ui/react-tabs": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz",
@@ -4538,9 +4620,9 @@
}
},
"node_modules/expo-sharing": {
- "version": "56.0.13",
- "resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-56.0.13.tgz",
- "integrity": "sha512-Yz6mBSqbU5dM6UzCjkKr1+B0JKADRuGj4Dokgs2fLysRTyjvO4mbmSTyY7AGQx3VYz0IUMoyPg4zqvsWPEFeDg==",
+ "version": "56.0.15",
+ "resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-56.0.15.tgz",
+ "integrity": "sha512-6Hy1+Mjy4UYXkFiDK3Ea934NUmA71i8dmZkDe+rrUHRzZAv4FR+q/VyiT7LzNFEqpT4wn4wcI66lc2QY526RsA==",
"license": "MIT",
"dependencies": {
"@expo/config-plugins": "^56.0.8",
@@ -4590,9 +4672,9 @@
}
},
"node_modules/expo/node_modules/@expo/cli": {
- "version": "56.1.11",
- "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-56.1.11.tgz",
- "integrity": "sha512-agoVjJ+cygAAjWSjk278a+UVcHDVZMKkBROkzWxpSzMK0tmRuQvtRZvAgFN/wvGZchxzs+zGur7j3txojVMVZw==",
+ "version": "56.1.13",
+ "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-56.1.13.tgz",
+ "integrity": "sha512-7n5VzlBr7TKW0BgWgpEopWy+v8buPhMvbSEsuXD+bI1YIJBopkfWAub0qTvlc357E8wWOvV5MJXYyoeRvoOjoQ==",
"license": "MIT",
"dependencies": {
"@expo/code-signing-certificates": "^0.0.6",
@@ -4601,18 +4683,18 @@
"@expo/devcert": "^1.2.1",
"@expo/env": "~2.3.0",
"@expo/image-utils": "^0.10.1",
- "@expo/inline-modules": "^0.0.9",
+ "@expo/inline-modules": "^0.0.10",
"@expo/json-file": "^10.2.0",
"@expo/log-box": "^56.0.12",
"@expo/metro": "~56.0.0",
- "@expo/metro-config": "~56.0.12",
+ "@expo/metro-config": "~56.0.13",
"@expo/metro-file-map": "^56.0.3",
"@expo/osascript": "^2.6.0",
- "@expo/package-manager": "^1.12.0",
+ "@expo/package-manager": "^1.12.1",
"@expo/plist": "^0.7.0",
- "@expo/prebuild-config": "^56.0.12",
+ "@expo/prebuild-config": "^56.0.14",
"@expo/require-utils": "^56.1.3",
- "@expo/router-server": "^56.0.11",
+ "@expo/router-server": "^56.0.12",
"@expo/schema-utils": "^56.0.0",
"@expo/spawn-async": "^1.8.0",
"@expo/ws-tunnel": "^1.0.1",
@@ -4671,17 +4753,17 @@
}
},
"node_modules/expo/node_modules/@expo/cli/node_modules/@expo/router-server": {
- "version": "56.0.11",
- "resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-56.0.11.tgz",
- "integrity": "sha512-cyROEK3gibypiyz2QR7zm1+LMHUQEj7KQopwZ/Fip75MYrQ/SYOMRFSTvchZXEipwMRjwYecE4jsnqNKyYWFZg==",
+ "version": "56.0.12",
+ "resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-56.0.12.tgz",
+ "integrity": "sha512-RqKV2/Z8BH/z8l0ngSpG6//5xxJPaF5dTQvSfPQ0nrvCjikGMeIvyj3B9BeLnmZZhxb3gBtXqrj3irAoiIp2aQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.4"
},
"peerDependencies": {
- "@expo/metro-runtime": "^56.0.11",
+ "@expo/metro-runtime": "^56.0.13",
"expo": "*",
- "expo-constants": "^56.0.14",
+ "expo-constants": "^56.0.16",
"expo-font": "^56.0.5",
"expo-router": "*",
"expo-server": "^56.0.4",
@@ -4704,46 +4786,6 @@
}
}
},
- "node_modules/expo/node_modules/@expo/metro-config": {
- "version": "56.0.12",
- "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-56.0.12.tgz",
- "integrity": "sha512-L9q423WwY6eUu4A3N8OaBDECuoUNukUQKomb0/LinwzG+QkU8cBvpELXwEngP7eTt1s6LB3tXcnPp/aMvLsojw==",
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.20.0",
- "@babel/core": "^7.20.0",
- "@babel/generator": "^7.20.5",
- "@expo/config": "~56.0.9",
- "@expo/env": "~2.3.0",
- "@expo/json-file": "~10.2.0",
- "@expo/metro": "~56.0.0",
- "@expo/require-utils": "^56.1.3",
- "@expo/spawn-async": "^1.8.0",
- "@jridgewell/gen-mapping": "^0.3.13",
- "@jridgewell/remapping": "^2.3.5",
- "@jridgewell/sourcemap-codec": "^1.5.5",
- "browserslist": "^4.25.0",
- "chalk": "^4.1.0",
- "debug": "^4.3.2",
- "getenv": "^2.0.0",
- "glob": "^13.0.0",
- "hermes-parser": "^0.33.3",
- "jsc-safe-url": "^0.2.4",
- "lightningcss": "^1.30.1",
- "msgpackr": "^2.0.1",
- "picomatch": "^4.0.4",
- "postcss": "^8.5.14",
- "resolve-from": "^5.0.0"
- },
- "peerDependencies": {
- "expo": "*"
- },
- "peerDependenciesMeta": {
- "expo": {
- "optional": true
- }
- }
- },
"node_modules/expo/node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -4772,21 +4814,6 @@
"node": ">=8"
}
},
- "node_modules/expo/node_modules/expo-asset": {
- "version": "56.0.14",
- "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-56.0.14.tgz",
- "integrity": "sha512-3rN/VGt4jhNOXbAz3gdSJzC/dSmX5ozHgtG/HQTCOzuRGBd1q2lzWoZewX8aU1fiWchQg9LasiLiAJi+u8oBbQ==",
- "license": "MIT",
- "dependencies": {
- "@expo/image-utils": "^0.10.1",
- "expo-constants": "~56.0.15"
- },
- "peerDependencies": {
- "expo": "*",
- "react": "*",
- "react-native": "*"
- }
- },
"node_modules/expo/node_modules/expo-keep-awake": {
"version": "56.0.3",
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-56.0.3.tgz",
@@ -4797,21 +4824,6 @@
"react": "*"
}
},
- "node_modules/expo/node_modules/hermes-estree": {
- "version": "0.33.3",
- "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz",
- "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==",
- "license": "MIT"
- },
- "node_modules/expo/node_modules/hermes-parser": {
- "version": "0.33.3",
- "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz",
- "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==",
- "license": "MIT",
- "dependencies": {
- "hermes-estree": "0.33.3"
- }
- },
"node_modules/expo/node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -5599,9 +5611,19 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
- "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
+ "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -6526,18 +6548,18 @@
"license": "MIT"
},
"node_modules/msgpackr": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-2.0.1.tgz",
- "integrity": "sha512-9J+tqTEsbHqY8YohazYgty7LgerFIWxvMLpUjqETSmjHojtJm2WnX2kK/2a1fLI7CO7ERP1YSEUXMucz4j+yBA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-2.0.2.tgz",
+ "integrity": "sha512-c5hYOXFbP79Slh6Dzd2wzk+jnV7mX1UxfMYtilnY1NmalXPqG8DGb5cYCMBrW4AsH3zekBBZd4QrKz9NhtvYLQ==",
"license": "MIT",
"optionalDependencies": {
- "msgpackr-extract": "^3.0.2"
+ "msgpackr-extract": "^3.0.4"
}
},
"node_modules/msgpackr-extract": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz",
- "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==",
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.4.tgz",
+ "integrity": "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -6548,12 +6570,12 @@
"download-msgpackr-prebuilds": "bin/download-prebuilds.js"
},
"optionalDependencies": {
- "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3",
- "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3",
- "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3",
- "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3",
- "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3",
- "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3"
+ "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4",
+ "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4",
+ "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4",
+ "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4",
+ "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4",
+ "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4"
}
},
"node_modules/multitars": {
@@ -7923,16 +7945,20 @@
}
},
"node_modules/react-native-view-shot": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/react-native-view-shot/-/react-native-view-shot-4.0.3.tgz",
- "integrity": "sha512-USNjYmED7C0me02c1DxKA0074Hw+y/nxo+xJKlffMvfUWWzL5ELh/TJA/pTnVqFurIrzthZDPtDM7aBFJuhrHQ==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/react-native-view-shot/-/react-native-view-shot-5.1.0.tgz",
+ "integrity": "sha512-JZgElCD82aO+hejIF/leUzI7JufL9mgJ6ChzGWIcdZ2ajpaEvvSnvIcw0qD32XWkrbId8wfSbyz/4u/ulTQzQA==",
"license": "MIT",
"dependencies": {
"html2canvas": "^1.4.1"
},
+ "engines": {
+ "node": ">=20",
+ "npm": ">=10"
+ },
"peerDependencies": {
- "react": "*",
- "react-native": "*"
+ "react": ">=18.0.0",
+ "react-native": ">=0.76.0"
}
},
"node_modules/react-native-web": {
diff --git a/package.json b/package.json
index 152bbdb..a9e9d0e 100644
--- a/package.json
+++ b/package.json
@@ -6,19 +6,19 @@
"@expo/vector-icons": "^15.1.1",
"@react-native-async-storage/async-storage": "2.2.0",
"@tanstack/react-query": "^5.100.14",
- "expo": "~56.0.4",
+ "expo": "~56.0.8",
"expo-camera": "~56.0.7",
"expo-constants": "~56.0.15",
- "expo-dev-client": "~56.0.15",
+ "expo-dev-client": "~56.0.18",
"expo-file-system": "~56.0.7",
"expo-font": "~56.0.5",
"expo-image": "~56.0.9",
"expo-linear-gradient": "~56.0.4",
- "expo-linking": "~56.0.11",
- "expo-location": "~56.0.13",
+ "expo-linking": "~56.0.13",
+ "expo-location": "~56.0.15",
"expo-media-library": "~56.0.6",
- "expo-router": "^56.2.6",
- "expo-sharing": "~56.0.13",
+ "expo-router": "~56.2.8",
+ "expo-sharing": "~56.0.15",
"expo-status-bar": "~56.0.4",
"nativewind": "^4.2.4",
"react": "19.2.3",
@@ -27,7 +27,7 @@
"react-native-reanimated": "4.3.1",
"react-native-safe-area-context": "~5.7.0",
"react-native-screens": "4.25.2",
- "react-native-view-shot": "4.0.3",
+ "react-native-view-shot": "5.1.0",
"react-native-web": "^0.21.2",
"react-native-worklets": "0.8.3",
"zustand": "^5.0.13"
diff --git a/src/components/dev/DevTestManager.tsx b/src/components/dev/DevTestManager.tsx
new file mode 100644
index 0000000..cebda75
--- /dev/null
+++ b/src/components/dev/DevTestManager.tsx
@@ -0,0 +1,562 @@
+import { Feather } from '@expo/vector-icons';
+import { router } from 'expo-router';
+import { type ReactNode, useMemo, useRef, useState } from 'react';
+import {
+ Animated,
+ Modal,
+ PanResponder,
+ Pressable,
+ ScrollView,
+ useWindowDimensions,
+ View,
+} from 'react-native';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+
+import { queryClient } from '@/providers/queryClient';
+import { AppText } from '@/components/AppText';
+import { playlistCurationById } from '@/mocks/playlistMocks';
+import { mockEndpointIds, useDevToolsStore } from '@/store/devToolsStore';
+import { useHomeFilterStore } from '@/store/homeFilterStore';
+import { useLibraryStore } from '@/store/libraryStore';
+import { useMomentLogStore } from '@/store/momentLogStore';
+import { usePlayerStore } from '@/store/playerStore';
+import { useRecommendationEventStore } from '@/store/recommendationEventStore';
+import { useTravelSessionStore } from '@/store/travelSessionStore';
+import { useUserProfileStore } from '@/store/userProfileStore';
+import { GeoPoint, MomentLog, PlaceContext, Track, TravelMode } from '@/types/domain';
+
+const BUTTON_SIZE = 58;
+const DEFAULT_DELAY_MS = 300;
+const SLOW_DELAY_MS = 1600;
+const samplePlaylist = playlistCurationById['busan-ocean'];
+const sampleTracks = samplePlaylist.tracks;
+
+const placePresets: Array<{
+ label: string;
+ location: GeoPoint;
+ place: PlaceContext;
+}> = [
+ {
+ label: '부산 광안리',
+ location: { lat: 35.1532, lng: 129.1186 },
+ place: {
+ category: '해변',
+ contentType: '관광지',
+ id: 'dev-busan-gwangalli',
+ imageUrl: 'https://tong.visitkorea.or.kr/cms2/website/76/2012176.jpg',
+ location: { lat: 35.1532, lng: 129.1186 },
+ overview: '바다와 야경, 산책 맥락을 테스트하는 개발용 장소입니다.',
+ source: 'mock',
+ title: '광안리 해수욕장',
+ },
+ },
+ {
+ label: '서울 야경',
+ location: { lat: 37.5512, lng: 126.9882 },
+ place: {
+ category: '야경',
+ contentType: '문화시설',
+ id: 'dev-seoul-night',
+ imageUrl: 'https://tong.visitkorea.or.kr/cms/resource_photo/96/4033396_image2_1.jpg',
+ location: { lat: 37.5512, lng: 126.9882 },
+ overview: '도시 야경과 감성 음악 추천을 테스트하는 개발용 장소입니다.',
+ source: 'mock',
+ title: '남산서울타워',
+ },
+ },
+];
+
+const topFilterOptions = ['전체', '근처', '지역 트렌드', '내 취향', '저장 많은'];
+const moodFilterOptions = ['전체', '잔잔한', '신나는', '감성적인', '청량한', '활기찬', '로컬한'];
+const travelModeOptions: Array<{ label: string; value: TravelMode }> = [
+ { label: '산책', value: 'walk' },
+ { label: '드라이브', value: 'drive' },
+ { label: '카페', value: 'cafe' },
+ { label: '바다', value: 'ocean' },
+ { label: '축제', value: 'festival' },
+ { label: '야경', value: 'night' },
+];
+
+type ManagerButtonProps = {
+ active?: boolean;
+ destructive?: boolean;
+ label: string;
+ onPress: () => void;
+};
+
+type ManagerSectionProps = {
+ children: ReactNode;
+ subtitle?: string;
+ title: string;
+};
+
+type StatusPillProps = {
+ label: string;
+};
+
+function clamp(value: number, min: number, max: number) {
+ return Math.min(Math.max(value, min), max);
+}
+
+function invalidateMockQueries() {
+ void queryClient.invalidateQueries();
+}
+
+function ManagerSection({ children, subtitle, title }: ManagerSectionProps) {
+ return (
+
+ {title}
+ {subtitle ? (
+ {subtitle}
+ ) : null}
+ {children}
+
+ );
+}
+
+function ManagerButton({
+ active = false,
+ destructive = false,
+ label,
+ onPress,
+}: ManagerButtonProps) {
+ const backgroundColor = active
+ ? '#ffffff'
+ : destructive
+ ? 'rgba(248,113,113,0.16)'
+ : 'rgba(255,255,255,0.1)';
+ const borderColor = active
+ ? '#ffffff'
+ : destructive
+ ? 'rgba(248,113,113,0.28)'
+ : 'rgba(255,255,255,0.12)';
+ const color = active ? '#050916' : destructive ? '#fecaca' : 'rgba(255,255,255,0.78)';
+
+ return (
+
+
+ {label}
+
+
+ );
+}
+
+function StatusPill({ label }: StatusPillProps) {
+ return (
+
+ {label}
+
+ );
+}
+
+function getSampleTrack(index = 0): Track {
+ return sampleTracks[index % sampleTracks.length];
+}
+
+export function DevTestManager() {
+ if (!__DEV__) {
+ return null;
+ }
+
+ return ;
+}
+
+function DevTestManagerContent() {
+ const insets = useSafeAreaInsets();
+ const { height, width } = useWindowDimensions();
+ const [isOpen, setIsOpen] = useState(false);
+ const maxX = Math.max(width - BUTTON_SIZE - 12, 12);
+ const maxY = Math.max(height - BUTTON_SIZE - Math.max(insets.bottom, 12) - 12, 80);
+ const defaultPosition = useMemo(
+ () => ({
+ x: 14,
+ y: Math.max(height - BUTTON_SIZE - Math.max(insets.bottom, 12) - 110, 90),
+ }),
+ [height, insets.bottom],
+ );
+ const pan = useRef(new Animated.ValueXY(defaultPosition)).current;
+ const lastPosition = useRef(defaultPosition);
+ const panResponder = useMemo(
+ () =>
+ PanResponder.create({
+ onMoveShouldSetPanResponder: (_, gesture) =>
+ Math.abs(gesture.dx) > 4 || Math.abs(gesture.dy) > 4,
+ onPanResponderMove: (_, gesture) => {
+ pan.setValue({
+ x: clamp(lastPosition.current.x + gesture.dx, 8, maxX),
+ y: clamp(lastPosition.current.y + gesture.dy, 64, maxY),
+ });
+ },
+ onPanResponderRelease: (_, gesture) => {
+ const nextPosition = {
+ x: clamp(lastPosition.current.x + gesture.dx, 8, maxX),
+ y: clamp(lastPosition.current.y + gesture.dy, 64, maxY),
+ };
+
+ lastPosition.current = nextPosition;
+ Animated.spring(pan, {
+ toValue: nextPosition,
+ useNativeDriver: false,
+ }).start();
+ },
+ }),
+ [maxX, maxY, pan],
+ );
+
+ const { selectedMoodFilter, selectedTopFilter, setSelectedMoodFilter, setSelectedTopFilter } =
+ useHomeFilterStore();
+ const { currentLocation, currentPlace, locationStatus, selectedMode, session } =
+ useTravelSessionStore();
+ const travelSessionActions = useTravelSessionStore((state) => ({
+ clearLocation: state.clearLocation,
+ endSession: state.endSession,
+ resetSession: state.resetSession,
+ setLocation: state.setLocation,
+ setLocationStatus: state.setLocationStatus,
+ setMode: state.setMode,
+ setPlace: state.setPlace,
+ startSession: state.startSession,
+ }));
+ const {
+ failedEndpointIds,
+ failAllEndpoints,
+ mockDelayMs,
+ resetMockRuntime,
+ setFailAllEndpoints,
+ setMockDelayMs,
+ toggleFailedEndpoint,
+ } = useDevToolsStore();
+ const { completeOnboarding, resetOnboarding } = useUserProfileStore();
+ const { logs, addLog, removeLog } = useMomentLogStore();
+ const { likedTracks, savedTracks, removeLikedTrack, removeSavedTrack, toggleLike, toggleSave } =
+ useLibraryStore();
+ const { clearTrack, currentTrack, setTrack } = usePlayerStore();
+ const { clearEvents, events } = useRecommendationEventStore();
+
+ const navigate = (path: string) => {
+ setIsOpen(false);
+ router.push(path as never);
+ };
+ const applyProfilePreset = () => {
+ completeOnboarding({
+ companionType: '친구',
+ locationRecommendationEnabled: true,
+ preferredGenres: ['K-POP', '인디', '팝'],
+ preferredMoods: ['청량한', '잔잔한'],
+ travelStyles: ['산책', '바다 보기'],
+ });
+ setSelectedTopFilter('전체');
+ setSelectedMoodFilter('청량한');
+ router.replace('/' as never);
+ };
+ const resetProfile = () => {
+ resetOnboarding();
+ setSelectedTopFilter('전체');
+ setSelectedMoodFilter('전체');
+ setIsOpen(false);
+ router.replace('/onboarding' as never);
+ };
+ const applyPlacePreset = (preset: (typeof placePresets)[number]) => {
+ travelSessionActions.setLocation(preset.location);
+ travelSessionActions.setPlace(preset.place);
+ invalidateMockQueries();
+ };
+ const setLocationState = (status: 'denied' | 'idle' | 'unavailable') => {
+ travelSessionActions.clearLocation();
+ travelSessionActions.setLocationStatus(status);
+ invalidateMockQueries();
+ };
+ const addSampleMomentLogs = () => {
+ const baseTime = Date.now();
+ const sampleLogs: MomentLog[] = [
+ {
+ createdAt: new Date(baseTime - 1000 * 60 * 24).toISOString(),
+ id: `dev-moment-busan-${baseTime}`,
+ location: placePresets[0].location,
+ moodTags: ['fresh', 'local'],
+ photoUri: 'https://tong.visitkorea.or.kr/cms2/website/76/2012176.jpg',
+ placeCategory: '해변',
+ placeId: placePresets[0].place.id,
+ placeName: '광안리 해수욕장',
+ sessionId: 'dev-session-busan',
+ source: 'camera',
+ syncStatus: 'local',
+ track: getSampleTrack(0),
+ travelMode: 'walk',
+ },
+ {
+ createdAt: new Date(baseTime - 1000 * 60 * 12).toISOString(),
+ id: `dev-moment-cafe-${baseTime}`,
+ location: placePresets[0].location,
+ moodTags: ['calm', 'emotional'],
+ photoUri: 'https://tong.visitkorea.or.kr/cms2/website/75/2012175.jpg',
+ placeCategory: '카페거리',
+ placeId: 'dev-busan-cafe',
+ placeName: '민락동 카페거리',
+ sessionId: 'dev-session-busan',
+ source: 'camera',
+ syncStatus: 'local',
+ track: getSampleTrack(2),
+ travelMode: 'cafe',
+ },
+ {
+ createdAt: new Date(baseTime - 1000 * 60 * 4).toISOString(),
+ id: `dev-moment-seoul-${baseTime}`,
+ location: placePresets[1].location,
+ moodTags: ['emotional'],
+ photoUri: 'https://tong.visitkorea.or.kr/cms/resource_photo/96/4033396_image2_1.jpg',
+ placeCategory: '야경',
+ placeId: placePresets[1].place.id,
+ placeName: '남산서울타워',
+ sessionId: 'dev-session-seoul',
+ source: 'camera',
+ syncStatus: 'local',
+ track: getSampleTrack(5),
+ travelMode: 'night',
+ },
+ ];
+
+ sampleLogs.forEach(addLog);
+ };
+ const clearMomentLogs = () => {
+ logs.forEach((log) => removeLog(log.id));
+ };
+ const seedLibrary = () => {
+ const libraryState = useLibraryStore.getState();
+
+ sampleTracks.slice(0, 3).forEach((track) => {
+ if (!libraryState.isLiked(track.id)) {
+ libraryState.toggleLike(track, samplePlaylist.id);
+ }
+ });
+ sampleTracks.slice(1, 4).forEach((track) => {
+ if (!libraryState.isSaved(track.id)) {
+ libraryState.toggleSave(track, samplePlaylist.id);
+ }
+ });
+ };
+ const clearLibrary = () => {
+ likedTracks.forEach((record) => removeLikedTrack(record.track.id));
+ savedTracks.forEach((record) => removeSavedTrack(record.track.id));
+ };
+ const resetMockState = () => {
+ resetMockRuntime();
+ invalidateMockQueries();
+ };
+ const setSlowMockState = () => {
+ setMockDelayMs(SLOW_DELAY_MS);
+ setFailAllEndpoints(false);
+ invalidateMockQueries();
+ };
+ const setFailAllMockState = () => {
+ setFailAllEndpoints(true);
+ invalidateMockQueries();
+ };
+ const toggleEndpoint = (endpointId: (typeof mockEndpointIds)[number]) => {
+ toggleFailedEndpoint(endpointId);
+ invalidateMockQueries();
+ };
+
+ return (
+ <>
+
+ setIsOpen(true)}
+ >
+
+
+
+
+
+ setIsOpen(false)} transparent visible={isOpen}>
+
+ setIsOpen(false)} />
+
+
+
+
+
+
+ Test Manager
+
+
+ 페이지 이동, 조건문, mock 실패/지연, 로컬 데이터를 빠르게 검수해요.
+
+
+ setIsOpen(false)}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ navigate('/')} />
+ navigate('/onboarding')} />
+ navigate('/playlist/busan-ocean')}
+ />
+ navigate('/playlist/seoul-night')}
+ />
+ navigate('/recap')} />
+ navigate('/recap-share/log-1')} />
+ navigate('/library')} />
+ navigate('/my')} />
+ navigate('/camera')} />
+
+
+
+
+
+
+
+
+ {topFilterOptions.map((filter) => (
+ setSelectedTopFilter(filter)}
+ />
+ ))}
+ {moodFilterOptions.map((filter) => (
+ setSelectedMoodFilter(filter)}
+ />
+ ))}
+
+
+
+ {placePresets.map((preset) => (
+ applyPlacePreset(preset)}
+ />
+ ))}
+ setLocationState('denied')}
+ />
+ setLocationState('unavailable')}
+ />
+ setLocationState('idle')}
+ />
+
+
+
+ {travelModeOptions.map((mode) => (
+ travelSessionActions.setMode(mode.value)}
+ />
+ ))}
+
+
+
+
+
+
+
+ setTrack(getSampleTrack(0), samplePlaylist.id)}
+ />
+
+
+
+
+
+
+
+
+ {mockEndpointIds.map((endpointId) => (
+ toggleEndpoint(endpointId)}
+ />
+ ))}
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/onboarding/OnboardingScreen.tsx b/src/components/onboarding/OnboardingScreen.tsx
index 56682d2..6204b4d 100644
--- a/src/components/onboarding/OnboardingScreen.tsx
+++ b/src/components/onboarding/OnboardingScreen.tsx
@@ -104,8 +104,8 @@ export function OnboardingScreen() {
};
const applyHomeFilters = (input: UserProfileInput) => {
- setSelectedTopFilter(input.preferredMoods[0] ?? '전체');
- setSelectedMoodFilter(input.travelStyles[0] ?? '전체');
+ setSelectedTopFilter('전체');
+ setSelectedMoodFilter(input.preferredMoods[0] ?? '전체');
};
const finish = (input: UserProfileInput) => {
diff --git a/src/components/recap-share/RecapCaptureFrame.tsx b/src/components/recap-share/RecapCaptureFrame.tsx
index 19516c5..ba162d3 100644
--- a/src/components/recap-share/RecapCaptureFrame.tsx
+++ b/src/components/recap-share/RecapCaptureFrame.tsx
@@ -1,5 +1,5 @@
import { forwardRef, PropsWithChildren, useImperativeHandle, useRef } from 'react';
-import ViewShot from 'react-native-view-shot';
+import ViewShot, { ViewShotRef } from 'react-native-view-shot';
export type RecapCaptureFrameHandle = {
capture: () => Promise;
@@ -9,7 +9,7 @@ type RecapCaptureFrameProps = PropsWithChildren;
export const RecapCaptureFrame = forwardRef(
function RecapCaptureFrame({ children }, ref) {
- const viewShotRef = useRef(null);
+ const viewShotRef = useRef(null);
useImperativeHandle(ref, () => ({
capture: () => viewShotRef.current?.capture?.() ?? Promise.resolve(undefined),
diff --git a/src/mock-server/delay.ts b/src/mock-server/delay.ts
index c165728..1eac49a 100644
--- a/src/mock-server/delay.ts
+++ b/src/mock-server/delay.ts
@@ -1,8 +1,15 @@
import { MockDelayOptions, MockEndpointId } from '@/mock-server/types';
+import { useDevToolsStore } from '@/store/devToolsStore';
const DEFAULT_DELAY_MS = 300;
function getConfiguredDelayMs() {
+ const runtimeDelayMs = useDevToolsStore.getState().mockDelayMs;
+
+ if (typeof runtimeDelayMs === 'number') {
+ return runtimeDelayMs;
+ }
+
const rawValue = process.env.EXPO_PUBLIC_MOCK_API_DELAY_MS;
const parsedValue = rawValue ? Number(rawValue) : DEFAULT_DELAY_MS;
@@ -25,6 +32,15 @@ function shouldFailEndpoint(endpointId: MockEndpointId, shouldFail?: boolean) {
return true;
}
+ const runtimeState = useDevToolsStore.getState();
+
+ if (
+ runtimeState.failAllEndpoints ||
+ runtimeState.failedEndpointIds.includes(endpointId)
+ ) {
+ return true;
+ }
+
const failedEndpointSet = getFailedEndpointSet();
return failedEndpointSet.has('*') || failedEndpointSet.has(endpointId);
diff --git a/src/providers/AppProviders.tsx b/src/providers/AppProviders.tsx
index 071db87..3d3b9d7 100644
--- a/src/providers/AppProviders.tsx
+++ b/src/providers/AppProviders.tsx
@@ -3,6 +3,15 @@ import { PropsWithChildren } from 'react';
import { queryClient } from '@/providers/queryClient';
+const DevTestManager = __DEV__
+ ? require('@/components/dev/DevTestManager').DevTestManager
+ : undefined;
+
export function AppProviders({ children }: PropsWithChildren) {
- return {children};
+ return (
+
+ {children}
+ {DevTestManager ? : null}
+
+ );
}
diff --git a/src/store/devToolsStore.ts b/src/store/devToolsStore.ts
new file mode 100644
index 0000000..97359b3
--- /dev/null
+++ b/src/store/devToolsStore.ts
@@ -0,0 +1,44 @@
+import { create } from 'zustand';
+
+import { MockEndpointId } from '@/mock-server/types';
+
+export const mockEndpointIds: MockEndpointId[] = [
+ 'home.featuredPlaylists',
+ 'home.moodRecommendations',
+ 'home.recentMusicLogs',
+ 'playlist.detail',
+ 'recap.list',
+ 'recap.share',
+ 'tour.nearbyPlaces',
+];
+
+type DevToolsState = {
+ failedEndpointIds: MockEndpointId[];
+ failAllEndpoints: boolean;
+ mockDelayMs?: number;
+ resetMockRuntime: () => void;
+ setFailAllEndpoints: (failAllEndpoints: boolean) => void;
+ setMockDelayMs: (mockDelayMs?: number) => void;
+ toggleFailedEndpoint: (endpointId: MockEndpointId) => void;
+};
+
+export const useDevToolsStore = create((set) => ({
+ failedEndpointIds: [],
+ failAllEndpoints: false,
+ mockDelayMs: undefined,
+ resetMockRuntime: () =>
+ set({
+ failedEndpointIds: [],
+ failAllEndpoints: false,
+ mockDelayMs: undefined,
+ }),
+ setFailAllEndpoints: (failAllEndpoints) => set({ failAllEndpoints }),
+ setMockDelayMs: (mockDelayMs) => set({ mockDelayMs }),
+ toggleFailedEndpoint: (endpointId) =>
+ set((state) => ({
+ failedEndpointIds: state.failedEndpointIds.includes(endpointId)
+ ? state.failedEndpointIds.filter((id) => id !== endpointId)
+ : [...state.failedEndpointIds, endpointId],
+ failAllEndpoints: false,
+ })),
+}));
diff --git a/src/store/playerStore.ts b/src/store/playerStore.ts
index 68bdb7f..b05a0d3 100644
--- a/src/store/playerStore.ts
+++ b/src/store/playerStore.ts
@@ -9,6 +9,7 @@ type PlayerState = {
isPlaying: boolean;
playlistId?: string;
source: PlayerSource;
+ clearTrack: () => void;
setTrack: (track: Track, playlistId?: string) => void;
toggle: () => void;
};
@@ -16,6 +17,13 @@ type PlayerState = {
export const usePlayerStore = create((set) => ({
isPlaying: false,
source: 'none',
+ clearTrack: () =>
+ set({
+ currentTrack: undefined,
+ isPlaying: false,
+ playlistId: undefined,
+ source: 'none',
+ }),
setTrack: (track, playlistId) =>
set({
currentTrack: track,