docs(chat): 날짜 구분선 요구사항과 검증을 기록한다
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
# 유저-크리에이터 채팅 날짜 구분선 구현 계획
|
||||
|
||||
> 상태: 구현 완료 / P1-GATE 실서버·접근성 QA 대기
|
||||
|
||||
| 문서 항목 | 내용 |
|
||||
|---|---|
|
||||
| 작성일 | `2026-10-16` |
|
||||
| 요구사항 기준 | [prd.md](prd.md), DATE-001~008 |
|
||||
| API 기준 | 해당 없음, 기존 메시지 timestamp 활용 |
|
||||
| 현재 Phase | Phase 1, 구현 완료 / 최종 QA 대기 |
|
||||
| 현재 활성 Goal | 없음. P1-GATE 외부 조건 대기 |
|
||||
|
||||
## 목표
|
||||
|
||||
유저-크리에이터 DM에서 표시되는 TEXT 메시지를 열람 기기의 현지 날짜 및 앱 언어로 구분하고, 페이지 병합과 ACK 갱신 후에도 구분이 정확하며 메시지 스크롤과 이전 메시지 로드를 유지한다.
|
||||
|
||||
## 현재 상태
|
||||
|
||||
| Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 |
|
||||
|---:|---|---:|---|---|
|
||||
| 1 | 외부 조건 대기 | `2/2` | `P1-GATE` | 실제 DM 계정·방 데이터와 VoiceOver 수동 검증 필요 |
|
||||
|
||||
- 2026-10-16 사용자 구현 요청으로 P1-T1을 시작한다. 완료한 항목만 검증 증거와 함께 갱신한다.
|
||||
- Goal 실행 체계나 token budget은 설정하지 않는다.
|
||||
|
||||
## 범위
|
||||
|
||||
### 포함
|
||||
|
||||
- 표시 TEXT 메시지 기준 날짜 행 파생, 인접한 현지 날짜 변경 지점과 현재 첫 메시지 위에 날짜 구분 항목 표시.
|
||||
- 앱 환경 locale에 맞춘 연·월·일·요일 문자열과 기기 시간대 기준 그룹핑.
|
||||
- 시간대, DST, locale, 과거 페이지 병합, 보류 메시지 상태 스냅샷 검증 및 SwiftUI 접근성 표시.
|
||||
- `SodaLive`와 `SodaLive-dev` 타깃의 신규 Swift 파일 등록과 두 타깃 빌드, 지정 수동 QA.
|
||||
|
||||
### 제외
|
||||
|
||||
- 요청 범위 밖 제품 코드·테스트·프로젝트 설정 변경. 이전 문서 전용 제한은 사용자 구현 요청으로 해제했다.
|
||||
- API 변경, 메시지별 기존 시각 포맷 변경, 언어 서비스 또는 `DateExtension` 변경.
|
||||
- VOICE 표시, 날짜 고정 헤더, 별도 ViewModel/DTO, 메시지 정렬·중복 제거·ACK 로직 변경, 네트워크 모킹 및 재시도 UI.
|
||||
- 사용자에게 확정받지 않은 별도 디자인 시스템이나 픽셀 사양 확장. 구체 스타일은 기존 앱 디자인 관례를 따르는 구현 판단이다.
|
||||
|
||||
## 기술적 제약과 구조
|
||||
|
||||
- 기술 스택: iOS 16.6 이상, Swift language version 5.0, Foundation, SwiftUI, 기존 `work/tests`의 `swiftc` 기반 계약 검사 패턴. 테스트 타깃이나 외부 의존성은 추가하지 않는다.
|
||||
- locale은 RoomView의 `@Environment(\.locale)`에서 소비한다. 앱 루트의 `LanguageContainer.environment.locale` 주입을 이용하며 언어 매핑을 복제하거나 `Locale.current`를 새 날짜 구현에 사용하지 않는다.
|
||||
- RoomView는 `@Environment(\.timeZone)`을 사용한다. 그룹핑과 포맷 양쪽에서 전달된 `Calendar`의 사본을 지정 timeZone으로 정규화한다.
|
||||
- formatter는 호출마다 설정된 `DateFormatter`를 쓰고 locale, calendar, timeZone을 적용한 뒤 `setLocalizedDateFormatFromTemplate("yMMMMdEEEE")`를 호출한다. 상대 날짜, 하드코딩된 언어 조각, 공유 mutable cache를 추가하지 않는다.
|
||||
- 행은 현재 표시 목록에서 파생한다. TEXT만 필터링한 뒤 원래 순서와 ID를 유지하고, `Date(timeIntervalSince1970: TimeInterval(createdAt) / 1000)`으로 표시 메시지마다 한 번 변환한다. 정렬, 별도 날짜 섹션, 상태 저장은 하지 않는다.
|
||||
- 첫 표시 행은 항상 날짜를 갖고, 후속 행은 직전 표시 TEXT와 `Calendar.isDate(_:inSameDayAs:)`가 false일 때만 날짜를 갖는다. 빈 입력에는 행이 없다.
|
||||
- 확인된 앱 파일 책임: `SodaLive/Sources/V2/Main/Chat/UserCreatorChat/Models/UserCreatorChatModels.swift`, `Models/UserCreatorChatDisplayMessage.swift`, `UserCreatorChatRoomView.swift`, `UserCreatorChatRoomViewModel.swift`, 기존 `Components/UserCreatorChatTextMessageItemView.swift`. 루트 locale 주입은 `SodaLive/Sources/App/SodaLiveApp.swift`에서 확인한다.
|
||||
|
||||
### 집중 검증 항목
|
||||
|
||||
| 위험 입력·조건 | 확인할 동작 | 담당 검증 |
|
||||
|---|---|---|
|
||||
| 앱 언어와 기기 지역 불일치 | 언어는 앱 locale, 날짜 경계는 기기 timeZone | P1-T1, Gate 언어 QA |
|
||||
| UTC 자정·현지 자정·DST | 24시간 차이나 UTC 날짜가 아닌 같은 현지 일자 비교 | P1-T2 날짜 경계 assertion |
|
||||
| 과거 페이지가 같은 날짜에 이어짐 | 첫 날짜 항목 이동, 중복 없음, 읽던 위치 유지 | P1-T2 prepend assertion, Gate 스크롤 QA |
|
||||
| pending이 서버의 다른 날짜로 확정됨 | 기존 빈 날짜 제거와 새 묶음 중복 방지 | P1-T2 ACK 스냅샷, Gate 서버 QA |
|
||||
| 날짜 항목 높이가 기존 viewport를 채움 | 첫 메시지 paging·짧은 목록 추가 로드·바닥 이동 보존 | P1-T2 hook 연결 확인, Gate 실제 화면 QA |
|
||||
|
||||
## Phase 1
|
||||
|
||||
**Phase 결과:** TEXT 메시지의 날짜 문자열과 파생 행·비고정 UI가 정확히 동작하며 기존 채팅 열람 흐름을 보존한다.
|
||||
|
||||
**선행조건:** 사용자 구현 착수 요청과 본 PRD의 확정 요구사항.
|
||||
|
||||
**Phase 완료 조건:** `P1-T1`, `P1-T2`, `P1-GATE` 완료, 검증 기록 누적.
|
||||
|
||||
### 구현 항목
|
||||
|
||||
#### Task 1.1 날짜 표현 formatter
|
||||
|
||||
**Goal `P1-T1`:** 지정된 앱 locale, calendar, 기기 timeZone에 따라 상대 표현 없이 날짜 전체를 문자열로 만든다.
|
||||
|
||||
- **시작 조건:** DATE-002~004 확정, `P1-T2` 전에 완료.
|
||||
- **완료 증거:** RED/GREEN/REFACTOR 체크 전체, 셸 검사 성공, 양쪽 앱 타깃에 신규 소스 등록.
|
||||
- **범위 밖:** 날짜 묶음 계산과 UI.
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `SodaLive/Sources/V2/Main/Chat/UserCreatorChat/Models/UserCreatorChatDatePresentation.swift` (향후 신규)
|
||||
- Modify: `SodaLive.xcodeproj/project.pbxproj` (신규 파일을 `SodaLive`와 `SodaLive-dev` Sources 양쪽에 등록)
|
||||
- Create: `work/tests/UserCreatorChatDatePresentationCheck.swift` (향후 신규)
|
||||
- Create: `work/tests/check-user-creator-chat-date-presentation.sh` (향후 신규)
|
||||
- Reference: `work/tests/check-gift-decoding-contract.sh` (임시 바이너리 컴파일 및 정리 패턴)
|
||||
|
||||
Runner는 `swiftc -swift-version 5`로 `Models/UserCreatorChatModels.swift`, `Models/UserCreatorChatDisplayMessage.swift`, formatter helper와 checker를 함께 컴파일하고 임시 실행 파일을 종료 시 정리한다.
|
||||
|
||||
**Interfaces:**
|
||||
|
||||
- Consumes: `Foundation.Locale`, `Foundation.Calendar`, `Foundation.TimeZone`, `Foundation.Date`.
|
||||
- Produces: `enum UserCreatorChatDatePresentation`의 `static func title(for date: Date, locale: Locale, calendar: Calendar, timeZone: TimeZone) -> String`.
|
||||
|
||||
- [x] **RED:** checker에 Gregorian UTC fixture `2026-10-16T12:00:00Z`의 한국어 정확 문자열 `2026년 10월 16일 금요일`, 영어 full weekday/month/day/year 포함, 일본어 연·월·일·요일 포함 검사를 작성한다. 오늘·어제·이전 연도도 모든 필드 유지, locale과 timeZone 독립성, 전달 calendar의 상이한 timeZone 정규화 검사를 포함한다.
|
||||
- [x] **RED 확인:** `sh work/tests/check-user-creator-chat-date-presentation.sh`를 실행한다. 최소 컴파일 가능한 임시 구현이 빈 문자열을 반환하도록 두어 한국어 exact-string assertion이 실패함을 확인한다. 컴파일/링크 오류는 의도한 RED가 아니다.
|
||||
- [x] **GREEN:** 위 인터페이스를 구현한다. formatter에 locale, calendar, timeZone을 지정하고 localized template `yMMMMdEEEE`를 사용한다.
|
||||
- [x] **GREEN 확인:** 같은 `sh work/tests/check-user-creator-chat-date-presentation.sh` 실행이 exit 0이고 `PASS:` 뒤에 통과 assertion 수와 `assertions`가 출력되는지 확인한다.
|
||||
- [x] **REFACTOR:** 이번 Task가 만든 중복만 정리한 후 checker를 다시 실행한다. 불필요한 공유 캐시·언어별 문자열은 만들지 않는다.
|
||||
|
||||
#### Task 1.2 날짜 행과 비고정 UI
|
||||
|
||||
**Goal `P1-T2`:** 현재 TEXT 표시 메시지로 날짜 구분 행을 파생하고 원래 메시지 목록과 함께 스크롤한다.
|
||||
|
||||
- **시작 조건:** `P1-T1` 완료, DATE-001·005~008 확정.
|
||||
- **완료 증거:** 행 경계/병합/전송 상태 테스트와 checker 통과, 수동 UI 검증 기록.
|
||||
- **범위 밖:** 정렬·pagination·ACK 구현 변경 및 비TEXT 표시.
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `SodaLive/Sources/V2/Main/Chat/UserCreatorChat/Models/UserCreatorChatDatePresentation.swift` (행 파생 계약 추가)
|
||||
- Create: `SodaLive/Sources/V2/Main/Chat/UserCreatorChat/Components/UserCreatorChatDateSeparatorView.swift` (향후 신규)
|
||||
- Modify: `SodaLive/Sources/V2/Main/Chat/UserCreatorChat/UserCreatorChatRoomView.swift`
|
||||
- Modify: `SodaLive.xcodeproj/project.pbxproj` (separator view를 두 앱 타깃 Sources에 등록)
|
||||
- Modify: `work/tests/UserCreatorChatDatePresentationCheck.swift` (행/스냅샷 검사 추가)
|
||||
|
||||
**Interfaces:**
|
||||
|
||||
- Consumes: `UserCreatorChatDatePresentation.title(for:locale:calendar:timeZone:)`, `[UserCreatorChatDisplayMessage]`.
|
||||
- Produces: `struct UserCreatorChatDatedMessage: Identifiable { let message: UserCreatorChatDisplayMessage; let separatorDate: Date?; var id: String { message.id } }` 및 `static func rows(from messages: [UserCreatorChatDisplayMessage], calendar: Calendar, timeZone: TimeZone) -> [UserCreatorChatDatedMessage]`.
|
||||
- Produces: `UserCreatorChatDateSeparatorView.init(title: String)`.
|
||||
|
||||
- [x] **RED:** checker에 빈 목록·단일 행·동일 날짜, 동일/상이 날짜 history prepend 후 첫 날짜 행 이동·중복 없음, VOICE-only 및 혼합 목록의 orphan 없음 검사를 추가한다. Gregorian calendar를 쓰고 서울 `2026-10-15T23:30Z`→`2026-10-16T00:30Z`는 구분 1개, 서울 `2026-10-16T14:30Z`→`15:30Z`는 2개, LA `2026-11-01T08:30Z`→`09:30Z`는 1개이며 다음 현지 자정에는 추가되는지 검증한다. pending→failed 날짜 유지, pending→server 동일/상이 날짜의 빈 항목 제거·중복 없음, 같은 timestamp의 순서·ID 유지와 기존 pending→server ID 변경도 스냅샷 assertion으로 먼저 작성한다.
|
||||
- [x] **RED 확인:** `sh work/tests/check-user-creator-chat-date-presentation.sh`를 실행한다. 행 생성의 컴파일 가능한 빈 결과 scaffold에서 단일 표시 메시지의 첫 `separatorDate` assertion이 실패함을 확인한다.
|
||||
- [x] **GREEN:** `rows(from:calendar:timeZone:)`는 TEXT 필터 후 순서와 message ID를 유지하며 첫 행과 현지 날짜 변경 행에만 날짜를 설정한다. RoomView는 환경 locale/timeZone 및 정규화 calendar로 행을 파생하고 `textMessages` projection 및 기존 first/last hook을 보존한다. separator를 `VStack(spacing: SodaSpacing.s16)`에서 해당 메시지 앞에 표시하며, 중앙 appFont12 regular gray400, 전체 폭, 다중 행, truncation 없음, VoiceOver 날짜 1회 읽기를 제공한다. 기존 메시지 `.id(message.id)`, 첫 메시지 `.onAppear -> loadMore`, content height preference, viewport auto-fill, 마지막 TEXT id 및 join의 `scrollTo(lastMessage.id, .bottom)`을 유지하고 날짜 행은 scroll/paging target으로 쓰지 않는다.
|
||||
- [x] **GREEN 확인:** checker 명령이 exit 0이고 RED 단계의 날짜 경계·병합·전송 상태·ID assertion이 모두 통과함을 확인한다. 스냅샷 검증을 실제 소켓 통합 검증으로 주장하지 않는다.
|
||||
- [x] **REFACTOR:** 신규 계산의 중복만 정리하고 checker를 다시 실행한다. 기존 메시지 item, ViewModel, 입력/헤더, 언어 서비스와 `DateExtension`은 수정하지 않는다.
|
||||
|
||||
P1-T2의 UI 증거는 원본 컴포넌트 네이티브 캡처 범위다. 실제 RoomView의 paging·ACK·스크롤 및 VoiceOver 상호작용은 아래 P1-GATE 미완료 항목으로 남긴다.
|
||||
|
||||
### 완료 조건
|
||||
|
||||
- [x] `P1-T1`, `P1-T2`의 구현 체크박스와 자동 검사·컴포넌트 렌더링 증거를 기록했다. 전체 사용자 흐름 승인은 Gate와 구분한다.
|
||||
- [x] DATE-001~008이 요구사항 추적 표의 Task 및 Gate 증거와 연결된다.
|
||||
- [x] `SodaLive` 및 `SodaLive-dev` 양쪽 타깃에 필요한 신규 Swift 소스가 포함된다.
|
||||
- [ ] P1-GATE의 실제 DM 흐름과 VoiceOver QA가 완료됐다.
|
||||
|
||||
### Phase 1 Gate
|
||||
|
||||
**Goal `P1-GATE`:** 자동 검사, 두 타깃 빌드와 대화 UI 수동 검증을 종합해 Phase 결과를 판정한다.
|
||||
|
||||
- **시작 조건:** `P1-T1`, `P1-T2` 완료.
|
||||
- **완료 증거:** 아래 명령 성공, 수동 QA 기록, 미해결 제한 명시.
|
||||
- **범위 밖:** 검증을 통과시키기 위한 테스트 완화 또는 관련 없는 코드 수정.
|
||||
- **TDD 예외 사유:** 기존 구현의 검증과 문서 기록만 수행하는 Gate다. **대체 검증 방법:** 아래 focused checker, 두 타깃 빌드와 수동 QA 결과를 요구사항별 완료 증거로 남긴다.
|
||||
|
||||
```bash
|
||||
sh work/tests/check-user-creator-chat-date-presentation.sh
|
||||
plutil -lint SodaLive.xcodeproj/project.pbxproj
|
||||
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build
|
||||
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
|
||||
```
|
||||
|
||||
**Expected:** checker assertion 전부 PASS, plist lint `OK`, 두 build 모두 exit code 0. 신규 소스의 양쪽 타깃 멤버십을 확인한다.
|
||||
|
||||
수동 검증:
|
||||
|
||||
- [ ] 앱 언어 ko/en/ja 및 System, 기기 언어와 앱 locale 불일치에서 앱 언어 표시와 기기 timeZone 날짜 경계가 각각 적용된다.
|
||||
- [ ] 초기 진입, join, incoming, send, ACK 후 바닥 위치, 첫 메시지 paging, 짧은 viewport auto-fill에서 구분선 높이가 스크롤 흐름을 깨지 않는다.
|
||||
- [ ] history 로드 전후 스크롤 연속성을 확인한다. 날짜 항목은 함께 스크롤되고 고정되지 않는다.
|
||||
- [ ] 사용 가능한 서버·테스트 데이터로 실제 전송 중·실패·ACK 후 날짜 변경을 확인한다. 스냅샷 검증과 구분해 기록하며 재현할 수 없으면 관련 QA를 미완료로 유지한다.
|
||||
- [ ] 좁은 폭, 큰 텍스트 크기, VoiceOver에서 잘림 없이 날짜 전체가 한 번 읽힌다.
|
||||
- [x] 구현 완료 후 변경 범위의 코드 리뷰 결과를 `docs/sample/sample-review.md`에 맞춰 `docs/20261016_유저_크리에이터_채팅_날짜_구분선/reviews/phase1-chat-date-separators.md`에 기록했다. 코드 범위 APPROVE와 전체 Gate BLOCKED를 구분한다.
|
||||
|
||||
## 요구사항 추적성
|
||||
|
||||
| 요구사항 | 실행 Task | 검증 증거 |
|
||||
|---|---|---|
|
||||
| DATE-001 | P1-T2 | 비어 있지 않은 표시 날짜 행과 UI 표시, Gate 초기 진입 |
|
||||
| DATE-002 | P1-T1, P1-T2 | ko/en/ja formatter, 앱 locale 환경 주입 및 불일치 QA |
|
||||
| DATE-003 | P1-T1, P1-T2 | calendar/timeZone 정규화, 서울 경계 및 LA DST 케이스 |
|
||||
| DATE-004 | P1-T1 | 연·월·일·요일, 오늘·어제·과거 연도 검사 |
|
||||
| DATE-005 | P1-T2 | 빈/단일/연속 날짜, history prepend 병합 스냅샷 |
|
||||
| DATE-006 | P1-T2, P1-GATE | 비고정 component와 스크롤 QA |
|
||||
| DATE-007 | P1-T2 | pending→failed 및 pending→server 날짜 변경/불변 스냅샷 |
|
||||
| DATE-008 | P1-T2 | TEXT 필터, VOICE-only 및 혼합 날짜 orphan 검사 |
|
||||
|
||||
## 실행 순서와 의존성
|
||||
|
||||
| 순서 | Goal | 선행조건 | 병행 가능 | 차단 시 다음 행동 |
|
||||
|---:|---|---|---|---|
|
||||
| 1 | `P1-T1` | 사용자 구현 착수 요청 | 아니요 | formatter assertion 또는 빌드 계약 문제를 기록하고 멈춤 |
|
||||
| 2 | `P1-T2` | `P1-T1` | 아니요 | 행 테스트 실패 원인을 수정 후 같은 Task 검증 재개 |
|
||||
| 3 | `P1-GATE` | 두 Task 완료 | 아니요 | 실패 소유 Task에 계획된 회귀 수정을 추가한 뒤 재검증 |
|
||||
|
||||
```text
|
||||
P1-T1 → P1-T2 → P1-GATE
|
||||
```
|
||||
|
||||
## 변경 금지와 중단 규칙
|
||||
|
||||
- PRD의 날짜 정책, 사용자 확정 사항, API 계약을 근거 없이 변경하지 않는다.
|
||||
- 날짜 sort/dedup, 별도 section state, synthetic ID, DTO, ViewModel 수정, sticky header, VOICE 표시를 추가하지 않는다.
|
||||
- 요구 범위 밖 리팩터링, dependency, 테스트 타깃을 만들지 않는다. `Pods/**`, `generated/**`, `build/**`도 수정하지 않는다.
|
||||
- 빌드 또는 테스트 오류를 감추기 위해 테스트를 삭제·완화하지 않는다. 소켓 연결 결과를 스냅샷 검증으로 과장하지 않는다.
|
||||
- fixture 또는 서버 응답이 필요한 라이브 QA를 사용할 수 없으면 수동 확인을 미완료로 기록한다. 서버/날짜 fixture를 위한 production mock이나 새 재시도 UI를 만들지 않는다.
|
||||
- 요구사항과 구현 경계가 충돌하거나 세 번의 서로 다른 수정 시도 뒤에도 차단되면 추가 코드를 작성하지 않고 원인, 관찰 결과, 남은 검증을 기록해 사용자 판단을 기다린다.
|
||||
- 리뷰에서 수정 필요가 확정되면 고치기 전에 리뷰 근거와 실행 가능한 회귀 수정 Task를 이 Phase에 추가한다.
|
||||
|
||||
## Progress
|
||||
|
||||
실제 문서 작업과 향후 구현 검증은 구분한다. 아래 체크박스는 미래 실행 항목이다.
|
||||
|
||||
### 문서 작성 실행, 2026-10-16
|
||||
|
||||
- 상태: 계획 작성 완료 / 구현 미착수.
|
||||
- 무엇을: 문서 정책, 계획 샘플, 현재 PRD를 읽고 본 계획의 Phase, 요구사항 매핑 및 PRD 링크/범위 결정 기록을 작성했다.
|
||||
- 어떻게: `docs/agent-guides/documentation-policy.md`, `docs/sample/sample-plan-task.md`, 현재 `prd.md`, DM View·모델·ViewModel, 앱 locale 주입, 기존 standalone checker와 Xcode 소스 등록 방식을 읽었다. `rg`로 두 앱 타깃, 최소 iOS 버전과 Swift 버전, XCTest 타깃 부재를 확인했다. 문서 작업 외의 빌드·테스트·앱 실행은 수행하지 않았다.
|
||||
- 문서 검증: 두 문서를 다시 읽고 `rg -n '^\| DATE-00[1-8]' docs/20261016_유저_크리에이터_채팅_날짜_구분선/plan-task.md`의 추적표 8행을 대조해 모든 요구사항이 Task와 검증 증거에 연결됨을 확인했다. 별도 문서 검색에서 미래 구현의 완료 표시·미치환 placeholder·행 끝 공백은 없었다. 두 구현 Task의 RED→GREEN→REFACTOR, Gate 예외 사유, PRD의 상대 링크와 DEC-013도 대조했다.
|
||||
- 미실행: `sh work/tests/check-user-creator-chat-date-presentation.sh`, `plutil -lint SodaLive.xcodeproj/project.pbxproj`, 두 xcodebuild 명령, 모든 UI 수동 QA. 계획 문서에 기재한 명령은 실행 증거가 아니다.
|
||||
- 남은 항목: 모든 구현 체크박스, 코드 및 Gate 검증.
|
||||
- 다음 행동: 구현은 별도 사용자 요청이 있을 때만 시작한다.
|
||||
|
||||
### P1-T1 실행 기록, 2026-10-16
|
||||
|
||||
- 상태: 완료. formatter, standalone checker 및 두 타깃 등록을 commit `e765c3c`에 기록했다.
|
||||
- RED: 컴파일 가능한 빈 문자열 scaffold에서 `expected '2026년 10월 16일 금요일', got ''`, exit 1을 확인했다.
|
||||
- GREEN: `sh work/tests/check-user-creator-chat-date-presentation.sh`를 구현자와 상위 에이전트가 각각 실행해 `PASS: 27 assertions`, exit 0을 확인했다. `plutil -lint SodaLive.xcodeproj/project.pbxproj`, `git diff --check` 통과.
|
||||
- 최초 GREEN의 일본어 기대 문자열에는 요일 앞 locale 공백이 누락되어 실패했다. locale 생성 결과를 확인하고 fixture만 교정했으며 필드 검사 요구를 완화하지 않았다.
|
||||
- 운영 타깃: `xcodebuild -quiet -workspace SodaLive.xcworkspace -scheme SodaLive -configuration Debug -destination 'generic/platform=iOS' -derivedDataPath "$RUN_TMP/standard-build" CODE_SIGNING_ALLOWED=NO build` exit 0. 기존 SDK 경고·중복 소스 등록 경고는 범위 밖으로 유지했다. T2 이후 다시 빌드한다.
|
||||
- 기존 회귀 기준선: 11개 기존 work/tests 스크립트, 지원 모드 포함 18회 실행 모두 exit 0. DM 화면 변경 후의 완료 증거와는 구분한다.
|
||||
- 커밋 검증: 한글 conventional subject 사전 검사와 커밋 직후 `work/scripts/check-commit-message-rules.sh` 통과. 실제 스크립트는 `--message`를 지원하지 않아 별도 subject 사전 검사 사용.
|
||||
- LSP 도구 없음: Foundation compiler 검사와 앱 빌드 결과를 기록하며 LSP 성공으로 대체 표기하지 않는다.
|
||||
|
||||
### P1-T2 및 Gate 실행 기록, 2026-10-16
|
||||
|
||||
- 구현: TEXT별 파생 날짜 행, 원본 message ID·순서 보존, 앱 locale·기기 timeZone 연결, 날짜 컴포넌트와 두 타깃 소스 등록을 완료했다. 기존 메시지 시각·ViewModel·API·ACK 로직은 수정하지 않았다.
|
||||
- 커밋: 화면 연결·파생 행·직접 회귀 테스트·두 타깃 등록을 `7a6d12e`로 기록했다. 한글 conventional subject 사전 검사와 커밋 직후 저장소 검사 스크립트가 통과했다.
|
||||
- RED: 빈 rows scaffold에서 `FAIL: single TEXT message must have its first separatorDate`를 확인했다. 이후 `sh work/tests/check-user-creator-chat-date-presentation.sh`는 `PASS: 113 assertions`, exit 0. 부모가 최종 소스로 재실행해 동일 결과를 확인했다.
|
||||
- 구현 판단: `textMessages`는 기존 필터를 유지하고 messageListView 내부에서 한 번 캡처해 rows를 계산한다. 계획의 rows→textMessages projection 대신 기존 scroll helper를 유지하는 최소 변경이다. custom caption은 `.custom(..., relativeTo: .caption)`, 영어는 시스템 `.caption`을 사용해 locale별 Dynamic Type을 지원한다.
|
||||
- 전체 빌드가 기존 앱 `Font` enum과 새 속성 타입 이름의 충돌을 검출했다. `SwiftUI.Font`로 한 줄 한정한 뒤 동일 명령 두 개가 모두 exit 0으로 통과했다. parse 검사는 이 충돌을 잡지 못했으므로 전체 컴파일 증거와 구별한다.
|
||||
- 운영 빌드: `xcodebuild -quiet -workspace SodaLive.xcworkspace -scheme SodaLive -configuration Debug -destination 'generic/platform=iOS' -derivedDataPath "$RUN_TMP/standard-build" CODE_SIGNING_ALLOWED=NO build` 성공.
|
||||
- 개발 빌드: `xcodebuild -quiet -workspace SodaLive.xcworkspace -scheme SodaLive-dev -configuration Debug -destination 'generic/platform=iOS Simulator' -derivedDataPath "$RUN_TMP/simulator-build" CODE_SIGNING_ALLOWED=NO build` 성공. 최초 전체 의존성 빌드는 600초 timeout 뒤 캐시를 이용해 재개했다.
|
||||
- 최종 회귀: 기존 11개 스크립트의 지원 모드 18회와 신규 날짜 검사 1회, 총 19회 모두 exit 0. 기존 테스트 파일은 수정하지 않았다. XCTest 타깃과 공식 lint 명령이 없어 이를 실행했다고 주장하지 않는다.
|
||||
- 네이티브 QA: 원본 컴포넌트와 폰트를 임시 앱에서 직접 컴파일해 iPhone SE3/iOS18.3의 ko/en/ja × normal/accessibility3 × light/dark 12장 촬영. 폰트 타입 수정 이후 재촬영하고 production 소스 9개 SHA-256 일치를 확인했다. 캡처는 [evidence](reviews/evidence/README.md)에 보존한다.
|
||||
- 앱 실행: 개발 앱을 소유된 시뮬레이터에 설치·실행해 홈 화면까지 확인했다. 인증된 DM 방은 확인하지 않았고 서버 데이터를 만들거나 개인 인증 정보를 읽지 않았다.
|
||||
- 코드 리뷰: Oracle이 코드 범위 APPROVE, 구체적인 잔여 코드 차단 사항 없음으로 판정했다. 최종 Gate는 실제 DM 및 VoiceOver QA 부족으로 BLOCKED다.
|
||||
- 최종 시각 재검토: 소스 해시 불일치와 시스템 추적 권한 팝업 가림이 있었던 중간 캡처는 사용하지 않았다. 최종 소스와 팝업 없는 환경으로 12장 재촬영 후 독립 검토자가 12/12 PASS, 잔여 시각 차단 사항 없음으로 판정했다. 이는 컴포넌트 한정 승인이며 전체 DM Gate 승인이 아니다.
|
||||
- 남은 항목: 실제 계정·roomId와 날짜가 다른 이력으로 paging·ACK·스크롤·System 언어를 검증하고 VoiceOver 읽기를 확인한다. LSP 도구 부재도 별도로 남긴다.
|
||||
- 정리: 최종 증거를 reviews/evidence에 보존하고 소유된 시뮬레이터 shutdown/delete, custom set 빈 목록과 하네스 프로세스 부재, 임시 루트·캐시·작업 메모 제거를 확인했다. 사용자 시뮬레이터·공유 캐시는 유지했다.
|
||||
|
||||
## Decision Log
|
||||
|
||||
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 |
|
||||
|---|---|---|---|---|---|
|
||||
| 2026-10-16 | DEC-PLAN-001 | 확정 | 현재 작업은 구현 계획 문서 작성과 PRD 연결 수정만 수행한다. 구현 체크박스는 전부 미완료로 두고 활성 Goal은 두지 않는다. | 사용자 명시 범위 및 최신 요청 | 전체 Phase, PRD DEC-013 |
|
||||
| 2026-10-16 | DEC-PLAN-002 | 에이전트 기술 설계 | Foundation formatter와 파생 행을 기존 DM 경계에서 구현하고 별도 XCTest target/dependency를 추가하지 않는다. | 기존 소스 확인과 저장소 checker 패턴에 따른 계획 판단, 사용자 별도 기술 승인 아님 | P1-T1, P1-T2 |
|
||||
| 2026-10-16 | DEC-PLAN-003 | 구현 판단 | separator는 중앙 appFont12 regular gray400, 전체 폭 다중 행, Dynamic Type 및 VoiceOver 지원으로 구현한다. | PRD가 위임한 시각 상세, 기존 앱 관례에 따른 계획 기준이며 사용자 확정 픽셀 사양은 아님 | P1-T2, P1-GATE |
|
||||
|
||||
## 발견된 문제와 제한
|
||||
|
||||
| ID | 상태 | 관찰 및 영향 | 처리 계획 |
|
||||
|---|---|---|---|
|
||||
| LIMIT-001 | 미검증 | Foundation checker는 날짜 계산과 formatter를 검사하지만 SwiftUI 레이아웃, 실제 viewport와 네트워크 ACK를 증명하지 않는다. | 빌드와 기기/시뮬레이터 수동 QA를 Gate에서 별도 기록한다. |
|
||||
| LIMIT-002 | 외부 조건 | 실제 incoming/history/ACK 흐름을 재현할 서버 계정 또는 날짜 fixture가 없을 수 있다. | 접근 불가 시 관련 QA를 미완료로 명시하고 production mock은 추가하지 않는다. |
|
||||
Reference in New Issue
Block a user