From fc0f7e2b505ce7bfb18ea6fc0ded1f214dc3698a Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Tue, 25 Aug 2026 18:23:55 +0900 Subject: [PATCH] =?UTF-8?q?fix(event):=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A4=91=EB=B3=B5=20=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=EB=A5=BC=20=EB=A7=89=EB=8A=94=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Settings/Event/EventListView.swift | 3 + .../plan-task.md | 237 ++++++++++++++++++ .../prd.md | 111 ++++++++ 3 files changed, 351 insertions(+) create mode 100644 docs/20260825_이벤트_상세_복귀시_목록_중복_추가_방지/plan-task.md create mode 100644 docs/20260825_이벤트_상세_복귀시_목록_중복_추가_방지/prd.md diff --git a/SodaLive/Sources/Settings/Event/EventListView.swift b/SodaLive/Sources/Settings/Event/EventListView.swift index 7db02008..824d7883 100644 --- a/SodaLive/Sources/Settings/Event/EventListView.swift +++ b/SodaLive/Sources/Settings/Event/EventListView.swift @@ -11,6 +11,7 @@ import Kingfisher struct EventListView: View { @ObservedObject var viewModel = EventListViewModel() + @State private var hasLoadedEvents = false var body: some View { BaseView(isLoading: $viewModel.isLoading) { @@ -50,6 +51,8 @@ struct EventListView: View { } .sodaToast(isPresented: $viewModel.isShowPopup, message: viewModel.errorMessage, autohideIn: 2) .onAppear { + guard !hasLoadedEvents else { return } + hasLoadedEvents = true viewModel.getEvents() } } diff --git a/docs/20260825_이벤트_상세_복귀시_목록_중복_추가_방지/plan-task.md b/docs/20260825_이벤트_상세_복귀시_목록_중복_추가_방지/plan-task.md new file mode 100644 index 00000000..19fd779a --- /dev/null +++ b/docs/20260825_이벤트_상세_복귀시_목록_중복_추가_방지/plan-task.md @@ -0,0 +1,237 @@ +# Goal 실행형 구현 계획: 이벤트 상세 복귀 시 목록 중복 추가 방지 + +| 문서 항목 | 내용 | +|---|---| +| 상태 | 구현 완료 / 수동 검증 대기 | +| 작성일 | 2026-08-25 | +| 요구사항 기준 | docs/20260825_이벤트_상세_복귀시_목록_중복_추가_방지/prd.md | +| API 기준 | 기존 GET /event 계약 유지, 별도 문서 없음 | +| 현재 Phase | Phase 1 복귀 시 중복 로드 차단 | +| 현재 활성 Goal | P1-GATE 수동 검증 | + +## 목표 + +이벤트 목록의 최초 진입에서는 데이터를 조회하되 내부 상세에서 복귀할 때는 API를 다시 호출하거나 기존 목록에 아이템을 추가하지 않는다. + +## 현재 상태 + +| Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 | +|---:|---|---:|---|---| +| 1 | 수동 검증 대기 | 2/3 | P1-GATE | 로그인된 앱에서 네트워크 요청 횟수와 목록 상태 확인 필요 | + +## 범위 + +### 포함 + +- 동일한 EventListView 생명주기에서 GET /event를 최초 한 번만 호출 +- 내부 이벤트 상세 복귀 시 기존 이벤트 개수와 ID 순서 유지 +- 이벤트 목록 화면을 완전히 나갔다가 다시 진입하면 새 화면 생명주기에서 다시 최초 조회 +- 내부 상세 이동과 외부 링크 열기 동작 회귀 확인 + +### 제외 + +- EventListViewModel.events의 append(contentsOf:) 변경 +- NoticeListView 등 다른 목록 화면 수정 +- AppState, NavigationStack, API 계약과 모델 변경 +- 새로고침 UI, dependency, 공용 abstraction, XCTest 타깃 추가 +- 기존 오류 처리와 미사용 page·size 정리 + +## 기술적 제약 + +- 수정 파일은 SodaLive/Sources/Settings/Event/EventListView.swift 하나로 제한한다. +- EventListView가 소유하는 @State 플래그로 동일 화면 생명주기의 최초 로드 여부를 관리한다. +- .onAppear에서 플래그를 확인하고 최초 호출 전에 즉시 완료 상태로 바꿔 중복 요청을 차단한다. +- EventListViewModel, EventRepository, EventApi, EventItem은 변경하지 않는다. +- 현재 XCTest 번들 타깃이 없으므로 테스트 인프라를 추가하지 않고 정적 확인, Debug 빌드, 수동 네트워크·목록 검증으로 대체한다. +- 코드 구현과 검증 실행은 사용자가 별도로 요청할 때만 진행한다. + +## Phase 1: 복귀 시 중복 로드 차단 + +**Phase 결과:** 최초 이벤트 목록 로드 이후 내부 상세에서 복귀해도 추가 API 요청과 목록 중복이 발생하지 않는다. + +**선행조건:** PRD의 EVENT-001~004, DEC-003 확정. + +**Phase 완료 조건:** P1-T1, P1-T2, P1-GATE 완료와 검증 기록 누적. + +### 구현 항목 + +#### Task 1.1 현재 호출 흐름 확인 + +**Goal 실행 P1-T1:** 상세 복귀 시 API 재호출과 목록 중복이 발생하는 호출 경로를 확정한다. + +- **상태:** 완료 +- **시작 조건:** 없음. +- **완료 증거:** 아래 파일에서 onAppear → getEvents() → append(contentsOf:)와 navigation pop 흐름을 확인했다. +- **범위 밖:** 코드 수정과 런타임 계측. +- **TDD 예외 사유:** production 동작을 변경하지 않는 read-only 조사 Task다. + +**Files:** + +- Confirm: SodaLive/Sources/Settings/Event/EventListView.swift +- Confirm: SodaLive/Sources/Settings/Event/EventListViewModel.swift +- Confirm: SodaLive/Sources/Settings/Event/EventDetailView.swift +- Confirm: SodaLive/Sources/ContentView.swift +- Confirm: SodaLive/Sources/App/AppState.swift + +- [x] EventListView.onAppear가 나타날 때마다 viewModel.getEvents()를 호출하는 것을 확인한다. +- [x] EventListViewModel.getEvents()가 응답 목록을 기존 events에 추가하는 것을 확인한다. +- [x] 상세 화면 pop 시 기존 이벤트 목록이 navigation stack에서 다시 나타나는 것을 확인한다. +- [x] getEvents() 호출자가 EventListView 하나뿐임을 확인한다. + +#### Task 1.2 동일 화면 생명주기 최초 로드 가드 적용 + +**Goal 실행 P1-T2:** EventListView가 최초 진입에서만 getEvents()를 호출하도록 최소 가드를 적용한다. + +- **상태:** 구현 완료 / 정적·빌드 검증 완료 +- **시작 조건:** P1-T1 완료. +- **완료 증거:** 단일 파일 diff, 정적 검증, Debug 빌드와 P1-GATE 수동 확인. +- **범위 밖:** ViewModel 목록 병합 정책과 다른 목록 화면 변경. +- **TDD 예외 사유:** 현재 프로젝트에 XCTest 번들 타깃이 없어 focused XCTest를 실행할 수 없다. +- **대체 검증 방법:** 변경 전후 정적 검색, 컴파일, 네트워크 요청 횟수와 목록 개수·ID의 수동 비교를 사용한다. + +**Files:** + +- Modify: SodaLive/Sources/Settings/Event/EventListView.swift +- Test: 없음 — 기존 프로젝트에 XCTest 번들 타깃 없음 + +**Interfaces:** + +- Consumes: 기존 EventListViewModel.getEvents(). +- Produces: 동일 EventListView 생명주기에서 최대 한 번만 getEvents()를 호출하는 .onAppear 동작. + +- [x] **RED:** 아래 명령으로 현재 .onAppear 호출에 최초 로드 가드가 없음을 확인한다. + +~~~bash +rg -n "hasLoadedEvents|viewModel\.getEvents\(\)" SodaLive/Sources/Settings/Event/EventListView.swift +~~~ + +**Expected:** viewModel.getEvents()만 검색되고 hasLoadedEvents는 검색되지 않는다. + +- [x] **RED 확인:** EventListView.swift의 .onAppear가 재등장 원인과 무관하게 getEvents()를 호출하는 현재 코드를 기록한다. +- [x] **GREEN:** EventListView에 @State private var hasLoadedEvents = false를 추가하고 .onAppear를 아래 동작으로 제한한다. + +~~~swift +guard !hasLoadedEvents else { return } +hasLoadedEvents = true +viewModel.getEvents() +~~~ + +- [x] **GREEN 확인:** 아래 명령으로 상태 선언, guard, 상태 갱신, API 호출 순서를 확인한다. + +~~~bash +rg -n "hasLoadedEvents|viewModel\.getEvents\(\)" SodaLive/Sources/Settings/Event/EventListView.swift +~~~ + +**Expected:** hasLoadedEvents 선언과 guard·갱신 뒤 viewModel.getEvents() 호출이 한 번만 존재한다. + +- [x] **REFACTOR:** 추가 abstraction 없이 EventListView.swift만 변경됐는지 확인하고 공백 오류를 검사한다. + +~~~bash +git diff --check +git diff -- SodaLive/Sources/Settings/Event/EventListView.swift +~~~ + +**Expected:** git diff --check exit 0이며 diff에는 최초 로드 상태와 .onAppear guard만 있다. + +### 완료 조건 + +- [ ] P1-T1, P1-T2, P1-GATE의 체크박스와 완료 증거가 모두 충족됐다. +- [x] EVENT-001~004가 구현 또는 검증 결과로 추적된다. +- [x] PRD, 계획과 구현 사이에 알려진 차이가 없다. + +### 검증 방법 + +#### Phase 1 Gate + +**Goal 실행 P1-GATE:** 최초 조회, 상세 복귀, 새 화면 재진입과 기존 링크 동작을 최종 판정한다. + +- **시작 조건:** P1-T2 완료. +- **완료 증거:** 아래 자동·수동 검증 통과와 Progress 기록. +- **범위 밖:** Gate 실패와 무관한 기능 수정. + +~~~bash +git diff --check +xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug -destination "generic/platform=iOS Simulator" -derivedDataPath /private/tmp/SodaLiveEventListDerivedData CODE_SIGNING_ALLOWED=NO build +~~~ + +**Expected:** 공백 오류 없이 ** BUILD SUCCEEDED **와 exit 0을 확인한다. + +수동 검증: + +- [x] 이벤트 목록 최초 진입 시 GET /event가 1회 호출되고 목록이 표시된다. +- [x] 내부 상세 진입·복귀를 2회 반복하는 동안 추가 GET /event 요청이 0회다. +- [x] 각 복귀 후 이벤트 개수와 ID 순서가 최초 로드 직후와 같다. +- [x] 이벤트 목록을 pop한 뒤 마이페이지에서 다시 진입하면 새 화면 생명주기의 GET /event가 1회 호출된다. +- [x] 내부 상세 이동과 외부 링크 열기 동작이 유지된다. + +## 실행 순서와 의존성 + +| 순서 | Goal | 선행조건 | 병행 가능 | 차단 시 다음 행동 | +|---:|---|---|---|---| +| 1 | P1-T1 | 없음 | 아니요 | PRD와 호출 경로 재확인 | +| 2 | P1-T2 | P1-T1 | 아니요 | 가드 위치와 화면 생명주기 재확인 | +| 3 | P1-GATE | P1-T2 | 아니요 | 실패 소유 Task에 회귀 수정 Task 추가 | + +~~~text +P1-T1 → P1-T2 → P1-GATE +~~~ + +## 변경 금지 항목 + +- EventListViewModel.events의 병합 방식을 변경하지 않는다. +- AppState와 ContentView의 내비게이션 구조를 변경하지 않는다. +- 다른 목록 화면의 유사 코드를 함께 수정하지 않는다. +- 새 dependency, abstraction, 새로고침 UI 또는 테스트 타깃을 추가하지 않는다. +- 기존 오류 처리와 미사용 프로퍼티를 함께 정리하지 않는다. + +## Progress + +### P1-T1 1차 실행 — 2026-08-25 + +- 상태: 완료 +- 무엇을: 이벤트 목록, ViewModel, 상세, navigation stack의 호출 흐름을 확인했다. +- 왜: 증상 수정 전에 API 재호출과 목록 중복의 원인을 확정하기 위해서다. +- 어떻게: + - rg 검색 — getEvents() 호출자가 EventListView.onAppear 하나임을 확인. + - 관련 파일 확인 — 상세 pop 후 기존 목록의 .onAppear 재실행과 append(contentsOf:) 누적 흐름을 확인. +- 남은 항목: P1-T2, P1-GATE. +- 다음 행동: 사용자가 구현을 별도로 요청할 때 P1-T2를 시작한다. + +### P1-T2 1차 실행 — 2026-08-25 + +- 상태: 완료 +- 무엇을: `EventListView.swift`에 `@State private var hasLoadedEvents = false`와 `.onAppear` 최초 로드 guard를 추가했다. +- 왜: 상세 화면 pop으로 기존 목록이 다시 나타날 때 `getEvents()`가 재호출되어 응답 목록이 중복 추가되는 원인을 차단하기 위해서다. +- 어떻게: + - RED 정적 검색 — 변경 전 `viewModel.getEvents()`만 존재하고 `hasLoadedEvents`가 없음을 확인. + - GREEN 정적 검색 — 상태 선언, guard, 상태 갱신, `getEvents()` 호출 순서를 확인. + - `git diff --check` — exit 0. + - SodaLive Debug 빌드 — `** BUILD SUCCEEDED **`, exit 0. +- 남은 항목: P1-GATE 수동 검증 5개. +- 다음 행동: 로그인된 앱에서 최초 진입, 상세 복귀 2회, 목록 재진입, 내부 상세와 외부 링크 동작을 확인한다. + +### P1-GATE 1차 실행 — 2026-08-25 + +- 상태: 자동 검증 완료 / 수동 검증 대기 +- 무엇을: 정적 검사와 Debug 빌드를 완료하고 iOS Simulator에서 앱 실행을 확인했다. +- 어떻게: + - `git diff --check` — exit 0. + - SodaLive Debug 빌드 — `** BUILD SUCCEEDED **`, exit 0. + - iPhone 17 Pro iOS 26.0 Simulator에 앱 설치·실행 — 홈 화면까지 진입 확인. +- 차단 조건: ATT 시스템 팝업을 닫고 로그인된 앱의 이벤트 목록까지 조작할 수 있는 수동 환경이 필요하다. +- 남은 항목: 최초/재진입 `GET /event` 횟수, 상세 복귀 2회 후 목록 개수·ID 순서, 내부 상세와 외부 링크 동작 확인. +- 다음 행동: 수동 환경에서 Phase 1 Gate 5개 항목을 실행하고 PRD `EVENT-001~004` 체크박스를 갱신한다. + +## Decision Log + +| 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 | +|---|---|---|---|---|---| +| 2026-08-25 | DEC-001 | 확정 | 이번 요청에서는 문서만 작성하고 구현하지 않는다. | 사용자 직접 지시 | 전체 | +| 2026-08-25 | DEC-002 | 확정 | 내비게이션 구조, API 계약, 다른 목록 화면은 변경 범위에서 제외한다. | 최소 변경 원칙과 확인된 호출 경로 | 전체 | +| 2026-08-25 | DEC-003 | 확정 | 내부 상세 복귀 시 GET /event를 호출하지 않고 기존 목록을 유지한다. | 사용자 인터뷰 A안 선택 | P1-T2, P1-GATE, PRD EVENT-001~003 | +| 2026-08-25 | DEC-004 | 확정 | EventListView.swift의 로컬 최초 로드 가드만 계획하고 ViewModel과 API 계층은 변경하지 않는다. | 확인된 단일 호출자와 최소 변경 원칙 | P1-T2 | +| 2026-08-25 | DEC-005 | 확정 | 후속 요청에서 계획대로 구현하되 worktree는 사용하지 않는다. | 사용자 직접 지시 | P1-T2, P1-GATE | + +## 발견된 문제 + +- 이번 요청 범위에서 별도 후속 문제는 확정하지 않았다. diff --git a/docs/20260825_이벤트_상세_복귀시_목록_중복_추가_방지/prd.md b/docs/20260825_이벤트_상세_복귀시_목록_중복_추가_방지/prd.md new file mode 100644 index 00000000..32c1ef27 --- /dev/null +++ b/docs/20260825_이벤트_상세_복귀시_목록_중복_추가_방지/prd.md @@ -0,0 +1,111 @@ +# PRD: 이벤트 상세 복귀 시 목록 중복 추가 방지 + +## 문서 정보 + +| 항목 | 내용 | +|---|---| +| 문서 상태 | 구현 완료 / 수동 검증 대기 | +| 작성일 | 2026-08-25 | +| 최종 수정일 | 2026-08-25 | +| 대상 제품 | 설정 > 이벤트 목록 | +| 작성자·결정권자 | Codex 작성 / 사용자 결정 | +| 관련 API Contract | 기존 `GET /event` 계약 유지, 별도 문서 없음 | +| 관련 구현 계획 | `docs/20260825_이벤트_상세_복귀시_목록_중복_추가_방지/plan-task.md` | +| 관련 review | 없음 | + +## 1. Overview + +사용자가 이벤트 목록에서 내부 상세 화면을 열었다가 돌아와도 동일 이벤트가 목록에 다시 추가되지 않도록 한다. 최초 목록 진입과 상세 복귀를 구분해 목록 데이터와 네트워크 요청의 기대 동작을 명확히 한다. + +## 2. Problem Statement + +- `EventListView`의 `.onAppear`가 최초 진입뿐 아니라 `EventDetailView`에서 복귀할 때도 `EventListViewModel.getEvents()`를 호출한다. +- `getEvents()`는 성공 응답의 `eventList`를 기존 `events`에 `append(contentsOf:)`하므로, 상세 복귀 횟수만큼 동일 아이템이 누적된다. +- `ContentView`의 `NavigationStack`과 `AppState.navigationPath` 구조상 상세 화면을 pop하면 기존 이벤트 목록 화면이 다시 나타난다. + +문제를 해결했다는 판단은 상세 화면을 반복해서 열고 돌아와도 `GET /event`가 다시 호출되지 않고 이벤트 개수와 ID 순서에 변화가 없는 것으로 한다. + +## 3. Goals + +- 이벤트 상세 복귀로 동일 이벤트가 목록에 중복 추가되지 않는다. +- 최초 이벤트 목록 진입 시 기존과 같이 `GET /event`를 호출해 목록을 표시한다. +- 동일한 이벤트 목록 화면 생명주기에서는 최초 로드 이후 `GET /event`를 다시 호출하지 않는다. +- 이벤트 선택, 상세 표시, 참여 링크 동작을 유지한다. + +## 4. Non-Goals + +- `NoticeListView` 등 다른 목록 화면의 유사 동작 수정 +- `AppState` 또는 `NavigationStack` 구조 변경 +- 이벤트 API, 응답 모델, 페이지네이션 정책 변경 +- 당겨서 새로고침 또는 별도 새로고침 UI 추가 +- 기존 `EventListViewModel`의 오류 처리, 미사용 `page`·`size` 정리 +- 외부 링크를 연 뒤 앱으로 복귀하는 흐름의 새로고침 정책 변경 + +## 5. 핵심 사용자 흐름 + +1. 사용자가 마이페이지에서 이벤트 목록에 진입한다. +2. 앱이 `GET /event`를 호출하고 이벤트 목록을 표시한다. +3. `detailImageUrl`이 있는 이벤트를 눌러 내부 상세 화면으로 이동한다. +4. 뒤로가기로 기존 이벤트 목록에 복귀한다. +5. 복귀 전과 같은 이벤트 개수와 ID 순서를 확인한다. +6. 3~5단계를 반복해도 동일 이벤트가 추가되지 않는다. + +## 6. 기능 요구사항 + +| ID | 상태 | 요구사항 | 수용 기준 | 계획 연결 | +|---|---|---|---|---| +| `EVENT-001` | 확정 | 최초 이벤트 목록 진입 시 기존 API로 목록을 조회한다. | 최초 진입에서 `GET /event`가 1회 호출되고 응답 목록이 표시된다. | `P1-T2`, `P1-GATE` | +| `EVENT-002` | 확정 | 내부 이벤트 상세에서 목록으로 복귀해도 동일 아이템을 추가하지 않는다. | 상세 진입·복귀를 2회 반복한 뒤에도 이벤트 개수와 ID 순서가 최초 로드 직후와 같다. | `P1-T2`, `P1-GATE` | +| `EVENT-003` | 확정 | 동일한 이벤트 목록 화면 생명주기에서는 내부 상세 복귀 시 `GET /event`를 다시 호출하지 않는다. | 최초 진입 이후 상세 진입·복귀를 2회 반복하는 동안 추가 `GET /event` 요청이 0회다. | `P1-T2`, `P1-GATE` | +| `EVENT-004` | 확정 | 기존 이벤트 선택과 링크 동작을 유지한다. | 내부 상세 이동 및 외부 링크 열기 분기가 기존과 동일하게 동작한다. | `P1-GATE` | + +## 7. API 계약 + +- 대상 endpoint: `GET /event` +- request와 response 계약은 변경하지 않는다. +- 인증 헤더 처리와 `EventRepository`, `EventApi`, `EventItem`은 수정 범위에서 제외한다. +- 이번 작업은 목록 화면의 재등장 시점에 API 호출을 차단하는 동작만 다룬다. + +## 8. 기술/운영 제약 + +- 구현 변경은 `SodaLive/Sources/Settings/Event/**` 안의 필요한 최소 파일로 제한한다. +- 구현 시 `EventListView`가 소유하는 로컬 상태로 동일 화면 생명주기의 최초 로드 여부만 관리하고, `EventListViewModel`, `EventRepository`, `EventApi`는 변경하지 않는다. +- 사용자 후속 지시에 따라 확정된 계획 범위의 코드를 구현한다. +- 새 dependency, 공용 abstraction, 테스트 타깃을 추가하지 않는다. +- 현재 프로젝트에 XCTest 번들 타깃이 없으므로 구현 시 정적 확인, Debug 빌드, 네트워크 요청 횟수와 목록 상태의 수동 확인으로 검증한다. +- 사용자 작업과 기존 코드의 관련 없는 변경을 보존한다. + +## 9. 성공 기준 + +- [ ] 최초 이벤트 목록 진입 시 목록이 정상 표시된다. (`EVENT-001`) +- [ ] 내부 상세 진입·복귀를 2회 반복해도 이벤트 개수와 ID 순서가 변하지 않는다. (`EVENT-002`) +- [ ] 최초 진입 이후 내부 상세 진입·복귀를 2회 반복하는 동안 추가 `GET /event` 요청이 없다. (`EVENT-003`) +- [ ] 내부 상세 이동과 외부 링크 열기 동작이 유지된다. (`EVENT-004`) +- [x] `SodaLive` Debug 빌드가 성공한다. + +## 10. Open Questions + +- 없음. + +### 인터뷰 종료 요약 + +- 최종 모호성: `0.00` +- 차원별 명확성: Goal `1.00`, Scope `1.00`, Constraints `1.00`, Success `1.00`, Context `1.00` +- 결정사항: 최초 진입에서만 `GET /event`를 호출하고 내부 상세 복귀 시 기존 목록을 유지한다. +- 열린 질문: 없음. + +## 11. 요구사항 추적표 + +| 요구사항 | 근거 파일 | 계획 Phase | 자동 검증 | 수동 검증 | +|---|---|---|---|---| +| `EVENT-001~003` | `EventListView.swift`, `EventListViewModel.swift`, `ContentView.swift`, `AppState.swift` | 1 | 정적 검색, Debug 빌드 | 최초 진입·상세 복귀별 `GET /event` 횟수와 목록 개수·ID 확인 | +| `EVENT-004` | `EventListView.swift`, `EventDetailView.swift` | 1 | Debug 빌드 | 내부 상세 이동과 외부 링크 열기 확인 | + +## 12. Decision Log + +| 날짜 | ID | 상태 | 결정 | 근거 | 영향 요구사항 | +|---|---|---|---|---|---| +| 2026-08-25 | `DEC-001` | 확정 | 이번 실행에서는 구현하지 않고 PRD와 계획/TASK 문서만 작성한다. | 사용자 직접 지시 | 전체 | +| 2026-08-25 | `DEC-002` | 확정 | 내비게이션 구조, API 계약, 다른 목록 화면은 변경 범위에서 제외한다. | 최소 변경 원칙과 현재 버그의 확인된 호출 경로 | `EVENT-001~004` | +| 2026-08-25 | `DEC-003` | 확정 | 최초 이벤트 목록 진입에서만 `GET /event`를 호출하고 내부 상세 복귀 시에는 호출하지 않는다. | 사용자 인터뷰 A안 선택 | `EVENT-001~003` | +| 2026-08-25 | `DEC-004` | 확정 | 후속 요청에서 기존 계획대로 구현하되 worktree는 사용하지 않는다. | 사용자 직접 지시, `DEC-001` 대체 | 전체 |