mycode/myApp/Expiranner/CLAUDE.md
songyc macbook ceb0ab8e16 feat(expiranner): 기록 날짜·시간 수정 + 매장별 지난 데이터 정리 + 주 시작 요일 설정
- RecordEditView 신규: 저장된 기록의 유통기한을 그래픽 달력 + '시간 추가'로 수정.
  날짜 상세 그리드의 길게 누르기 메뉴와 전체화면 뷰어 상단 버튼 양쪽에서 진입
- 설정 '데이터 관리'를 매장별로 분리: 지난 기록이 있는 매장만 행으로 나열해
  개별 삭제, 2개 매장 이상일 때만 '모든 매장에서 삭제' 행 노출(각각 확인 대화상자)
- 주 시작 요일 설정(일요일/월요일): 월 캘린더와 촬영 플로우 미니 달력의
  그리드·요일 기호·주말 색이 모두 따라감 (기본은 기존과 같은 일요일 시작)
- CLAUDE.md를 현재 동작(설정 화면, hasTime, 동적 색)에 맞게 갱신

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J2HQuA6RPtkh45QTMN83i2
2026-07-28 02:29:36 +09:00

70 lines
6.2 KiB
Markdown

# Expiranner Project Rules
## 1. 프로젝트 개요
- 이름: Expiranner — 매장별 유통기한 점검 도구 (개인용, 배포 없음)
- 루트: `Expiranner/` (모든 작업은 이 루트에서 수행)
- 소스 폴더: `IOS/` — pbxproj가 파일시스템 동기화 그룹(`PBXFileSystemSynchronizedRootGroup`)이라 이 폴더에 Swift 파일을 넣기만 하면 빌드에 포함된다. `Expiranner` 하위 폴더를 새로 만들지 말 것.
- 프레임워크: SwiftUI + SwiftData (Core Data 금지)
- 타깃: iOS 17+, iPhone 중심
- 언어: 모든 UI 텍스트와 로그는 한국어
- 외관: **기본 다크**, 설정에서 시스템/라이트/다크 선택(`AppearanceMode`, `@AppStorage(appearanceStorageKey)`). Theme 색은 전부 `Color.dynamic(light:dark:)` 동적 색.
- 디자인 토큰: `Theme.swift` (다크 기준 배경 `#0B0E13`, 카드 `#161B25`, 민트 액센트 `#3BE39F`, rounded 폰트 디자인)
## 2. 핵심 플로우 (이 동작이 앱의 전부)
1. **매장 선택** (`StoreListView`): 앱 첫 화면. 매장 목록 표시, `+`로 추가, 컨텍스트 메뉴로 이름 변경/삭제(기록 연쇄 삭제).
2. **연/월 선택** (`YearMonthView`): 연도 좌우 이동 + 1~12월 그리드, 월별 기록 개수 배지.
3. **월 캘린더** (`MonthCalendarView`): 날짜 셀마다 그 날짜가 유통기한인 제품 사진 썸네일 + 개수 배지. 좌우 화살표로 월 이동. 오늘 날짜 민트 테두리.
4. **날짜 상세** (`DayDetailView`): 시트로 그 날짜의 사진 그리드, D-day 칩(D-n / 오늘까지 / n일 지남), 시각 포함 기록엔 시각 배지. 탭하면 전체화면 뷰어(`PhotoViewerView`, 스와이프 + 날짜·시간 수정 + 삭제). 사진 길게 눌러 컨텍스트 메뉴로도 날짜·시간 수정(`RecordEditView`) 가능. [선택] 버튼으로 다중 선택 모드 → 전체 선택/해제 + 일괄 삭제(확인 대화상자).
5. **스캔 플로우** (`CaptureFlowView`, 전체화면): FAB(카메라 버튼) → 촬영 → [재촬영]/[사용하기] → **현재 보고 있는 달의 날짜만** 선택 가능한 미니 달력 → [저장하기] → 성공 햅틱 + 토스트 → 자동으로 카메라 복귀. `X`로 캘린더 복귀. 손전등 토글 지원. 날짜 선택 후 나타나는 '시간 추가' 버튼으로 시각(시:분)까지 선택 저장 가능(기본은 날짜만 — 이 흐름을 침해하지 말 것).
6. **로딩 화면**: 시스템 런치 스크린(루트 `Info.plist``UILaunchScreen``LaunchBackground` 색 + `LaunchIcon` 이미지) → 인앱 스플래시(`ExpirannerApp.swift``SplashView`, 약 1초 후 페이드아웃)로 이어짐. 런치 스크린과 스플래시의 글리프 크기(110pt)를 맞춰 끊김이 없게 유지할 것.
7. **설정** (`SettingsView`, 첫 화면 헤더 톱니로 진입): 화면 모드(시스템/라이트/다크), 주 시작 요일(일/월, `@AppStorage(weekStartStorageKey)` — 달력 그리드 전체가 따름), 데이터 관리(**매장별** '기간 지난 데이터 삭제' + 2개 매장 이상일 때만 전체 삭제 행).
## 3. 데이터 모델 (`Models.swift`)
```swift
@Model final class Store {
var name: String
var createdAt: Date
@Relationship(deleteRule: .cascade, inverse: \ProductRecord.store)
var records: [ProductRecord] = []
}
@Model final class ProductRecord {
var expiryDate: Date // hasTime=false면 자정 정규화, true면 시각 포함
var hasTime: Bool = false // 유통기한에 시각(시:분) 포함 여부
var createdAt: Date
@Attribute(.externalStorage) var imageData: Data // 최대 1600px JPEG
var thumbnailData: Data // 최대 360px JPEG (캘린더/그리드용)
var store: Store?
}
```
- 뷰에서는 `@Query`로 전체를 받고 `$0.store === store`로 메모리 필터링한다 (Predicate로 관계 비교하지 말 것 — iOS 17 호환성 문제 회피).
- 스키마 변경 시 `ExpirannerApp.init`의 저장소 초기화 폴백이 기존 데이터를 지우고 재생성한다.
## 4. 파일 구성 (`IOS/`)
- `ExpirannerApp.swift` — 엔트리, ModelContainer(실패 시 저장소 리셋 폴백)
- `Models.swift` — Store, ProductRecord
- `Theme.swift` — 색상/카드 스타일/햅틱(`Haptics`)/달력 유틸(`KoreanCalendar`)/이미지 다운스케일
- `StoreListView.swift`, `YearMonthView.swift`, `MonthCalendarView.swift`, `DayDetailView.swift`
- `CaptureFlowView.swift` — 촬영→확인→날짜선택(+시간 추가)→저장 상태 머신
- `CameraService.swift` — AVCaptureSession 래퍼(`@MainActor`) + `CameraPreview`
- `SettingsView.swift` — 화면 모드 / 주 시작 요일 / 매장별 지난 데이터 정리
- `RecordEditView.swift` — 저장된 기록의 유통기한(날짜·시간) 수정 시트
## 5. 코딩 규칙
- 연도 등 숫자를 Text에 넣을 때 반드시 `Text(verbatim:)` 사용 (천 단위 구분 방지: "2,026년" 버그).
- 날짜 계산은 전부 `KoreanCalendar.calendar` (gregorian + ko_KR) 사용.
- 새 모듈 심볼 사용 시 명시적 import 필요 (`MEMBER_IMPORT_VISIBILITY` 켜져 있음 — 예: `@Published``import Combine`).
- 저장/삭제 후 `try? context.save()` + 햅틱 호출이 관례.
- 불필요한 메타데이터 필드, 복잡한 설정 화면, 알림 기능 등을 임의로 추가하지 말 것.
## 6. 빌드 / 검증
```bash
xcodebuild -project Expiranner.xcodeproj -scheme Expiranner \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro,OS=18.5' \
-configuration Debug build CODE_SIGNING_ALLOWED=NO
```
- 시뮬레이터에서는 카메라가 동작하지 않으므로 스캔 플로우는 실기기에서 확인.
- 앱 아이콘 원본: 루트의 `AppIcon.svg`(밝은 민트 배경) → `qlmanage -t -s 1024`로 렌더 → PIL로 알파 제거 → `IOS/Assets.xcassets/AppIcon.appiconset/AppIcon.png`.
- 런치 글리프(`LaunchIcon.imageset`, 투명 배경 필수): qlmanage가 SVG를 흰 배경으로 렌더하는 경우가 있으므로 PIL로 도형을 직접 그려 생성했다(2x=220px, 3x=330px). 루트 `LaunchIcon.svg`는 도형 좌표 원본.
- `GENERATE_INFOPLIST_FILE=YES` + `INFOPLIST_FILE=Info.plist` 병합 구성 — 루트 `Info.plist`에는 UILaunchScreen만 두고 나머지 키는 빌드 설정(INFOPLIST_KEY_*)으로 관리.