From ec62dc83de1e540a6cbfa3c954c271446740c2f7 Mon Sep 17 00:00:00 2001 From: songyc macbook Date: Mon, 13 Jul 2026 21:08:48 +0900 Subject: [PATCH] feat(siri): background Live Activity, spoken action names, localized phrases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 시리 단축어 점검에서 나온 추천 수정 일괄 적용: - StartTimeActionIntent·StopTimeActionIntent가 LiveActivityIntent 채택 — 시리/단축어의 백그라운드 실행에서도 Live Activity 시작이 허용되어 앱을 열지 않아도 다이나믹 아일랜드에 측정이 표시된다. RunActionIntent(위젯 버튼)는 확장 프로세스 응답성 튜닝을 유지하기 위해 의도적으로 제외. - 행동 이름 파라미터 문구 추가: "시리야, 하루 다님에서 독서 측정 시작"처럼 되묻지 않고 한 번에 실행. 어휘 갱신을 위해 앱 시작 시 updateAppShortcutParameters() 호출 (새 행동은 다음 실행부터 인식). - IOS/AppShortcuts.xcstrings 신설 — 시리 문구 ko/en/ja 지역화. 빌드 로그의 --no-app-shortcuts-localization가 사라지고 root.ssu.yaml에 en/ja 발화가 포함되는 것 확인. - RunActionIntent isDiscoverable=false — 생 UUID 파라미터라 사용자가 조합할 수 없는 위젯 전용 동작을 단축어 앱·스포트라이트에서 숨김 (위젯 버튼은 그대로 동작). - 결제 화면 기능 목록에 '시리 단축어' 추가. 이 과정에서 featureRow가 String 파라미터라 기능 목록 6줄 전체가 미지역화였던 버그 발견 — LocalizedStringKey로 바꾸고 en/ja 12개 문자열 번역. 검증: -intentSmokeTest 9개 시나리오 전부 통과(LiveActivityIntent 전환 후 재확인), 프리미엄 화면 렌더링 확인, Debug·Store 스킴 빌드 성공. 남은 확인: 시리 음성 인식·백그라운드 Live Activity 실표시는 실기기 필요. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7 --- myApp/HaruDanim/CLAUDE.md | 4 +- .../Haru_Danim.xcodeproj/project.pbxproj | 4 +- myApp/HaruDanim/IOS/AppShortcuts.xcstrings | 146 +++++++++++++ myApp/HaruDanim/IOS/Core/AppShortcuts.swift | 10 + myApp/HaruDanim/IOS/Haru_DanimApp.swift | 3 + myApp/HaruDanim/IOS/Localizable.xcstrings | 195 ++++++++++++++++++ myApp/HaruDanim/IOS/Views/SettingsView.swift | 5 +- myApp/HaruDanim/Shared/HaruDanimIntents.swift | 12 +- 8 files changed, 373 insertions(+), 6 deletions(-) create mode 100644 myApp/HaruDanim/IOS/AppShortcuts.xcstrings diff --git a/myApp/HaruDanim/CLAUDE.md b/myApp/HaruDanim/CLAUDE.md index f4ea571..38872a1 100644 --- a/myApp/HaruDanim/CLAUDE.md +++ b/myApp/HaruDanim/CLAUDE.md @@ -259,7 +259,9 @@ xcrun xcstringstool sync IOS/Localizable.xcstrings --stringsdata "${files[@]}" ## 12. 시리 단축어 (`Shared/HaruDanimIntents.swift`, 프리미엄 게이트) -인텐트: 측정 시작/종료(짧은 기록 무시 규칙 동일 적용) · 횟수 추가(1~999) · 행동 누적값 조회 · 목표 진행률 조회 · 다짐 진행률 조회(atMost는 한도 문구). `AppShortcutsProvider`에 대표 문구 등록(현재 한국어 전용 — AppShortcuts 지역화 미구성, 빌드 로그 `--no-app-shortcuts-localization`). 엔티티(Action/Goal/Quest)는 EntityStringQuery + '선택 안 함' 항목(위젯 설정용 센티널이 단축어 앱 선택지에도 노출됨). 도움말 프리미엄 그룹에 사용법 항목 있음. 로직 검증은 `-intentSmokeTest` (§14) — perform() 9개 시나리오를 앱 안에서 실행하고 결과를 Documents/intent-smoke-test.txt로 남긴다. +인텐트: 측정 시작/종료(짧은 기록 무시 규칙 동일 적용, **LiveActivityIntent 채택** — 시리 백그라운드 실행에서도 다이나믹 아일랜드 시작 허용) · 횟수 추가(1~999) · 행동 누적값 조회 · 목표 진행률 조회 · 다짐 진행률 조회(atMost는 한도 문구). `RunActionIntent`(위젯 버튼 전용)는 `isDiscoverable=false`로 단축어 앱에서 숨김이며 확장 프로세스 응답성 유지를 위해 LiveActivityIntent를 채택하지 않는다. +- **시리 문구**: `AppShortcutsProvider`에 기본 + 행동 이름 파라미터 문구("하루 다님에서 \(\.$action) 측정 시작" — 되묻지 않고 한 번에 실행). 번역은 **`IOS/AppShortcuts.xcstrings`**(일반 카탈로그와 별개, ko/en/ja). 행동 어휘는 앱 시작 시 `updateAppShortcutParameters()`로 갱신(새 행동은 다음 실행부터 음성 인식). +- 엔티티(Action/Goal/Quest)는 EntityStringQuery + '선택 안 함' 항목(위젯 설정용 센티널이 단축어 앱 선택지에도 노출됨 — 알려진 트레이드오프). 도움말 프리미엄 그룹에 사용법 항목, 결제 화면 기능 목록에도 표기. 로직 검증은 `-intentSmokeTest` (§14) — perform() 9개 시나리오를 앱 안에서 실행하고 결과를 Documents/intent-smoke-test.txt로 남긴다. --- diff --git a/myApp/HaruDanim/Haru_Danim.xcodeproj/project.pbxproj b/myApp/HaruDanim/Haru_Danim.xcodeproj/project.pbxproj index d0bad53..74ce3c9 100644 --- a/myApp/HaruDanim/Haru_Danim.xcodeproj/project.pbxproj +++ b/myApp/HaruDanim/Haru_Danim.xcodeproj/project.pbxproj @@ -601,8 +601,8 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = "하루 다님"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle"; - INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "내보낸 리포트 이미지를 사진 앨범에 저장하기 위해 필요해요."; INFOPLIST_KEY_NSCalendarsFullAccessUsageDescription = "일기 타임테이블에 캘린더 일정을 함께 표시하기 위해 필요해요."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "내보낸 리포트 이미지를 사진 앨범에 저장하기 위해 필요해요."; INFOPLIST_KEY_NSSupportsLiveActivities = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -642,8 +642,8 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = "하루 다님"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle"; - INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "내보낸 리포트 이미지를 사진 앨범에 저장하기 위해 필요해요."; INFOPLIST_KEY_NSCalendarsFullAccessUsageDescription = "일기 타임테이블에 캘린더 일정을 함께 표시하기 위해 필요해요."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "내보낸 리포트 이미지를 사진 앨범에 저장하기 위해 필요해요."; INFOPLIST_KEY_NSSupportsLiveActivities = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; diff --git a/myApp/HaruDanim/IOS/AppShortcuts.xcstrings b/myApp/HaruDanim/IOS/AppShortcuts.xcstrings new file mode 100644 index 0000000..7d6956b --- /dev/null +++ b/myApp/HaruDanim/IOS/AppShortcuts.xcstrings @@ -0,0 +1,146 @@ +{ + "sourceLanguage" : "ko", + "strings" : { + "${applicationName} 목표 진행률 알려 줘" : { + "localizations" : { + "en" : { + "stringSet" : { + "state" : "translated", + "values" : [ + "Show my goal progress in ${applicationName}", + "What's my goal progress in ${applicationName}" + ] + } + }, + "ja" : { + "stringSet" : { + "state" : "translated", + "values" : [ + "${applicationName}の目標達成率を教えて" + ] + } + }, + "ko" : { + "stringSet" : { + "state" : "translated", + "values" : [ + "${applicationName} 목표 진행률 알려 줘" + ] + } + } + } + }, + "${applicationName}에서 측정 시작" : { + "localizations" : { + "en" : { + "stringSet" : { + "state" : "translated", + "values" : [ + "Start tracking in ${applicationName}", + "Start a timer in ${applicationName}", + "Start tracking ${action} in ${applicationName}", + "Start ${action} in ${applicationName}" + ] + } + }, + "ja" : { + "stringSet" : { + "state" : "translated", + "values" : [ + "${applicationName}で計測開始", + "${applicationName}で計測を始めて", + "${applicationName}で${action}の計測を開始", + "${applicationName}で${action}を始めて" + ] + } + }, + "ko" : { + "stringSet" : { + "state" : "translated", + "values" : [ + "${applicationName}에서 측정 시작", + "${applicationName} 시간 측정 시작해 줘", + "${applicationName}에서 ${action} 측정 시작", + "${applicationName}에서 ${action} 시작해 줘" + ] + } + } + } + }, + "${applicationName}에서 측정 종료" : { + "localizations" : { + "en" : { + "stringSet" : { + "state" : "translated", + "values" : [ + "Stop tracking in ${applicationName}", + "Stop the timer in ${applicationName}", + "Stop tracking ${action} in ${applicationName}", + "Stop ${action} in ${applicationName}" + ] + } + }, + "ja" : { + "stringSet" : { + "state" : "translated", + "values" : [ + "${applicationName}で計測終了", + "${applicationName}で計測を終えて", + "${applicationName}で${action}の計測を終了", + "${applicationName}で${action}を終えて" + ] + } + }, + "ko" : { + "stringSet" : { + "state" : "translated", + "values" : [ + "${applicationName}에서 측정 종료", + "${applicationName} 시간 측정 끝내 줘", + "${applicationName}에서 ${action} 측정 종료", + "${applicationName}에서 ${action} 끝내 줘" + ] + } + } + } + }, + "${applicationName}에서 횟수 추가" : { + "localizations" : { + "en" : { + "stringSet" : { + "state" : "translated", + "values" : [ + "Add a count in ${applicationName}", + "Log something in ${applicationName}", + "Add a ${action} count in ${applicationName}", + "Log ${action} in ${applicationName}" + ] + } + }, + "ja" : { + "stringSet" : { + "state" : "translated", + "values" : [ + "${applicationName}で回数を追加", + "${applicationName}に記録して", + "${applicationName}で${action}の回数を追加", + "${applicationName}に${action}を記録して" + ] + } + }, + "ko" : { + "stringSet" : { + "state" : "translated", + "values" : [ + "${applicationName}에서 횟수 추가", + "${applicationName}에 기록 추가해 줘", + "${applicationName}에서 ${action} 횟수 추가", + "${applicationName}에 ${action} 기록해 줘" + ] + } + } + } + } + }, + "version" : "1.0" +} \ No newline at end of file diff --git a/myApp/HaruDanim/IOS/Core/AppShortcuts.swift b/myApp/HaruDanim/IOS/Core/AppShortcuts.swift index 3a199ca..d033160 100644 --- a/myApp/HaruDanim/IOS/Core/AppShortcuts.swift +++ b/myApp/HaruDanim/IOS/Core/AppShortcuts.swift @@ -8,12 +8,18 @@ import AppIntents struct HaruDanimShortcuts: AppShortcutsProvider { + // 행동 이름이 든 문구(\(\.$action))는 "시리야, 하루 다님에서 독서 측정 시작"처럼 + // 되묻지 않고 한 번에 실행된다. 행동 목록이 바뀌면 시리 어휘를 갱신해야 하므로 + // 앱 시작 시 updateAppShortcutParameters()를 호출한다 (Haru_DanimApp.init). + // 문구 번역은 IOS/AppShortcuts.xcstrings (일반 문자열 카탈로그와 별개 파일). static var appShortcuts: [AppShortcut] { AppShortcut( intent: StartTimeActionIntent(), phrases: [ "\(.applicationName)에서 측정 시작", "\(.applicationName) 시간 측정 시작해 줘", + "\(.applicationName)에서 \(\.$action) 측정 시작", + "\(.applicationName)에서 \(\.$action) 시작해 줘", ], shortTitle: "측정 시작", systemImageName: "play.circle.fill" @@ -23,6 +29,8 @@ struct HaruDanimShortcuts: AppShortcutsProvider { phrases: [ "\(.applicationName)에서 측정 종료", "\(.applicationName) 시간 측정 끝내 줘", + "\(.applicationName)에서 \(\.$action) 측정 종료", + "\(.applicationName)에서 \(\.$action) 끝내 줘", ], shortTitle: "측정 종료", systemImageName: "stop.circle.fill" @@ -32,6 +40,8 @@ struct HaruDanimShortcuts: AppShortcutsProvider { phrases: [ "\(.applicationName)에서 횟수 추가", "\(.applicationName)에 기록 추가해 줘", + "\(.applicationName)에서 \(\.$action) 횟수 추가", + "\(.applicationName)에 \(\.$action) 기록해 줘", ], shortTitle: "횟수 추가", systemImageName: "plus.circle.fill" diff --git a/myApp/HaruDanim/IOS/Haru_DanimApp.swift b/myApp/HaruDanim/IOS/Haru_DanimApp.swift index 7f1a122..cd876b2 100644 --- a/myApp/HaruDanim/IOS/Haru_DanimApp.swift +++ b/myApp/HaruDanim/IOS/Haru_DanimApp.swift @@ -5,6 +5,7 @@ // Created by 송예찬 on 7/7/26. // +import AppIntents import SwiftUI import SwiftData @@ -31,6 +32,8 @@ struct Haru_DanimApp: App { WatchSyncManager.shared.activate() // CloudKit 원격 변경(다른 기기·위젯 인텐트의 쓰기)이 오면 위젯·워치·라이브 액티비티 갱신 CloudSyncMonitor.shared.start(container: container) + // 시리 문구의 행동 이름 어휘(\(\.$action) 문구) 갱신 — 행동 추가/삭제는 다음 실행에 반영 + HaruDanimShortcuts.updateAppShortcutParameters() } var body: some Scene { diff --git a/myApp/HaruDanim/IOS/Localizable.xcstrings b/myApp/HaruDanim/IOS/Localizable.xcstrings index 91da3ed..0594f86 100644 --- a/myApp/HaruDanim/IOS/Localizable.xcstrings +++ b/myApp/HaruDanim/IOS/Localizable.xcstrings @@ -1,6 +1,22 @@ { "sourceLanguage": "ko", "strings": { + "\"시리야, 하루 다님에서 측정 시작\" — 말과 단축어 자동화로 기록해요.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "\"Hey Siri, start tracking in HaruDanim\" — track by voice and Shortcuts automations." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "「Hey Siri、ハルダニムで計測開始」— 声とショートカットの自動化で記録できます。" + } + } + } + }, "%@": { "localizations": { "en": { @@ -972,6 +988,22 @@ } } }, + "iCloud로 기기 간 데이터가 동기화돼요.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Your data syncs across devices via iCloud." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "iCloudでデバイス間のデータが同期されます。" + } + } + } + }, "iCloud로 아이패드 등 다른 기기와 데이터가 동기화돼요. 변경 사항은 앱을 완전히 종료했다가 다시 실행하면 적용돼요.": { "localizations": { "en": { @@ -1198,6 +1230,22 @@ } } }, + "개수 제한 해제": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Unlimited items" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "個数制限の解除" + } + } + } + }, "건강": { "localizations": { "en": { @@ -3775,6 +3823,7 @@ } }, "목표 달성률을 ${span} 기준 ${style}(으)로 표시": { + "extractionState": "stale", "localizations": { "en": { "stringUnit": { @@ -5494,6 +5543,22 @@ } } }, + "시리 단축어": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Siri Shortcuts" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Siriショートカット" + } + } + } + }, "시리 단축어는 프리미엄 기능이에요. 하루 다님 앱의 설정 → 프리미엄에서 잠금 해제해 주세요.": { "localizations": { "en": { @@ -5670,6 +5735,38 @@ } } }, + "아이패드 동기화": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "iPad sync" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "iPad同期" + } + } + } + }, + "아이패드 일기": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "iPad diary" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "iPadダイアリー" + } + } + } + }, "아주 넓게": { "localizations": { "en": { @@ -5751,6 +5848,22 @@ } } }, + "애플워치 앱": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Apple Watch app" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Apple Watchアプリ" + } + } + } + }, "앱 설정과 일치": { "localizations": { "en": { @@ -6231,6 +6344,22 @@ } } }, + "워치에서 추적하고 컴플리케이션으로 확인해요.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Track on your watch and check complications at a glance." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウォッチで記録し、コンプリケーションで確認できます。" + } + } + } + }, "워치와 아이폰은 근처에 있을 때 자동으로 맞춰져요. 값이 다르게 보이면 워치 앱을 아래로 당겨 새로고침하거나, 아이폰에서 앱을 한 번 열어 주세요. 모음 탭의 새로고침 버튼도 도움이 돼요.": { "localizations": { "en": { @@ -6424,6 +6553,22 @@ } } }, + "위젯에서 바로 행동을 실행하고 달성률을 확인해요.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Run actions and check progress right from widgets." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウィジェットから直接アクションを実行し、達成率を確認できます。" + } + } + } + }, "음식": { "localizations": { "en": { @@ -8092,6 +8237,7 @@ } }, "측정 시작": { + "extractionState": "stale", "localizations": { "en": { "stringUnit": { @@ -8124,6 +8270,7 @@ } }, "측정 종료": { + "extractionState": "stale", "localizations": { "en": { "stringUnit": { @@ -9117,6 +9264,22 @@ } } }, + "하루 정리와 애플 펜슬 다이어리를 사용해요.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Wrap up your day and journal with Apple Pencil." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "1日のまとめとApple Pencilダイアリーが使えます。" + } + } + } + }, "하루 통계": { "localizations": { "en": { @@ -9722,6 +9885,22 @@ } } }, + "행동·목표·다짐을 무제한으로 만들 수 있어요.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Create unlimited actions, goals, and quests." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アクション・目標・クエストを無制限に作れます。" + } + } + } + }, "행동별 시간 (일별)": { "localizations": { "en": { @@ -9914,6 +10093,22 @@ } } }, + "홈·잠금화면 위젯": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Home & Lock Screen widgets" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ホーム・ロック画面ウィジェット" + } + } + } + }, "화": { "comment": "요일 축약(화요일)", "localizations": { diff --git a/myApp/HaruDanim/IOS/Views/SettingsView.swift b/myApp/HaruDanim/IOS/Views/SettingsView.swift index 930f492..23bec6c 100644 --- a/myApp/HaruDanim/IOS/Views/SettingsView.swift +++ b/myApp/HaruDanim/IOS/Views/SettingsView.swift @@ -344,6 +344,7 @@ struct PremiumView: View { featureRow("icloud.fill", "아이패드 동기화", "iCloud로 기기 간 데이터가 동기화돼요.") featureRow("applewatch", "애플워치 앱", "워치에서 추적하고 컴플리케이션으로 확인해요.") featureRow("book.closed.fill", "아이패드 일기", "하루 정리와 애플 펜슬 다이어리를 사용해요.") + featureRow("mic.fill", "시리 단축어", "\"시리야, 하루 다님에서 측정 시작\" — 말과 단축어 자동화로 기록해요.") } header: { Text("프리미엄으로 할 수 있는 것") } footer: { @@ -552,7 +553,9 @@ struct PremiumView: View { } } - private func featureRow(_ symbol: String, _ title: String, _ detail: String) -> some View { + // title·detail은 LocalizedStringKey — String이면 리터럴이 카탈로그로 추출되지 않아 + // 영어·일본어에서도 한국어로 노출됐다 (기능 목록 전체가 미지역화였던 버그) + private func featureRow(_ symbol: String, _ title: LocalizedStringKey, _ detail: LocalizedStringKey) -> some View { HStack(alignment: .top, spacing: 12) { Image(systemName: symbol) .font(.system(size: 18)) diff --git a/myApp/HaruDanim/Shared/HaruDanimIntents.swift b/myApp/HaruDanim/Shared/HaruDanimIntents.swift index 927e5ab..d49d04d 100644 --- a/myApp/HaruDanim/Shared/HaruDanimIntents.swift +++ b/myApp/HaruDanim/Shared/HaruDanimIntents.swift @@ -383,6 +383,10 @@ struct RunActionIntent: AppIntent { static let title: LocalizedStringResource = "행동 실행" static let description = IntentDescription("시간 측정 행동은 시작/종료를 전환하고, 횟수 기록 행동은 횟수를 1 추가해요.") + /// 위젯 버튼 전용이므로 단축어 앱·스포트라이트에는 숨긴다 — 파라미터가 생 UUID 문자열이라 + /// 사용자가 직접 조합할 수 없는 동작이다 (isDiscoverable false여도 위젯 Button(intent:)은 동작). + static let isDiscoverable = false + /// 위젯 버튼 전용으로 AppEntity 대신 UUID 문자열을 직접 받는다. /// AppEntity 파라미터는 버튼을 누를 때마다 EntityQuery로 재조회(그 안에서 ModelContainer를 /// 통째로 다시 여는 무거운 작업)를 거쳐야 perform이 실행돼, "눌러도 반응이 없고 여러 번 연타해야 @@ -436,7 +440,10 @@ enum ActionRunner { // MARK: - 인텐트: 시간 측정 시작 -struct StartTimeActionIntent: AppIntent { +/// LiveActivityIntent 채택: 시리/단축어의 백그라운드 실행에서도 Live Activity 시작이 +/// 허용되어, 앱을 열지 않아도 다이나믹 아일랜드에 측정이 표시된다. (앱 프로세스에서 실행됨 — +/// 위젯 버튼이 쓰는 RunActionIntent는 확장 프로세스 응답성 튜닝을 유지하려 채택하지 않는다) +struct StartTimeActionIntent: LiveActivityIntent { static let title: LocalizedStringResource = "시간 측정 시작" static let description = IntentDescription("시간 측정 행동의 측정을 시작해요.") @@ -463,7 +470,8 @@ struct StartTimeActionIntent: AppIntent { // MARK: - 인텐트: 시간 측정 종료 -struct StopTimeActionIntent: AppIntent { +/// LiveActivityIntent 채택 이유는 StartTimeActionIntent 참고 (종료 시 Live Activity 갱신/제거) +struct StopTimeActionIntent: LiveActivityIntent { static let title: LocalizedStringResource = "시간 측정 종료" static let description = IntentDescription("측정 중인 행동의 시간 측정을 종료해요.")