240 Commits

Author SHA1 Message Date
songyc macbook
0aed7a9ec5 fix(goal): evaluate ended goals at launch and foreground, not only on goal tab
종료일이 지난 목표의 자동 판정(evaluateIfEnded)이 목표 탭 진입 시에만
실행돼, 목표 탭을 열지 않으면 위젯·워치·모음 탭 카드에 '진행 중'으로
계속 남아 있었다. 앱 실행/포그라운드 복귀(scenePhase .active) 시점에
같은 판정을 추가로 실행한다.

- 목표 탭 onAppear 로직과 동일한 코드 — 진행 중 목표만 조회, 변경이
  있을 때만 DataChange.commit (위젯·워치·라이브 액티비티 갱신)
- 다짐 없는 목표는 evaluateIfEnded가 건드리지 않으므로 '확인 필요'
  직접 선택 흐름은 그대로
- 위젯 확장에서는 판정하지 않음 (프로바이더 경합·CloudKit 내보내기
  지연 위험 회피)
- 검증용 DEBUG 인자 -endGoalYesterday "제목" 추가. 시뮬레이터에서
  목표 탭을 열지 않은 콜드 스타트만으로 statusRaw가 achieved로
  전환되는 것을 스토어 조회로 확인

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 19:37:43 +09:00
songyc macbook
d023f3eca1 fix(action): commit tag order and favorite changes immediately
꼬리표 순서 변경 시트와 즐겨찾기 토글(리스트 스와이프·상세 화면)이
DataChange.commit 없이 autosave에만 의존해, 저장 확정이 지연되고
꼬리표 순서를 쓰는 워치 앱 그룹핑이 다음 커밋 시점까지 낡은 순서로
남을 수 있었다.

- TagOrderSheet: 드래그마다가 아니라 시트가 닫힐 때 1회만 커밋
  ('완료'와 스와이프 내리기 모두 onDisappear 경로) — 위젯 리로드 연발 방지
- 즐겨찾기 토글 2곳: 변경 즉시 커밋

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 19:35:29 +09:00
songyc macbook
417070bde7 chore: rewrite CLAUDE.md from actual code, plus safe hardening fixes
CLAUDE.md v2.0 — 기획서(v1.0)를 폐기하고 실제 구현 기준의 현행 명세로 전면 재작성:
프로젝트 구조·빌드/검증 루틴·도메인 모델(CloudKit 규칙)·집계/판정 규칙·데이터 계층
(LocalPrefs/DataChange)·화면별 상세·내보내기·프리미엄/StoreKit·위젯 6종·워치·시리·
DEBUG 런치 인자·작업 컨벤션까지 새 세션이 이 문서만으로 파악 가능하게 정리.

동작 보존 범위의 안전 수정 3건:
- Color.hexString: 와이드 컬러(P3) 성분을 0...1로 클램프 — 음수/1 초과 값이
  %02X를 거치며 자릿수 깨진 hex가 태그·목표 색으로 저장되던 잠재 버그 방지
- 일기 타임테이블 필터: 선택 즉시 명시적 save — 캘린더 일정 선택 경로와 동일하게
  영속을 보장 (autosave 의존 제거)
- 통계 평균 "%.1f회"가 String(format:)이라 영어·일본어에서도 '회'로 노출되던 것을
  Format.countAverage로 지역화 (en "times" / ja "回", 카탈로그 동기화 포함)

Debug·Store(Release) 스킴 빌드 검증 완료.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 18:45:53 +09:00
songyc macbook
93dd3d1d9f feat(diary): per-date action filter for the summary timetable
일기 타임테이블에서 그날 기록된 행동 중 원하는 것만 골라 볼 수 있게.
캘린더 일정 선택과 마찬가지로 날짜별로 저장되고 내보내기에도 반영된다.

- DiaryEntry.hiddenActionIDs (행동 uuid 문자열, 날짜별·동기화 안전)
- 타임테이블 카드 우상단에 필터 버튼 추가 (숨긴 개수 배지, 캘린더 버튼 옆)
- 기존 RecordFilterSheet 재사용 — 그날 기록된 행동만 꼬리표별로 나열
- 필터는 타임테이블에만 적용, 기록 목록·요약 통계는 전체 유지
  (ExportSnapshot.replacingTimetableSections로 내보내기도 동일 규칙)
- 도움말 갱신: 목표 달성 판정 기준, 일기 타임테이블 일정·필터 항목
- en/ja 번역 추가 (Live Activity의 미번역 문자열 3건도 함께 보충)
- 검증용 런치 인자 -diaryShowActionFilter, -diaryHideFirstAction 추가

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 18:24:22 +09:00
songyc macbook
85a98c1ab0 feat(goal): configurable achievement threshold per goal
목표 종료 판정이 '다짐 전체 달성'으로 고정되어 있던 것을, 목표를 만들 때
달성 판정 기준(%)을 정할 수 있게 확장. 다짐들의 평균 달성률(달성=100%,
미달성=현재 주기 진행률)이 기준 이상이면 달성 완료로 판정한다.

- Goal.achieveThresholdPercent (기본 100 = 기존 동작 유지, CloudKit 호환)
- evaluateIfEnded/manualFinish가 questAchievementRatio ≥ 기준으로 판정
- 목표 추가/수정 화면에 '달성 판정 기준' 슬라이더 (10~100%, 5% 단위)
- 수동 종료 안내 문구에 기준이 100% 미만이면 해당 기준 표기
- 검증용 런치 인자 -goalShowEditor 추가
- 그 외 진행률 표시·연산은 변경 없음

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 18:24:05 +09:00
songyc macbook
5f9593923a fix(diary): pencil-only drawing with finger panning, and truly sharp strokes
- 실기기에서 drawingPolicy .pencilOnly — 손가락은 절대 필기되지 않고,
  확대 상태에서 한 손가락으로 페이지를 상하좌우 이동(팬)한다. 이동/확대
  제스처는 allowedTouchTypes=direct로 손가락 전용이라 펜슬 획을 뺏지 않음
  (시뮬레이터는 입력 수단이 없어 anyInput 유지). 배치 모드에선 한 손가락을
  요소 드래그에 양보(팬 두 손가락)
- 선명도 재수정: SharpPencilCanvasView가 목표 래스터 배율(기기 스케일 ×
  현재 줌, 상한 ×3)을 뷰+레이어 트리에 유지하고, 획을 그릴 때마다·레이아웃
  때마다 재적용 — PencilKit이 새 획 렌더 레이어를 기본 해상도로 만들어
  흐려지던 문제와, 기존 1.5배 슈퍼샘플링의 다운샘플 소프트닝을 함께 제거
  (화면 픽셀과 1:1 매칭이 가장 선명)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 17:31:09 +09:00
songyc macbook
4c3b5a6c03 feat(diary): show selected calendar events in the timetable, per date
- DiaryEntry.calendarEventIDs: 날짜별로 어떤 캘린더(EventKit) 일정을 넣을지
  따로 저장 — 오늘은 포함, 내일은 미포함 같은 선택이 가능
- 타임테이블 카드 우상단 캘린더 버튼(+선택 개수 배지) → 일정 선택 팝업:
  그날의 일정 목록에서 전체 선택/해제 또는 개별 체크, 권한 미허용 시 설정
  이동 안내. 종일 일정은 제외, 하루 경계에 걸치면 잘라서 표시
- 일정은 행동 블록과 구분되는 외곽선+은은한 채움 스타일(캘린더 색)로 렌더,
  일기 내보내기(PDF/이미지)에도 동일하게 포함 — 내보내기 요약도 화면과
  같은 24시간 타임테이블로 통일
- NSCalendarsFullAccessUsageDescription 추가, 새 문자열 en/ja 번역
- 검증 인자: -diarySeedEvents YES, -diaryShowCalendarPicker YES

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 17:31:09 +09:00
songyc macbook
4f9fdfba11 chore(xcode): normalize Store scheme serialization (Xcode strips comments)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 17:15:37 +09:00
songyc macbook
957618fccd feat(app): add Home tab refresh button and device-specific help guide
- 모음 탭 왼쪽 위 새로고침 버튼(RefreshButton/AppRefresh): 루트 뷰 identity
  리셋으로 모든 @Query를 재조회(앱 재시작과 같은 효과) + 라이브 액티비티·
  위젯·워치 스냅숏 동기화. 다른 기기의 iCloud 변경이 화면에 늦게 보일 때
  재시작 없이 반영. iPhone·iPad 공통, Release(스토어) 빌드에도 포함
- 설정 → 지원 → 도움말(HelpView): 기본 개념/모음 탭/목표/기록·통계/동기화/
  프리미엄 공통 + iPhone은 화면 구성(탭바·버블 메뉴·다이나믹 아일랜드)과
  애플워치(기록·컴플리케이션·동기화) 섹션, iPad는 일기 섹션. 접이식 주제별
  구성, 새 문자열 59개 en/ja 번역
- 검증 인자: -helpPreview YES

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 17:09:11 +09:00
songyc macbook
040198beb4 feat(premium): add store-identical Release run scheme and real-purchase priority
- 'Haru_Danim-Store' 공유 스킴 추가: Release 구성으로 실행해 DEBUG 코드
  (프리미엄 테스트 토글·검증용 런치 인자)가 전부 빠진 '스토어에 올릴
  그대로'의 앱을 실기기에서 확인. StoreKit 로컬 구성은 유지되어 실제 결제
  시트가 뜨되 돈은 청구되지 않는다
- 실구매/복원 시 DEBUG 테스트 토글 강제 모드를 자동 해제(실권한 우선),
  개발용 섹션에 '토글 강제 해제' 버튼과 동작 설명 footer 추가
- Localizable.xcstrings는 Xcode 직렬화 포맷 정규화

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 16:14:11 +09:00
songyc macbook
14fc5cb5bd feat(premium): complete StoreKit 2 purchase experience
기존 StoreKit 2 골격(상품 3종·구매·복원·권한 주입) 위에 실사용 경험 완성:
- PremiumStore.currentPlan: 현재 이용 중인 대표 플랜(평생 > 만료 먼 구독)
  추적, 구매·복원 후 위젯 타임라인 즉시 갱신
- 프리미엄 화면: '이용 중인 플랜' 섹션(플랜 이름·다음 갱신일·평생 안내),
  구독 관리 시트(manageSubscriptionsSheet), 구독 중 평생 이용권 전환 안내,
  년 구독 절약률·평생 '한 번 결제' 배지, 자동 갱신·해지·복원 안내 문구
- 잠금 안내 개선: 행동/목표/다짐 무료 한도 알림에 '프리미엄 알아보기'
  버튼 + PremiumSheetView 모달, 아이패드 일기 잠금 화면에도 버튼 추가
- 기능 소개에 '아이패드 일기' 추가, 새 문자열 en/ja 번역

가격(월 5,000/년 50,000/일회성 80,000)은 HaruDanim.storekit(로컬 테스트)과
App Store Connect에서 각각 수정 가능. DEBUG 강제 토글은 그대로 유지.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 15:52:49 +09:00
songyc macbook
c6959950c2 feat(widget): fill quest list adaptively in goal progress widget
목표 진행률 위젯의 다짐 목록이 고정 3개(중형)/8개(대형) 제한이었던 것을
QuestBarsColumn으로 교체 — 셀의 실제 높이에 들어가는 만큼 최대한 표시
(행 14pt+간격 6pt 기준 계산)하고, 넘치는 개수는 헤더 오른쪽에 '+N' 표기.
기기·패밀리가 커지면 자동으로 더 보인다.

그 외 개수 제한 검토: 다짐 현황 위젯 그리드(4/8/16)는 스펙 고정, 통계
차트 시리즈(4/6)는 가독성 제한, 잠금화면 점(6)은 공간 제한이라 유지.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 15:14:26 +09:00
songyc macbook
5916e17b25 fix(diary): keep pencil strokes sharp at any zoom level
페이지가 논리 768×1024pt 고정 + 스케일 변환이라 PKCanvasView 래스터가
비정수 축소에선 뭉개지고 확대에선 픽셀이 보였음 → 캔버스(내부 렌더 뷰
포함)의 contentScaleFactor를 기본 1.5배 슈퍼샘플링 + 줌 배율 추종(기기
스케일 ×2.5 상한)으로 조정. 레이아웃 변경과 핀치 종료 시점에 갱신.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 15:08:08 +09:00
songyc macbook
8d4d20fc76 feat(diary): make the mood area a large square tile
'오늘 기분'을 카드의 주인공인 200pt 정사각형 타일로 재배치 — 사진은 꽉
채우고(이모지는 우하단 배지), 이모지만 있으면 크게 가운데, 비어 있으면
'탭해서 남기기' 플레이스홀더. 오른쪽에 이모지/사진/지우기 캡슐 버튼 열.
새 문자열 en/ja 번역 추가.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 15:08:08 +09:00
songyc macbook
daf4fc3157 fix(radial): add glass pill background behind bubble tab labels
글래스 버블 메뉴의 탭 이름이 맨 글자(그림자만)라 라이트 모드 밝은 배경
위에서 잘 안 보였음 → 구슬과 같은 ultraThinMaterial 캡슐 배경 + 화이트
보더 + 소프트 섀도를 붙여 라이트/다크 어디서나 읽히게 함.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 14:58:45 +09:00
songyc macbook
939406ac74 feat(brand): logo v4 — walker on a clock planet, with light/dark/tinted icon variants
콘셉트 전면 재디자인: 하루라는 행성(시계)이 화면 아래를 채우고 그 위를
사람이 걸어간다. 시계는 링+3/6/9 눈금+10:10 골드 바늘, 사람 뒤로 링을
따라 남은 발자국, 소프트 섀도로 입체감.

iOS 아이콘 외형 3종(에셋 카탈로그 luminosity 변형):
- 라이트: 웜 화이트 배경 + 초록 시계 + 검정 사람 + 골드 바늘
- 다크: 딥 그린 그라데이션 배경 + 웜 화이트 드로잉 (기존 톤 유지)
- 틴티드: 투명 배경 + 그레이스케일 (시스템이 틴트 적용)
스플래시 로고도 라이트/다크 변형 제공(테마 따라 자동 전환), 워치·워치
위젯은 다크 단일(외형 변형 미지원 플랫폼).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 14:30:01 +09:00
songyc macbook
6b0b6055eb feat(diary): customizable first-page sections — show/hide and reorder
- 일기 상세 메뉴에 '첫 화면 구성' 시트 추가: 7개 섹션(오늘 기분/요약
  지표/이 날의 목표/오늘 할 일/타임테이블/기록 상세/통계 막대)을
  드래그로 순서 변경, 스위치로 표시/숨김 (모든 날짜 공통 적용)
- DiarySummaryPage가 설정 순서대로 렌더링, 숨긴 섹션 제외. 와이드
  레이아웃은 타임테이블 표시 중일 때만 2컬럼(타임테이블 왼쪽 전담)
- 기존 '오늘 기분 표시' 토글은 새 숨김 목록으로 1회 이관
- 검증 인자: -diaryShowConfig YES, -diary.sectionOrder/-diary.hiddenSections
- 새 문자열 en/ja 번역 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 05:57:11 +09:00
songyc macbook
209042575e feat(brand): logo v3 — person walking toward the clock of their day
시계와 사람을 포함해 슬로건('당신의 하루에 다녀가다')과 앱 콘셉트(시간
추적)를 직접 표현: 우상단 웜 화이트 시계 링(골드 10:10 바늘) + 그곳을
향해 걷는 웜 화이트 픽토그램 + 지나온 발자국 점 2개. 색 구성은 유지.
iOS·워치·워치 위젯 아이콘과 스플래시 로고 일괄 교체.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 05:57:11 +09:00
songyc macbook
921eb4bccf feat(brand): redesign logo around the slogan — footsteps visiting the sun of your day
'당신의 하루에 다녀가다'를 직접 표현: 골드 해(하루)를 향해 대각선으로
걸어가는 웜 화이트 발자국 캡슐 3개(멀수록 작아지는 깊이감) + 은은한 링.
기존 색 구성(딥 그린 그라데이션/웜 화이트/머스터드 골드) 유지.
iOS·워치·워치 위젯 앱 아이콘과 스플래시 로고에 일괄 적용.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 03:47:12 +09:00
songyc macbook
ecb7b43d31 feat(diary): redesign summary page as daily review and add note pinch-zoom, line spacing
- 첫 페이지를 '하루 정리' 화면으로 개편: 기록 유무와 무관한 24시간 고정
  타임테이블(trimHours 옵션), 그 날짜에 진행 중이던 목표들의 다짐별 진행
  현황 카드(하루 다짐은 그날 실적, 주간/월간/기간 다짐은 그 날짜까지 누적,
  '이하 유지'는 한도 게이지로 구분), 기록 상세·통계 막대 재배치
- 노트 페이지: UIScrollView 기반 핀치줌(두 손가락, fit~4배, 배치 모드에선 잠금)
- 줄 노트 줄 간격 설정(좁게/보통/넓게/아주 넓게, 페이지별 저장·내보내기 반영)
- 새 문자열 en/ja 번역 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 03:46:58 +09:00
songyc macbook
1f77264852 fix(startup): survive store open failures instead of crashing before splash
일기 기능의 스키마 변경(모델 4종 추가) 후 기존 데이터가 있는 기기에서 첫 실행 시
마이그레이션이 필요한데, 이때 위젯 프로세스와의 스토어 경합이나 마이그레이션 실패가
발생하면 makeContainer()가 곧장 fatalError로 죽어 스플래시도 못 띄우는 문제 방어:
- 일시적 경합 대비 최대 3회 재시도(점증 대기)
- 그래도 실패하면 스토어를 타임스탬프 백업으로 보존 후 새로 생성해 앱은 반드시 켜지게
- 검증: 스토어 파일 고의 손상 → 백업 생성 + 정상 부팅 확인

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 03:15:38 +09:00
songyc macbook
8c803d4d6e feat(brand): new geometric logo — day ring + footsteps, applied to all app icons and splash
- DesignAssets/AppLogo.svg 벡터 원본 (하루=열린 링, 지금=골드 점, 다님=발자국 캡슐 2개)
- iOS·워치 앱·워치 위젯 AppIcon + 스플래시 AppLogo 전부 교체 (기존 플레이스홀더 삭제)
- CoreGraphics 렌더로 불투명(알파 없음) 1024px PNG 생성 — 앱 아이콘 알파 채널 이슈 해소

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 03:02:00 +09:00
songyc macbook
40d917d4e2 feat(diary): add iPad-only premium diary tab with calendar, pencil notes, and export
- 달력(작성일 마커: 기분 이모지/연필 점) → 날짜별 일기, 이번 달 작성 수 표시
- 첫 페이지: 날짜·오늘 기분(이모지/사진, 끄기 옵션)·할 일 체크리스트(스크리블 입력)·
  타임테이블·통계(내보내기 스냅숏 재사용으로 화면·내보내기 일치)
- 노트 페이지: PencilKit 캔버스(도구 팔레트) + 줄 노트 배경 + 사진/도형(사각형·원·화살표·선)
  배치 모드(드래그·핀치), 페이지 무한 추가 — 논리 크기 768×1024 고정으로 WYSIWYG 내보내기
- 내보내기: 구간/복수 날짜(MultiDatePicker) → PDF 1개 또는 날짜별 세로 스티치 PNG
- SwiftData 모델 4종(CloudKit 호환 패턴) 스키마 추가, PremiumFeature.diary,
  iPhone 탭 컨텍스트(탭바·더보기·라디얼)에서 일기 제외, en/ja 번역 39건
- 검증 인자: -diarySeed/-diaryOpenToday/-diaryPage N/-diaryShowExport/-diaryExportRun pdf|image

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 02:51:30 +09:00
songyc macbook
5c3017aeaa fix(l10n): complete ko/en/ja coverage — sync catalogs, translate 123 strings, localize stats titles and filter sheet
- 카탈로그 4종 xcstringstool sync + stale 키 64개 제거
- 버블 메뉴·위젯 5종·이미지 내보내기 신규 문자열 en/ja 전량 번역
- StatsTabView 카드 제목·RecordFilterSheet 제목이 String 파라미터라 미현지화되던 것을 LocalizedStringKey로 수정
- HistoryView 필터 칩 문자열 String(localized:) 누락 수정
- 위젯 익스텐션 InfoPlist.xcstrings 신설(표시 이름 현지화), iOS에 사진 권한 문구 ko/en/ja 추가, 워치 ja 앱 이름 'Haru Danim'으로 통일

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 02:24:28 +09:00
songyc macbook
cde4c4001b feat(export): add high-res image export of records and stats as report posters
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 02:05:16 +09:00
songyc macbook
b897db47db fix(widget): resolve white-on-white rendering bug in Liquid Glass and tinted themes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 01:55:54 +09:00
songyc macbook
221dce1259 refactor(ui): redesign floating menu as one-hand glass bubble arc
The vertical capsule stack reached ~414pt up the screen, putting the top
tabs out of thumb range. Replace it with 'Glass Bubbles': liquid-glass
orbs that fan out from the FAB along a semicircular arc whose radius
(~152pt, clamped to screen width) keeps every tab inside the natural
thumb sweep from the bottom-center pivot.

- Each bubble: ultraThinMaterial circle + top white gradient (specular
  light) + white 0.4 hairline + soft shadow; label with shadow below.
  Current tab rendered as green glass with white symbol.
- Entrance: bubbles fly from the FAB to their arc slot while rotating
  -40deg -> 0 with a snappy spring (response 0.32, damping 0.72) and a
  per-item 0.03s stagger, like droplets bursting in sequence; collapse
  snaps back with no delay. All transform-only (offset/scale/opacity/
  rotation) so no per-frame blur recomputation.
- Struct renamed FloatingCapsuleMenu -> GlassBubbleMenu; storage key and
  NavStyle.radial rawValue unchanged for compatibility.
- Settings copy updated: nav style now '글래스 버블 메뉴' with a
  description of the fan-out and one-handed reach; order editor renamed
  '버블 메뉴 순서' with left-to-right arc wording.

Verified: clean rebuild (stale incremental link produced an old binary
despite BUILD SUCCEEDED; wiped DerivedData), expanded-arc screenshot
shows all 7 bubbles within thumb radius with legible labels, and the
settings screen shows the new name/description.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 01:31:45 +09:00
songyc macbook
49dd1d0ff2 feat(widgets): complete redesign of 5 widget types with modular Intents, fixed floating menu UI/Strings
Widget system rebuilt as 5 independent widgets, each with its own
WidgetConfigurationIntent registered in the WidgetBundle:

- ① ActionRun: multi-action selection, value display option (day/week/
  month/hidden), full tag-color cells; small 1 / medium 4 (2x2) / large
  4 big cells or 8 compact cells (2x4) via largeStyle. Unfilled slots
  render as dashed blank cells.
- ② GoalProgress: multi-goal selection; medium [2 goals | goal+top
  quests], large [4 goals | 2x goal+quests | 1 goal + all quests] via
  medium/largeStyle. New QuestBarRow / GoalWithQuestsCellView.
- ③ QuestRing: multi-quest selection + span; surface-card ring (tag
  color) with centered icon, name and "오늘 N%" text; interactive run
  via RunActionIntent; small 1 / medium 2 / large 4 (2x2).
- ④ QuestStatus (display-only): goal selection + span; rings with name
  only (no %), goal icon+name top-left; small 4 (2x2) / medium 8 (4x2) /
  large [16 (4x4) | 4 goals | 2 goals] via largeStyle.
- ⑤ StatsChart: multi-action selection capped per family (2/4/6) +
  3 span modes; same design scaled by family.

Common foundation:
- Theme option revived on every intent: match app / light fixed / dark
  fixed (WidgetThemeOption AppEnum) applied through widgetAppTheme(_:)
  with proper iOS 17 containerBackground; non-fullColor rendering modes
  (tinted/clear/accented) keep the Color.clear + semantic-color defense
  so content never goes white-on-white.
- Providers are family-aware (capacity by size) and slot-pad selections
  (WidgetStore.slots); defaults fill from existing data so fresh widgets
  never look empty.
- Interactive path unchanged: RunActionIntent(actionID:) +
  IntentStore.performWrite -> reloadAllTimelines.

Floating menu polish:
- All glass capsules now share a uniform width (longest label wins,
  locale-proof) for a tidy popped-up list.
- Settings strings updated: nav style renamed to '플로팅 글래스 캡슐'
  with description matching the capsule popup, order editor retitled
  '캡슐 메뉴 순서' with bottom-to-top wording.

Verified: BUILD SUCCEEDED (iPhone 17 Pro sim); widget preview harness
extended with per-variant scroll anchors and all 15 layout variants
screenshot-checked, including forced accented mode (legible translucent
cells) and blank-slot rendering; capsule menu and settings text
screenshot-checked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 01:17:39 +09:00
songyc macbook
bd60697419 refactor(ui): redesign floating menu to detached glass capsules for better aesthetics and performance
Replace the single semicircle dome + arc-arranged icons (which distorted and
pushed labels outside the fan) with independent floating glass capsules that pop
up in a vertical stack from the FAB.

- Each tab is now a self-sizing Capsule: icon + label with generous padding so
  text can never overflow; ultraThinMaterial background, thin white translucent
  border (.stroke white 0.4), and a soft drop shadow for a floating glass look.
- Current tab gets a subtle green tint behind the content.
- Removed the heavy DomeShape glass fan and its scale/opacity blur transition
  plus all per-item arc math; appearance is driven purely by offset + scale +
  opacity under one snappy spring (response 0.3, damping 0.7), so no per-item
  blur passes and no progressive frame drops.
- Renamed RadialMenu -> FloatingCapsuleMenu; RadialTabView / settings keys /
  nav-style routing unchanged.

Verified: BUILD SUCCEEDED (iPhone 17 Pro sim) and expanded-state screenshot
confirms all 7 capsules render legibly with text fully inside each pill, no
distortion, current tab highlighted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 00:06:55 +09:00
songyc macbook
dd95dcd28f fix(widget): resolve invisible content bug by applying proper containerBackground and dynamic contrast for transparent/tinted themes
On a tinted/transparent (or iOS 18 accented) home screen, the Action Run
widget turned into a solid white block with invisible content, while the
other widgets rendered fine. Cause: only the Action Run cell paints an opaque
tag-color background (WidgetTintedCellBackground) with hardcoded
.foregroundStyle(.white) text on top. In any non-.fullColor rendering mode
the system re-interprets that opaque fill toward white and the white text
disappears — classic white-on-white. The other widgets already use the
adaptive AppTheme.surface plus semantic .primary/.secondary text, which is
why they were unaffected.

Fix, confined to ActionRunCellView:
- Read @Environment(\.widgetRenderingMode). In .fullColor keep the tag-color
  fill + white text (unchanged). In accented/vibrant, drop the opaque fill to
  a faint translucent tint and render content with the semantic .primary color
  so the system's tint keeps a clear foreground/background contrast.
- No hardcoded .white survives on the tinted path; the container background is
  still owned by widgetAppTheme()'s .containerBackground(for: .widget) (clear
  in non-fullColor so the system glass shows through), so this composes safely
  with iOS 18 home-screen customization.

The Live Activity's white-on-tint icon (HaruDanimWidgets) is intentionally
left as-is — Live Activities always render full color and aren't subject to
home-screen widgetRenderingMode.

Verified in the in-app widget preview with a new DEBUG affordance
(-widgetRenderMode accented|vibrant, added to WidgetPreviewScreen): forcing
accented mode, the Action Run cells now show legible dark icons/labels/values
on a faint tint instead of blank white. Build succeeds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-12 23:35:09 +09:00
songyc macbook
dc9434a241 refactor(ui): aggressively prune widget config parameters and optimize floating menu performance/glass styling
Widget configuration — delete the multi-slot pickers entirely
--------------------------------------------------------------
Every home/lock widget exposed 3–4 separate entity slots (action1–4,
goal1–4, quest1–4, secondGoal) regardless of family, cluttering the
long-press config screen with slots a given size never renders. Replaced
that with a single anchor selection per widget that self-configures by size
family (the view already prefixes by family; providers now build the
candidate list from one selection):

- ActionRun (A): one anchor Action → fills following actions (main-tab order)
  up to the family's count.
- GoalBars (B): one anchor Goal → fills following goals up to the count.
- QuestRing (C): one Goal → renders that goal's quests, sized by family.
- GoalQuestGrid (D): one Goal (dropped secondGoal) → its quests; large now
  shows up to 16 rings of the single goal.
- Stats: one anchor Action → plus following actions on the same chart.

New WidgetStore helpers actionsFrom/goalsFrom/questsOfGoal centralize the
"anchor + following / goal's quests" logic; the None sentinel is ignored
gracefully so clearing falls back to sensible defaults. No @Model, timeline,
or reload/sync logic touched.

Floating radial menu (iPhone) — performance + glassmorphism
-----------------------------------------------------------
Each of the ~7 icons and ~7 labels had its own .ultraThinMaterial, so the
expanded menu ran ~15 separate blur passes that re-rendered every frame as
content scrolled behind them — the source of the progressive frame drops.
Now a single unified glass dome (one DomeShape filled with .ultraThinMaterial)
sits behind the whole fan and is the only blur; icons are lightweight symbols
(only the current tab gets a solid green chip) and labels are plain text with
a legibility shadow. The dome is styled per glassmorphism: a soft white
top-to-bottom gradient overlay, a thin white.opacity(0.2) border, and a soft
shadow; the FAB gets the same gradient/border treatment.

Animation is snappier and lighter: the per-item implicit spring animations
with staggered delays are gone — one withAnimation(.easeOut ~0.24 open /
.easeIn ~0.18 close) drives the whole fan, and the press style uses a short
easeOut instead of a spring.

Verified in the simulator: build succeeds; the expanded menu renders the
single glass dome with all tabs and no crash (light/dark, expanded/collapsed);
the refactored C/D widgets render from a single selected goal, self-sized by
family.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-12 23:26:59 +09:00
songyc macbook
4a03868163 fix(goals): cap daily progress contributions to 100% to prevent weekly and monthly progress distortion
Weekly/monthly progress for a daily quest summed each day's raw value into
the numerator, so one over-achieving day leaked its overage into the span
total. Example: two daily quests with a 5-minute target; on one day quest A
did 5 min (100%) and quest B did 10 min (200%). Both are "done" for that day,
but the weekly ratio counted quest B as 600s vs quest A's 300s — B carried
double the weight, and its over-achievement masked other days' shortfalls and
could push per-quest weekly display past 100%.

spanProgress now aggregates a daily 'atLeast' quest's week/month value as the
sum of per-day values each capped at that day's target (min(dayValue, target)),
via a new private spanValue(for:range:now:). This equals averaging per-day
capped ratios, so a single 200% day contributes exactly one day's worth — no
day can cover for another, and per-quest weekly/monthly display never exceeds
100%. Scope is deliberately narrow:

- day span is untouched — a single day still shows 200% over-achievement.
- 'atMost' (stay-under) quests keep their existing per-day 100%/0% logic;
  capping the value would make them always pass, so they use the raw value
  unchanged — the two rules coexist without contradiction.
- weekly/monthly/custom quests accumulate freely within their period, so no
  per-day cap applies to them.

Only the progress calculation changed; raw records are never altered, so the
records/stats screens still show the true 10-minute total.

Verified in the simulator with a temporary isolated in-memory self-test
(removed before commit): quest A (100%) and quest B (200%) produce identical
weekly ratios (0.1429 each), quest B's weekly display stays <= 100%, and its
day-span display remains 200%. Build succeeds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-12 22:38:35 +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
a02e06a9df feat: add reordering to floating radial menu and fix weekly timetable step
Radial menu icon order (iPhone floating nav only):
- Settings → 내비게이션 gains a '반원 메뉴 순서 편집' link (shown only when the
  radial style is selected) that opens a drag-to-reorder list of all 7 tabs.
- Order is stored device-local via @AppStorage(SettingsKeys.radialTabOrder)
  (not CloudKit-synced); RadialMenu reads it through AppTab.radialOrder(from:),
  which always yields the full tab set and appends any missing/unknown value in
  default spec order — so partial or legacy strings can never drop a tab.
- iPad/Mac sidebars and the watch target are untouched (they never read the key).

History timetable date step:
- The date header </> buttons now jump by a full week (7 days) when the
  timetable is in '일주일' view, and by 1 day otherwise (list / daily timetable).
- The calendar popover and '오늘' still set the date directly, so those paths and
  record loading are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-12 09:13:20 +09:00
songyc macbook
8f2b5fab93 feat(nav): add experimental iPhone radial floating navigation menu
Adds an alternative iPhone navigation style, switchable in Settings, that
replaces the bottom tab bar with a single center floating button which fans
all tabs out into an upper 180° semicircle.

- Settings → 내비게이션: pick 기본 하단 탭 바 / 중앙 플로팅 반원 메뉴.
  Stored device-local via @AppStorage(SettingsKeys.navStyle) (not synced);
  the 탭바 구성 section auto-hides in radial mode since all tabs are shown.
- iPhone (Compact) only: RootNavigationView branches on NavStyle. iPad/Mac
  sidebar and the watch target are untouched.
- Radial menu (RadialNavigationView): glass FAB at bottom-center over a
  tab-bar-hidden TabView (per-tab navigation state preserved). Tapping the
  FAB blooms all 7 tabs into an upper 180° arc with per-icon name labels;
  tapping an icon routes immediately and closes; tapping the FAB or the
  dimming scrim closes. Current tab is highlighted.
- Drops the 더보기 indirection in radial mode via AppRouter.allTabsDirect,
  so cross-tab routing (openHistory/openStats) selects tabs directly.
- Liquid Glass (.ultraThinMaterial) styling on the FAB, icons and labels.
- Overlay is kept top-most in the ZStack with hit-testing gated to the
  expanded state, so scrolling grid content can never intercept the button.
- DEBUG: -navStyle radial / -radialExpanded YES launch args for simulator
  verification (no way to tap widgets/overlays via simctl).

Scope: iPhone navigation only; no changes to iPad/Mac sidebar, the watch
app, core data/models, or widget code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-12 09:01:50 +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
9e81cd3d03 feat: auto-adapt layout for iPadOS/macOS with NavigationSplitView and hide irrelevant settings
- Add SidebarRootView: iPad/Mac (Designed for iPad) uses a NavigationSplitView
  sidebar listing all 7 tabs, sharing every existing screen (rootView) untouched
- Branch by device idiom (DeviceLayout.isPad) so iPhone keeps its tab bar UI,
  logic, and Live Activity behavior pixel-for-pixel, including landscape
- AppRouter gains sidebar mode: cross-tab routing (기록 확인/통계 보기) selects
  the target tab directly instead of detouring through the More tab
- Hide iPhone-only settings on iPad/Mac: 탭바 구성 section and the main grid's
  per-row column picker are meaningless with a sidebar/adaptive grid
- Main tab wide-screen layout: pinned goal cards flow in an adaptive grid
  (340-520pt cards) instead of full-width paging; action buttons auto-fill
  columns at 150-220pt so nothing stretches on large displays
- Verified on iPad Pro 11" simulator (sidebar, settings, stats routing,
  timetable) and iPhone 17 Pro (unchanged tab bar); both build clean

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-11 09:28:06 +09:00
songyc macbook
2567b53b71 fix: correct daily goal progress calculation and optimize widget refresh logic
- 목표 하루 진행률은 오늘이 수행일인 다짐만 모수로 계산 (요일·날짜 지정으로
  오늘 쉬는 다짐은 분모에서 제외, QuestProgress.isScheduled 신설)
- 오늘 수행할 다짐이 하나도 없는 날은 0% 대신 100%(완료 상태)로 표기
- 주간/월간 진행률은 기존대로 전체 다짐 기준 유지
- 이벤트 기반 위젯 새로 고침: 앱 내 모든 핵심 Mutation(측정 시작/종료,
  횟수 추가, 기록 추가·수정·삭제, 행동·꼬리표·목표·다짐 CRUD, 순서 변경,
  목표 상태 변경)의 끝단에서 DataChange.commit() 호출 — 저장 즉시 확정 후
  Live Activity·위젯 타임라인·워치 스냅숏을 한 번에 동기화
- 주기적 백그라운드 새로 고침 없이 변경 시점에만 갱신해 WidgetKit
  업데이트 예산 절약 (타이머 표시는 기존 Text(style: .timer)로 시스템이
  자체 렌더링하므로 타임라인 소모 없음)
- 하루 시작 시간·주 시작 요일 설정 변경 시에도 위젯 집계 갱신
- 시뮬레이터 검증: 월·수·금 다짐이 있는 목표의 토요일 하루 진행률이
  33%가 아닌 50%(오늘 수행 다짐 2개 평균)로 표시됨 확인

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-11 08:30:12 +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
bc6cd010b7 fix(widgets): 리퀴드 글라스 테마 시인성 개선 및 틴트/클리어 렌더링 모드 대응
- 글라스 테마: 틴트 셀 불투명도 0.5→0.72, 테두리 강화로 흰 텍스트 대비 확보
- 글라스 테마 전용 텍스트 그림자(glassTextShadow) 추가 — 홈 위젯 5종 셀 콘텐츠에 적용
- widgetRenderingMode != .fullColor(홈 화면 틴트/클리어)일 때 커스텀 배경·강제
  컬러 스킴을 끄고 시스템 바이브런트 렌더링에 맡기도록 분기
- 잠금화면 위젯: AccessoryWidgetBackground + widgetAccentable + .primary 전경색으로
  비브런트/악센트 렌더링에서 시인성 확보
- 미리보기 하네스: 글라스 테마가 시스템 컬러 스킴을 따르도록 수정

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-10 21:18:43 +09:00
songyc macbook
bef503465c chore(i18n): 문자열 카탈로그를 Xcode 표준 포맷으로 재직렬화
빌드 과정에서 Xcode가 정규화한 포맷 반영 (내용 변화 없음,
DEBUG 전용 문자열 2건 stale 표기 유지)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-10 15:43:42 +09:00
songyc macbook
c57c39002b feat(widgets): 홈 위젯 풀블리드 적용 및 다짐 현황 간격 개선
- 홈 위젯 5종에 contentMarginsDisabled() 적용: 시스템 기본 베젤 제거,
  여백은 각 뷰가 직접 관리 (다중 셀 10pt, 콘텐츠형 13pt)
- 소형(단일 셀) 위젯은 셀이 위젯 전체를 여백 없이 채움 — 셀 배경/테두리가
  ContainerRelativeShape로 위젯 모서리에 정확히 맞음 (A·B·C 위젯)
- 다짐 현황(D) 위젯 간격 개선: 링 그리드 행 간격 6→12, 열 간격 4→8,
  섹션 간격 6→10, 대형 목표 2개 간격 10→16, 링-이름 간격 확대
- 미리보기 하니스도 여백 제거를 반영해 실제 렌더링과 일치

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-10 15:42:37 +09:00
songyc macbook
c0c59af0ac fix(watch): 컴플리케이션 페이스 목록 미노출 수정 + 워치 앱 이름 다국어화
- 컴플리케이션이 페이스 편집 목록에 아예 안 나오던 원인: 추천 구성
  (recommendations)의 description에 문자열 보간을 사용해 WidgetKit이
  "Formatted text for AppIntentRecommendation is not supported" assertion으로
  익스텐션을 즉사시킴 → 시스템이 위젯 목록 조회 자체를 실패
  → Text(verbatim:)으로 교체. 시뮬레이터 chronod 로그로 3종 kind
  (HaruWatchStatus/GoalRate/QuestRate) 정상 등록 확인 (크래시 0)
- 추천 구성을 캐시된 목표/다짐 기반으로 개선 (모든 파라미터 채움)
- 워치 앱·워치 위젯 익스텐션 표시 이름 현지화: InfoPlist.xcstrings 추가
  (ko 하루다님 / en Haru Danim / ja ハルダニム)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-10 15:42:37 +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
1e62c082d3 feat(premium): StoreKit 2 결제 레이어 구현 (실결제 연결 직전 단계)
- StoreKitEntitlementProvider: Transaction.currentEntitlements 기반 실권한
  조회 + Transaction.updates 이벤트 반영. 앱 시작 시 PremiumManager.bootstrap
  으로 주입 (확장 타깃은 기존 PremiumGate 캐시 그대로)
- PremiumStore: 상품 로드/구매/복원 (월·년 구독 + 평생 이용권 3종)
- PremiumView: 실구매 UI (상품 목록·가격·구매 복원, 미로드 시 재시도 폴백)
- DEBUG 강제 프리미엄(-premium, 테스트 토글) 중에는 StoreKit 결과가
  덮어쓰지 않도록 보호
- HaruDanim.storekit 로컬 테스트 구성(월 5,000 / 년 50,000 / 일회성 80,000원,
  ko·en·ja 현지화) + 메인 스킴에 연결 → Xcode 실행으로 결제 흐름 테스트 가능
- 남은 것: App Store Connect에 productID 3종 등록만 하면 실결제 연결 완료

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-10 14:49:32 +09:00
songyc macbook
0cdb566ad5 fix(watch): 컴플리케이션이 워치 앱을 열지 않아도 갱신되도록 수정
- 워치 앱 시작(App.init) 시점에 WCSession 활성화: 백그라운드로 깨어나
  스냅숏을 받을 수 있도록 델리게이트를 미리 설정
- iPhone이 transferCurrentComplicationUserInfo 전용 채널로도 스냅숏 전송
  (전송 예산 고려: 측정 상태 변화 또는 30분 경과 시에만)
- 워치 didReceiveUserInfo 수신 → 캐시 저장 + 컴플리케이션 타임라인 갱신
- ComplicationStore.defaults 단일 인스턴스화

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-10 14:44:48 +09:00
songyc macbook
e5550eb106 fix(theme): 테마 즉시 적용 및 위젯 테마 실적용 + 리퀴드 글라스 구현
- AppGroup.defaults를 단일 인스턴스(static let)로 변경: 접근마다 새
  UserDefaults를 만들어 @AppStorage(store:) 관찰이 끊기던 문제 수정
  → 설정에서 테마를 바꾸면 재시작 없이 즉시 적용됨
- 위젯 containerBackground에 강제 컬러 스킴을 직접 지정: 시스템이
  배경을 위젯 환경으로 그려 라이트/다크 고정·앱 테마 일치가 실제
  위젯에서 무시되던 문제 수정
- 테마 변경 시 위젯 타임라인 갱신 (앱 테마와 일치 옵션 즉시 반영)
- 리퀴드 글라스 실구현: 머티리얼+하이라이트 배경(GlassWidgetBackground),
  유리 테두리 셀(WidgetCellBackground), 색이 비치는 틴트 셀
  (WidgetTintedCellBackground, 행동 실행 위젯 적용)
- 검증 인자 -themeAutoToggle YES 추가 (3초 후 테마 반전)

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