Screen-time integration branch: iOS calendar UX, OAuth resilience, evening review - #50
Screen-time integration branch: iOS calendar UX, OAuth resilience, evening review#50oyeong011 wants to merge 18 commits into
Conversation
- deleteEvent now swallows 404 as idempotent success, retries without If-Match on 412 (etag stale), and refreshes + retries on 401. - Adds LocalizedError conformance with Korean user-facing messages so iOS no longer surfaces raw "Could not delete google calendar." text. - Regression tests cover the three new recovery paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Tap on the evening review / midnight rollover notification opens the popover (new AppDelegate handler) and routes MainView straight to the review panel via NotificationCenter signal. - runAutomaticEveningReviewIfNeeded no longer moves todos behind the user's back. Auto-apply only runs when the new profile.eveningReviewAutoApply flag is explicitly enabled in Settings. - SettingsView grows an "auto-reschedule unfinished items" toggle under Evening Review, with KO/EN strings. - Pulls in the in-progress EveningReviewRescheduler + ReviewService scheduling helpers that this flow depends on. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- scripts/reauth-cli.sh probes codex + claude CLI auth state, with --fix for interactive re-login and --codex-api-key to swap a stuck ChatGPT-account codex session over to API-key mode (the failure mode that blocked our last delegated codex run). - docs/oauth-troubleshooting.md walks through the four common reasons the macOS Google sign-in flow ends in "cancel" (consent screen still in Testing, wrong OAuth client type, missing bundled credentials, 90s loopback timeout). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존: refresh 응답이 HTTP 401/403 이면 무조건 logout() — rate limit, 일시 5xx, 클럭 스큐 같은 일시 오류에도 키체인 토큰이 날아가서 사용자 가 매번 재로그인하게 됨. 수정: - JSON 본문 먼저 파싱 → error="invalid_grant" / unauthorized_client / invalid_client 일 때만 logout() 실행 - 비-200 응답에 error 키 없으면 토큰 보존, 다음 호출 때 재시도 scope 축소도 함께: 풀 `calendar` (restricted, CASA Tier 2 평가 필수) → sensitive 등급 `calendar.events` + `calendar.calendarlist.readonly` + `userinfo.email` 조합. Calen은 캘린더 자체 생성/삭제/공유 안 하므로 충분. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Lazy resolve: chat 호출 시점에 선택된 provider CLI 경로만 재탐색 (앱 시작 시 양쪽 다 resolve하던 비용 제거) - PATH allowlist: 환경 PATH에서 찾은 경로도 /opt/homebrew, /usr/local, /usr/bin, /bin, $HOME 하위만 허용 (임의 디렉터리 실행 방지) - CLI 실행 환경 PATH 확장: nvm/asdf/volta/bun/cargo/npm-global 등 사용자 설치 경로 포함해 claude/codex 의 노드 의존성 해소율 향상 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Shared: - CalendarEvent.isCompleted 필드 추가 (Codable 마이그레이션 포함). Google Calendar 양방향 sync는 extendedProperties.private.calenCompleted 마커로 처리. - CalendarUX/CalendarInteractionMath: 이벤트 드래그/리사이즈 시 시작-종료 시간 계산 / 다른 날로 시프트 / 그리드 좌표 ↔ 시간 변환을 공유 로직으로 추출 (iOS HomeView/MonthGridView, macOS EveningReviewRescheduler가 함께 사용). iOS (CaleniOS): - HomeView/MonthGridView/WeekExpansionView: 주/월 그리드에서 드래그·롱 프레스로 이벤트 이동·리사이즈, 빈 슬롯 탭으로 즉시 일정 생성, 완료 토글 인터랙션 통합. - DayDetailSheet/EventDetailSheet: 완료 상태 표시·토글, 리사이즈 핸들, 반복 일정 메타데이터 표시 강화. - ReviewTabView: 일간/주간 리뷰 진입점 + 평가 입력 UI. - ReviewViewModel/HomeViewModel: 캘린더 인터랙션 상태(in-flight 드래그 대상, 시프트 미리보기) 관리. - Schedule/Color/Theme/FakeEventRepository: 새 인터랙션·완료 상태에 필요한 모델·테마 보강. Tests: - CalenTests: automaticEveningReschedule 트리거 조건 / 완료 마커 직렬화 / CalendarInteractionMath 검증. - CalendarUXRegressionTests (신규): 그리드 좌표 변환, 다른 날 시프트, 드래그 리사이즈 경계 케이스 회귀 방지. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- docs/index.html: 카피·섹션 재정렬, 헤로 영역에 제품 mockup 이미지 사용 - docs/calen-product-mock.png 추가: 새 헤로용 시각 자료 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6fd4a5853b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| repo.replaceInMemory(updated) | ||
| UIImpactFeedbackGenerator(style: .medium).impactOccurred() | ||
| Task { @MainActor in | ||
| do { | ||
| let saved = try await repo.update(updated) | ||
| repo.replaceInMemory(saved) |
There was a problem hiding this comment.
Keep the home calendar in sync after sheet edits
When an event is toggled, dragged, or resized from the day sheet, this path updates only repo.events; the month grid and week expansion are driven by HomeViewModel.schedulesInMonth, and there is no subscription or refresh after this sheet mutates the repository. In the Google-backed flow, dismissing the sheet therefore leaves the main calendar showing the old time/completion state until a later refetch/month navigation. Route these edits through the view model or notify the parent to patch/refresh schedulesInMonth after the save/rollback.
Useful? React with 👍 / 👎.
| /// 기본 seed=false. 과거에 true가 기본이었는데, 사용자가 빈 셀을 눌러도 오늘 기준 | ||
| /// +1/+2일 등의 가짜 시드 이벤트("오프사이트 워크숍" 등)가 시트에 표시되는 혼란이 있었음. | ||
| /// Preview/QA에서 데모 데이터가 필요하면 명시적으로 `FakeEventRepository(seed: true)`로 호출. | ||
| public init(seed: Bool = false) { |
There was a problem hiding this comment.
Preserve local day details when Google is disconnected
When HomeView is not using Google it still passes HomeViewModel.eventRepository to DayDetailSheet, but that repository is now constructed as FakeEventRepository() and this default leaves events empty; nothing mirrors the SwiftData Schedule rows that populate schedulesInMonth into this repo. In the logged-out/local mode, tapping a date therefore opens an empty day sheet even when the month grid contains local schedules. Either feed the local schedules into the sheet or keep the repository populated for the non-Google path.
Useful? React with 👍 / 👎.
Hardened Runtime requires loaded frameworks to share the main binary's Team ID. Sparkle artifact ships ad-hoc signed without --options runtime, so combining it with `codesign --options runtime --sign -` on the main bundle causes dyld to reject the framework with "different Team IDs" (SIGKILL at launch, same class as v0.4.5 regressions). Solve: when DEVELOPER_ID is absent, re-sign Sparkle (XPC services → Autoupdate → Updater.app → framework) with the same `--options runtime --sign -` combo so both ends are ad-hoc + hardened. Sparkle auto-update won't work in this mode (acceptable: this path is for local test/internal distribution only — production builds set DEVELOPER_ID and follow the normal Developer ID code path above). Detected while smoke-testing 0.4.69 ad-hoc build locally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
syncHistory()가 364일 × 모든 캘린더 이벤트를 파싱하면서, 이벤트마다 parseGoogleDateTime / parseGoogleAllDayDate가 ISO8601DateFormatter /DateFormatter 를 새로 alloc 했다. ICU init 비용으로 메인 스레드가 sustained 44~89% CPU 로 수십 초간 점유되는 핫스팟이었음 (v0.4.69 sample.txt 확인 — 거의 모든 시간이 parseGoogleDateTime → NSISO8601DateFormatter init 에서 소비). 정적 인스턴스 + NSLock 으로 재사용해 alloc 비용 제거. 포매터들은 stateless하게 사용되므로 락 구간 짧음. 측정 결과 (v0.4.69 → v0.4.70 로컬 빌드): - idle CPU 44~89% → 0.0% - RSS 187 MB → 63 MB build(macos): drop --options runtime in ad-hoc signing path Hardened Runtime + ad-hoc Sparkle 조합은 library validation 이 team ID 매칭을 강요해서 dyld가 Sparkle 로드를 거부 (v0.4.69 ad-hoc 빌드 launch 실패). 로컬/내부 배포용 ad-hoc 빌드는 runtime 옵션을 빼고 --deep ad-hoc 로 일원 서명. production (DEVELOPER_ID 있음) 분기는 그대로 hardened runtime 유지. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
"오늘 일정 알려줘" 같은 질문에 AI가 "0개" 라고 답하던 버그. ChatView.sendMessage()가 viewModel.calendarEvents / categories 는 AIService에 주입했지만 viewModel.todos 는 주입하지 않았다. buildCalendarContext()도 캘린더 이벤트만 인지하고 todos 는 모르므로 사용자가 우측 패널에서 보고 있는 할일(perf실습, 로봇플젝, 등)이 컨텍스트에 전혀 없는 상태에서 답을 만들었음. 수정: - AIService.cachedTodos 필드 추가 - ChatView.sendMessage()가 viewModel.todos 도 주입 - buildCalendarContext()가 오늘~14일 todos 를 "=== 향후 2주 할일 ===" 섹션으로 컨텍스트 말미에 첨부 (캘린더 이벤트와 분리 표시, 완료 체크 마커 포함) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
feature/screen-time-integration브랜치 누적 작업 15 commit 을 develop으로 머지. 크게 다음 영역:iOS
macOS (Calen)
Shared
CalendarEvent.isCompleted+ GoogleextendedProperties.calenCompleted양방향 syncCalendarUX/CalendarInteractionMath추출 (iOS 드래그 / macOS evening reschedule 공유)Docs / Tooling
docs/oauth-troubleshooting.md)scripts/reauth-cli.sh)Test plan
swift test— 357 tests / 16 suites 통과 (Tests/CalendarUXRegressionTests, GoogleCalendarClientTests 신규 포함)🤖 Generated with Claude Code