create-saas-starter-workspace 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +17 -0
  3. package/README.md +19 -0
  4. package/bin/index.mjs +201 -0
  5. package/package.json +25 -0
  6. package/src/scaffold.mjs +109 -0
  7. package/src/validate.mjs +22 -0
  8. package/templates/workspace/.claude/settings.json +44 -0
  9. package/templates/workspace/.claude/skills/bridge-guide/SKILL.md +71 -0
  10. package/templates/workspace/.claude/skills/eas-deploy-guide/SKILL.md +107 -0
  11. package/templates/workspace/.claude/skills/go-live/SKILL.md +66 -0
  12. package/templates/workspace/.claude/skills/kickoff/SKILL.md +72 -0
  13. package/templates/workspace/.claude/skills/launch/SKILL.md +69 -0
  14. package/templates/workspace/.claude/skills/native-app-guide/SKILL.md +102 -0
  15. package/templates/workspace/.claude/skills/preview/SKILL.md +43 -0
  16. package/templates/workspace/.claude/skills/probe/SKILL.md +17 -0
  17. package/templates/workspace/.claude/skills/release/SKILL.md +51 -0
  18. package/templates/workspace/.claude/skills/sketch/SKILL.md +19 -0
  19. package/templates/workspace/.claude/skills/store-release-guide/SKILL.md +239 -0
  20. package/templates/workspace/.claude/skills/vercel-cron/SKILL.md +17 -0
  21. package/templates/workspace/.claude/skills/warmup/SKILL.md +33 -0
  22. package/templates/workspace/AGENTS.md +39 -0
  23. package/templates/workspace/CLAUDE.md +2 -0
  24. package/templates/workspace/LICENSE +17 -0
  25. package/templates/workspace/README.md +20 -0
  26. package/templates/workspace/START-HERE.md +52 -0
  27. package/templates/workspace/gitignore +18 -0
  28. package/templates/workspace/mobile/.env.example +25 -0
  29. package/templates/workspace/mobile/AGENTS.md +69 -0
  30. package/templates/workspace/mobile/App.tsx +47 -0
  31. package/templates/workspace/mobile/CLAUDE.md +2 -0
  32. package/templates/workspace/mobile/LICENSE +17 -0
  33. package/templates/workspace/mobile/README.md +73 -0
  34. package/templates/workspace/mobile/app.config.ts +153 -0
  35. package/templates/workspace/mobile/assets/android-icon-background.png +0 -0
  36. package/templates/workspace/mobile/assets/android-icon-foreground.png +0 -0
  37. package/templates/workspace/mobile/assets/android-icon-monochrome.png +0 -0
  38. package/templates/workspace/mobile/assets/favicon.png +0 -0
  39. package/templates/workspace/mobile/assets/icon.png +0 -0
  40. package/templates/workspace/mobile/assets/splash-icon.png +0 -0
  41. package/templates/workspace/mobile/docs/web-adapter/README.md +130 -0
  42. package/templates/workspace/mobile/docs/web-adapter/route-app-bridge.ts +235 -0
  43. package/templates/workspace/mobile/eas.json +31 -0
  44. package/templates/workspace/mobile/gitignore +45 -0
  45. package/templates/workspace/mobile/index.ts +12 -0
  46. package/templates/workspace/mobile/package.json +38 -0
  47. package/templates/workspace/mobile/pnpm-lock.yaml +5201 -0
  48. package/templates/workspace/mobile/src/auth/LoginScreen.tsx +192 -0
  49. package/templates/workspace/mobile/src/bridge/capabilities.test.ts +44 -0
  50. package/templates/workspace/mobile/src/bridge/capabilities.ts +42 -0
  51. package/templates/workspace/mobile/src/bridge/contract.test.ts +49 -0
  52. package/templates/workspace/mobile/src/bridge/contract.ts +146 -0
  53. package/templates/workspace/mobile/src/bridge/messaging.test.ts +49 -0
  54. package/templates/workspace/mobile/src/bridge/messaging.ts +33 -0
  55. package/templates/workspace/mobile/src/bridge/reader.test.ts +52 -0
  56. package/templates/workspace/mobile/src/bridge/reader.ts +31 -0
  57. package/templates/workspace/mobile/src/bridge/router.test.ts +124 -0
  58. package/templates/workspace/mobile/src/bridge/router.ts +89 -0
  59. package/templates/workspace/mobile/src/config/env.ts +51 -0
  60. package/templates/workspace/mobile/src/i18n.ts +71 -0
  61. package/templates/workspace/mobile/src/session/secureSession.ts +63 -0
  62. package/templates/workspace/mobile/src/session/sessionHandoff.ts +151 -0
  63. package/templates/workspace/mobile/src/ui/ErrorView.tsx +75 -0
  64. package/templates/workspace/mobile/src/ui/LoadingView.tsx +38 -0
  65. package/templates/workspace/mobile/src/ui/OfflineView.tsx +73 -0
  66. package/templates/workspace/mobile/src/webview/Host.tsx +353 -0
  67. package/templates/workspace/mobile/src/webview/linkBoundary.test.ts +57 -0
  68. package/templates/workspace/mobile/src/webview/linkBoundary.ts +58 -0
  69. package/templates/workspace/mobile/tsconfig.json +8 -0
  70. package/templates/workspace/mobile/vitest.config.ts +14 -0
  71. package/templates/workspace/package.json +9 -0
  72. package/templates/workspace/scripts/doctor.mjs +291 -0
  73. package/templates/workspace/ssb/README.md +10 -0
  74. package/templates/workspace/ssb/contract.ts +146 -0
  75. package/templates/workspace/ssb/reader.ts +31 -0
  76. package/templates/workspace/web/.env.example +39 -0
  77. package/templates/workspace/web/.gitattributes +1 -0
  78. package/templates/workspace/web/.github/workflows/ci.yml +61 -0
  79. package/templates/workspace/web/.vscode/settings.json +8 -0
  80. package/templates/workspace/web/AGENTS.md +103 -0
  81. package/templates/workspace/web/CLAUDE.md +2 -0
  82. package/templates/workspace/web/DESIGN.md +18 -0
  83. package/templates/workspace/web/LICENSE +17 -0
  84. package/templates/workspace/web/README.md +48 -0
  85. package/templates/workspace/web/app/error.tsx +28 -0
  86. package/templates/workspace/web/app/favicon.ico +0 -0
  87. package/templates/workspace/web/app/global-error.tsx +19 -0
  88. package/templates/workspace/web/app/globals.css +130 -0
  89. package/templates/workspace/web/app/layout.tsx +33 -0
  90. package/templates/workspace/web/app/not-found.tsx +12 -0
  91. package/templates/workspace/web/app/page.tsx +11 -0
  92. package/templates/workspace/web/components/ui/button.tsx +58 -0
  93. package/templates/workspace/web/components.json +25 -0
  94. package/templates/workspace/web/docs/ENVIRONMENTS.md +102 -0
  95. package/templates/workspace/web/docs/LIMITS.md +54 -0
  96. package/templates/workspace/web/eslint.config.mjs +46 -0
  97. package/templates/workspace/web/features/.gitkeep +0 -0
  98. package/templates/workspace/web/gitignore +51 -0
  99. package/templates/workspace/web/instrumentation-client.ts +16 -0
  100. package/templates/workspace/web/instrumentation.ts +12 -0
  101. package/templates/workspace/web/lib/app-env.ts +12 -0
  102. package/templates/workspace/web/lib/bridge/contract.ts +146 -0
  103. package/templates/workspace/web/lib/bridge/reader.ts +31 -0
  104. package/templates/workspace/web/lib/env.server.ts +33 -0
  105. package/templates/workspace/web/lib/env.ts +21 -0
  106. package/templates/workspace/web/lib/logger.ts +32 -0
  107. package/templates/workspace/web/lib/supabase/admin.ts +14 -0
  108. package/templates/workspace/web/lib/supabase/client.ts +9 -0
  109. package/templates/workspace/web/lib/supabase/server.ts +24 -0
  110. package/templates/workspace/web/lib/utils.ts +6 -0
  111. package/templates/workspace/web/next.config.ts +16 -0
  112. package/templates/workspace/web/npmrc +14 -0
  113. package/templates/workspace/web/package.json +60 -0
  114. package/templates/workspace/web/pnpm-lock.yaml +9155 -0
  115. package/templates/workspace/web/postcss.config.mjs +7 -0
  116. package/templates/workspace/web/sentry.edge.config.ts +9 -0
  117. package/templates/workspace/web/sentry.server.config.ts +9 -0
  118. package/templates/workspace/web/supabase/migrations/.gitkeep +0 -0
  119. package/templates/workspace/web/tests/setup.ts +1 -0
  120. package/templates/workspace/web/tests/utils.test.ts +12 -0
  121. package/templates/workspace/web/tsconfig.json +35 -0
  122. package/templates/workspace/web/vercel.json +6 -0
  123. package/templates/workspace/web/vitest.config.ts +15 -0
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: go-live
3
+ description: Provision GitHub private repo, Vercel project, Supabase dev/prod, Sentry. Wire env across 3 environments + GitHub Secrets. First deploy and security audit. Use when user runs /go-live after /warmup.
4
+ ---
5
+
6
+ # /go-live
7
+
8
+ dev/prod 클라우드 환경을 뚫어 첫 배포를 진행한다. 최신 외부 서비스 문서를 먼저 탐색하여 올바른 흐름을 이해한다.
9
+
10
+ > 이 워크스페이스는 web+app이고, 배포 단위는 `web/`다. GitHub repo·`vercel link`·Supabase·Sentry·`git push`는 모두 `web/` 안에서 수행한다. 워크스페이스 루트에서 수행하지 않는다. 먼저 `cd web/`. `mobile/`은 별도이며 앱 단계 `/kickoff`에서 다룬다.
11
+
12
+ ## 1. 이름·GitHub repo
13
+
14
+ 이름을 먼저 결정한다. 이후 모든 리소스가 이 이름을 기반으로 한다. private repo를 생성한다. secret scanning push protection은 private free에서 미지원이고 GitHub Advanced Security 유료 기능이다. 9-6에서 점검만 skip한다.
15
+
16
+ ## 2. Supabase dev + prod
17
+
18
+ `<name>-dev`와 `<name>-prod`를 생성한다. Region과 DB password는 Owner와 협의한다. ref·url·publishable·secret을 Management API로 추출한다. 단, 응답 필드명이 버전마다 달라질 수 있으니 jq로 탐색해 적응한다.
19
+
20
+ dev Auth redirect URL은 즉시 설정한다(`localhost:3000/**` + `https://*.vercel.app/**`). prod redirect는 7-1로 미룬다. 실 prod 도메인이 `<name>.vercel.app`이 아닐 수 있다.
21
+
22
+ ## 3. Vercel link + env 주입
23
+
24
+ `vercel link`를 실행한다. production은 prod Supabase + `SUPABASE_PROD_REF`, preview·development은 dev + `SUPABASE_DEV_REF`로 채운다. 끝나면 `vercel env pull`로 `.env.local`을 동기화한다.
25
+
26
+ ## 4. Sentry org·project + env 4종
27
+
28
+ config 파일은 템플릿에 이미 있다. env만 채우면 끝나며 wizard는 불필요하다. `sentry-cli`로 org·project를 조회하거나 생성한다. auth token은 sentry-cli로 발급할 수 없다. Owner가 대시보드(Settings → Auth Tokens)에서 발급해 전달한다.
29
+
30
+ env 4종(`NEXT_PUBLIC_SENTRY_DSN`·`SENTRY_ORG`·`SENTRY_PROJECT`·`SENTRY_AUTH_TOKEN`)을 Vercel 3환경 + GitHub Secrets 모두에 주입한 뒤 `vercel env pull`로 로컬을 동기화한다.
31
+
32
+ ## 5. `logs` 테이블 마이그레이션
33
+
34
+ 감사 로그용이다. `service_role`만 insert하는 정책과 RLS를 동봉한다. dev는 자동 적용하고, Owner confirm 후 prod에 적용한다.
35
+
36
+ ## 6. GitHub Secrets
37
+
38
+ CI 빌드용이다. `.env.local`에서 읽어 `gh secret set`으로 주입한다. publishable + URL + Sentry 4종만 넣는다. secret key와 DB ref는 CI 컴파일에 불필요하다(보안 표면을 최소화한다).
39
+
40
+ ## 7. 첫 푸시·배포
41
+
42
+ main에 push하면 CI와 Vercel 배포가 동시에 트리거된다.
43
+
44
+ ## 7-1. prod Auth redirect URL 갱신
45
+
46
+ 배포 후 실 production 도메인을 Vercel CLI/API로 확인한다. prod Supabase의 `site_url`·`uri_allow_list`를 갱신한다.
47
+
48
+ ## 8. Owner 직접 (Hobby 한도)
49
+
50
+ - CI Required check: Vercel Deployment Checks API는 Pro 전용이다. Hobby에서는 GitHub repo Settings → Branches → branch protection rule에 CI workflow를 Required status check로 지정한다(CI 통과 전 main merge를 차단한다).
51
+ - Preview Deployment Protection: Vercel 대시보드에서 Preview에 Vercel Authentication을 ON으로 켜 preview URL 크롤러를 차단한다. Hobby Standard에서 가능하며 external user 1명으로 제한된다.
52
+
53
+ ## 9. 보안 진단
54
+
55
+ | # | 검사 | 실패 시 |
56
+ |---|---|---|
57
+ | 1 | `.env*` gitignored | `.gitignore` 패치 |
58
+ | 2 | secret key가 `web/app/`·`web/components/`에 노출 (grep) | **critical** |
59
+ | 3 | `NEXT_PUBLIC_` 접두사가 secret·service·token에 | **critical** |
60
+ | 4 | repo visibility = PRIVATE | confirm 후 전환 |
61
+ | 5 | 8단계 대시보드 설정 완료 | Owner에 확인 |
62
+ | 6 | secret scanning push protection 활성 | private+free는 미지원(GitHub Advanced Security 유료) — 점검 자체 skip |
63
+
64
+ ## confirm 경계
65
+
66
+ prod `db push` (5)와 public→private 전환 (9-4)은 Owner confirm을 받는다. 그 외는 자동이다.
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: kickoff
3
+ description: "Configure the workspace's already-present mobile/ shell to wrap your deployed web — a one-time bootstrap: interview what to build (identity, tabs, features, login), give an honest webview-fit verdict (advisory, never blocking) and disclose up-front cost (Apple $99 / Google $25 / EAS limits) before any effort, set the app identity + deployed web URL, connect login, pick initial capabilities, and leave a plan. Use when appifying a deployed web for the first time, or invokes /kickoff."
4
+ user-invocable: true
5
+ ---
6
+
7
+ # /kickoff
8
+
9
+ 배포된 웹을 처음으로 앱으로 감싸는 1회 부트스트랩이다. 킥오프 미팅 한 흐름으로 진행한다. 이해(intake), 정직한 진단·비용, 구성(정체성·URL·로그인·능력), 계획·인계 순서다.
10
+
11
+ `mobile/`은 이 워크스페이스에 이미 들어 있다. `/kickoff`은 그것을 새로 만들지 않고, 당신의 배포된 웹에 맞춰 설정하는 1회 셋업이다. 계약(`contract.ts`·`reader.ts`)도 이미 `web/lib/bridge/`에 바이트 단위로 동일하게 설치돼 있다. `web/`은 배포돼 있어야 한다(`/go-live` 후 `git push`로 prod LIVE). 진입 루트(워크스페이스)에서 작동하며 `mobile/`·`web/`은 cwd 하위 서브디렉토리로 다룬다.
12
+
13
+ > 이미 한 번 구성했다면 부트스트랩 대상이 아니다. 미리보기는 `/preview`, 업데이트는 `/release`로 안내한다.
14
+
15
+ 폼이 아니라 대화로, 한 번에 하나씩, 추천값을 제시하며 묻는다. 비용은 먼저 알린다. 제한은 정직하게 고지한다. 끝에 명확한 다음 단계를 남긴다.
16
+
17
+ ## 1. 웹 감지 · intake (이해)
18
+
19
+ `web/`과 그 prod 배포 URL을 확인한다(앱이 띄울 단일 타깃). 그다음 핵심 5개를 하나씩 묻는다.
20
+
21
+ 1. 앱 정체성. 표시 이름·번들 ID·스킴·아이콘.
22
+ 2. 화면/탭 구조. 웹의 어떤 경로가 앱의 진입·탭이 되는가.
23
+ 3. 기능. 무엇을 쓰고 싶은가(능력 분류는 6단계에서 정리한다).
24
+ 4. 로그인. 이메일/비번·매직링크인지, 소셜 로그인(구글/애플)을 원하는지.
25
+ 5. 비용·셋업 예고. 2단계에서 공개한다고 미리 알린다.
26
+
27
+ ## 2. 정직한 webview-fit 판정 + 비용 (진단)
28
+
29
+ intake ①~③로 webview 적합도를 스스로 판정하고 확인만 받는다(막지 않는다, 최종 결정은 Owner). 핵심 경험이 60fps 제스처·카메라 AR·미디어 캡처거나, 앱 자체가 주력 상품이거나 게임이면 네이티브로 따로 만들 것을 권고한다. 이 셸은 그 제작에 맞지 않다.
30
+
31
+ 이어서 들어가는 비용을 노력을 들이기 전에 공개한다.
32
+
33
+ - Apple Developer $99/년, Google Play $25(둘 다 환불 불가).
34
+ - EAS 무료 티어 한도(동시성 1·월 빌드 수·큐 대기).
35
+
36
+ Owner confirm. 비용을 알린 뒤 계속할지 확정받고서야 다음 단계로 간다.
37
+
38
+ ## 3. mobile/ 구성
39
+
40
+ `mobile/`은 이미 워크스페이스에 있으니 스캐폴드 단계는 없다. 여기서는 셸을 당신 웹에 맞춰 설정한다.
41
+
42
+ - intake ①의 정체성(이름·번들 ID·스킴·아이콘)을 `mobile/app.config.ts`에 반영한다. 번들 ID·스킴은 확정 뒤 바꾸기 어려우니 Owner confirm으로 굳힌다.
43
+ - `EXPO_PUBLIC_WEB_URL`을 배포된 prod 웹 주소(https)로 설정한다. `mobile/.env.example`·`mobile/eas.json`의 자리표시 값은 그대로 빌드하면 시작 시 에러로 막힌다(흰 화면 대신 정직하게 실패하도록 설계됨). `EXPO_PUBLIC_*`는 빌드 시점에 인라인되므로 주소를 바꾸면 재빌드가 필요하다.
44
+
45
+ ## 4. 웹 어댑터 (web/)
46
+
47
+ 계약(`contract.ts`·`reader.ts`)은 이미 `web/lib/bridge/`에 바이트 단위로 동일하게 설치돼 있고(CI checksum이 드리프트를 막는다) 다시 복사하지 않는다. 웹이 앱과 실제로 대화하게 만드는 얇은 옵트인 레이어만 필요할 때 추가한다. 규약·보안 불변식은 인라인하지 않고 `bridge-guide`를 따른다.
48
+
49
+ 세션 핸드오프는 fast-follow다. v1은 호출자가 없다. 기본 로그인은 웹뷰 안의 웹 로그인이 그대로 동작하고, 핸드오프 라우트(POST `/auth/app-bridge` + nonce)는 네이티브 소셜 로그인을 켤 때만 살아난다. 토큰은 브릿지·URL로 보내지 않는다(1회용 nonce만 보낸다).
50
+
51
+ ## 5. 로그인 연결
52
+
53
+ intake ④에서 고른 길을 잇는다. 이메일/비번·매직링크는 웹뷰 안 웹 로그인이 그대로라 추가 배선이 없다. 소셜 로그인(구글/애플)은 fast-follow 경로다. 웹뷰 안 OAuth는 막혀 있어 네이티브로 구현하며, 콘솔·자격증명 셋업은 `/launch`가 맡는다. 소셜 로그인을 쓰면 Apple 4.8상 "애플로 로그인"도 함께 필요함을 고지한다.
54
+
55
+ ## 6. 초기 능력 선택
56
+
57
+ 푸시를 켤지 정한다(켜기 권장). v1에서 켤 수 있는 네이티브 능력은 푸시뿐이다. 파일 업로드·공유·딥링크·보안 저장은 셸이 기본으로 갖춘다.
58
+
59
+ 켜면 `expo-notifications`·권한·토큰 등록·`PUSH_TOKEN_UPDATED` 송출·웹의 `push.v1` 게이트를 배선한다(네이티브 변경이므로 재빌드한다). 계약·셸 cold-start 규약은 `bridge-guide`·`native-app-guide`, 스토어 푸시 자격증명(APNs·FCM)은 `/launch`가 맡는다.
60
+
61
+ ## 7. 계획 · 인계
62
+
63
+ 단일 plan 파일을 남긴다. 정체성·탭·기능·로그인·능력·비용·후속을 적은 결정 기록이며, 이후 빌드 흐름의 입력이 된다.
64
+
65
+ 다음은 화면을 만들며 앱을 보는 단계인 `/preview`로 이어진다. 깊은 요구사항은 `/probe`에 위임한다.
66
+
67
+ ## confirm 경계
68
+
69
+ - 비용 announce 후 계속 여부(Apple $99 · Google $25 · EAS 한도). Owner confirm.
70
+ - webview-fit 부적합. 권고하되 막지 않으며, 계속 여부는 Owner가 결정한다.
71
+ - 번들 ID·스킴 확정. 사실상 비가역이므로 Owner confirm.
72
+ - 첫 스토어 로그인·2FA·결제·콘솔 셋업은 이 스킬 범위 밖이다. `/launch`가 맡는다.
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: launch
3
+ description: Set up App Store Connect and Play Console for the first release — developer accounts, app records, signing keys, store listing, privacy declarations — then the first manual submit-for-review. Use when the Owner is ready to ship to the stores for the first time, or invokes /launch.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # /launch
8
+
9
+ 앱을 앱스토어·플레이스토어에 처음 올리기 위한 1회 출시 준비다. 개발자 계정, 앱 레코드, 서명 키, 스토어 리스팅, 프라이버시 선언을 갖추고 첫 수동 심사 제출까지 데려간다. 비개발자에게 가장 큰 난관이라, go-live처럼 단계별로 확인받으며 진행한다.
10
+
11
+ 상세 규약·콘솔 절차·심사 통과 전략은 `store-release-guide`가 정본이고, EAS 빌드·제출 명령은 `eas-deploy-guide`가 정본이다. 이 스킬은 순서와 게이트만 잡고, 세부는 가이드를 가리킨다.
12
+
13
+ ## 진행 방식 (3단계)
14
+
15
+ 1. **읽기전용 조사**: 계정 유무, 기존 자격증명, 배포된 웹의 컴플라이언스 표면(계정 삭제·트래커)을 먼저 살핀다.
16
+ 2. **일괄 플랜**: AskUserQuestion으로 결정과 비용을 한 번에 묻는다.
17
+ 3. **confirm 실행**: 비가역·돈·2FA가 걸린 일만 Owner confirm 뒤 진행한다.
18
+
19
+ ## 누가 무엇을 하나 (가역성)
20
+
21
+ - 🟢 AI / CLI: 자격증명 파일 작업, EAS 빌드·제출, `eas.json`의 public 값.
22
+ - 🟡 Owner 대시보드 (AI가 정확히 안내): 첫 Apple 로그인과 2FA, 스크린샷, App Privacy·Data Safety 라벨, Play 서비스 계정 JSON.
23
+ - 🔴 Owner 직접 (대리 불가): 카드 결제, 신원 검증, 멤버십 가입.
24
+
25
+ ## 1. 개발자 계정 — 가장 먼저 (🔴 Owner)
26
+
27
+ Apple은 연 $99, Google은 $25, 둘 다 환불 불가다. Individual/Personal을 권장한다(D-U-N-S를 피하고 신원검증만 거친다). 개인 법적 이름이 공개 판매자명이 되므로, 한 줄 confirm을 받는다.
28
+
29
+ 🔴 트랩 — 리드타임을 지배: Google 신규 개인계정은 production 전에 비공개(closed) 테스트 **12명·14일 연속**이 강제다(내부 테스트는 카운트하지 않는다). 이게 출시 전체에서 가장 긴 지연이므로, **Google Play Console 가입을 가장 먼저** 해 둔다. Apple도 결제 후 멤버십 활성화에 시간이 걸린다. 두 계정 모두 코드 작업 전에 시작한다.
30
+
31
+ ## 2. 앱 레코드·서명 자격증명 (🟢 대부분 자동)
32
+
33
+ EAS가 iOS 인증서·프로비저닝·APNs 키와 Android 업로드 키스토어를 자동 생성한다(`.cer`/`.jks`는 손대지 않는다). iOS ASC API 키도 EAS 자동 생성이 기본이며, Account Holder의 1회 'Request Access' 약관 동의가 선행이다. iOS는 첫 제출이 ASC 앱 레코드를 자동 생성해 TestFlight까지 간다.
34
+
35
+ 🔴 트랩: Android 첫 AAB는 Play Console에 **수동으로 1회 업로드**해야 한다(Play API는 첫 릴리스를 만들지 못한다). 첫 자동 제출 실패는 정상이니 선고지한다. 이후부터 `eas submit -p android`가 동작한다. Play 제출용 서비스 계정 JSON은 항상 수동(🟡)이며 EAS 서버에 저장한다(`eas.json`에 시크릿 경로를 넣지 않는다). 절차는 `store-release-guide`.
36
+
37
+ ## 3. 푸시 자격증명 (kickoff에서 푸시를 켰을 때만)
38
+
39
+ 앱은 Expo Push Token만 쓴다. iOS APNs 키는 EAS 자동(🟢)이고, Android FCM v1 서비스 계정은 Firebase Console에서 수동 생성한 뒤 EAS에 업로드한다(🟡). `google-services.json`(public·커밋), FCM SA JSON(시크릿), Play 제출 SA JSON은 각각 별개 파일이니 혼동하지 않는다. 매핑은 `store-release-guide`.
40
+
41
+ ## 4. 소셜 로그인 콘솔 (소셜을 켰을 때만, 🟡)
42
+
43
+ Google OAuth 클라이언트는 Web·iOS·Android 3종이고, 코드에는 webClientId(Web)만 넣는다. Apple은 게이트로 네이티브 idToken(기본·bundle ID만)을 권장한다. 한국 개발자는 2026-01-01부터 웹 OAuth용 Services ID에 server-to-server 엔드포인트가 강제되므로, 네이티브 방식이 그 부담을 피한다.
44
+
45
+ 🟡 트랩: iOS reversed URL scheme은 플러그인에 **수동으로 붙여넣는다**(자동 계산이 아니다). SHA-1 지문은 **모든** 서명 키를 등록한다(EAS 빌드 키 포함, 일부만 하면 `DEVELOPER_ERROR`). 소셜을 켜면 Apple 4.8에 따라 Sign in with Apple도 동등하게 들어가고, Supabase Apple provider 실배선까지 점검한다. 절차는 `store-release-guide`.
46
+
47
+ ## 5. 스토어 리스팅·컴플라이언스 (🟡 수동)
48
+
49
+ 스크린샷, 연령등급(IARC), App Privacy(iOS)·Data Safety(Android) 라벨은 수동이다. 세 프라이버시 산출물(ASC 라벨·Data Safety·`PrivacyInfo.xcprivacy`)은 taxonomy가 달라 복붙이 안 되므로, 단일 데이터 인벤토리에서 각각 매핑한다.
50
+
51
+ 게이트로 점검할 항목:
52
+ - **계정 삭제 (5.1.1(v))**: 배포된 웹에 인앱 삭제 경로가 있고, 네이티브 소셜 포함 모든 로그인에서 도달 가능한지 확인한다(웹뷰 렌더는 인앱으로 인정된다).
53
+ - **ATT**: 기본은 No다. 단, 웹뷰가 로드하는 배포된 웹의 트래커(GA4·Pixel 등)는 개발자 책임이므로, 배포 웹 스크립트를 실점검한다.
54
+
55
+ 세부 기준은 `store-release-guide`.
56
+
57
+ ## 6. 첫 제출 → 공개 심사 (🔴 트랩 + confirm)
58
+
59
+ production 빌드 하나를 만들어 두 스토어 테스트 채널로 보낸다(iOS는 TestFlight, Android는 내부 트랙). 빌드·제출 명령은 `eas-deploy-guide`.
60
+
61
+ 🔴 트랩: `eas submit`은 **공개 심사를 제출하지 않는다**. 테스트 채널까지만 올린다. 공개 출시는 수동 + 명시 confirm이다:
62
+ - **iOS**: App Store Connect에서 "Submit for Review".
63
+ - **Android**: 비공개 테스트 12명·14일 게이트를 통과한 뒤 production으로 승급.
64
+
65
+ 같은 production 빌드를 그대로 공개로 승급한다. 전용 preview 빌드를 따로 만들지 않는다.
66
+
67
+ ## confirm 경계
68
+
69
+ 개발자 계정 가입·결제 (1, 🔴), 공개 심사 제출과 production 승급 (6, 🔴)은 Owner confirm을 받는다. 자격증명 파일 작업과 테스트 채널 제출은 자동(🟢)이다. 첫 Apple 로그인과 2FA·라벨·SA JSON은 Owner가 대시보드에서 진행한다(🟡, AI가 안내).
@@ -0,0 +1,102 @@
1
+ ---
2
+ name: native-app-guide
3
+ description: 네이티브 셸(WebView 래퍼) 작업 시 참고. 앱 껍데기·WebView 호스트·로딩/에러/오프라인 화면·Android 뒤로가기·외부 링크/origin 경계(ALLOWED_ORIGINS)·딥링크 열기(URL 라우팅)·safe-area(노치)·파일 업로드 권한 배선·크래시 복구 작업 시 자동 로드됩니다. (앱↔웹 메시지/세션 핸드오프는 bridge-guide.)
4
+ user-invocable: false
5
+ ---
6
+
7
+ # 셸 레시피 — 앱 껍데기가 메우는 네이티브 공백
8
+
9
+ 배포된 prod 웹 https를 로드하는 얇은 WebView 셸이다. 모든 화면·기능·로그인은 웹이 가지고, 셸은 WebView가 막거나 못 하는 네이티브 공백만 메운다. 셸이 진짜 네이티브로 제공하는 건 다음이다. 오프라인 화면(유일하게 분명한 네이티브 전용 화면), 첫/하드 로드와 크래시 재마운트에 한정한 로딩·에러 오버레이, Android 하드웨어 뒤로가기, 외부 링크의 시스템 브라우저 라우팅, safe-area inset 주입, 카메라/마이크/파일 업로드 권한 배선(능력 자체는 웹·라이브러리의 것). 구현은 `src/webview/`에 모이고, 링크 경계는 `src/config/env.ts`의 `ALLOWED_ORIGINS`를 SSOT로 쓴다.
10
+
11
+ 셸은 4.2(최소기능, [App Review Guidelines](https://developer.apple.com/app-store/review/guidelines/))를 통과하도록 설계된 게 아니라 출발점이다. 4.2를 넘으려면 Owner가 진짜 네이티브 차별점을 더해야 한다. 흔한 다음 단계는 네이티브 푸시인데 v1엔 없다(contract.ts에 forward-compat `PUSH_TOKEN_UPDATED` 상수와 `push.v1` capability 자리만 있고, `expo-notifications`·토큰 취득·핸들러는 전무하다). 켜려면 AI가 expo-notifications를 추가하고, 권한·토큰을 등록하고, `PUSH_TOKEN_UPDATED`로 웹에 전달하고, cold-start는 딥링크 섹션의 가드를 배선한다. 자세한 4.2·스토어 심사는 `store-release-guide`.
12
+
13
+ ## 먼저: 추측하지 말고 확인
14
+
15
+ SDK 56 / RN 0.85 / React 19는 학습 데이터와 다르다. 네이티브 API를 쓰기 전에 설치된 타입(`node_modules/react-native-webview/lib/*.d.ts` 등)이나 v56 docs(https://docs.expo.dev/versions/v56.0.0/)를 읽어라.
16
+
17
+ - 이벤트 구독은 `subscription.remove()`로 정리한다(RN 0.85에서 `removeEventListener` 제거됨).
18
+ - 컴포넌트는 `React.JSX.Element`를 반환한다. strict TS를 쓰고 `any`를 회피한다.
19
+ - `react-native-url-polyfill/auto`가 `index.ts`에서 먼저 로드돼야 `new URL().origin`이 Hermes에서 동작한다(링크 allow-list의 전제).
20
+ - `EXPO_PUBLIC_WEB_URL`은 빌드 시점에 인라인된다(바꾸려면 재빌드). 인라인 출처는 클라우드 빌드=`eas.json` `build.<profile>.env`, 로컬 `expo start`=Owner가 만든 `.env`다. `.env`는 커밋하지 않고(`.gitignore`), 커밋되는 `.env.example`은 플레이스홀더(`https://your-app.vercel.app`)다. `env.ts` 가드는 protocol/hostname만 본다. 실기기인지 시뮬레이터인지 못 구분하므로 localhost URL도 가드는 통과하나 실기기에선 실패한다. "실기기=prod https"는 코드가 강제하는 불변식이 아니라 Owner/AI 규칙이다.
21
+
22
+ ## safe-area (노치 여백)
23
+
24
+ `env(safe-area-inset-*)`는 양 플랫폼 다 못 믿는다(Android WebView 138+가 0px, iOS WKWebView 첫 페인트 점프). 그래서 책임을 분리한다: 네이티브는 inset 값을 측정하고, 웹은 레이아웃을 맡는다.
25
+
26
+ 1. 네이티브가 `useSafeAreaInsets()`로 inset을 측정한다.
27
+ 2. WebView `<meta viewport>`에 `viewport-fit=cover`를 둔다.
28
+ 3. `injectedJavaScriptBeforeContentLoaded`로 그 값을 커스텀 CSS 변수(`--ssb-inset-top/right/bottom/left`, `env()`가 아님)에 주입한다.
29
+ 4. 웹이 그 변수로 패딩한다.
30
+
31
+ `SafeAreaProvider`에 `initialMetrics={initialWindowMetrics}`를 줘 첫 프레임 flash를 방지한다. px→dp→CSS px 변환에 주의한다. edge-to-edge는 SDK 56에서 강제 기본이다(Android 15/targetSdk 35). 상태바·내비바 아이콘 스타일은 `react-native-edge-to-edge`의 `SystemBars` 한 컴포넌트로 제어한다(App.tsx `<SystemBars style="dark" />`). SDK 56에서 `expo-status-bar`의 backgroundColor/translucent props는 제거됐고, Expo가 edge-to-edge에선 SystemBars를 권한다.
32
+
33
+ ## Android 뒤로가기
34
+
35
+ `canGoBack()`이면 `webView.goBack()` + `return true`. 아니면 `return false`로 OS가 종료·예측-뒤로를 처리하게 한다. 종료확인 Alert는 선택적 레거시다(Android 13/14 예측-뒤로와 충돌). cleanup은 `BackHandler.addEventListener(...).remove()`.
36
+
37
+ ## 링크 경계 (외부 링크)
38
+
39
+ 부수효과 없는 순수 분류자 `classifyLink(url, allowedOrigins)`(`src/webview/linkBoundary.ts`)가 단일 SSOT다. 셸의 모든 링크 판단이 이 한 곳을 공유한다: 내비게이션(`onShouldStartLoadWithRequest` + `onOpenWindow`)과 브릿지 OPEN_EXTERNAL. 보안 경계(origin 허용목록·스킴 게이트)이므로 네이티브 import 없이 Node(vitest)로 단위 테스트한다(`linkBoundary.test.ts`).
40
+
41
+ 세 가지 판정:
42
+ - 파싱 실패 → `"block"`(웹뷰에도 OS에도 안 넘김).
43
+ - http(s) + origin ∈ `ALLOWED_ORIGINS`(env.ts) → `"in-app"`.
44
+ - 그 외(허용 외 http(s) + `mailto:`/`tel:`/`geo:`/커스텀 스킴) → `"external"`.
45
+
46
+ 소비:
47
+ - 내비게이션(`routeNavigation`): `"in-app"`→`true`(로드) · `"external"`→`Linking.openURL`+`false` · `"block"`→`false`.
48
+ - OPEN_EXTERNAL(`bridgeCtx.openExternal`): 웹의 명시적 escape 명령이므로 분류자를 안전 게이트로만 쓴다. `!== "block"`이면 `Linking.openURL`로 연다(허용 origin이라도 시스템 브라우저로 연다. 허용목록은 암묵적 내비게이션만 통제하고 명시적 명령은 막지 않는다).
49
+
50
+ `new URL(url).origin` 등가 비교를 쓴다(`startsWith` 금지. 서브도메인 prefix 위장을 회피한다). 동일 origin은 반드시 in-app로 처리한다(Android #2819 유령 리로드 회피). `onOpenWindow`는 필수다(`_blank` 백지 방지). `setSupportMultipleWindows`는 설치된 라이브러리에서 기본 TRUE이고(CVE-2020-6506) 코드는 이 prop을 설정하지 않는다. 기본값에서 `onOpenWindow`가 동작하므로 prop을 세팅하지 마라. `injectedJavaScriptForMainFrameOnly={true}` + `onMessage`에서 ns 게이트(브릿지는 `bridge-guide`).
51
+
52
+ ## 딥링크 (현재 = 스캐폴드/미구현)
53
+
54
+ v1 현재 동작은 이렇다. 스킴은 OS가 앱으로 라우팅하도록 설정돼 있지만 inbound 핸들러가 없다(`getInitialURL`·url 리스너 없음). 그래서 앱은 딥링크된 경로로 가지 않고 기본 `WEB_URL`로만 연다. `expo-linking`은 외부 링크를 시스템 브라우저로 보내는 outbound 전용으로만 쓰인다(링크 경계 섹션).
55
+
56
+ inbound 딥링크를 켜고 싶을 때의 레시피(아직 만들지 않았고, 필요 시 AI가 배선한다):
57
+
58
+ - 진입점(foreground·cold-start·universal link)을 같은 봉투 파서로 수렴시킨다.
59
+ - cold-start: `expo-linking` `getInitialURL()`을 HELLO 후 적용한다.
60
+ - foreground: `Linking.addEventListener("url", ...)` → 구독은 `.remove()`로 정리한다.
61
+ - 푸시를 추가하면 cold-start에 `getLastNotificationResponseAsync`, 응답엔 in-memory one-shot 가드와 `clearLastNotificationResponseAsync`를 둘 다 둔다(전자=re-render 루프 방어, 후자=warm-remount stale-read 방어). `DEFAULT_ACTION_IDENTIFIER`를 확인한다.
62
+
63
+ ## 크래시 복구
64
+
65
+ WebView 프로세스가 죽으면 `key`를 증가시켜 통째 재마운트한다(`reload()`가 아니다. Android는 인스턴스 폐기+재생성을 요구한다).
66
+
67
+ - iOS: `onContentProcessDidTerminate`.
68
+ - Android: `onRenderProcessGone` — `return true` 필수(false면 앱이 같이 죽는다).
69
+ - 크래시 URL을 맹목 재로드하지 말고 `lastKnownUrl`을 복원한다.
70
+
71
+ ## 오프라인 · 당겨서 새로고침
72
+
73
+ 오프라인 화면은 유일하게 분명한 네이티브 전용 화면이다. 네트워크가 없으면 웹이 로드·fetch를 못 하므로 웹에 대응물이 없다.
74
+
75
+ - 라이브 연결 끊김은 `onError`로 못 잡는다(로드 실패만 잡는다). `@react-native-community/netinfo`가 필수다: `NetInfo.addEventListener(state => ...)` → 구독은 `.remove()`로 정리한다. 끊기면 오프라인 화면(`offline.*` i18n 키)을 표시한다.
76
+ - 당겨서 새로고침: iOS는 `pullToRefreshEnabled` 한 줄이다(라이브러리 prop, 기본 false). Android는 별도 배선이 필요하다(fast-follow. `pullToRefreshEnabled`는 iOS 전용).
77
+
78
+ ## 로딩 · 에러 오버레이 (첫/하드 로드 + 크래시 재마운트만)
79
+
80
+ 네이티브 로딩·에러 오버레이는 첫/하드 WebView 로드(빈 WebView)와 크래시 재마운트에만 덮는다. 인앱 Next.js SPA(클라이언트) 내비게이션은 네이티브 WebView에 보이지 않으므로, 페이지 전환 로딩과 인페이지/SPA 에러는 웹 자체 UI가 처리한다(셸이 중복 제공하지 않는다).
81
+
82
+ - 로딩: `startInLoadingState`/`renderLoading`을 쓰지 않는다(그 이름은 docs에만 있고 코드엔 없다). 직접 관리하는 `loading` state를 첫 `onLoadEnd`에서 해제한다(`loading` i18n).
83
+ - 에러: `onError`(네트워크/전송) + `onHttpError`(메인 프레임 에러 상태)만으로 에러 화면을 띄운다(`error.*` i18n + 재시도 버튼). 재시도 = `key` 증가 통째 재마운트(크래시 복구와 동일).
84
+ - 사용자 문자열은 전부 `src/i18n.ts` `t()`를 경유한다.
85
+
86
+ ## 카메라 · 마이크 · 파일 업로드 (능력은 웹·라이브러리, 셸은 권한만)
87
+
88
+ 능력 자체는 웹의 것(`getUserMedia`)이고 `<input type=file>` 피커는 라이브러리(`react-native-webview`)가 제공한다. 셸은 그게 동작하도록 권한 배선만 더한다(셸이 구현하는 기능이 아니다). https가 필수다.
89
+
90
+ - iOS usage string 3종: `NSCameraUsageDescription`·`NSPhotoLibraryUsageDescription`·`NSMicrophoneUsageDescription`(`NSPhotoLibraryAddUsageDescription`은 저장 구현 시만) + WebView `mediaCapturePermissionGrantType="grant"`. iOS는 usage string 누락 시 Apple이 런타임에 앱을 종료시킨다.
91
+ - Android: `CAMERA` 권한과 `<queries>` IMAGE_CAPTURE 인텐트 가시성(app.config.ts `withImageCaptureQuery` 플러그인). Android 11+ 패키지 가시성 때문에 둘은 별개 요구사항이다. 권한=카메라 사용, queries=카메라 앱 조회(`resolveActivity`). queries가 누락되면 갤러리·파일 선택은 되나 `<input type=file capture>`의 '카메라 촬영' 옵션이 안 뜬다(크래시는 아니다).
92
+ - 키보드 focus: `app.config.ts` `android.softwareKeyboardLayoutMode: "resize"` · iOS는 WebView prop `keyboardDisplayRequiresUserAction={false}`(Host.tsx).
93
+
94
+ ## 로그인 · 소셜(FAST-FOLLOW)
95
+
96
+ 로그인은 웹이 담당한다. 기본 상태에서 앱은 로그인에 아무것도 하지 않는다. 웹 로그인 화면이 웹뷰 안에 그대로 뜨고 쿠키·세션도 웹의 `@supabase/ssr`가 처리한다. 네이티브 로그인 모달(`src/auth/LoginScreen.tsx`)은 웹이 `REQUEST_SESSION_INSTALL`을 보낼 때만 뜨는 옵트인 스캐폴드이며, 유일하게 동작하는 버튼('이메일로 계속')은 모달을 닫아 웹 로그인으로 되돌릴 뿐이다(인증·재로드·네비게이션 없음). 소셜 원클릭(Google/Apple)은 disabled 'coming soon' 스텁이다(fast-follow).
97
+
98
+ 소셜을 켤 땐 웹뷰 안 OAuth로 하지 말고 네이티브로 구현한다: 네이티브 SDK로 idToken을 받아 세션 핸드오프(`requestSessionHandoff`, 상세는 bridge-guide). 이유는 UX가 아니라 동작 자체다. Google은 임베디드 웹뷰 OAuth를 차단하고([`disallowed_useragent`, 2021/2023 시행](https://developers.googleblog.com/upcoming-security-changes-to-googles-oauth-20-authorization-endpoint-in-embedded-webviews/)), [RFC 8252](https://datatracker.ietf.org/doc/html/rfc8252)도 임베디드 user-agent를 금지한다. 그래서 시스템 브라우저(ASWebAuthenticationSession/Custom Tabs)나 네이티브 자격증명 선택기로 빠져나가야 한다. Apple 4.8은 조건부다: 구글 등 서드파티 소셜을 제공할 때만 네이티브 Sign in with Apple도 함께 필요하다(이메일/비번만이면 미적용).
99
+
100
+ ## 세션 핸드오프 (요약 — 상세는 bridge-guide)
101
+
102
+ 서버 Set-Cookie + 303(네이티브 쿠키 주입이 아니다). URL `?token=`은 금지하고 1회용 nonce 교환을 쓴다. 로그아웃은 reload로 불충분하므로 `key` 증가 + iOS WebKit 쿠키/데이터스토어 클리어를 한다. ready 핸드셰이크는 웹 ack 단독으로 두지 않는다(`onMessage`는 fire-and-forget이라 iOS에서 드롭된다). 네이티브 타임아웃+재시도 fallback으로 흰 화면 hang을 방지한다.
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: preview
3
+ description: Build the app and ship that build to the store test channel (TestFlight on iOS, Play internal test on Android) so the Owner installs it on a real phone and sees it running against the prod web over https — no dedicated preview build, and Expo Go will not work because of native modules. Use when the Owner wants to preview the app while building it, or invokes /preview.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # /preview
8
+
9
+ 앱을 만드는 내내 반복하는 동작이다. Owner가 앱이 실제 폰에서 도는 모습을 본다.
10
+
11
+ 미리보기는 production 빌드 하나를 스토어 테스트 채널(iOS TestFlight, Android Play 내부 테스트)로 보내 실폰에서 확인한다. 그 빌드를 나중에 그대로 공개로 승급하므로 전용 preview 빌드를 따로 만들지 않는다. EAS 빌드 수를 절약하고, 실제 출시 아티팩트를 그대로 검증한다. 빌드 프로파일, 제출, OTA 판단 등 상세 흐름은 `eas-deploy-guide`를 따른다.
12
+
13
+ 두 가지가 항상 참이다.
14
+
15
+ - Expo Go로는 뜨지 않는다(네이티브 모듈). 진짜 빌드가 필요하다.
16
+ - 실기기는 prod 웹 https를 로드한다. localhost는 실폰에서 카메라, Web Crypto, Secure 쿠키를 소리 없이 깨뜨리는 함정이다(이유는 `eas-deploy-guide`).
17
+
18
+ ## 1. 첫 빌드 준비 (Owner)
19
+
20
+ - Expo 계정 로그인은 Owner가 직접 한다(2FA). 이후 빌드는 AI가 자동으로 돌린다.
21
+ - 스토어 테스트 채널은 개발자 계정이 있어야 쓴다. 아직 `/launch` 전이라 계정이 없으면 EAS 내부배포로 먼저 설치한다(iOS는 UDID 등록, `store-release-guide`). 계정과 채널 셋업은 `/launch`가 맡는다.
22
+
23
+ ## 2. 빌드 → 테스트 채널
24
+
25
+ ```bash
26
+ npx eas-cli build --profile production --platform all --auto-submit --no-wait
27
+ # iOS → TestFlight, Android → Play 내부 테스트
28
+ ```
29
+
30
+ 첫 빌드는 큐를 포함해 10–20분 걸린다(멈춘 게 아니다). 백그라운드 진행은 항상 `--no-wait`로 한다. JS만 바뀐 변경은 빌드 풀을 쓰지 않으니 재빌드 없이 OTA로 미리 본다(`eas-deploy-guide`).
31
+
32
+ ## 3. 실폰에서 확인
33
+
34
+ TestFlight(iOS) 또는 초대 링크(Android)로 설치해 앱을 연다. 앱이 prod 웹 https를 띄우고 화면이 돌면 성공이다. 로그인, 푸시처럼 실기기에서만 검증되는 항목은 `eas-deploy-guide` 플랫폼 매트릭스를 따른다.
35
+
36
+ ## 셸만 빠르게 고칠 때 (선택·고급)
37
+
38
+ 네이티브 셸(WebView 호스트 등)만 빠르게 반복할 땐 Mac이나 에뮬 시뮬레이터에 `development` 프로파일(Metro Fast Refresh)을 쓴다. 시뮬레이터에 한해 웹뷰를 localhost로 가리킬 수 있다(실기기는 금지). 배선은 `eas-deploy-guide`(빌드 프로파일)와 `native-app-guide`(셸 레시피)를 따른다. 대부분은 여기까지 가지 않는다. 웹 UI는 브라우저에서 고친다.
39
+
40
+ ## confirm 경계
41
+
42
+ - 첫 Expo 계정 로그인과 2FA는 Owner가 직접 한다(AI 대리 불가). 이후 빌드는 자동이다.
43
+ - 스토어 공개 승급은 하지 않는다. `/preview`는 테스트 채널까지만 책임지고, 공개 출시는 `/launch`와 `/release` 몫이다.
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: probe
3
+ description: Adaptive interview to probe an idea, plan, feature, data model, or any decision — surfacing gray areas, challenging assumptions, brainstorming alternatives. One question at a time, with a recommended answer. Use when the Owner wants to think through something with you, stress-test a design, or invokes /probe.
4
+ ---
5
+
6
+ # /probe
7
+
8
+ Owner와 공유된 이해에 도달할 때까지 집요하게 조사하라.
9
+ 회색지대를 없애야한다.
10
+
11
+ 결정들을 하나씩 짚어 내려가며, 서로 얽힌 의존 관계를 풀어라.
12
+
13
+ Owner의 인지부하를 줄이기 위해,
14
+ AskUserQuestion을 사용하여 한 번에 하나씩 질문하라.
15
+ 각 질문마다 네가 추천하는 답안도 표시하라.
16
+
17
+ 만약 어떤 질문이 현재 디렉토리를 탐색해서 답할 수 있다면, 대신 디렉토리를 탐색하라.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: release
3
+ description: Ship an app update the right way, every time after the first launch — most product changes ship as a plain web deploy (zero store action, since the app just re-loads the deployed web), JS-only shell changes go out as an instant OTA update, and only native, config, permission, or SDK changes need a rebuild with a marketing-version bump and a store resubmit. Use when the Owner wants to release an update after launch, or invokes /release.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # /release
8
+
9
+ 출시 뒤로 계속 반복하는 흐름이다. 변경할 때마다 부른다. 변경의 성격에 따라 길이 셋으로 갈린다. 웹은 언제나 SSOT이고, 앱은 배포된 웹을 다시 띄우는 얇은 셸이다. 그래서 대부분의 변경은 스토어를 건드리지 않고 끝난다.
10
+
11
+ ## 1. 무엇이 바뀌었나 — 길 가르기
12
+
13
+ 먼저 이번 변경이 어디에 닿는지 본다. 헷갈리면 더 무거운 쪽(재빌드)으로 간다.
14
+
15
+ | 변경 | 길 | 조치 |
16
+ |------|-----|------|
17
+ | 웹/UI — 제품 기능·화면·로직 | 웹 배포 | 웹 repo `git push` → 자동배포. 스토어 액션 0 |
18
+ | RN 셸 순수 JS/TSX/이미지/스타일 | OTA | `npx eas-cli update` — 즉시, 재빌드·재제출 없음 |
19
+ | 네이티브 모듈·`app.config.ts`·plugins·scheme·권한·SDK 업·계약 메시지 추가 | 재빌드 + 재제출 | marketing version 올림 → 빌드 → 테스트 채널 → 공개 승급 |
20
+
21
+ `runtimeVersion`은 `appVersion`을 따른다. OTA는 같은 marketing version으로 나간 빌드에만 도달한다. 네이티브가 한 줄이라도 바뀌면 OTA로 못 내보낸다.
22
+
23
+ ## 2. 웹 변경 (대부분)
24
+
25
+ 제품의 기능·화면·로직은 모두 웹에 있다. 그래서 평소 릴리스는 웹 repo에서 끝난다. push하면 웹이 배포되고, 앱은 다음 로드에 새 웹을 띄운다. 앱 빌드도 스토어 작업도 없다. 새 브릿지 능력을 쓰는 웹 코드는 항상 게이트되어, 구버전 앱이나 브라우저에서도 안전하게 비활성으로 처리된다(게이트 규약은 `bridge-guide`).
26
+
27
+ ## 3. OTA — 셸 JS만 (eas update)
28
+
29
+ 네이티브 셸의 순수 JS/TSX/이미지/스타일 변경은 스토어 없이 `npx eas-cli update`로 즉시 내보낸다. `runtimeVersion=appVersion`이라 같은 marketing version으로 출시된 빌드에만 도달한다. 네이티브·설정·SDK가 끼면 OTA로 못 내보낸다. 그때는 4번으로 간다. 명령·프로파일·무료 티어 한도는 `eas-deploy-guide`.
30
+
31
+ ## 4. 재빌드 + 재제출 — 네이티브 변경
32
+
33
+ 네이티브 모듈·`app.config.ts`·plugins·scheme·권한·SDK 업·계약 메시지 추가가 끼면 새 바이너리가 필요하다.
34
+
35
+ - marketing version 올림은 **필수**다. `app.config.ts`의 `version`을 올린다. 안 올리면 ITMS-90186으로 거절된다(`autoIncrement`는 buildNumber/versionCode만 올린다).
36
+ - 빌드 + 자동 제출 → iOS는 TestFlight, Android은 내부 트랙까지 간다.
37
+ - 같은 production 빌드를 실기기에서 테스트 채널로 확인하고, 그대로 공개로 승급한다.
38
+ - 구체 명령·`eas.json` 프로파일·자격증명·무료 티어는 `eas-deploy-guide`.
39
+
40
+ ## 5. 공개 승급 (Owner confirm)
41
+
42
+ `eas submit`은 테스트 채널까지만 올린다. 공개 심사 제출은 하지 않는다. 공개 출시는 심사·공개가 걸려 되돌리기 어렵다. 그래서 Owner가 콘솔에서 직접 + 명시 confirm으로 한다.
43
+
44
+ - **iOS** — App Store Connect에서 "Submit for Review".
45
+ - **Android** — production 트랙으로 승급. 신규 개인계정은 비공개 테스트 12명·14일 게이트를 통과한 뒤에야 가능하다.
46
+
47
+ 콘솔 절차·테스트 게이트·심사 통과 전략·컴플라이언스는 `store-release-guide`.
48
+
49
+ ## confirm 경계
50
+
51
+ 공개 출시 승급(ASC "Submit for Review" · Play production)은 Owner 직접 + 명시 confirm. 웹 배포·OTA·테스트 채널 빌드/제출은 자동.
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: sketch
3
+ description: Make working pages in the dev server so Owner can pick from design options or verify data/state scenarios. Spread alternatives — don't pre-commit to one best way. Use when the Owner needs to choose a design direction, see how a screen holds up across data/state cases, or invokes /sketch.
4
+ ---
5
+
6
+ # /sketch
7
+
8
+ sketch 페이지 경로에 Owner의 의도에 맞춰 스케치한다.
9
+ Owner는 dev 서버 브라우저를 열고, 결과물을 확인하여 피드백·확정할 수 있어야한다.
10
+
11
+ 디자인 시안은 서로 다른 디자인 안들을 펼쳐, Owner가 어느 룩으로 갈지 고를 수 있게 한다.
12
+ 시나리오는 데이터·상태 케이스(Best / Common / Worst 등)를 충분히 표현하여 Owner의 의사결정에 도움을 준다.
13
+
14
+ 프로젝트의 디자인 시스템을 탐색하여 따른다.
15
+ 업데이트가 필요한 source가 보이면 Owner 확인 후 반영한다.
16
+
17
+ 확정 후에는 선택된 안만 sketch 경로에 남기고 나머지는 정리한다.
18
+ 남은 안은 다음 디자인 작업의 레퍼런스로 누적된다.
19
+ 정리 방식이 모호하면 Owner에게 묻는다.