fix(settings): collapse day-start time wheel into a value row

- 설정 '하루 시작 시간'의 상시 노출 휠이 화면을 계속 차지한다는 실기기 피드백 → 평소엔 값 행(라벨 + 현재 시각 + 셰브론)만 보이고 탭하면 인라인 휠이 펼쳐지는 방식으로 변경
- 팝오버(컴팩트)로 되돌리지 않음 — 다이얼 감속 중 닫힘 시 정착값 유실 문제가 재발하므로 인라인 휠 유지가 안전. 접힘/펼침은 표시 상태일 뿐 값 반영 경로는 동일
- 접근성: 행에 라벨+현재 값 낭독. 마감 시각·기록 편집기는 일시적 시트라 상시 노출 유지 (§6.5 문서화)
- 검증 인자 -settingsShowDayStartWheel 신설(펼친 상태로 시작) — 접힘·펼침 스크린샷 확인, Debug/Store 빌드 성공, 카탈로그 무변화(외부 재저장으로 오염된 워킹트리 카탈로그는 HEAD로 복원)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014CkLDi8Lr21vGqo4SjZaTp
This commit is contained in:
songyc macbook 2026-07-18 09:18:35 +09:00
parent 9626e6841e
commit dbbbfb8506
2 changed files with 43 additions and 8 deletions

View File

@ -199,7 +199,7 @@ xcrun xcstringstool sync Widgets/Localizable.xcstrings --stringsdata "${widget_f
### 6.5 기록 탭 (`HistoryView`, `RecordEditors`)
- **범위 조회**: 기록은 전량 로드하지 않고 선택 날짜가 속한 **주 범위**만 @Query로 가져온다(`HistoryRecordsView` — SwiftData 동적 쿼리 패턴: 날짜가 바뀌면 새 조건으로 재생성). 하루 경계 걸침·진행 중 세션은 "시작 < 범위끝 AND (종료 없음 OR 종료 > 범위시작)" 겹침 조건으로 포함
- 날짜 헤더(화살표 이동, 날짜 탭=달력 팝오버, "오늘" 버튼) + 목록/타임테이블 세그먼트
- 목록: 시간 기록(시작~종료·총 시간·메모)·횟수 기록(시각·+n·메모), 탭하면 수정 시트. **수정·추가 시트의 시각 입력은 상시 노출 휠 다이얼**(`RecordEditors.swift` RecordTimeWheel) — 컴팩트 팝오버는 다이얼 감속 중 닫히면 정착값이 유실되는 실기기 보고가 있어 폐기. `in:` 범위 제약도 하한 근처 선택을 소리 없이 되감아 금지(검증은 종료<시작 경고 문구+저장 비활성). 편집 상태는 단위 절사(flooredToMinute) 실측정 기록의 찌꺼기(14:32:47) 남으면 다이얼로 맞춘 1시간이 59분으로 표시되는 어긋남 방지. 다짐 '마감 시각설정 '하루 시작 시간' 같은 RecordTimeWheel(components: .hourAndMinute) 통일 앱의 시각 입력 다이얼은 전부 상시 노출
- 목록: 시간 기록(시작~종료·총 시간·메모)·횟수 기록(시각·+n·메모), 탭하면 수정 시트. **수정·추가 시트의 시각 입력은 상시 노출 휠 다이얼**(`RecordEditors.swift` RecordTimeWheel) — 컴팩트 팝오버는 다이얼 감속 중 닫히면 정착값이 유실되는 실기기 보고가 있어 폐기. `in:` 범위 제약도 하한 근처 선택을 소리 없이 되감아 금지(검증은 종료<시작 경고 문구+저장 비활성). 편집 상태는 단위 절사(flooredToMinute) 실측정 기록의 찌꺼기(14:32:47) 남으면 다이얼로 맞춘 1시간이 59분으로 표시되는 어긋남 방지. 다짐 '마감 시각설정 '하루 시작 시간' 같은 RecordTimeWheel(components: .hourAndMinute) 통일 앱의 시각 입력 다이얼은 전부 인라인 (팝오버 금지). 설정 '하루 시작 시간' 상시 노출 거슬린다는 실기기 피드백으로 **평소엔 값 행(라벨+현재 시각+셰브론), 탭하면 펼침** 마감 시각·기록 편집기는 일시적 시트라 상시 노출 유지
- 타임테이블: 24시간 축(하루 시작 시간 기준), 하루/일주일 전환(주간 보기에서 화살표는 7일씩 점프). 시간=색 블록, 횟수=색 점. 블록/점 탭=수정
- 필터: `RecordFilterSheet`(공용) — 꼬리표 아래 행동이 그룹된 트리, **"제외한 행동 ID" 집합**으로 관리해 기본이 전체 선택. 활성 시 초록 칩 표시
- 내보내기: 현재 날짜/모드/필터 그대로 `ExportBuilder.history` 스냅숏 → 이미지 시트
@ -332,7 +332,7 @@ xcrun xcstringstool sync Widgets/Localizable.xcstrings --stringsdata "${widget_f
| 측정 알림 | `-settings.longSessionAlertHours <N>`(설정 주입) `-longSessionAlertTestSeconds <N>`(시간 대신 N초 발화, provisional 권한 자동) `-alertDump`(예약 상태 → Documents/session-alerts.txt) |
| 목표 | `-goalShowEditor` `-goalShowFinished` `-goalReorder` `-goalScrollBottom` `-questShowEditor`(다짐 편집기 표시 — 마감 시각 다짐 우선, 없으면 첫 다짐) `-endGoalYesterday "제목"`(종료일을 어제로 — 실행 시점 자동 판정 검증) `-watchGoals <N>`(생성순 목표 N개를 '애플워치에서 보기'로 설정, 0=전체 해제 — 설정 후 스냅숏 즉시 푸시, 컴플리케이션 필터 검증용) `-streakDump`(모든 다짐의 연속 계산 결과 → Documents/streak-dump.txt) `-progressSelfTest`(spanProgress 수치 37건 자가 검증[환산 캡·수행일 집계·마감·atMost·custom] → Documents/progress-self-test.txt — 인메모리 컨테이너·설정 고정이라 실데이터/기기 설정 무영향) |
| 기록/통계 | `-historyMode timetable` `-historyWeekly` `-excludeActions "이름,이름"` `-historyShowFilter` `-statShowFilter` `-statSpan <day|week|month>` `-statScrollBottom` `-showExport` `-exportDump`(Documents/export-dump.png) `-recordShowEditor <session|count>`(최신 기록 수정 시트) |
| 설정/기타 | `-settingsScrollGoal` `-settingsScrollPremium` `-premiumPreview` `-helpPreview`(+`-helpExpandAll`: 전 주제 펼침, `-helpScrollTo <help-main 등>`: 이미지 주제로 스크롤) `-dataExportPreview`(+`-dataExportRun`이면 CSV 자동 생성 후 Documents 복사) `-widgetPreview YES|lock` `-widgetPreviewScroll <앵커>` `-widgetRenderMode accented|vibrant`(틴트/투명 홈 렌더링 분기 검증) `cloudSync`(standard bool로 강제) `-intentSmokeTest`(시리 인텐트 10개 시나리오 실행 → Documents/intent-smoke-test.txt, -seedDemo·-premium과 함께) |
| 설정/기타 | `-settingsScrollGoal` `-settingsScrollPremium` `-settingsShowDayStartWheel`(하루 시작 시간 휠 펼침 시작) `-premiumPreview` `-helpPreview`(+`-helpExpandAll`: 전 주제 펼침, `-helpScrollTo <help-main 등>`: 이미지 주제로 스크롤) `-dataExportPreview`(+`-dataExportRun`이면 CSV 자동 생성 후 Documents 복사) `-widgetPreview YES|lock` `-widgetPreviewScroll <앵커>` `-widgetRenderMode accented|vibrant`(틴트/투명 홈 렌더링 분기 검증) `cloudSync`(standard bool로 강제) `-intentSmokeTest`(시리 인텐트 10개 시나리오 실행 → Documents/intent-smoke-test.txt, -seedDemo·-premium과 함께) |
| 버블 | `-radialExpanded` `-radialDoubleTapTest`(2초 뒤 FAB 더블탭 재현 — `-settings.radialDoubleTapTab <탭>`과 함께) |
| 일기 | `-diarySeed`(필기 획 2줄 포함 — 달력 화면에서 먼저 시드 후 재실행으로 열 것) `-diaryOpenToday`(**`-startTab diary` 필수**) `-diaryPage <N>` `-diaryZoomScale <배>`(2.5초 뒤 fit×N 프로그램 줌 — 획 유지·선명도 확인) `-diaryShowConfig` `-diaryShowExport`(달력 화면 onAppear — `-diaryOpenToday`와 함께 쓰면 안 뜸) `-diaryExportRun pdf|image`(결과를 Documents로 복사) `-diaryShowCalendarPicker` `-diarySeedEvents` `-diaryHideFirstAction` `-diaryShowActionFilter` `-diaryHideFirstGoal` `-diaryShowGoalFilter` `-diarySeedPhoto`(오늘=이모지+사진·어제=사진만 기분 사진 시드 — 달력 백레이어 검증) `-diarySeedTemplate`(2쪽 샘플 PDF 양식 생성) `-diaryShowTemplates`(양식 관리 시트) `-diaryShowPageChooser`(페이지 추가 선택 시트) `-diaryApplyTemplate`(첫 양식으로 페이지 추가) / 섹션 강제: `-diary.sectionOrder "timetable,hero,goals"` `-diary.hiddenSections "mood,todos,records,bars"` |
| 워치 | `-complicationPreview` `-complicationScroll` `-complicationScrollTo <stale\|quest>` `-autoRunFirstAction` `-autoStopRunning`(측정 중 행 탭과 동일 경로로 대표 측정 종료 — 페어링 심 E2E용) |

View File

@ -43,6 +43,15 @@ struct SettingsView: View {
)
}
/// ( )
@State private var showsDayStartWheel: Bool = {
#if DEBUG
// : -settingsShowDayStartWheel YES
if UserDefaults.standard.bool(forKey: "settingsShowDayStartWheel") { return true }
#endif
return false
}()
/// DatePicker
private var dayStartDate: Binding<Date> {
Binding {
@ -146,13 +155,39 @@ struct SettingsView: View {
Text("\(Format.weekdayShort(weekday))요일").tag(weekday)
}
}
// ,
// .
// :
// (§6.5)
VStack(alignment: .leading, spacing: 0) {
Text("하루 시작 시간")
RecordTimeWheel(
label: String(localized: "하루 시작 시간"),
selection: dayStartDate,
components: .hourAndMinute
)
Button {
withAnimation(.snappy(duration: 0.25)) {
showsDayStartWheel.toggle()
}
} label: {
HStack {
Text("하루 시작 시간")
.foregroundStyle(.primary)
Spacer()
Text(dayStartDate.wrappedValue, format: .dateTime.hour().minute())
.foregroundStyle(showsDayStartWheel ? AppTheme.green : .secondary)
Image(systemName: "chevron.down")
.font(.caption2.weight(.semibold))
.foregroundStyle(.tertiary)
.rotationEffect(.degrees(showsDayStartWheel ? 180 : 0))
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.accessibilityLabel(Text("하루 시작 시간"))
.accessibilityValue(Text(dayStartDate.wrappedValue, format: .dateTime.hour().minute()))
if showsDayStartWheel {
RecordTimeWheel(
label: String(localized: "하루 시작 시간"),
selection: dayStartDate,
components: .hourAndMinute
)
}
}
} header: {
Text("날짜 기준")