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 레퍼런스 인덱스 갱신
- WatchRoute/WatchFallbackRoute — 출석·The Ping 두 축 진입점과 폴백 3종(권한 거부·연결 끊김·큐잉)을 경로로 확보 - WatchRouter — 앱 셸이 소유하는 @observable 단일 스택. NavigationPath 대신 [WatchRoute] 구체 배열로 두어 스택 내용을 테스트로 검증 - WatchRootView — navigationDestination 단일 등록. 목적지는 후속 이슈가 교체할 플레이스홀더 - HomeGlanceView/HomeGlanceViewModel — solid 카드 글랜스, 세션·공지 행을 tap-chip 으로 승격, 세션 없을 때 "오늘 세션 없음" 빈 상태 - 승인 대기는 WatchStatus.pending(중립+인디고 링). 앰버 #FFB340 은 개정 스펙상 지각 전용이라 쓰지 않음 - 카운트·식별자는 전 레이어 String, Int 변환은 비교 시점에만 (규칙 #2). 숫자 아닌 값은 0건 처리 - watchAppProject 헬퍼에 includesTests 추가 + WKApplication 키 보강 — 없으면 테스트 호스트 설치가 거부됨 - ContentView("Hello Watch") 제거
- "오늘 세션" 라벨 + "오늘 세션 없음" 값이 같은 말을 두 줄로 반복해 위계가 흐려지던 문제 - 빈 상태는 문구 한 줄만 카드에 남긴다. 세션이 있을 때의 라벨/값 위계는 그대로
- WatchPingItem: 스냅샷 공지 + 낙관적 읽음 오버레이를 합쳐 최신순 목록을 만드는 표시 모델. 안읽음·긴급·필수 확인 세 신호를 색이 아닌 글자로도 푸는 VoiceOver 라벨 포함 - PingInbox: 앱 셸이 소유하는 수신함. applicationContext 수신분과 requestSync 결과 중 더 새로운 스냅샷을 고르고, 실패해도 캐시를 버리지 않아 iPhone 이 멀어져도 마지막 공지를 읽을 수 있다 - 확인 전송은 transferUserInfo 큐 채널(.noticeRead) — 연결이 끊겨 있어도 접수되고 앱 종료 후에도 이어진다. 전송 즉시 읽음을 오버레이해 목록·상세가 같은 상태를 본다 - WatchConnectivityError → AppError 매핑: 스키마 불일치는 손상과 구분해 업데이트 안내로 낸다 - PingListView / PingDetailView 를 WatchRoute 의 .pingList · .pingDetail 목적지에 연결 - watchListRowBackground 에 leadingAccent 추가 — 긴급 색바를 행 배경이 그려 행 끝까지 닿게 한다 - 테스트 21건 추가 (WCSession 없이 정렬·오버레이·스냅샷 선택·에러 매핑 검증)
# Conflicts: # UMCApp/Core/WatchDesignSystem/Sources/Components/WatchStatusBadge.swift # UMCApp/Core/WatchDesignSystem/Sources/Surfaces/WatchSurface.swift # UMCApp/Tuist/ProjectDescriptionHelpers/Project+WatchApp.swift # UMCApp/UMCWatchApp/Project.swift # UMCApp/UMCWatchApp/Sources/Home/HomeGlanceView.swift # UMCApp/UMCWatchApp/Sources/Home/HomeGlanceViewModel.swift # UMCApp/UMCWatchApp/Sources/Routing/WatchRootView.swift # UMCApp/UMCWatchApp/Sources/Routing/WatchRoute.swift # UMCApp/UMCWatchApp/Sources/UMCWatchApp.swift # UMCApp/UMCWatchApp/Tests/HomeGlanceViewModelTests.swift # UMCApp/UMCWatchApp/Tests/WatchRouterTests.swift # docs/claude/watch-design-system.md
|
develop 병합 과정에서 두 가지를 브랜치 원본과 다르게 정리했습니다. 1. 브랜치의
2. 브랜치가 추가한 빈 검증: |
🔗 관련 이슈
Closes #1208
선행 PR 위에 쌓았습니다 — 아래가 먼저 머지돼야 합니다.
✨ PR 유형
새로운 기능 (워치 앱 화면 추가)
📷 스크린샷 or 영상(UI 변경 시)
시뮬레이터 캡처는 아직 첨부하지 못했습니다 — iPhone 컴패니언이 스냅샷을 밀어 주는 경로(#1210 의 iPhone 측 퍼블리셔)가 아직 붙지 않아, 워치 단독 실행 시 실제 공지 데이터가 들어오지 않습니다.
대신
#if DEBUG프리뷰 6종으로 상태별 화면을 모두 확인할 수 있습니다.PingListView — 미확인·긴급·확인함PingListView — 빈 상태PingListView — A11y 크기PingDetailView — 미확인 긴급PingDetailView — 확인 완료PingDetailView — A11y 크기🛠️ 작업내용
표시 모델 —
WatchPingItemWatchNotice와 워치에서 방금 누른 낙관적 읽음 오버레이를 합쳐 최신순 목록을 만듭니다.isAlert) · 필수 확인(isMustRead) 세 신호를 서로 다른 축으로 유지합니다. 확인한 긴급 공지는 긴급 신호만 남습니다.accessibilityLabel)을 모델이 직접 만듭니다.상태 —
PingInbox(@Observable,Loadable)WatchRouter와 같은 자리). 확인 버튼은 상세에서 눌리고 결과는 목록이 즉시 반영해야 해서, 화면 로컬 상태로는 이 왕복이 성립하지 않습니다.WatchSessionCoordinator.receivedState(applicationContext 수신분)와requestSync()결과를 각각 들고,generatedAt이 더 새로운 쪽을 화면에 그립니다. 동률이면 iPhone 이 퍼블리시한 컨텍스트를 택합니다.WatchConnectivityError→AppError매핑. 스키마 불일치는 페이로드 손상과 구분해 「iPhone 앱을 업데이트해 주세요」로 냅니다.확인 전송 (양방향 동기화)
transferUserInfo큐 채널(.noticeRead)로 보냅니다 — 계약상 이 종류가 허용하는 유일한 채널이고, 연결이 끊겨 있어도 접수되며 앱이 종료돼도 시스템이 전송을 이어 갑니다.화면
PingListView—List+Loadable분기(로딩 / 빈 상태 / 목록 / 실패). 행 배경은watchListRowBackground로 불투명 서피스만 씁니다(Glass 미적용, 스펙 §2.2).PingDetailView— 본문ScrollView+.safeAreaInset(edge: .bottom)하단 고정 확인 CTA. 확인 완료 시 비활성 + 사유 표시.WatchRootView의navigationDestination에.pingList·.pingDetail(noticeID:)를 연결했습니다.WatchRoute케이스는 추가하지 않았습니다(✨ Feature: Watch 홈 통합 글랜스 + 네비게이션 IA 골격 #1206 이 정의한 열거형을 그대로 씁니다).디자인 시스템
watchListRowBackground(isSelected:leadingAccent:)에leadingAccent를 추가했습니다. 긴급 색바를 행 배경이 그려야 행 끝까지 닿습니다 — 콘텐츠에 그리면 셀 인셋만큼 떠서 시안과 어긋납니다.의존성
UMCWatchApp에UMCFoundation을 추가했습니다.Loadable·AppError를 워치 전용으로 다시 만들지 않기 위해서이며, 해당 모듈은 이미destinations: [.iPhone, .appleWatch]로 워치 슬라이스를 빌드합니다.테스트 21건 추가 —
WCSession없이 검증되도록 정렬·오버레이·스냅샷 선택·에러 매핑을 순수 함수로 분리했습니다.📋 추후 진행 상황
updateApplicationContext로 밀어 주는 경로와, 워치가 보낸.noticeRead를 서버 수신 확인 API 로 중계하는 핸들러는 ♻️ Refactor: WatchConnectivity 도메인 페이로드 계약 정의 + 양방향 수신 경로 구현 #1210 iPhone 측 후속 작업입니다. 이 PR 은 워치 쪽 소비·전송만 담당합니다.WatchColor.statusError(#FF453A) 를 썼습니다. 시안에 워치 전용 긴급 색이 따로 있다면 토큰을 교체해야 합니다.📌 리뷰 포인트
PingInbox.latest(_:_:).requestSync()는coordinator.receivedState를 갱신하지 않아 출처가 둘로 갈립니다.generatedAt비교 + 동률 시 컨텍스트 우선이 맞는 규칙인지 봐 주세요.transferUserInfo는 시스템이 전달을 보장하므로 롤백 경로를 두지 않았습니다.List행·화면 배경에 Glass 를 쓰지 않았습니다(워치 스펙 §2.2). 누락된 곳이 있는지 봐 주세요.✅ Checklist
PR이 다음 요구 사항을 충족하는지 확인해주세요!!!
🧪 검증
xcodebuild test -scheme UMCWatchApp -destination 'platform=watchOS Simulator,name=Apple Watch Series 11 (46mm)'cd UMCApp && make test