15 Commits

Author SHA1 Message Date
songyc macbook
0e6804b531 feat(progress): 주기 몫 채운 주간·월간 다짐의 하루 진행률을 '이번 주/달 달성' 상태로 표시
- 주간/월간 '이상 달성' 다짐이 현재 주기 몫을 채우면 하루 게이지가 0% 대신
  완료 상태(게이지 가득+초록 ✓+문구)로 표시 — 목표 탭 행·모음 카드·위젯 ②(체크)·
  ③(문구)·④(링 가득)·시리 하루 응답 공유
- 목표 하루 평균(combinedSpanRatio)에도 해당 다짐을 1.0으로 반영 —
  위젯 ② 목표 바·워치 목표 게이지·시리 목표 응답·일기 헤더 자동 반영
- 판정은 QuestProgress.isPeriodFulfilled 단일 지점 — 기준일의 논리적 하루 끝
  클리핑으로 일기 과거 날짜 소급 차단. spanProgress 수치·판정(judgment)·연속(streak)
  불변(표시 계층 전용). 하루 다짐·특정 기간·atMost는 비대상, 잠금화면·워치
  컴플리케이션은 공간 제약으로 수치 유지
- 도움말 '진행률 읽는 법' 문구 갱신, l10n en/ja 완료(전 카탈로그 stale 0·missing 0)
- 검증: -progressSelfTest 37→49건 ALL PASS(기존 37건 수치 무변경), 신규
  -seedPeriodFulfilled 시드로 ko/en/ja·위젯 실렌더 확인, 시리 스모크 10건 OK,
  Debug/Store 빌드 성공(워치 타깃은 Shared 미포함·무변경)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVeduv1eNdXjk1ay4tSgBg
2026-08-01 06:45:01 +09:00
songyc macbook
623217b907 fix(l10n): natural English word order for quest summaries + help refresh
- 전체 점검(코드 정독 + 폰·패드·워치 시뮬 검증)에서 발견된 번역·도움말 보완
- 다짐 요약 en 어순 수정: "30m Reach at least" → "Reach at least 30m" — 포맷 키 '%@ · %@ %@'의 en을 위치 지정자(%1$@ · %3$@ %2$@)로 재배열, ko/ja 불변
- 시리 다짐 엔티티 부제가 일반 보간이라 카탈로그를 안 타던 것을 String(localized:)로 교정 — 위젯 카탈로그에 같은 키 번역 추가
- 도움말 일기 항목에 달력 기분 사진 배경 안내 한 문장 추가 (ko/en/ja, stale 키 정리)
- CLAUDE.md §14: -premium 인자가 콜드 스타트 동안만 유효함(지속은 DEBUG 토글) 명시
- 검증: 9개 카탈로그 missing/stale 클린, 인텐트 스모크 10/10, progressSelfTest 37건 PASS, streak 덤프 정상, 3개 스킴(iOS Debug/Store·워치) 빌드 성공, en 목표 탭 스크린샷으로 어순 확인

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014CkLDi8Lr21vGqo4SjZaTp
2026-07-18 00:31:11 +09:00
songyc macbook
2a176fa8a2 fix(quest): count scheduled days only for daily-quest spans + rest-day state
진행률 검증 보고에서 확정한 결정 반영 (사용자 위임):

- [계산 수정] 하루 단위 다짐의 주/월 집계를 **방향 무관 수행일 기록만** 합산 — 목표량(scaledTarget)이 수행일 수 환산인데 '이하 유지'만 비수행일 기록까지 값에 섞여 분모·분자가 어긋나던 비대칭 해소 (월수금 1시간 이하 다짐이 목요일 시청으로 한도 초과 표시되던 문제). atLeast의 하루 기여 캡·마감 창 클립은 기존 유지
- [표시 수정] 수행일 아닌 날(요일·날짜 다짐의 쉬는 날, 기간 밖 custom)의 '하루' 게이지를 '수행일 아님' 상태로 — 쉬는 날 기록은 주/월 집계에 안 잡히는 값이라 퍼센트(예 133%)로 보여주면 "오늘 채웠다"는 오해 유발. 목표 탭 행·모음 카드(문구+빈 바), 위젯 ③(문구·누적값 숨김)·②(— 대시)·④(링 흐림, 스냅숏 isRestDay), 시리 '오늘' 조회("오늘은 수행일이 아니에요") 공유. 워치·잠금화면 점은 공간 제약으로 수치 유지(문서화)
- [의도 확정·유지] atMost 하루 게이지의 환산 한도 페이스 경고 / 다짐 생성 이전 기록 인정 / 월 경계 주 혼입 / 적용일 없는 달 0% / custom 종료 후 게이지 — CLAUDE.md §4.2에 트레이드오프로 명시
- 검증: -progressSelfTest 37건 ALL PASS(E 비수행일 제외·M 비대칭 수정·N 기간 밖 판정 신규), 목표 탭 스크린샷(달리기 월수금: 하루 '수행일 아님'/주간 0%/월간 7%), 위젯 미리보기 렌더 무결, Debug·Store·워치 3스킴 빌드 성공
- 도움말 '진행률 읽는 법' 전면 보강(수행일 집계·페이스 캡·이하 유지 이분법+하루 경고) en/ja 완비, 신규 키 2종('수행일 아님', 시리 응답) IOS·위젯 카탈로그 클린(763/213키)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-16 22:59:15 +09:00
songyc macbook
7a6731a1f5 fix(l10n): wrap four raw Korean literals missed by catalog extraction
- 기록 탭 목록의 진행 중 세션 표기 "진행 중"이 문자열 보간 안 String 리터럴이라 영어·일본어 UI에 한국어로 노출되던 것 수정 — String(localized:)로 기존 카탈로그 키 재사용 (시뮬레이터 en "In progress" / ko "진행 중" 스크린샷 검증)
- 같은 패턴 3곳 동일 수정: 꼬리표 편집 미리보기 행 "미리 보기"(String 삼항), 시리 다짐 엔티티 부제 "목표 없음"(?? 폴백), 워치 행동 목록 제목 "꼬리표 없음"(String 파라미터) — 전부 기존 번역 키 재사용이라 카탈로그 변경 없음(xcstringstool sync 무변화 확인)
- 검증: Debug·Store·워치 3스킴 빌드 성공, 시리 인텐트 스모크 테스트 10/10, streak-dump 수치 손검산 일치

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-16 02:38:05 +09:00
songyc macbook
e4bba280ad feat(control): Control Center toggle for running an action (iOS 18 control)
제어 센터/잠금화면 하단/액션 버튼에 배치할 수 있는 '행동 실행' 컨트롤:
- 행동 하나를 골라(미선택 시 배치 첫 행동) 시간형은 시작/종료 토글,
  횟수형은 탭마다 +1 (토글 표시는 꺼짐으로 복귀)
- 전용 ToggleActionControlIntent(SetValueIntent + LiveActivityIntent) —
  앱 프로세스 실행이라 제어 센터에서 시작해도 다이나믹 아일랜드가 바로 뜸.
  단축어 앱에는 숨김(isDiscoverable=false), 프리미엄 게이트 적용
- DataChange.commit / IntentStore.performWrite에
  ControlCenter.reloadAllControls() 추가 — 앱/위젯/시리/워치 어느 경로로
  측정 상태가 바뀌어도 컨트롤 토글 표시가 따라온다

시뮬레이터에는 제어 센터에 컨트롤을 추가할 수단이 없어 빌드·인텐트
메타데이터 등록 확인까지 검증 (실기기에서 배치·토글 확인 필요).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-14 00:19:36 +09:00
songyc macbook
076dad26d8 feat(widget): adopt LiveActivityIntent on the widget run button too
RunActionIntent(위젯 실행 버튼)도 LiveActivityIntent를 채택 — 시스템이
앱 프로세스에서 실행해, 위젯 탭으로 시간 측정을 시작해도 시리 경로와
동일하게 다이나믹 아일랜드가 바로 뜬다.

과거 '위젯 버튼 먹통' 증상 때문에 확장 프로세스 고정을 유지했었으나,
해당 증상은 위젯 재추가·기기 재부팅(캐시)으로 해소된 것으로 확인되어
제약을 풀었다. 실기기에서 응답성 회귀가 보이면 이 인텐트의
LiveActivityIntent 채택 한 줄만 되돌리면 된다 (코드 주석에도 기록).

검증: -intentSmokeTest에 위젯 실행 토글 시나리오 추가(시작 → 토글 종료),
10개 시나리오 전부 통과. Debug·Store 스킴 빌드 성공.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 21:37:42 +09:00
songyc macbook
ec62dc83de feat(siri): background Live Activity, spoken action names, localized phrases
시리 단축어 점검에서 나온 추천 수정 일괄 적용:

- 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 21:08:48 +09:00
songyc macbook
c7024ea612 fix(widget): fix AppIntent button interactions and streamline widget configuration options while preserving real-time sync
Two independent widget issues, both confined to the widget/intent layer.
The app→widget real-time sync path (IntentStore.refresh + performWrite +
reloadAllTimelines) is intentionally left untouched.

1. Interactive button taps were unreliable ("touch feedback but nothing
   changes; works only after several taps"). The run buttons passed a full
   AppEntity parameter — RunActionIntent(action: cell.entity). On every tap
   WidgetKit re-resolves that AppEntity through ActionEntityQuery.entities(for:),
   which opens a brand-new ModelContainer from disk each time. That heavy
   entity-resolution round-trip on the tap hot path is the classic cause of
   dropped/laggy interactive-widget taps — the write logic itself was already
   proven reliable last session. Replaced RunActionIntent's AppEntity parameter
   with a plain UUID String (actionID) that deserializes instantly with no
   query; perform() looks the action up inside the existing single-context
   performWrite path. One tap now fires immediately. RunActionIntent is
   widget-only (not a Siri AppShortcut), so this touches no shortcut. Both call
   sites (ActionRunCellView, QuestRingActionCellView) updated; the now-unused
   ActionCellSnapshot.entity helper removed.

2. Configuration screens were cluttered — every widget exposed action/goal/
   quest slots 1–4 regardless of family, so a Small widget's long-press config
   listed slots it never renders. Added a parameterSummary to all six
   WidgetConfigurationIntents so the config reads as one clean sentence, and
   labeled secondary slots with the size that uses them (e.g. "행동 2 (중형·대형)",
   "목표 3 (대형)"). WidgetKit can't hide parameters by family, so this makes the
   optional slots self-explanatory instead of noise.

Verified: BUILD SUCCEEDED (app + embedded Haru_DanimWidgets.appex); cold launch
with the widget preview renders without crashing. Physical home/lock-screen
button taps still warrant a real-device pass (the simulator can't tap widget
overlays).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-12 21:50:27 +09:00
songyc macbook
32306510c8 fix(widget): make interactive AppIntent writes reliable and crash-proof
Interactive home/lock-screen widget buttons (count +1, timer start/stop)
gave touch feedback but often didn't persist, only working after several
rapid taps. Two compounding defects in the widget-extension write path:

- The extension could crash mid-write. refresh()/makeContainer() opens a
  new ModelContainer on every intent perform and every timeline read; in
  the extension that hit a local-store branch whose failure was fatalError.
  A transient App Group store contention with the running main app crashed
  the whole appex — the tap looked dead and only a later retry won the race.
  Split makeContainer into a throwing makeContainerThrowing(); refresh() now
  wraps it in try? and keeps the previous container on failure, so the
  extension degrades instead of crashing. The app's DataStore.shared keeps
  its crash-on-failure semantics.

- Fetch → mutate → save could span different contexts and save errors were
  swallowed. IntentStore.context is a computed container.mainContext, and
  commit() re-read it for a try? save(), so a mid-flight container swap left
  the mutation on a dead context and any save failure vanished silently.
  Added IntentStore.performWrite(_:) which captures one context for the whole
  fetch+mutate+save, saves with a retry (no silent try?), then fires the
  Live Activity hook and reloadAllTimelines. All four mutating intents
  (RunAction/StartTime/StopTime/AddCount) now fetch in that single context.

No @Model or main-app logic changed — fix is confined to the widget/intent
data layer. Verified: 5 rapid RunActionIntent.perform() calls persist +5 with
no lost writes across three cold launches (ok=true each). Build succeeds
(app + embedded Haru_DanimWidgets.appex).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-12 11:01:47 +09:00
songyc macbook
f7ddd42260 refactor(widget): complete widget architecture overhaul for reliable interactions and removed bloated configurations
- IntentStore now recreates the ModelContainer on demand in the widget
  extension (IntentStore.refresh()), called before every AppIntent
  perform() and every provider's makeEntry(). Fixes stale/blank widget
  buttons and double-toggle bugs caused by the extension process
  reading an old in-memory snapshot after CloudKit remote changes.
- Replaced the fixed 15/30-minute polling Timeline policy (which burns
  WidgetKit's daily reload budget across 6 widget kinds) with
  WidgetRefresh.timeline: idle widgets wait until the next logical-day
  boundary since value changes are already pushed via
  reloadAllTimelines, while widgets showing a live ratio (a currently
  running quest/goal) get pre-computed future entries so progress
  rings and bars keep advancing without extra reloads.
- Removed the per-widget "위젯 테마" (match app / light / dark / liquid
  glass) configuration option from all 5 home-screen widgets. It
  duplicated the same enum five times, and the liquid-glass variant's
  custom background/scheme override fought the system's own
  vibrant/tinted rendering in tinted Home Screens, which was part of
  the "블랙 화면"/렌더링 오류 reports. Widgets now simply follow the
  app's own theme setting (WidgetAppearance.appScheme), which is what
  "앱 테마와 일치" already defaulted to.
- Added shared gallery/placeholder sample data (ActionCellSnapshot,
  QuestCellSnapshot, GoalSnapshot, StatsChart sample points) so the
  widget gallery and redacted placeholders show a realistic shape
  instead of an empty/blank card.
- Unified empty-state UI via WidgetEmptyView across all 5 widgets.

Scope: Widgets/*, Shared/HaruDanimIntents.swift (widget-facing
IntentStore only), IOS/Views/WidgetPreviewScreen.swift (DEBUG preview
tool). No changes to app core data models, sync, or view logic.
2026-07-11 22:41:54 +09:00
songyc macbook
fbd4810628 fix: resolve widget sync crashes via local AppGroup container and improve goal widget to show all sub-goals
위젯 동기화 크래시 수정:
- DataStore: CloudKit 미러링을 메인 앱 프로세스 전담으로 분리.
  위젯 등 앱 확장(.appex)은 CloudKit 엔타이틀먼트가 없고 메모리·수명
  제약으로 미러링 설정이 크래시/행을 유발하므로, 확장 프로세스는 항상
  같은 App Group 스토어 파일을 로컬 전용(cloudKitDatabase: .none)으로
  연다. 확장이 쓴 변경은 메인 앱이 히스토리로 집어 CloudKit에 내보냄
- CloudSyncMonitor 신설: 메인 앱이 NSPersistentStoreRemoteChange
  (다른 기기의 CloudKit 가져오기·위젯 인텐트의 쓰기)를 디바운스로 받아
  라이브 액티비티 → 위젯 타임라인 → 워치 스냅숏 순으로 즉시 갱신
- 인터랙티브 위젯(AppIntent)의 컨테이너 매핑 점검: IntentStore.commit이
  저장 직후 reloadAllTimelines를 이미 보장함을 확인·문서화

모음 탭 목표 진행 현황 카드 개선:
- '다짐별로 보기'의 상위 3개 제한 제거 — 모든 다짐 확인 가능
- 화면 균형을 위해 접힘/펼침 UX 적용: iPhone은 3개, iPad는 6개까지
  접힌 상태로 보여주고 '다짐 N개 더 보기' 버튼으로 카드 안에서 펼침
  (카드 탭 이동과 충돌하지 않는 독립 버튼, 스냅 애니메이션)
- DebugSeed: 접힘/펼침 검증용 4번째 다짐 추가, -expandGoalCard 인자 추가

검증: iPhone 17 Pro·iPad Pro 11 시뮬레이터에서 접힘/펼침 렌더링과
사이드바 레이아웃 확인, 전체 타깃(iOS·워치·위젯) 빌드 성공

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-11 21:35:20 +09:00
songyc macbook
3dd53c4fc6 refactor: decouple all UI preferences and layout settings from CloudKit sync to local device storage
- Add Shared/LocalPrefs.swift: device-local UI preferences stored in App Group
  UserDefaults (never synced by iCloud), read identically by app, widgets,
  watch snapshot, and App Intents on the same device
- Move to local storage: main-tab action button order (UUID array), pinned
  goal-progress cards on the main tab, goal quest-list collapse state, and
  per-action memo-prompt popup — reordering or repinning on iPhone no longer
  touches the iPad and vice versa
- Defense logic: actions not in the local order list (e.g. newly received via
  CloudKit from another device) are automatically appended to the end, sorted
  by legacy sortOrder then creation date
- One-time migration adopts existing @Model values (sortOrder, showsOnMain,
  isCollapsed, promptsForNote) into local prefs at launch; model properties
  stay in place so the CloudKit schema is untouched and records keep syncing
- Data remains fully synced: actions, goals, quests, sessions, count entries,
  statistics, and goal/tag/quest list order are untouched by this change
- Verified in simulator: legacy data migrates losslessly, grid renders from
  local order, unknown action lands at the end; iOS+watch+widgets build clean

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-11 20:58:34 +09:00
songyc macbook
081c388f00 fix: 워치-라이브 액티비티 동기화, 앱 이름 띄어쓰기, 차트 라벨 겹침, 위젯 '선택 안 함' 옵션
라이브 액티비티(다이내믹 아일랜드) 워치 연동:
- 워치 실행 명령이 세션 활성화 전이거나 sendMessage 실패 시 유실되던 문제를
  transferUserInfo 큐 폴백으로 보강 (iPhone didReceiveUserInfo 수신 처리 추가)
- 큐로 지연 도착한 실행 명령이 엉뚱한 토글을 일으키지 않도록 5분 유효 시간 부여
- 백그라운드에서 Live Activity 시작이 거부되면 pendingStartRetry로 기억했다가
  앱 포그라운드 복귀(scenePhase active) 시 재시도 + DB 상태 재동기화
- WatchSync 진단용 os_log(notice) 추가, DEBUG -autoRunFirstAction 검증 훅 추가
- 시뮬레이터 검증: 워치 시작 → 아이폰 세션 시작 + 다이내믹 아일랜드 생성,
  워치 종료 → 세션 종료 + 아일랜드 소멸 확인 (큐 폴백 채널은 시뮬레이터
  transferUserInfo 한계로 실기기 확인 필요)

앱 이름 전역 띄어쓰기 ('하루다님' → '하루 다님'):
- pbxproj CFBundleDisplayName 6곳, InfoPlist.xcstrings 3개(ko), 스플래시(ContentView),
  워치 내비게이션 타이틀, 컴플리케이션 문구, 문자열 카탈로그 키 일괄 변경
- 워치 카탈로그 영문 값 HaruDanim → Haru Danim 통일

통계 위젯 X축 라벨 겹침 방지:
- 날짜 축 포인트가 7개를 초과하면(한 달·일별 등) 라벨을 숨기고 눈금만 표시

위젯 설정 '선택 안 함' 옵션:
- Action/Goal/Quest 엔티티에 zero-UUID 센티널 추가, 제안 목록 최상단 노출
- 2·3번째 슬롯 선택을 되돌릴 수 있고, 위젯 조회 단계에서 자연스럽게 걸러짐
- ko/en/ja 번역 추가 (None / 選択しない)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-10 21:47:42 +09:00
songyc macbook
a6c040cd6a feat(i18n): 전체 UI 다국어 지원 (한국어·영어·일본어)
- 언어 설정 실적용: 변경 시 AppleLanguages 오버라이드 저장, 재시작 안내 footer
- 자동 추출되지 않던 일반 문자열(탭 이름, 픽커 라벨, 상태 뱃지, 포매터,
  삼항 문구, 심볼 카테고리 등)을 String(localized:)로 전환
- 위젯·워치 앱·컴플리케이션 타깃에 Localizable.xcstrings 신설
  (중복 리소스 방지 pbxproj 예외 추가), xcstringstool sync로 키 추출
- 4개 카탈로그 전체 키(고유 430개)에 en/ja 번역 주입
- 영어의 긴 횟수 단위("times")가 잘리지 않도록 모음 탭 셀 축소 허용
- 검증: -AppleLanguages "(en)"/"(ja)"로 모음 탭·설정 화면 확인

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-10 15:03:24 +09:00
songyc macbook
18f49bc0db feat(intents): 시리 단축어용 App Intents 구현 (CLAUDE.md §10)
- 엔티티: ActionEntity/GoalEntity/QuestEntity (목록 제안 + 이름 검색)
- 인텐트 6종: 시간 측정 시작/종료, 횟수 추가, 행동 누적값, 목표/다짐 진행률 조회
  — 조회 인텐트는 값을 반환해 단축어에서 변수로 활용 가능
- RunActionIntent: 인터랙티브 위젯 버튼용 토글(시간형 시작/종료, 횟수형 +1)
- ActionRunner: 실행 로직 공용화 (짧은 기록 무시 규칙 포함)
- 모든 인텐트에 프리미엄 게이트, 실행 후 위젯 타임라인 갱신
- IntentHooks: 앱 프로세스에서 Live Activity 갱신 주입
- 시리 대표 문구 4종 (AppShortcutsProvider)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-10 08:59:31 +09:00