Conversation
- WatchEnvelope 봉투 코덱 추가 — JSON 을 단일 키에 Data 로 실어 sendMessage · updateApplicationContext · transferUserInfo 세 채널이 하나의 코덱을 공유 - WatchMessage(요청 5종) · WatchReply(응답 4종) · WatchConnectivityError(실패 11종) 계약 정의, 수동 Codable 로 스키마 버전 상한 검증 - 페이로드 값 타입 추가 — WatchSessionState · WatchSchedule · WatchNotice · WatchAttendanceRequest / Result. 서버 정수 식별자는 전 레이어 String (절대 규칙 #2) - WatchSessionCoordinator 를 @mainactor @observable 로 재작성, WCSessionDelegate 양방향 수신 경로 구현 (요청 핸들러 주입 · userInfo AsyncStream · 앱 컨텍스트 상태) - replyHandler 없이 sendMessage 를 호출해 성공 시 continuation 이 매달리던 버그를 구조적으로 제거 — 모든 전송이 replyHandler 를 넘긴다 - WatchMessenger 삭제 — 구현체 1개짜리 pass-through 프로토콜이었고 Sendable 준수가 non-Sendable 코디네이터와 충돌 - CoreWatchConnectivity 에 테스트 타깃 활성화 (includesTests: true) - 계약 테스트 22개 추가 (2 suites)
- Core/WatchDesignSystem 모듈 신설 — iOS 카탈로그는 watchOS 에서 항상 dark 를 resolve 하므로(브랜드색이 #4869F0 대신 #4264F0 이 됨) 워치 전용 레이어로 분리 - 토큰 3종 추가: WatchColor(표면·브랜드·상태·텍스트 20종) · WatchTypography(WatchTextRole 5종) · WatchLayout(코너·보더·패딩·스페이싱) - 표면 API 추가: watchCard(_:leadingAccent:) · watchScreenBackground() · watchListRowBackground(isSelected:) - 컴포넌트 2종 추가: WatchActionButton(역할 3종) · WatchStatusBadge(상태 5종) - Glass 절제 규칙을 API 로 강제 — WatchCardStyle 을 닫힌 enum 으로 두어 카드에 Glass 를 넣을 경로 자체를 없애고, Glass API 는 WatchActionButton 한 파일로 한정 - 토큰 드리프트 가드 테스트 추가 — iOS Colors.xcassets 를 파싱해 브랜드색 불일치 시 실패 - docs/claude/watch-design-system.md 신규 · CLAUDE.md 레퍼런스 인덱스 갱신
- CoreWatchConnectivity 에 ComplicationSnapshot·ComplicationStore 추가 — WatchSessionState 를 워치페이스가 필요로 하는 최소 형태로 축약하고 App Group UserDefaults 로 익스텐션에 전달 - ComplicationTimeline 이 세션 시작·종료·체크인/정시/지각 마감을 경계 시각으로 삼아 최대 6개 엔트리 생성 — 1분 폴링 대신 값이 바뀌는 순간에만 엔트리를 둔다 - UMCWatchComplication 익스텐션 신설: 다음 세션·출석 상태·미확인 공지 3종이 accessoryCircular·Rectangular·Inline 을 모두 지원 - 출석 8상태를 심볼·라벨·링 3채널로 구분 — accented/vibrant 에서 색이 치환돼도, 색각 이상 사용자에게도 구분이 살아남는다 - WatchSessionCoordinator 수신 시 스냅샷을 저장하고 타임라인을 리로드 — 워치는 서버를 직접 폴링하지 않으므로 이 경로가 유일한 갱신 통로 - Project+WidgetExtension 헬퍼를 destinations·deploymentTargets·displayName 로 일반화 (기본값이 기존 iOS 설정이라 UMCAppWidget 은 무변경) - CoreWatchConnectivity 테스트 21개 추가 (스냅샷 도출 15 · 스토어 6)
- docs/claude/watch-complications.md 신규 — 데이터 흐름(워치는 서버를 폴링하지 않는다)·모듈 배치 근거·App Group 함정·출석 8상태 매핑 표·tinted 규칙· 타임라인 정책·확장 체크리스트·테스트·트러블슈팅 6건 - CLAUDE.md 상세 레퍼런스 표에 인덱스 행 추가
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🔗 관련 이슈
Closes #1215
의존 이슈(둘 다 아직 develop 미머지 → 이 브랜치에 선반영한 stacked PR):
a5854ea75bbf9b4e실기기 동작 선행 조건: #1211 —
WatchSessionCoordinator.publishSessionState(_:)는 정의만있고 iPhone 앱에서 부르는 곳이 아직 0건입니다(#1211 이 그 배선을 다룹니다). 즉 이 PR 은
"스냅샷이 도착하면 워치페이스가 무엇을 어떻게 그리는가"까지를 완결하고, 그 앞단(푸시 → iPhone →
updateApplicationContext)은 #1211 에서 연결됩니다. 그때까지 실기기 워치페이스는「iPhone 로그인 필요」 폴백에 머무릅니다.
✨ PR 유형
watchOS 워치페이스 Complication 3종을 WidgetKit accessory family 로 신규 구현했습니다.
📷 스크린샷 or 영상(UI 변경 시)
워치페이스에 Complication 을 물리적으로 배치해야 나오는 화면이라 시뮬레이터 캡처는
GUI 조작이 필요합니다. 리뷰어가 직접 확인할 수 있도록 재현 절차만 남깁니다.
각 위젯이
.accessoryCircular·.accessoryRectangular·.accessoryInline3 패밀리를모두 지원하므로, 슬롯 종류에 관계없이 배치됩니다.
🛠️ 작업내용
1. Complication 스냅샷 도출 로직 (
CoreWatchConnectivity)Complication 이 그릴 값은 뷰가 아니라 Core 에서 계산합니다. 위젯 익스텐션은 유닛 테스트
타깃이 붙지 않아, 로직이 익스텐션에 있으면 검증할 방법이 없기 때문입니다.
Sources/Complication/ComplicationSnapshot.swiftWatchSessionState(#1210) → 워치페이스가 필요로 하는 최소 형태로 축약Sources/Complication/ComplicationStore.swiftUserDefaults읽기/쓰기 +WidgetCenter.reloadAllTimelines()ComplicationSnapshot(state:)— 다음 세션(끝나지 않은 것 중 가장 이른 것, 동시간대는scheduleId로 타이브레이크) · 출석 상태 · 미확인 공지 수를 한 번에 도출ComplicationAttendanceState— 8상태(none/upcoming/awaiting/pending/present/late/excused/absent). 서버 확정 상태(PRESENT/LATE/EXCUSED/ABSENT)는 그대로 쓰고,미확정 구간은 출석 윈도우와 현재 시각으로 계산
ComplicationTimeline.entries(from:now:)— 세션 시작/종료·체크인 시작·정시 마감·지각 마감을경계 시각으로 삼아 최대 6개 엔트리 생성. 1분 폴링 대신 "값이 바뀌는 순간"에만 엔트리를 둡니다
2. 위젯 익스텐션 신설 (
UMCApp/UMCWatchComplication/)UMCWatchComplicationBundle— 3종 위젯 번들ComplicationProvider— 3종이 공유하는 단일TimelineProvider.경계 시각이 있으면
.atEnd, 없으면 1시간 뒤.after(_:)폴백NextSessionComplication— 다음 세션까지 남은 시간(원형은
ProgressView(timerInterval:)로 링·숫자를 시스템이 갱신 → 엔트리 소비 0)AttendanceStatusComplication— 출석 상태 심볼 + 라벨, 승인 대기는 링으로 구분PingCountComplication— 미확인 공지 수 (99 초과 시99+, 사각형은 "N분 전 기준" 신선도 표기)ComplicationStyle—.fullColor전용 틴트 매핑 + 로그아웃 공통 뷰3. iPhone → 워치 → 워치페이스 동기화 경로
UMCWatchApp/Sources/ComplicationSyncModifier.swift에서WatchSessionCoordinator(#1210)의receivedState변화를 구독해 스냅샷을 저장합니다. 워치는 서버를 직접 폴링하지 않으므로(설계 스펙 §7) 이 경로가 Complication 이 최신값을 얻는 유일한 통로입니다.
initial: true를 준 이유는 콜드런치 시딩 — 활성화 시점에 이미 도착해 있던 컨텍스트에는델리게이트 콜백이 다시 오지 않아 첫 값이 통째로 누락됩니다.
4. Tuist 구성
Project+WidgetExtension.swift를destinations/deploymentTargets/displayName파라미터로 일반화 (기본값이 기존 iOS 설정이라
UMCAppWidget은 무변경)group.com.umc.product.watch신규 — iPhone 과 워치는 App Group 컨테이너를공유하지 못해 iOS 위젯의
group.com.umc.product.widget을 재사용할 수 없습니다UMCWatchApp에 익스텐션 의존 추가 →Embed Foundation Extensions에.appex자동 임베드5. 테스트
CoreWatchConnectivity에 21개 추가 (스냅샷 도출 15 · 스토어 6).경계 시각 산출, 8상태 매핑, 미로그인/미동기화 폴백, 색맹 안전성(색 없이도 3채널로 구분되는지)을
잠급니다.
📋 추후 진행 상황
후속 이슈에서 이어지는 작업
publishSessionState(_:)호출부만 생기면 별도 수정 없이 워치페이스까지 값이 흐릅니다.사람만 할 수 있는 작업 (코드·스펙 쪽은 이 PR 에서 전부 끝냈습니다):
group.com.umc.product.watch를 생성하고워치 앱·익스텐션 App ID 에 capability 를 추가한 뒤 프로비저닝 프로파일 재발급.
이 작업 전까지는 실기기에서 스냅샷 공유가 조용히 실패합니다(시뮬레이터는 동작).
코드 기본값은 이미 "색에 의존하지 않는 3채널(심볼·라벨·링) 인코딩"으로 잠가 뒀으므로
목업이 나와도 로직 변경 없이 틴트 매핑만 조정하면 됩니다.
UMCApp/UMCWatchApp/UMCWatchApp.entitlements에 Keychain Access Group키를 병합 (이 PR 에서 파일을 신규 생성했으므로 충돌 지점입니다).
📌 리뷰 포인트
ComplicationTimeline.boundaries가 고르는 경계 시각이 스펙과 맞는지.watchOS 는 Complication 리로드 예산이 빠듯해서, 1분 간격 엔트리 대신 "값이 바뀌는 순간"만
엔트리로 두고 카운트다운은 시스템(
ProgressView(timerInterval:))에 맡겼습니다..pending과.excused가 같은 틴트 — 스펙상 둘 다 "확정되지 않았거나 예외" 축이라색으로 가르지 않고 심볼/링으로 구분했습니다. 의도한 해석이 맞는지 봐주세요.
complicationTint(_:mode:)가.fullColor가 아닐 때.primary를 주는 이유는, 커스텀 색을 넘기면 시스템 치환 대상이 하나로 뭉개져 강조 계층이통째로 사라지기 때문입니다.
Project+WidgetExtension일반화 — 기본 인자로 기존 iOS 동작을 보존했는지(
UMCAppWidget빌드로 확인했습니다).✅ Checklist
PR이 다음 요구 사항을 충족하는지 확인해주세요!!!
검증 결과
make test(UMCApp, iPhone 17 Pro)** TEST SUCCEEDED **make test SCHEME=CoreWatchConnectivitymake build SCHEME=UMCWatchComplication(watchOS)make build SCHEME=UMCWatchAppmake build SCHEME=UMCAppWidget(iOS 회귀)AppProduct/무변경)git diff --stat -- AppProduct/비어 있음glassEffect·@StateObject·@Published·NavigationView)