songyc macbook b751247ba7 feat(keging): 아이폰 시작 세트의 워치 심박 측정 — startWatchApp + 미러링 세션, 배지·사전 경고 폐지 (9차)
- 의도(사용자 TestFlight 피드백): 폰으로 운동해도 워치를 차고 있으면 워치가 심박을 재고,
  진동은 시작한 기기에서만. 종전 '워치 연동됨' 배지는 워치 앱 포그라운드 여부(isReachable)만
  보여 주던 것이라 폐지
- iOS: WatchHeartRateBridge — 시작 시 HKHealthStore.startWatchApp(폰 설정 운동 유형)으로 워치 앱을
  깨우고, 워치가 미러링한 HKWorkoutSession을 workoutSessionMirroringStartHandler로 받아 심박 수신,
  완주/중단 명령 전송(finish/discard). 워치를 못 열거나 30초 안에 미러링이 안 붙으면 폰 단독
  (+설정에 따라 '워치 앱을 열지 못했어요' 알럿, app.watchWarn 재사용) 및 WatchConnectivity로
  워치 세션 폐기 명령. 워치 저장 결과(saved/failed)는 WC로 회신받고 15초 내 미회신이면 폰 사후 저장 폴백.
  워치 깨우기는 폰 건강 권한 응답 뒤에(첫 실행 경합 방지). 건강 읽기 권한(심박·활성 에너지) 추가
  — 워치 세션 센서 수집용(권한 공유)
- 워치: WatchAppDelegate.handle(workoutConfiguration) → MirroredWorkoutSession(라이브 빌더, 무진동·무타이머,
  심박을 미러링 채널로 전송, finish 시 저장+강도 relate 후 WC로 결과 통보, discard/끊김 시 폐기,
  미러링 30초 타임아웃, 앱 재기동 시 잔류 세션 복구·폐기). 화면 '아이폰에서 진행 중 ♥bpm'(중단 버튼).
  WatchHealthAuth로 권한 공용화
- UI: 첫 화면 배지·확인 창 제거, 운동 화면 횟수 아래 워치 상태 줄(연결 중/♥bpm/아이폰만),
  설정 토글 '워치 연결 실패 경고', 도움말 3건 갱신. l10n 카탈로그 3종 missing/stale 0
- DEBUG: -testConfig(35초 세트), -mockWatchHeartRate <bpm>(상태 줄 QA·스크린샷). watchHR 카테고리 notice 로그
- 시뮬 검증(페어링 심): startWatchApp → 워치 기동·권한·세션·가짜 심박·'아이폰에서 진행 중'까지 동작,
  워치 startMirroring 성공 반환 — 그러나 폰 미러링 핸들러는 시뮬에서 불리지 않음(애플 샘플도 실기기 전용)
  → 30초 타임아웃·알럿·WC 폐기·폰 폴백 저장 경로 확인, 워치 자체 세트 정상. 3타깃 빌드 그린·경고 0.
  미러링 수신·완주 저장·회신은 실기기 TestFlight 재테스트 필요
- 문서·마케팅: CLAUDE.md §3.8 신설·§1/§3.7/§4/§6/§7 갱신, 설명 3언어·whats-new·review-notes·PRIVACY.md
  (아이폰 읽기 권한 설명 — GitHub 게시본 갱신 필요), 스크린샷 01·08(배지 제거)·02·03(♥78 상태 줄) 재촬영·합성

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UPmp29DQHY6Ahfss5SRopT
2026-09-05 10:13:48 +09:00
..

Marketing — App Store 등록 자료 (케깅)

ASC(App Store Connect) 등록 때 그대로 붙여넣을 자료 모음. 하루 다님 Marketing/ 구성을 그대로 옮겨 왔다. 앱 기능·문구가 바뀌면 이 폴더도 함께 갱신한다 — 특히 description의 기능 목록, 스크린샷에 보이는 화면.

구성

AppStore/
  app-name-subtitle.txt        로케일별 이름·부제목 (각 30자 이내)
  description-{ko,en,ja}.txt   앱 설명 (4000자 이내) — 로케일별 '설명' 란
  keywords.txt                 키워드 (로케일별 100자 이내) — '키워드' 란
  promotional-text.txt         프로모션 텍스트 (170자 이내, 심사 없이 수정 가능)
  whats-new-1.0.txt            버전 1.0 '새로운 기능'
  review-notes.txt             ASC 입력값(카테고리·URL·연령 등급) + App Review 메모(영문) + 업로드 체크
Screenshots/
  iphone-6.9/{ko,en,ja}/       아이폰 6.9″ 원본 (1320×2868px — iPhone 17 Pro Max 시뮬레이터)
  watch/{ko,en,ja}/            애플워치 원본 (416×496px — Series 11 46mm. 워치 앱이 있는 앱은 워치 스크린샷 필수)
  framed-iphone-6.9/…          ★ 업로드 대상: 홍보 문구+브랜드 배경 합성판 (같은 캔버스 크기라 ASC에 그대로)
  framed-watch/…               ★ 업로드 대상: 〃
tools/
  frame_screenshots.swift      합성 도구 — `swift Marketing/tools/frame_screenshots.swift` (Keging 폴더에서).
                               문구·색은 파일 상단에서 수정, 원본을 다시 찍으면 이 도구만 재실행하면 됨
../Docs/PRIVACY.md             개인정보처리방침(ko/en/ja) — GitHub 공개 저장소에 올려 URL로 등록 (아래 ⚠️)
  • 아이패드 슬롯 없음(iPhone 전용 앱, TARGETED_DEVICE_FAMILY=1). ASC는 6.9″ 아이폰 + 워치 한 세트씩 필수, 나머지 크기는 자동 축소
  • 스크린샷 구성 (1.0, 2026-09-05):
    • 아이폰 9장 = 01 첫 화면 / 02 운동 중·수축(워치 심박 줄) / 03 운동 중·이완 / 04 하루 통계(오늘의 타임라인) / 05 주간 통계 / 06 운동 세팅 시트 / 07 다이나믹 아일랜드 확장 뷰(홈 화면에서 길게 누름) / 08 다크 모드 / 09 완료 화면
    • 워치 3장 = 01 시작 화면 / 02 운동 중 / 03 세팅 페이지
  • 상태바는 9:41·배터리 100%로 오버라이드된 상태로 촬영 (워치 시뮬레이터는 오버라이드 미지원 — 실제 시각 표시)
  • 문구 오버레이 규정: Apple은 스크린샷에 마케팅 문구·배경 합성을 허용(실제 앱 화면이 반영되어 있으면 됨) — framed 세트는 이 관행을 따른 것
  • ⚠️ 개인정보처리방침 URL은 하루 다님과 같은 방식 — GitHub에 PRIVACY.md 하나만 든 공개 저장소(notalentprogrammer/Keging)를 만들고 Docs/PRIVACY.md를 올린다(저장소 생성은 사용자 계정으로). URL이 달라지면 description 3종·review-notes의 링크를 함께 고칠 것

스크린샷 재생성 방법

케깅은 데모 시드가 통계뿐(-seedStats)이라 로케일마다 앱을 지울 필요는 없고, 실행마다 -AppleLanguages "(xx)" -AppleLocale xx_XX만 바꾸면 된다. 촬영 스크립트 원본은 세션 스크래치에 있었으므로 아래 요령을 기준으로 재작성한다.

# 0) Debug 빌드 후 앱 경로 (Keging 스킴이 워치 앱까지 빌드·내장)
DD=$(ls -dt ~/Library/Developer/Xcode/DerivedData/Keging-* | head -1)
APP=$DD/Build/Products/Debug-iphonesimulator/Keging.app
PHONE=9142EFF2-892B-4C87-B5A2-937527A3E3A2   # iPhone 17 Pro Max (iOS 26.5) — 마케팅용
WATCH=B3B2F7F0-0414-4170-834B-E820BC4F2595   # Apple Watch Series 11 46mm (watchOS 26.5) — PHONE과 페어링됨(simctl pair)

# 1) 부팅 (시간대 주입 — 아래 '시간대 정합') + 상태바 오버라이드
SIMCTL_CHILD_TZ=Pacific/Auckland xcrun simctl boot $PHONE
xcrun simctl status_bar $PHONE override --time "9:41" --batteryState charged --batteryLevel 100 --wifiBars 3 --cellularBars 4
SIMCTL_CHILD_TZ=Pacific/Auckland xcrun simctl boot $WATCH

# 2) 설치 — 워치 앱은 페어링돼 있어도 자동 설치되지 않으므로 내장 번들을 직접 설치
xcrun simctl install $PHONE "$APP"
xcrun simctl install $WATCH "$APP/Watch/KegingWatch Watch App.app"

# 3) 화면별 런치 인자 (매 실행 공통: -noHealth -AppleLanguages "(ko)" -AppleLocale ko_KR / en en_US / ja ja_JP)
#    ★ 폰 첫 화면의 '워치 연결됨' 배지는 워치 시뮬에서 케깅 워치 앱이 포그라운드일 때만 뜬다 — 01·08 촬영 직전에 워치 앱을 launch
#  01-main       : -resetConfig                      (기본 세팅 10·5·20 — 9차부터 워치 배지 없음)
#  09-finished   : -quickConfig -autoStart → 8초 뒤    (완주 화면. 다음 실행에 -resetConfig 필수)
#  06-setup      : -resetConfig -openTimerSettings
#  02-workout    : -autoStart -mockWatchHeartRate 78 → 약 36초 뒤 (3회차 수축, 7초 남음) / 03-relax: 같은 실행 약 45초 뒤 (3회차 이완)
#                  (-mockWatchHeartRate = 워치 없이 '♥78 워치 심박' 상태 줄을 흉내 — 실제 미러링은 시뮬 미지원)
#  08-dark       : simctl ui $PHONE appearance dark → 기본 실행 → 촬영 → appearance light
#  05-stats-week : -seedStats -openStats -statsScope week
#  04-stats-day  : -seedStats -openStats  ← ⚠️ '시간대 정합' 2단계 부팅 후 촬영
#  07-dynamic-island : -autoStart 로 운동 중 → Device > Home → 아일랜드 길게 누름(확장 뷰) → 촬영 (아래 '다이나믹 아일랜드')
#  워치 01-main: 기본 / 03-settings: -openSettings / 02-workout: -autoStart → 약 36초 뒤
# 4) 합성: swift Marketing/tools/frame_screenshots.swift
  • ⚠️ simctl io screenshot기존 파일을 덮어쓰지 않음 — 재촬영 전 rm 필수
  • ⚠️ simctl launch는 실행 중인 앱을 재시작하지 않는다 — 매번 simctl terminate 후 launch (DEBUG 인자는 콜드 스타트에서만 읽음)

시간대 정합 (상태바 9:41 ↔ 앱 안의 시각)

하루 다님 요령 계승: 부팅 시 SIMCTL_CHILD_TZ=<IANA id>로 주입해야 전 프로세스에 적용된다(launch 시점 주입은 유실됨).

  • 일반 화면(0103·0509): "지금이 오전 9시대"인 시간대 — 2026-09-05 새벽(KST 0506시) 촬영은 Pacific/Auckland(UTC+12 → 0809시대)
  • 하루 탭(04)만 2단계 부팅: -seedStats의 오늘 세션이 08:00·13:17·21:34 고정이고 타임라인은 '지금' 뒤의 세션도 그리므로, "지금"이 21:34 이후(밤)인 시간대로 폰을 재부팅해 찍는다(같은 시각 KST 새벽이면 Europe/London(UTC+1) → 22시대). 이때 상태바는 --time "10:41"로 바꿔 '지금' 룰과 어긋나지 않게(자정을 넘기면 오늘 기록이 사라지니 23시대는 피할 것)

다이나믹 아일랜드 확장 뷰 (07) — Simulator.app 창을 마우스로 조작

simctl에는 터치 주입이 없다. open -a Simulator로 창을 띄운 뒤 AppleScript(System Events)로 Device 메뉴를 클릭하고, CGEvent로 마우스 클릭·길게 누름을 보낸다(세션 스크래치의 mouse/longpress 스위프트 헬퍼 — 재작성 요령: CGEvent(mouseEventSource:mouseType:mouseCursorPosition:mouseButton:)로 down/up 게시).

  • 창 좌표 매핑: Window > Show Device Bezels 끄기 + Window > Pixel Accurate → 창 position/size를 AppleScript로 읽는다(2026-09-05: position {1033,31}, size {625,1409}, 1x 디스플레이). 스크린샷 px → 맥 pt: x = 1033 + px × (625/1320), y = 31 + 51(툴바) + py × (625/1320). 케깅 아이콘 클릭으로 검증 후 사용
  • 절차: -autoStart → 20초 → Device > Home(메뉴 클릭) → 아일랜드 중앙(px 660,110 → 맥 1345.5,134)을 1.2초 길게 누름 → 확장 뷰 촬영 → 아일랜드 탭으로 앱 복귀
  • ⚠️ 잠금화면(Device > Lock)은 시뮬이 항상 켜짐(AOD) 형태로 그려 Live Activity의 초 단위가 "3:——"로 나와 스토어용 부적합 — 첫 잠금 때 "실시간 현황 허용" 물음창이 뜨므로(허용 버튼 클릭 필요) 굳이 잠금화면을 찍을 땐 참고
  • 홈 화면 컴팩트 아일랜드(두 색 점+남은 시간)는 정상 렌더 — 필요하면 Home 직후 촬영

워치 상태바의 빨간 '아이폰 연결 끊김' 아이콘 (실측 2026-09-05)

  • 비페어링 워치를 새로 부팅하면 앱 화면 상태바가 깨끗하지만, 워치에서 운동을 한 번 시작하면(HKWorkoutSession/런타임 세션) 그 순간부터 아이콘이 붙고 재부팅 전엔 사라지지 않는다. 페어링 워치는 폰이 켜져 있어도 처음부터 붙어 있었다
  • 그래서 01·03(시작 화면·세팅)은 로케일마다 워치를 재부팅한 직후(부팅 완료 + 12초 → 01 → 03) 찍고, 02(운동 중)는 아이콘이 붙은 채 찍은 뒤 python3 Marketing/tools/clean_watch_statusbar.py Marketing/Screenshots/watch/*/02-workout.png로 상태바 아이콘만 배경색으로 지운다(실기기에선 실제로 끊겼을 때만 뜨는 시뮬 잔재 — 앱 내용은 무수정). 검출은 상태바 우상단 빨간 픽셀 수(도구 내부)
  • -AppleLanguages 런치 인자로 워치 앱 문구는 바뀌지만 워치 상태바 시각은 실제 시각(오버라이드 불가)

Live Activity(아일랜드)는 시스템 언어를 따른다

위젯 확장은 앱의 -AppleLanguages 인자를 받지 않으므로 en/ja 07 컷은 시뮬 시스템 언어를 바꿔 재부팅 후 찍는다: xcrun simctl spawn $PHONE defaults write "Apple Global Domain" AppleLanguages -array en + AppleLocale -string en_US → shutdown/boot(TZ 주입) → 부팅 완료 후 25초 대기 → 상태바 오버라이드 → 앱을 한 번 워밍업 실행(5초)·종료 → -autoStart 콜드 스타트 → 18초 → Home → 길게 누름 → 촬영. 끝나면 ko로 복원. 홈 화면 앱 라벨도 함께 바뀐다. ⚠️ 부팅 직후(8초) 바로 -autoStart하면 Live Activity가 등록되지 않아 아일랜드가 비어 있었다(2026-09-05 실측) — 대기·워밍업이 필요

출시 절차 체크리스트 (케깅 — 무료·인앱 없음·CloudKit 없음)

하루 다님 README §①~⑪에서 케깅에 해당하는 것만. = 처리 대기. 금액·세무가 걸린 내용은 ASC 안내문으로 최종 확인 권장.

① 계약

  • 무료 앱이라 유료 앱 계약 불필요 — ASC → 비즈니스에서 무료 앱 계약(Free Apps) 활성 상태만 확인 (하루 다님 출시로 이미 활성)

② 개인정보처리방침 공개 (앱 생성 전)

  • GitHub에 공개 저장소 notalentprogrammer/Keging 생성 → Docs/PRIVACY.mdPRIVACY.md로 올림 → https://github.com/notalentprogrammer/Keging/blob/main/PRIVACY.md 열리는지 확인

③ ASC 앱 생성 · 앱 정보 (review-notes.txt에 입력값 정리)

  • 신규 앱: iOS / 이름 케깅 - 케겔 운동 타이머 / 기본 언어 한국어 / 번들 ID com.yechan.Keging / SKU keging-ios
  • 카테고리 건강 및 피트니스 / 연령 등급 설문 전부 없음 → 4+ / 개인정보 URL·지원 URL 입력 / 현지화 en·ja 추가(이름·부제목은 app-name-subtitle.txt)
  • 앱 개인정보 보호: 데이터 수집 안 함 → 게시 / 가격: 무료, 국가 전체

④ 버전 1.0 페이지 (ko/en/ja 전환하며 입력)

  • 스크린샷: iPhone 6.9″ = framed-iphone-6.9/<언어>/01~09 / Apple Watch = framed-watch/<언어>/01~03
  • 프로모션 텍스트 / 설명 / 키워드 / 새로운 기능(whats-new-1.0) / 지원 URL
  • App Review 정보: 로그인 필요 없음 + 연락처 + 메모(review-notes.txt 영문 그대로 — HealthKit·백그라운드 오디오·워치 설명)
  • 출시 방식: 자동 또는 수동

⑤ 아카이브 → 업로드 → TestFlight ( 처리 10~60분)

  • 세 타깃 버전·빌드 일치(1.0/1) 확인 → 스킴 Keging + Any iOS Device → Archive → Distribute → Upload
  • 수출 규정 질문에 '아니요'(표준 암호화만)
  • TestFlight 내부 테스트: 진동 3종·완료 진동·백그라운드 진동·DI/잠금화면·워치 기록 합류·건강 기록

⑥ 심사 제출 ( 보통 24~48시간)

  • 빌드 선택 → 제출. 거절 시 해결 센터 — 예상 쟁점: 백그라운드 오디오 모드(2.5.4) 용도 소명(review-notes 메모), HealthKit 용도 문구(Info.plist 3언어 등록됨)

⑦ 승인 → 출시 → 직후

  • 스토어 다운로드로 첫 실행·워치 앱 설치·건강 기록 확인
  • CLAUDE.md §1 현재 상태·§8에 출시 완료 반영