docs(ui): clarify per-device scope of 'ignore short sessions' setting

- 실사용 보고: 기기 간 시작·종료(아이폰↔아이패드↔워치) 시 짧은 기록이 저장되는 현상
- 원인: 설정이 기기별(App Group defaults, iCloud 미동기화)이고 검사는 '종료를 실행한
  기기'의 설정 기준 — 워치 종료는 페어링 아이폰에서 실행되므로 아이폰 설정 적용.
  기기마다 설정값이 다르면 짧은 기록이 저장됨 (로직 결함 아님, 모든 종료 경로에 검사 존재)
- 동결 중 동작 변경 대신 문구를 실제 동작에 맞게 명시: 설정 '측정' 푸터 + 도움말
  '짧은 기록 무시' 주제에 기기별 저장·종료 기기 기준·워치=아이폰 규칙 추가 (ko/en/ja)
- en 기능명 표기를 피커와 동일한 'Ignore short sessions'로 통일, ja는 計測 용어 유지
- 검증 인자 -settingsScrollMeasure 신설(측정 섹션 스크롤) — ko/ja/en 푸터 렌더 확인
- 카탈로그: 신규 2키(en/ja 완비), 옛 2키 stale 정리 — missing 0·stale 0
- CLAUDE.md §6.8 기기별 동작 명시 + §14 인자 추가. Debug/Store 빌드 성공

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014CkLDi8Lr21vGqo4SjZaTp
This commit is contained in:
songyc macbook 2026-07-19 23:09:56 +09:00
parent 459c85700f
commit e34ce8fa31
4 changed files with 16 additions and 10 deletions

View File

@ -225,7 +225,7 @@ xcrun xcstringstool sync Widgets/Localizable.xcstrings --stringsdata "${widget_f
### 6.8 설정 탭 (`SettingsView`)
- 테마(라이트/다크 — 즉시 적용, App Group 저장으로 위젯 공유) / 언어(재시작 적용, AppleLanguages 오버라이드)
- iPhone만: 내비게이션 방식(탭바/버블) + 탭바 구성 or 버블 순서·두 번 눌러 바로 이동(토글+대상 탭)
- 모음 탭 목표 카드 선택(개수 무제한)+표시 방식 / 주 시작 요일 / 하루 시작 시간 / 짧은 기록 무시 / **장시간 측정 알림**(1~12시간, 켜는 순간 알림 권한 요청 — `SessionAlertManager`가 LiveActivityManager.sync 길목에서 진행 세션과 예약을 동기화, 세션 종료·설정 변경 시 예약 제거) / 대표 시간 기준(가장 먼저·나중에 시작 — 기본 latest)
- 모음 탭 목표 카드 선택(개수 무제한)+표시 방식 / 주 시작 요일 / 하루 시작 시간 / 짧은 기록 무시(**기기별 설정** — App Group defaults라 iCloud 동기화 안 됨. 검사는 종료를 실행한 기기의 설정 기준이고 워치 종료는 페어링 아이폰에서 실행되므로 아이폰 설정 적용 — 기기 간 시작·종료 시 값이 다르면 짧은 기록이 저장될 수 있음. 실사용 보고로 확인된 의도된 동작, 설정 푸터·도움말에 명시) / **장시간 측정 알림**(1~12시간, 켜는 순간 알림 권한 요청 — `SessionAlertManager`가 LiveActivityManager.sync 길목에서 진행 세션과 예약을 동기화, 세션 종료·설정 변경 시 예약 제거) / 대표 시간 기준(가장 먼저·나중에 시작 — 기본 latest)
- 데이터: **데이터 내보내기** — 행동·시간·횟수·목표·다짐을 CSV 5파일로 (ISO 8601, UTF-8 BOM, 무료 포함)
- 프리미엄 화면(§8) / 도움말(`HelpView` — 기기별 주제 분기, DisclosureGroup. **그룹당 대표 주제 1개에 예시 스크린샷**(IOS/HelpImages/help-<이름>-<ko|en|ja>.jpg, 총 18장 ≈1.6MB) — 언어별 파일을 HelpImage 로더가 표시 언어로 선택(jpg라 UIImage(named:) 대신 URL 로드). 앱 화면·문구가 크게 바뀌면 해당 스크린샷도 재촬영할 것: 시뮬 로케일별 재시드 후 §14 인자로 촬영, 워치는 상태바 크롭) / 버전(번들 CFBundleShortVersionString 표시)
@ -334,7 +334,7 @@ xcrun xcstringstool sync Widgets/Localizable.xcstrings --stringsdata "${widget_f
| 측정 알림 | `-settings.longSessionAlertHours <N>`(설정 주입) `-longSessionAlertTestSeconds <N>`(시간 대신 N초 발화, provisional 권한 자동) `-alertDump`(예약 상태 → Documents/session-alerts.txt) |
| 목표 | `-goalShowEditor` `-goalShowFinished` `-goalReorder` `-goalScrollBottom` `-questShowEditor`(다짐 편집기 표시 — 마감 시각 다짐 우선, 없으면 첫 다짐) `-endGoalYesterday "제목"`(종료일을 어제로 — 실행 시점 자동 판정 검증) `-watchGoals <N>`(생성순 목표 N개를 '애플워치에서 보기'로 설정, 0=전체 해제 — 설정 후 스냅숏 즉시 푸시, 컴플리케이션 필터 검증용) `-streakDump`(모든 다짐의 연속 계산 결과 → Documents/streak-dump.txt) `-progressSelfTest`(spanProgress 수치 37건 자가 검증[환산 캡·수행일 집계·마감·atMost·custom] → Documents/progress-self-test.txt — 인메모리 컨테이너·설정 고정이라 실데이터/기기 설정 무영향) |
| 기록/통계 | `-historyMode timetable` `-historyWeekly` `-excludeActions "이름,이름"` `-historyShowFilter` `-statShowFilter` `-statSpan <day|week|month>` `-statScrollBottom` `-showExport` `-exportDump`(Documents/export-dump.png) `-recordShowEditor <session|count>`(최신 기록 수정 시트) |
| 설정/기타 | `-settingsScrollGoal` `-settingsScrollPremium` `-settingsShowDayStartWheel`(하루 시작 시간 휠 펼침 시작) `-premiumPreview` `-helpPreview`(+`-helpExpandAll`: 전 주제 펼침, `-helpScrollTo <help-main 등>`: 이미지 주제로 스크롤) `-dataExportPreview`(+`-dataExportRun`이면 CSV 자동 생성 후 Documents 복사) `-widgetPreview YES|lock` `-widgetPreviewScroll <앵커>` `-widgetRenderMode accented|vibrant`(틴트/투명 홈 렌더링 분기 검증) `cloudSync`(standard bool로 강제) `-intentSmokeTest`(시리 인텐트 10개 시나리오 실행 → Documents/intent-smoke-test.txt, -seedDemo·-premium과 함께) |
| 설정/기타 | `-settingsScrollGoal` `-settingsScrollPremium` `-settingsScrollMeasure`(측정 섹션 — 짧은 기록 무시 푸터 검증) `-settingsShowDayStartWheel`(하루 시작 시간 휠 펼침 시작) `-premiumPreview` `-helpPreview`(+`-helpExpandAll`: 전 주제 펼침, `-helpScrollTo <help-main 등>`: 이미지 주제로 스크롤) `-dataExportPreview`(+`-dataExportRun`이면 CSV 자동 생성 후 Documents 복사) `-widgetPreview YES|lock` `-widgetPreviewScroll <앵커>` `-widgetRenderMode accented|vibrant`(틴트/투명 홈 렌더링 분기 검증) `cloudSync`(standard bool로 강제) `-intentSmokeTest`(시리 인텐트 10개 시나리오 실행 → Documents/intent-smoke-test.txt, -seedDemo·-premium과 함께) |
| 버블 | `-radialExpanded` `-radialDoubleTapTest`(2초 뒤 FAB 더블탭 재현 — `-settings.radialDoubleTapTab <탭>`과 함께) |
| 일기 | `-diarySeed`(필기 획 2줄 포함 — 달력 화면에서 먼저 시드 후 재실행으로 열 것) `-diaryOpenToday`(**`-startTab diary` 필수**) `-diaryPage <N>` `-diaryZoomScale <배>`(2.5초 뒤 fit×N 프로그램 줌 — 획 유지·선명도 확인) `-diaryShowConfig` `-diaryShowExport`(달력 화면 onAppear — `-diaryOpenToday`와 함께 쓰면 안 뜸) `-diaryExportRun pdf|image`(결과를 Documents로 복사) `-diaryShowCalendarPicker` `-diarySeedEvents` `-diaryHideFirstAction` `-diaryShowActionFilter` `-diaryHideFirstGoal` `-diaryShowGoalFilter` `-diarySeedPhoto`(오늘=이모지+사진·어제=사진만 기분 사진 시드 — 달력 백레이어 검증) `-diarySeedTemplate`(2쪽 샘플 PDF 양식 생성) `-diaryShowTemplates`(양식 관리 시트) `-diaryShowPageChooser`(페이지 추가 선택 시트) `-diaryApplyTemplate`(첫 양식으로 페이지 추가) / 섹션 강제: `-diary.sectionOrder "timetable,hero,goals"` `-diary.hiddenSections "mood,todos,records,bars"` |
| 워치 | `-complicationPreview` `-complicationScroll` `-complicationScrollTo <stale\|quest>` `-autoRunFirstAction` `-autoStopRunning`(측정 중 행 탭과 동일 경로로 대표 측정 종료 — 페어링 심 E2E용) |

View File

@ -6122,18 +6122,18 @@
}
}
},
"설정 → 측정에서 '짧은 기록 무시'를 켜면 설정한 시간보다 짧게 측정하고 끝낸 기록은 저장하지 않아요. 행동 버튼을 실수로 눌렀다가 바로 껐을 때 의미 없는 기록이 쌓이는 것을 막아 줘요. 기준 시간은 3초부터 1분까지 고를 수 있고, 위젯·워치·시리로 종료할 때도 똑같이 적용돼요.": {
"설정 → 측정에서 '짧은 기록 무시'를 켜면 설정한 시간보다 짧게 측정하고 끝낸 기록은 저장하지 않아요. 행동 버튼을 실수로 눌렀다가 바로 껐을 때 의미 없는 기록이 쌓이는 것을 막아 줘요. 기준 시간은 3초부터 1분까지 고를 수 있고, 위젯·워치·시리로 종료할 때도 적용돼요. 이 설정은 기기마다 따로 저장되고 측정을 끝낸 기기의 설정이 적용되니, 아이폰과 아이패드를 함께 쓴다면 두 기기 모두 설정해 주세요. 애플워치에서 끝낸 측정은 페어링된 아이폰의 설정을 따라요.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Turn on 'Ignore short sessions' in Settings → Tracking, and sessions shorter than the time you set won't be saved. It keeps meaningless records from piling up when you tap an action button by mistake and stop it right away. You can choose from 3 seconds to 1 minute, and it also applies when you stop from widgets, Apple Watch, or Siri."
"value": "Turn on 'Ignore short sessions' in Settings → Tracking, and sessions shorter than the time you set won't be saved. It keeps meaningless records from piling up when you tap an action button by mistake and stop it right away. You can choose from 3 seconds to 1 minute, and it also applies when you stop from widgets, Apple Watch, or Siri. This setting is saved separately on each device and the setting of the device that ends the measurement applies, so if you use both an iPhone and an iPad, set it on both. Measurements ended on Apple Watch follow the paired iPhone's setting."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "設定 → 計測で「短い記録を無視」をオンにすると、設定した時間より短く計測して終えた記録は保存されません。アクションボタンを誤って押してすぐ止めたときに、意味のない記録がたまるのを防げます。基準時間は3秒〜1分から選べ、ウィジェット・Apple Watch・Siriから終了したときも同じように適用されます。"
"value": "設定 → 計測で「短い記録を無視」をオンにすると、設定した時間より短く計測して終えた記録は保存されません。アクションボタンを誤って押してすぐ止めたときに、意味のない記録がたまるのを防げます。基準時間は3秒〜1分から選べ、ウィジェット・Apple Watch・Siriから終了したときも同じように適用されます。 この設定は端末ごとに別々に保存され、計測を終了した端末の設定が適用されるため、iPhoneとiPadを併用している場合は両方で設定してください。Apple Watchで終了した計測は、ペアリングされたiPhoneの設定に従います。"
}
}
}
@ -6186,18 +6186,18 @@
}
}
},
"설정한 시간보다 짧게 측정하고 종료한 기록은 저장하지 않아요. 버튼을 실수로 눌렀을 때 유용해요. 장시간 측정 알림은 측정이 설정한 시간을 넘기면 알려줘요 — 끄는 걸 잊었을 때 유용해요.": {
"설정한 시간보다 짧게 측정하고 종료한 기록은 저장하지 않아요. 버튼을 실수로 눌렀을 때 유용해요. 장시간 측정 알림은 측정이 설정한 시간을 넘기면 알려줘요 — 끄는 걸 잊었을 때 유용해요.\n이 설정들은 기기마다 따로 저장돼요. 짧은 기록 무시는 측정을 끝낸 기기의 설정이 적용되니, 아이폰과 아이패드를 함께 쓴다면 두 기기 모두 설정해 주세요. 애플워치에서 끝낸 측정은 페어링된 아이폰의 설정을 따라요.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Sessions shorter than the set time aren't saved — useful for accidental taps. The long-session alert notifies you when tracking passes the set time — useful when you forget to stop."
"value": "Sessions shorter than the set time aren't saved — useful for accidental taps. The long-session alert notifies you when tracking passes the set time — useful when you forget to stop.\nThese settings are saved separately on each device. 'Ignore short sessions' uses the setting of the device that ends the measurement, so if you use both an iPhone and an iPad, set it on both. Measurements ended on Apple Watch follow the paired iPhone's setting."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "設定した時間より短い計測は保存されません。誤タップに便利です。長時間計測の通知は、計測が設定時間を超えるとお知らせします — 終了し忘れたときに便利です。"
"value": "設定した時間より短い計測は保存されません。誤タップに便利です。長時間計測の通知は、計測が設定時間を超えるとお知らせします — 終了し忘れたときに便利です。\nこれらの設定は端末ごとに別々に保存されます。「短い記録を無視」は計測を終了した端末の設定が適用されるため、iPhoneとiPadを併用している場合は両方で設定してください。Apple Watchで終了した計測は、ペアリングされたiPhoneの設定に従います。"
}
}
}

View File

@ -184,7 +184,7 @@ struct HelpView: View {
HelpTopic(
symbol: "clock.badge.xmark",
title: "짧은 기록 무시",
body: "설정 → 측정에서 '짧은 기록 무시'를 켜면 설정한 시간보다 짧게 측정하고 끝낸 기록은 저장하지 않아요. 행동 버튼을 실수로 눌렀다가 바로 껐을 때 의미 없는 기록이 쌓이는 것을 막아 줘요. 기준 시간은 3초부터 1분까지 고를 수 있고, 위젯·워치·시리로 종료할 때도 똑같이 적용돼요."
body: "설정 → 측정에서 '짧은 기록 무시'를 켜면 설정한 시간보다 짧게 측정하고 끝낸 기록은 저장하지 않아요. 행동 버튼을 실수로 눌렀다가 바로 껐을 때 의미 없는 기록이 쌓이는 것을 막아 줘요. 기준 시간은 3초부터 1분까지 고를 수 있고, 위젯·워치·시리로 종료할 때도 적용돼요. 이 설정은 기기마다 따로 저장되고 측정을 끝낸 기기의 설정이 적용되니, 아이폰과 아이패드를 함께 쓴다면 두 기기 모두 설정해 주세요. 애플워치에서 끝낸 측정은 페어링된 아이폰의 설정을 따라요."
),
HelpTopic(
symbol: "hand.tap.fill",

View File

@ -181,8 +181,9 @@ struct SettingsView: View {
} header: {
Text("측정")
} footer: {
Text("설정한 시간보다 짧게 측정하고 종료한 기록은 저장하지 않아요. 버튼을 실수로 눌렀을 때 유용해요. 장시간 측정 알림은 측정이 설정한 시간을 넘기면 알려줘요 — 끄는 걸 잊었을 때 유용해요.")
Text("설정한 시간보다 짧게 측정하고 종료한 기록은 저장하지 않아요. 버튼을 실수로 눌렀을 때 유용해요. 장시간 측정 알림은 측정이 설정한 시간을 넘기면 알려줘요 — 끄는 걸 잊었을 때 유용해요.\n이 설정들은 기기마다 따로 저장돼요. 짧은 기록 무시는 측정을 끝낸 기기의 설정이 적용되니, 아이폰과 아이패드를 함께 쓴다면 두 기기 모두 설정해 주세요. 애플워치에서 끝낸 측정은 페어링된 아이폰의 설정을 따라요.")
}
.id("measureSection")
Section {
Picker("대표 시간 기준", selection: $liveActivityMode) {
ForEach(LiveActivityMode.allCases) { mode in
@ -271,6 +272,11 @@ struct SettingsView: View {
withAnimation { proxy.scrollTo("premiumSection", anchor: .top) }
}
}
if UserDefaults.standard.bool(forKey: "settingsScrollMeasure") {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
withAnimation { proxy.scrollTo("measureSection", anchor: .top) }
}
}
}
#endif
}