feat(diary): dashed calendar event blocks for visibility + per-date goal filter on the summary page

일기 요약 페이지 개선 2건:

1) 캘린더 일정 블록 가시성
   기존 옅은 채움(0.14)+실선 외곽선은 배경과 겹쳐 잘 안 보였다.
   구분 방향은 유지하되(꼬리표 색과 캘린더 색이 같아도 헷갈리지 않게)
   구분 수단을 '농도'에서 '스타일'로 변경 — 채움을 0.3으로 올려
   또렷하게 하고, 행동 블록(불투명 채움)과는 점선 테두리(1.5pt)로
   구분한다. 화면과 일기 내보내기(PDF/PNG)가 같은 뷰를 쓰므로 함께 반영.

2) '이 날의 목표' 필터
   카드 우상단에 타임테이블 필터와 같은 모양의 필터 버튼 추가 —
   그날 진행 중인 목표 중 보고 싶은 것만 선택(숨긴 개수 노란 배지).
   선택은 날짜별로 DiaryEntry.hiddenGoalIDs(uuid 문자열, 기본값 있는
   새 필드 — CloudKit 규칙 준수)에 저장되어 기기 간 동기화에 안전.
   시트는 전체 선택/해제 + 개별 체크(캘린더 일정 선택과 동일 패턴),
   열람 전용(프리미엄 만료)에서는 버튼 숨김, 빈 엔트리 정리 조건에도
   목표 필터 선택 보존 추가. 모두 숨기면 안내 문구 표시.

검증: Debug·Store 빌드 성공, iPad 시뮬레이터에서 점선 캘린더 블록·
필터 버튼·팝업(첫 목표 숨김 → 카드 제외 + 배지 1) 스크린샷 확인.
신규 문자열 ko/en/ja 번역(missing 0, 문구 수정 stale 1건 삭제).
DEBUG 인자 -diaryHideFirstGoal/-diaryShowGoalFilter 추가.
도움말(일정·필터 항목)과 CLAUDE.md(§3·§6.7·§14) 갱신.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
This commit is contained in:
songyc macbook 2026-07-14 18:39:46 +09:00
parent e652aff0e9
commit 7a6a2aeb54
6 changed files with 224 additions and 21 deletions

View File

@ -88,7 +88,7 @@ xcrun xcstringstool sync IOS/Localizable.xcstrings --stringsdata "${files[@]}"
| `CountEntry` | timestamp, amount, note | |
| `Goal` | uuid, title, symbolName, colorHex, startDate, endDate?, statusRaw(inProgress/achieved/notAchieved), **achieveThresholdPercent(기본 100)**, sortOrder | isCollapsed·showsOnMain은 레거시 |
| `Quest` | uuid, goal, targetAction 또는 targetTag, measureRaw, periodRaw(daily/weekly/monthly/custom), scheduleModeRaw(everyDay/weekdays/monthDays/ordinalWeekday), weekdays[], monthDays[], ordinalWeek/Weekday, customStart/End, targetSeconds/targetCount, directionRaw(atLeast/atMost), sortOrder | 꼬리표 대상이면 measure는 생성 시 선택, `targetActions`는 해당 measure의 행동만 |
| `DiaryEntry` | dayKey(논리적 하루 키), moodEmoji, moodImageData(externalStorage), **calendarEventIDs[]**(그날 타임테이블에 넣을 EKEvent id), **hiddenActionIDs[]**(그날 타임테이블에서 숨길 Action.uuid 문자열) | 날짜당 1개. 날짜별 선택은 uuid 문자열로 저장(기기 간 동기화 안전) |
| `DiaryEntry` | dayKey(논리적 하루 키), moodEmoji, moodImageData(externalStorage), **calendarEventIDs[]**(그날 타임테이블에 넣을 EKEvent id), **hiddenActionIDs[]**(그날 타임테이블에서 숨길 Action.uuid 문자열), **hiddenGoalIDs[]**(그날 '이 날의 목표' 카드에서 숨길 Goal.uuid 문자열) | 날짜당 1개. 날짜별 선택은 uuid 문자열로 저장(기기 간 동기화 안전) |
| `DiaryTodo` | text, isDone, sortOrder | 요약 페이지 체크리스트 |
| `DiaryPage` | index, lined, lineSpacing, drawingData(PKDrawing, externalStorage) | 768×1024 논리 좌표 |
| `DiaryPageItem` | kind(photo/rectangle/ellipse/arrow/line/**text**), imageData, **text**(텍스트 상자 내용), colorHex, centerX/Y·widthRatio(비율 좌표), rotationDegrees | |
@ -192,7 +192,8 @@ xcrun xcstringstool sync IOS/Localizable.xcstrings --stringsdata "${files[@]}"
- 상세는 진입만 해도 엔트리를 생성(loadEntry)하므로, 달력 onAppear가 **완전히 빈 엔트리만 정리**(기분·할일·페이지 없음 + calendarEventIDs·hiddenActionIDs 비어있음 + 페이지 0개 — 캘린더/필터 선택이나 빈 노트 페이지가 있으면 보존)
- 상세: 좌우 페이지 넘김. 1페이지=하루 정리 요약, 이후=펜슬 노트, 마지막=페이지 추가 자리
- **요약 페이지 섹션** (표시·순서는 "첫 화면 구성" 시트, 전 날짜 공통, standard defaults): 오늘 기분(대형 정사각 타일: 이모지/사진) · 요약 지표 · 이 날의 목표(그날 기준 다짐 현황, 주간/월간은 그날까지 누적) · 오늘 할 일 · 타임테이블 · 기록 상세 · 통계 막대. 화면 720pt 이상이면 타임테이블 좌측 고정 2컬럼
- 타임테이블 카드 우상단 버튼: **필터**(그날 기록된 행동만 나열된 팝업에서 표시 선택, 날짜별 저장 `hiddenActionIDs`, 숨긴 개수 노란 배지 — 타임테이블에만 적용, 요약 수치·기록 목록은 전체 유지) + **캘린더**(그날의 EKEvent 선택, 날짜별 저장, 외곽선 스타일 블록). 둘 다 내보내기에 동일 반영
- 타임테이블 카드 우상단 버튼: **필터**(그날 기록된 행동만 나열된 팝업에서 표시 선택, 날짜별 저장 `hiddenActionIDs`, 숨긴 개수 노란 배지 — 타임테이블에만 적용, 요약 수치·기록 목록은 전체 유지) + **캘린더**(그날의 EKEvent 선택, 날짜별 저장, **점선 테두리 + 채움 0.3 블록** — 행동 블록은 불투명 채움이라 스타일로 구분, 옅은 채움 0.14는 가시성 부족으로 폐기). 둘 다 내보내기에 동일 반영
- '이 날의 목표' 카드 우상단 **목표 필터**: 그날 진행 중인 목표 중 표시할 것 선택(날짜별 저장 `hiddenGoalIDs`, 숨긴 개수 노란 배지, 열람 전용에선 버튼 숨김). 이미지 내보내기의 요약에는 목표 카드가 없으므로 내보내기 무영향
- **프리미엄 만료 열람 모드**: 이미 쓴 일기(hasContent)가 있으면 잠금 화면 대신 달력을 열람 전용으로 연다 — 배너 + 일기 있는 날짜만 활성, `environment(\.diaryReadOnly)`로 전 화면의 편집 UI(기분/할일/필기/필터/캘린더/⋯메뉴) 비활성, 열람 진입은 엔트리를 생성하지 않음. 일기를 쓴 적 없으면 기존 잠금 안내
- 노트 페이지: 768×1024 논리 좌표 고정 + scaleEffect. 배치 요소: 사진·도형(사각/원/화살표/선)·**텍스트 상자**(키보드 입력, 배치 모드에서 '글 수정'으로 내용·색 편집). ⋯ 메뉴 '페이지 순서 변경'으로 노트 페이지 드래그 재배열(요약은 항상 첫 장). **실기기 필기는 펜슬 전용, 손가락은 이동/줌만**(시뮬레이터는 anyInput). 핀치줌 최대 4배, `SharpPencilCanvasView`가 줌에 맞춰 래스터 배율 유지(획 선명도). ⚠️ 배율 적용은 **뷰의 contentScaleFactor만** — 서브레이어 contentsScale+setNeedsDisplay를 직접 건드리면 PencilKit 획 타일 픽셀이 지워진 채 재렌더되지 않아 확대 시 필기가 사라진다(수정된 버그). 줄 노트(간격 4단계)·사진·도형(사각/원/화살표/선) 배치 모드(드래그·핀치 크기 조절, 저장은 비율 좌표)
@ -307,7 +308,7 @@ xcrun xcstringstool sync IOS/Localizable.xcstrings --stringsdata "${files[@]}"
| 기록/통계 | `-historyMode timetable` `-historyWeekly` `-excludeActions "이름,이름"` `-historyShowFilter` `-statShowFilter` `-statSpan <day|week|month>` `-statScrollBottom` `-showExport` `-exportDump`(Documents/export-dump.png) |
| 설정/기타 | `-settingsScrollGoal` `-settingsScrollPremium` `-premiumPreview` `-helpPreview` `-dataExportPreview`(+`-dataExportRun`이면 CSV 자동 생성 후 Documents 복사) `-widgetPreview YES|lock` `-widgetPreviewScroll <앵커>` `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` / 섹션 강제: `-diary.sectionOrder "timetable,hero,goals"` `-diary.hiddenSections "mood,todos,records,bars"` |
| 일기 | `-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` / 섹션 강제: `-diary.sectionOrder "timetable,hero,goals"` `-diary.hiddenSections "mood,todos,records,bars"` |
| 워치 | `-complicationPreview` `-complicationScroll` `-complicationScrollTo <stale\|quest>` `-autoRunFirstAction` |
---

View File

@ -3824,6 +3824,22 @@
}
}
},
"모든 목표를 숨겼어요. 필터에서 다시 선택할 수 있어요.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "All goals are hidden. You can choose them again in the filter."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "すべての目標を非表示にしました。フィルターで再度選択できます。"
}
}
}
},
"모음": {
"localizations": {
"en": {
@ -4273,6 +4289,22 @@
}
}
},
"목표 필터": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Goal Filter"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "目標フィルター"
}
}
}
},
"목표 확인": {
"localizations": {
"en": {
@ -5271,6 +5303,22 @@
}
}
},
"선택한 목표만 이 날짜의 '이 날의 목표' 카드에 표시돼요. 날짜마다 따로 선택할 수 있어요.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Only the selected goals appear on this date's \"Goals of the Day\" card. You can choose separately for each date."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "選択した目標だけがこの日付の「この日の目標」カードに表示されます。日付ごとに別々に選択できます。"
}
}
}
},
"선택한 목표의 진행 현황이 모음 탭 상단에 표시돼요. 탭하면 목표 상세로 이동해요. 2개 이상 선택하면 카드를 옆으로 쓸어 넘겨 한 장씩 볼 수 있고, 표시 방식(다짐별로 각각 / 전체 다짐 합산)은 모든 카드에 똑같이 적용돼요.": {
"localizations": {
"en": {
@ -8586,18 +8634,18 @@
}
}
},
"첫 페이지 타임테이블 카드 오른쪽 위 캘린더 버튼으로 그날의 캘린더 일정을 골라 함께 표시할 수 있어요. 필터 버튼을 누르면 그날 기록된 행동 중 타임테이블에 보여줄 것만 고를 수 있어요. 선택 모두 날짜마다 따로 저장되고, 일기를 내보낼 때도 그대로 반영돼요.": {
"첫 페이지 타임테이블 카드 오른쪽 위 캘린더 버튼으로 그날의 캘린더 일정을 골라 함께 표시할 수 있어요(일정은 점선 테두리로 구분돼요). 필터 버튼을 누르면 그날 기록된 행동 중 타임테이블에 보여줄 것만 고를 수 있고, '이 날의 목표' 카드에도 필터 버튼이 있어 보고 싶은 목표만 골라 둘 수 있어요. 선택 모두 날짜마다 따로 저장되고, 타임테이블 선택은 일기를 내보낼 때도 그대로 반영돼요.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Use the calendar button at the top right of the first page's timetable card to pick that day's calendar events to show alongside your records. The filter button lets you choose which of that day's recorded actions appear on the timetable. Both choices are saved per date and carry over to diary exports."
"value": "Use the calendar button at the top right of the first page's timetable card to pick that day's calendar events and show them together (events are marked with a dashed outline). Tap the filter button to choose which of that day's recorded actions appear on the timetable, and the \"Goals of the Day\" card also has a filter button so you can pick just the goals you want to see. All selections are saved per date, and the timetable selections carry over to diary exports."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "最初のページのタイムテーブルカード右上のカレンダーボタンで、その日のカレンダー予定を選んで一緒に表示できます。フィルターボタンを押すと、その日に記録した行動のうちタイムテーブルに表示するものだけを選べます。どちらの選択も日付ごとに保存され、日記の書き出しにもそのまま反映されます。"
"value": "最初のページのタイムテーブルカード右上のカレンダーボタンで、その日のカレンダー予定を選んで一緒に表示できます(予定は破線の枠で区別されます)。フィルターボタンを押すと、その日に記録した行動のうちタイムテーブルに表示するものを選べます。「この日の目標」カードにもフィルターボタンがあり、見たい目標だけを選んでおけます。選択はすべて日付ごとに保存され、タイムテーブルの選択は日記の書き出しにもそのまま反映されます。"
}
}
}

View File

@ -134,14 +134,15 @@ struct DiaryRootView: View {
}
/// . hasContent(· · )
/// ·
/// , ( ) .
/// · ·
/// , ( ) .
private func cleanupEmptyEntries() {
var changed = false
for entry in diaryEntries
where !entry.hasContent
&& entry.calendarEventIDs.isEmpty
&& entry.hiddenActionIDs.isEmpty
&& entry.hiddenGoalIDs.isEmpty
&& (entry.pagesStorage ?? []).isEmpty {
context.delete(entry)
changed = true
@ -823,6 +824,9 @@ private struct DiarySummaryPage: View {
@State private var showingActionFilter = false
@State private var filterExcluded: Set<PersistentIdentifier> = []
/// ' ' entry.hiddenGoalIDs(uuid , )
@State private var showingGoalFilter = false
private var math: DayMath { DayMath() }
/// ( , )
@ -854,6 +858,13 @@ private struct DiarySummaryPage: View {
}
}
/// (uuid )
private var visibleGoals: [Goal] {
guard !entry.hiddenGoalIDs.isEmpty else { return activeGoals }
let hidden = Set(entry.hiddenGoalIDs)
return activeGoals.filter { !hidden.contains($0.uuid.uuidString) }
}
/// ( 24 + )
private var daySnapshot: ExportSnapshot {
ExportBuilder.history(
@ -975,6 +986,9 @@ private struct DiarySummaryPage: View {
excludedActionIDs: $filterExcluded
)
}
.sheet(isPresented: $showingGoalFilter) {
DiaryGoalFilterSheet(entry: entry, goals: activeGoals)
}
.onChange(of: filterExcluded) { _, newValue in
// + (uuid )
let hidden = orderedActions
@ -1004,6 +1018,16 @@ private struct DiarySummaryPage: View {
filterExcluded = timetableExcludedIDs
showingActionFilter = true
}
// : -diaryHideFirstGoal YES
if UserDefaults.standard.bool(forKey: "diaryHideFirstGoal"),
entry.hiddenGoalIDs.isEmpty,
let first = activeGoals.first {
entry.hiddenGoalIDs = [first.uuid.uuidString]
}
// : -diaryShowGoalFilter YES
if UserDefaults.standard.bool(forKey: "diaryShowGoalFilter") {
showingGoalFilter = true
}
}
#endif
}
@ -1018,7 +1042,10 @@ private struct DiarySummaryPage: View {
ExportHeroRow(stats: daySnapshot.hero)
case .goals:
DiaryGoalsCard(
goals: activeGoals,
goals: visibleGoals,
totalCount: activeGoals.count,
hiddenCount: activeGoals.count - visibleGoals.count,
onFilter: (readOnly || activeGoals.isEmpty) ? nil : { showingGoalFilter = true },
dayKey: entry.dayKey,
referenceNow: referenceNow,
math: math
@ -1149,27 +1176,62 @@ private struct DiarySummaryPage: View {
/// - // : " "
/// - ' ' (, )
private struct DiaryGoalsCard: View {
///
let goals: [Goal]
/// ( )
let totalCount: Int
/// ( )
let hiddenCount: Int
/// . nil = ( )
let onFilter: (() -> Void)?
let dayKey: Date
let referenceNow: Date
let math: DayMath
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
HStack(spacing: 8) {
Text("이 날의 목표")
.font(.subheadline.weight(.semibold))
Spacer()
Text(String(localized: "\(goals.count)개 진행 중"))
Text(String(localized: "\(totalCount)개 진행 중"))
.font(.caption2)
.foregroundStyle(.secondary)
if let onFilter {
//
Button(action: onFilter) {
HStack(spacing: 4) {
Image(systemName: "line.3.horizontal.decrease")
.font(.system(size: 12, weight: .semibold))
if hiddenCount > 0 {
Text(verbatim: "\(hiddenCount)")
.font(.system(size: 11, weight: .bold).monospacedDigit())
}
}
.foregroundStyle(hiddenCount > 0 ? AppTheme.yellow : AppTheme.green)
.padding(.horizontal, 8)
.padding(.vertical, 5)
.background(
(hiddenCount > 0 ? AppTheme.yellow : AppTheme.green).opacity(0.12),
in: Capsule()
)
}
.buttonStyle(.plain)
.accessibilityLabel(Text("목표 필터"))
}
}
if goals.isEmpty {
Text("이 날짜에 진행 중인 목표가 없어요")
.font(.caption)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity)
.padding(.vertical, 14)
Group {
if totalCount == 0 {
Text("이 날짜에 진행 중인 목표가 없어요")
} else {
Text("모든 목표를 숨겼어요. 필터에서 다시 선택할 수 있어요.")
}
}
.font(.caption)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity)
.padding(.vertical, 14)
} else {
ForEach(Array(goals.enumerated()), id: \.element.uuid) { index, goal in
if index > 0 {
@ -1345,6 +1407,92 @@ private struct DiaryGoalsCard: View {
}
}
// MARK: -
/// ' ' .
/// / + , .
private struct DiaryGoalFilterSheet: View {
@Bindable var entry: DiaryEntry
/// ( )
let goals: [Goal]
@Environment(\.dismiss) private var dismiss
@Environment(\.modelContext) private var context
private var hidden: Set<String> { Set(entry.hiddenGoalIDs) }
private var allSelected: Bool { entry.hiddenGoalIDs.isEmpty }
var body: some View {
NavigationStack {
List {
Section {
Button(allSelected ? String(localized: "전체 해제") : String(localized: "전체 선택")) {
save(allSelected ? goals.map(\.uuid.uuidString) : [])
}
.font(.callout.weight(.semibold))
ForEach(goals, id: \.uuid) { goal in
goalRow(goal)
}
} footer: {
Text("선택한 목표만 이 날짜의 '이 날의 목표' 카드에 표시돼요. 날짜마다 따로 선택할 수 있어요.")
}
}
.scrollContentBackground(.hidden)
.background(AppTheme.background)
.navigationTitle("목표 필터")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("닫기") { dismiss() }
}
}
}
.presentationDetents([.medium, .large])
}
private func goalRow(_ goal: Goal) -> some View {
let id = goal.uuid.uuidString
let isOn = !hidden.contains(id)
return Button {
toggle(id)
} label: {
HStack(spacing: 10) {
Image(systemName: goal.symbolName)
.font(.system(size: 13, weight: .semibold))
.foregroundStyle(.white)
.frame(width: 28, height: 28)
.background(goal.color, in: RoundedRectangle(cornerRadius: 8, style: .continuous))
Text(goal.title)
.font(.subheadline)
.foregroundStyle(.primary)
.lineLimit(1)
Spacer()
Image(systemName: isOn ? "checkmark.circle.fill" : "circle")
.font(.title3)
.foregroundStyle(isOn ? AppTheme.green : .secondary)
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
private func toggle(_ id: String) {
var ids = entry.hiddenGoalIDs
if let index = ids.firstIndex(of: id) {
ids.remove(at: index)
} else {
ids.append(id)
}
save(ids)
}
private func save(_ ids: [String]) {
entry.hiddenGoalIDs = ids
entry.updatedAt = .now
try? context.save()
}
}
// MARK: -
private struct DiaryMoodCard: View {

View File

@ -1174,20 +1174,23 @@ private struct ExportTimetableView: View {
.offset(x: 2, y: y)
}
/// : +
/// : ( ) ** ** .
/// (0.14)+
/// (0.3) , .
private func eventBlockView(_ block: ExportTimetableData.Block, columnWidth: CGFloat) -> some View {
let y = CGFloat(block.startFrac - Double(data.hourLo)) * hourHeight
let height = max(CGFloat(block.endFrac - block.startFrac) * hourHeight, 5)
return RoundedRectangle(cornerRadius: 4, style: .continuous)
.fill(block.color.opacity(0.14))
.fill(block.color.opacity(0.3))
.overlay(
RoundedRectangle(cornerRadius: 4, style: .continuous)
.strokeBorder(block.color.opacity(0.55), lineWidth: 1)
.strokeBorder(block.color.opacity(0.9),
style: StrokeStyle(lineWidth: 1.5, dash: [4, 3]))
)
.overlay(alignment: .topLeading) {
if !weekly && height >= 18 {
Label(block.name, systemImage: block.symbol)
.font(.caption2)
.font(.caption2.weight(.semibold))
.foregroundStyle(block.color)
.lineLimit(1)
.padding(4)

View File

@ -201,7 +201,7 @@ struct HelpView: View {
HelpTopic(
symbol: "calendar.badge.plus",
title: "타임테이블에 일정·필터",
body: "첫 페이지 타임테이블 카드 오른쪽 위 캘린더 버튼으로 그날의 캘린더 일정을 골라 함께 표시할 수 있어요. 필터 버튼을 누르면 그날 기록된 행동 중 타임테이블에 보여줄 것만 고를 수 있어요. 선택 모두 날짜마다 따로 저장되고, 일기를 내보낼 때도 그대로 반영돼요."
body: "첫 페이지 타임테이블 카드 오른쪽 위 캘린더 버튼으로 그날의 캘린더 일정을 골라 함께 표시할 수 있어요(일정은 점선 테두리로 구분돼요). 필터 버튼을 누르면 그날 기록된 행동 중 타임테이블에 보여줄 것만 고를 수 있고, '이 날의 목표' 카드에도 필터 버튼이 있어 보고 싶은 목표만 골라 둘 수 있어요. 선택 모두 날짜마다 따로 저장되고, 타임테이블 선택은 일기를 내보낼 때도 그대로 반영돼요."
),
HelpTopic(
symbol: "applepencil",

View File

@ -28,6 +28,9 @@ final class DiaryEntry {
/// uuid ( ).
/// , .
var hiddenActionIDs: [String] = []
/// ' ' uuid ( ).
/// CloudKit (§3) .
var hiddenGoalIDs: [String] = []
var createdAt: Date = Date()
var updatedAt: Date = Date()