Conversation
- 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") 제거
- 일정 모델을 옮기지 않고 HomeDomain·NoticeDomain·CoreDomain 의 Domain 타겟을 [.iPhone, .appleWatch] 로 개방 — #981 의 "Home* 단일 소유자" 경계 유지 - ActivityDomain 의 HomeDomain·CoreDomain 조건부 의존(.when([.ios])) 제거로 watchOS 링크 실패(unable to resolve module dependency) 해소 - AttendanceTimeWindow 시간대 판정을 순수 이니셜라이저로 추출하고 ChallengerAttendanceUseCase 가 위임 — 판정 규칙 단일 소스화(동작 불변) - UMCWatchApp 이 ActivityDomain·HomeDomain 을 링크하고 출석 목록 화면에서 AttendanceTimeWindow(schedule:now:) 를 직접 호출 - 판정 로직 테스트 15종 추가 · 경계/플랫폼 정책을 build-and-modules.md 에 반영
- "오늘 세션" 라벨 + "오늘 세션 없음" 값이 같은 말을 두 줄로 반복해 위계가 흐려지던 문제 - 빈 상태는 문구 한 줄만 카드에 남긴다. 세션이 있을 때의 라벨/값 위계는 그대로
# Conflicts: # UMCApp/UMCWatchApp/Project.swift # UMCApp/UMCWatchApp/Sources/ContentView.swift
- WatchAttendanceListView: Loadable 4분기(로딩·실패·빈 목록·목록) · 진행 중 행 solid 선택 표면 · 행 탭으로 세션/결과 진입 - WatchAttendanceSessionView: 정시 · 지각 · 지오펜스 이탈 3상태를 한 화면이 데이터에서 파생, 마감 카운트다운과 Crown 재측정 - WatchAttendanceResultView: 출석 확정 · 지각 · 공결 · 결석 4결과 + 승인 대기를 한 화면이 파생 - WatchAttendanceOutcome 추가: 서버 EXCUSED 를 present 로 접는 ScheduleAttendanceStatus 로는 "공결에 초록을 주지 않는다"는 표시 규칙을 낼 수 없어 워치 표시 전용 enum 을 분리 - WatchAttendanceViewModel 을 앱 셸 소유 environment 로 승격 — 목록·세션·결과가 같은 일정 집합과 결과 캐시를 본다 - 시간대 판정은 ActivityDomain 의 AttendanceTimeWindow 에 위임, 지오펜스 반경은 AttendancePolicy.geofenceRadius 재사용 - WatchRootView destination 을 실제 화면으로 교체 (일정 미도착 시 플레이스홀더 폴백) - Project.swift 에 UMCFoundation 명시 링크 (Loadable · AppError 직접 사용) - 테스트 24종 추가 (총 44 tests / 6 suites 통과)
3 tasks
# Conflicts: # UMCApp/Core/WatchDesignSystem/Sources/Components/WatchStatusBadge.swift # UMCApp/Tuist/ProjectDescriptionHelpers/Project+WatchApp.swift # UMCApp/UMCWatchApp/Project.swift # UMCApp/UMCWatchApp/Sources/Attendance/WatchAttendanceListView.swift # UMCApp/UMCWatchApp/Sources/Attendance/WatchAttendanceViewModel.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
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 #1207
✨ PR 유형
워치 출석 플로우 화면을 구현했습니다. 이슈의 "화면 7종"은 3개 네비게이션 목적지(목록 · 세션 · 결과)가 데이터에서 파생하는 7가지 시각 상태입니다.
WatchRoute케이스를 새로 추가하지 않았습니다 — #1206 이 남긴 설계 의도("#1207 은 케이스를 추가하지 않고WatchRootView의 destination 만 실제 화면으로 바꿔 끼우면 된다",WatchRoute.swift)를 그대로 따랐습니다.WatchAttendanceListViewAttendanceTimeWindow.onTimeAttendanceTimeWindow.lateWindowAttendancePolicy.geofenceRadius(50m)WatchAttendanceOutcome.present.late.excused.absent/.pending📷 스크린샷 or 영상(UI 변경 시)
시뮬레이터 캡처는 아직 붙이지 않았습니다. 대신 각 화면에 상태별
#Preview를 넣어 Xcode 캔버스에서 바로 확인할 수 있습니다 (정시 · 지각 · 지오펜스 이탈 · 결과 5종 · 빈 목록 · Dynamic Type.accessibility3).🛠️ 작업내용
1. 출석 목록 (
WatchAttendanceListView)Loadable4분기 렌더링: 로딩 · 실패 · 빈 목록(ContentUnavailableView) · 목록watchListRowBackground(isSelected:)로 끌어올림2. 출석 세션 (
WatchAttendanceSessionView)onTimeEndAt/lateEndAt)에서만 만듭니다. 정책이 없으면 타이머를 감춥니다 — 클라이언트 상수로 마감 시각을 지어내면 서버 판정과 다른 숫자를 카운트다운하게 됩니다WatchActionButton의disabledReason단일 경로만 사용.primary입니다. 상태색(앰버)과 액션색(인디고)은 분리된 축이라 상태가 버튼 색을 바꾸지 않습니다3. 출석 결과 (
WatchAttendanceResultView).loading)는 실패가 아니라 승인 대기입니다. 결과는ATTENDANCE_STATUS_CHANGED푸시로만 도착하고 승인까지 수십 분~며칠 걸립니다(설계 §3.1)4.
WatchAttendanceOutcome신설 (표시 전용 enum)HomeDomain.ScheduleAttendanceStatus는 서버의"EXCUSED"를.present로 접습니다. 그래서는 설계 §3.2 의 "공결에 초록(출석 확정)을 주지 않는다"를 표현할 수 없습니다checkmark.seal)과 링 없음으로 구분합니다5. 상태 소유권
WatchAttendanceViewModel을 앱 셸(UMCWatchApp) 소유.environment로 승격했습니다. 목록·세션·결과 세 화면이 같은 일정 집합과 같은 결과 캐시를 봐야 해서 화면마다 소유하면 상태가 갈라집니다 (WatchRouter와 같은 절대 규칙 🎉 [Chore] 프로젝트 초기 설정 및 환경 구성 #1 예외)ActivityDomain.AttendanceTimeWindow(♻️ Refactor: Watch 앱이 ActivityDomain 출석 UseCase 사용 — 일정 모델 공용 위치 확정 #1212)에 위임, 지오펜스 반경은AttendancePolicy.geofenceRadius를 그대로 씁니다 — 워치가 iPhone 과 다른 반경으로 판정하면 같은 자리에서 기기별로 결과가 갈립니다Glass 절제 규칙 (설계 §2.2)
grep -rn "glassEffect\|buttonStyle(.glass" UMCApp/UMCWatchApp/Sources→ 0건. Glass 는WatchActionButton(✨ Feature: Watch 디자인 토큰·공통 컴포넌트 구축 (Glass 절제 규칙) #1205) 내부에만 있습니다. 화면 배경 · 카드 배경 ·List행 배경 · 결과 풀스크린 배경 모두 solid 입니다📋 추후 진행 상황
선행 PR 의존 — 이 브랜치는 아래 3개 위에 쌓여 있어 머지 전까지 diff 에 선행 커밋이 함께 보입니다. 선행 PR 이 머지되면 이 PR 의 diff 는 워치 출석 화면만 남습니다.
feat/1205WatchColor·Font.watch·WatchLayout·watchCard·WatchActionButton·WatchStatusBadgefeat/1206WatchRoute·WatchRouter·WatchRootViewrefac/1212AttendanceTimeWindow· 멀티플랫폼 Domain 타깃후속 이슈에서 붙일 것 — 이번 PR 은 화면과 표시 로직까지입니다. 실제 데이터 연결은 다음 이슈들 소관이라 주입 지점만 열어 뒀습니다.
apply(schedules:), 출석 요청 전송 →requestAttendance(),ATTENDANCE_STATUS_CHANGED푸시 수신 →apply(outcome:for:)CLLocation측정 →apply(distanceMeters:), Crown 재측정 →remeasure()내부의 실제 재측정 트리거WatchRootView의 해당 destination 은 아직 플레이스홀더입니다cumulativePresentCount·excuseReason은 iPhone 이 함께 밀어주면 채워집니다 (현재 기본값"0"/nil→ 미노출)사람이 확인해야 할 것
📌 리뷰 포인트
WatchAttendanceOutcome분리가 맞는 선택인지 —ScheduleAttendanceStatus에.excused를 추가하는 쪽이 근본적이지만 iOS 출석률 집계·기존 화면 전반에 파급됩니다. 워치 표시 전용으로 막은 게 적절한 경계인지 봐주세요location == nil)의 CTA 처리 (설계 문서에 명시 없음) — 지오펜스를 요구하면 CTA 가 영원히 비활성으로 남아서, 장소가 없는 일정은 시간창만으로 판정하도록 했습니다 (WatchAttendanceSessionViewModel.requiresGeofence).expired) 일정의 배지 색 (설계 문서에 명시 없음) —.error(빨강)로 뒀습니다. 출석 자체가 불가능한 종료 상태라.error축에 올렸는데, 이미 출석 결과가 나온 지난 일정에 빨간 배지가 붙는 게 과한지 판단이 필요합니다WatchStatusBadge를 안 쓰는 이유 — 그 컴포넌트는 심볼 폰트를.cardLabel로 고정해서 결과 화면의 대형 심볼(.metric) 크기를 낼 수 없습니다. 팔레트 렌더링 순서(점, 링)는 배지와 동일하게 맞췄습니다. 컴포넌트에 크기 파라미터를 여는 편이 나으면 ✨ Feature: Watch 디자인 토큰·공통 컴포넌트 구축 (Glass 절제 규칙) #1205 쪽에서 조정하겠습니다Project.swift의UMCFoundation명시 링크 —Loadable·AppError를 워치에서 직접 쓰므로HomeDomain경유 전파에 기대지 않고 직접 링크했습니다✅ Checklist
PR이 다음 요구 사항을 충족하는지 확인해주세요!!!
make test통과 — 44 tests / 6 suites,platform=watchOS Simulator,name=Apple Watch Series 11 (46mm)AppProduct/무변경@Observable만 사용 (앱 생명주기 전역 관리자 예외 명시)