Skip to content

MSG-489 feat: [웹] AI 경로추천 — 지역 자동 이동·1km 축척 고정·2차 자동 재요청·출발지 자동 판정 - #105

Merged
gyuhochoime merged 6 commits into
developfrom
feat/ai-route-mentioned-area-auto-move-origin
Aug 29, 2026
Merged

MSG-489 feat: [웹] AI 경로추천 — 지역 자동 이동·1km 축척 고정·2차 자동 재요청·출발지 자동 판정#105
gyuhochoime merged 6 commits into
developfrom
feat/ai-route-mentioned-area-auto-move-origin

Conversation

@gyuhochoime

@gyuhochoime gyuhochoime commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

🎫 관련 티켓

MSG-488(PR #104) 위에서 갈라졌고, MSG-490(walk-paths 실경로)과 병렬로 진행됐습니다. docs/spec/MSG-488.md §4-2 파일 소유권 표의 489열만 접촉해 490 소유 5파일 diff 0줄입니다(머지 순서 무관).

📌 작업 내용

1. mentionedArea 자동 이동 + 2차 자동 재요청

  • 응답에 mentionedArea가 있으면 확인 없이 centerLat/Lng로 이동 + 줌을 1km 단(MAP_SCALE_1KM_ZOOM)으로 고정 + 토스트("서면으로 이동했어요 / 지도 범위 약 1km 기준으로 동선을 짜요") 후 새 뷰포트로 2차 요청 1회. 2차 동안 패널은 로딩이고 1차 결과는 그리지 않습니다. kind(MOVE/ZOOM_OUT)는 분기하지 않습니다 — 생성 타입이 유니언이 아니라 raw string이라 미지 값이 뚫립니다.
  • 착수 전 실측으로 설계가 갈렸습니다: 2차 자동 재요청은 서버 10초 rate limit의 예외가 아닙니다(로그인 세션 연속 호출 → 두 번째 429/14429, 요청 시작 +11s는 200, Retry-After 없음). 그래서 1차 requestedAt 기준 잔여 시간을 기다린 뒤 발사하고, 대기 동안 로딩을 유지합니다.
  • Figma 15666:12621 토스트의 "약 2km"는 1km로 정정해 구현했습니다(사용자 확정).

2. 출발지(origin) 자동 판정

  • 현위치가 현재 뷰포트 안이면 요청에 origin을 싣고 입력 카드에 "현재 위치에서 출발" 행을 띄웁니다. 결과 화면에서는 그 행이 사라지고 버튼 문구 "현재 위치에서 다시 짜기"로만 알립니다(Figma 15666:13139).
  • shared/geolocation.getCurrentPosition()권한 거부를 삼키고 서면 좌표를 돌려주는 문제로 "거부면 origin 생략"을 구조적으로 만족할 수 없어, 가산 export getCurrentPositionOrNull()을 추가했습니다(기존 소비 3곳 무수정).

3. 추천 시 항상 1km 축척 고정 (사용자 지시)

  • 제출하면 항상 현재 중심 기준 1km로 맞춘 뒤 요청합니다("다시 짜기"도 동일). 이미 1km면 줌 명령 없이 즉시 발사 — 같은 값 zoomToidle을 내지 않아 요청이 영영 안 나갑니다.
  • 기존 "뷰포트 한 변 0.5도 초과일 때만 정규화" 규칙은 여기에 흡수됐고, 서버 상한 가드는 발사 직전 가드(exceedsViewportSpan)로 위치를 옮겨 부활시켰습니다. 발사 4경로(즉시·정규화 대기·상한 만료·2차)가 모두 send() 한 지점을 지납니다.
  • 결과 도착 후에는 재정규화가 없어 자유롭게 확대할 수 있습니다.

4. /ai-route에서 집계 클러스터 마커 숨김 (사용자 지시)

  • gateRouteClusters(clusters, pathname)MapShell의 파생 결과에만 적용합니다(집계 훅 3종 무수정 → 홈·도감 영향 0). 집계층은 줌 16(100m) 미만에서만 존재하므로 경로 게이트 하나로 "100m보다 넓어지면 안 보이게"가 충족됩니다.

5. 문구·하네스

  • 14429 안내를 서버와 같은 문장 **"요청이 너무 잦습니다. 잠시 후 다시 시도해주세요"**로 교체(FE 고정 문구 상수 — 서버 응답 문자열을 렌더하지는 않습니다). 종전 문구는 사유를 알려주지 않아 재시도를 연속으로 누른 사용자가 같은 안내만 반복해서 봤습니다.
  • 웹 실동작 검증(page-verification 3-A)에 탭 가시성·rAF 프레임률 전제 조건을 신설했습니다 — 아래 "발견한 함정" 참조.

✅ 체크리스트

  • pnpm lint / pnpm typecheck / pnpm --filter web test run 통과
  • 수용 기준 검증 완료 (아래 첨부)
  • 문서 업데이트 (docs/STATUS.md · docs/spec/MSG-489.md · docs/decisions/DECISIONS.md)

🔍 검증 요약

자동 검증 (루트 풀 게이트, 전건 통과)

게이트 결과
pnpm test web 1481 + mobile·packages 982 통과
pnpm typecheck 통과
pnpm lint (oxlint) 경고 0 · 신규 비활성 주석 0
pnpm format:check 통과
pnpm check:duplication 통과 (재등재 감사 — 추가 패밀리 전건이 변경 파일 포함, 무관 흡수 0)
openapi-ts 드리프트 0
pnpm --filter web build 성공

수용 기준

로직 L1~L25(vitest) · 화면 S1~S12(브라우저 실동작 + Figma 대조) 전건 통과, 확인불가 0.

실동작 실측 (실 API · 로그인 세션 · 전면 탭 121fps)

항목 실측
정규화(방해 없음) 8km 제출 → 315ms에 축척 1km로 발사, 200·8곳
정규화(계속 방해) 16km 제출 후 5초간 축소 연타(→128km) → 3,088ms 종결, recommend 0건, 로딩 아닌 안내+"다시 시도"
복구 "다시 시도" → 851ms에 1km 발사, 200
2차 대기(드래그 3회 방해) 1차 +226ms → 토스트 "서면으로 이동했어요"(2,912ms) → 2차 +10,718ms(요청 시각 기준 10,492ms), 200·8곳 — 연기 없음
요청 범위 recommend 전건 span 0.1211° × 0.2966° ≤ 0.5°, 비200은 의도적 14429 1건뿐
14429 문구 화면에 "요청이 너무 잦습니다. 잠시 후 다시 시도해주세요"
origin 재판정 해운대 뷰포트 1차 origin 없음 → 자동 이동 후 2차 origin 있음, 버튼 "현재 위치에서 다시 짜기"
클러스터 /ai-route 줌 10·12·13·16 전부 0개 / 같은 뷰포트 홈은 줌 12에서 3개(종전대로)
기타 2차 로딩 중 1차 잔상 0 · 결과 후 확대 시 추가 호출 0 · Figma 편차 0 · 콘솔 에러 0

codex 리뷰(push 전 게이트) 지적 2건 — 모두 실제 결함, 반영 완료

  • [P1] 2차 mutation에 onLoginRequired가 없어, 1차 성공과 지연된 2차 사이 세션 만료 시 401에도 로그인 모달이 뜨지 않았습니다. (문서에 "알려진 한계"로 적혀 있었으나 결함이었습니다)
  • [P2] 목표 뷰포트 미도달 상태에서 bounds·zoom이 갱신될 때마다 상한 타이머가 처음부터 다시 걸려, 계속 패닝하면 2차가 무한정 연기되고 패널이 로딩에 갇혔습니다 — "영구 로딩 금지" 계약 정면 위반. 지적은 2차만 짚었지만 정규화 대기도 같은 구조여서 함께 고쳤습니다. 마감을 사이클당 1회만 계산하는 순수 함수(advanceSettleDeadline)로 교체했습니다.

📸 스크린샷

_workspace/MSG-489/screenshots/ (자동 이동 결과 + 클러스터 0개 / 홈 클러스터 대조쌍)

💡 추가 논의할 사항

검증에서 발견한 함정 — 계측이 조용히 35배 틀렸습니다

브라우저 자동화 탭이 백그라운드였고 rAF가 0.5~1fps로 정지해 있었습니다. 같은 줌 전이가 숨은 탭 11,162ms · 전면 탭 316ms로 측정됐고, 그 값이 "정규화 대기 14.2초 무피드백"이라는 리포트 문장과 후속 티켓 권장까지 만들어 냈습니다. 더 나쁜 건 동작 자체가 달라진다는 점입니다 — 숨은 탭에서는 네이버 지도가 idle을 내지 않아 뷰포트 스토어가 옛 값에 머물고, 그 상태로 상한이 발화해 정규화되지 않은 범위(1.9°×4.7°)로 요청이 나가 서버 400(14401)이 재현됐습니다. 오염된 계측이 실제 결함을 가리고 있었습니다. 그래서 계측 신뢰성을 검증자의 주의력이 아니라 절차의 전제 조건으로 내렸습니다(8368b1f).

선행 문서 정정

MSG-488 §11 / STATUS의 "gateFillCellszoom<16에서 채움 셀을 버려 격자 초록 틴트가 안 보인다 → MSG-489의 1km 고정이 해소한다"는 두 절 다 사실과 다릅니다. 실제 게이트는 색 있는 셀을 남기는 필터이고 AI 경로 셀은 전부 색을 가져 통과합니다(zoom 13에서 폴리곤 8개 DOM 실측). 1km(13)는 어차피 임계(16) 아래입니다. 저줌에서 육안으로 안 보이는 실제 원인은 셀이 6~7px이라 28px 마커에 가리는 것이고, 결과 후 확대하면 보입니다(줌 16 육안 확인).

소유권 이탈 1건 (의도)

widgets/map-shell/MapShell.tsx +10/−2 — 488이 "완결·489 0줄"로 선언한 파일이지만, 클러스터가 이 파일에서 파생되므로 다른 경로가 없고 사용자가 명시 지시했습니다. MSG-490도 이 파일 0줄이라 병렬 머지 충돌은 없습니다.

후속 후보

  • features/dex/use-region-stat-query.ts의 private useCurrentCoords와 이 티켓 동명 훅이 구조상 3복제(baseline 등재) — 공통화하려면 dex의 "조회 전 null 대기" 계약을 tri-state로 바꿔야 합니다.
  • 자동 이동 순간 축척 바(role="status", 기존 구현)가 "1km"를 토스트와 함께 낭독합니다(내용 일치).
  • 2차 대기 중 탭 숨김/복귀 조합은 단위 테스트로만 고정돼 있습니다(복귀 후 <2,200ms 종결).

🤖 Generated with Claude Code

https://claude.ai/code/session_01QkrsSVoDCWaPAV45gzKpZX

gyuhochoime and others added 5 commits August 29, 2026 13:17
- 제출하면 항상 현재 중심 기준 1km(MAP_SCALE_1KM_ZOOM)로 맞춘 뒤 요청한다("다시 짜기"도 동일). 이미 1km면 줌 명령 없이 즉시 발사 — 같은 값 zoomTo는 idle을 내지 않아 요청이 나가지 않는다
- 종전 "뷰포트 한 변 0.5도 초과일 때만 정규화"(needsSpanNormalize) 규칙은 위 규칙에 흡수·폐기. 다만 서버 상한 가드는 exceedsViewportSpan으로 부활시켜 **발사 직전**에 둔다 — 정규화 대기·상한 만료·2차를 포함한 발사 4경로가 모두 send() 한 지점을 지난다
- 줌 맞추는 동안 로딩 표시(normalizePending). rate-limit 기준 시각 requestedAt은 실제 mutate 시점에만 기록해 2차 조기 발사(14429)를 막는다
- 2차 자동 재요청은 "목표 뷰포트 도달" 판정 후 발사하고, 도달 실패 시에도 상한 뒤 종결한다 — 영구 로딩과 확정 실패 요청 둘 다 금지
- 탭이 숨은 동안에는 정착 상한 타이머를 걸지 않는다. 백그라운드 탭에서는 네이버 지도가 idle을 내지 않아 뷰포트 스토어가 옛 값에 머물고, 그 상태로 상한이 발화해 정규화되지 않은 범위(1.9°x4.7°)로 요청이 나가 서버 400(14401)이 재현됐다
- /ai-route에서 다른 집계 클러스터 마커(지역 점령·미션·핫구역)를 숨긴다 — gateRouteClusters를 MapShell 파생 결과에만 적용. 집계층은 줌 16(100m) 미만에서만 존재하므로 경로 게이트 하나로 충족된다. 홈·도감은 불변
- 검증: 결함 재현 시나리오에서 과대 뷰포트 요청 0건·복귀 후 정상 발사, 세션 recommend 7건 전건 span<=0.5°·200. 클러스터 /ai-route 0개 vs 홈 동일 뷰포트 3개. S6 간격 10,498ms·14429 없음, 결과 후 확대 시 추가 호출 0. 게이트 6종 green(web 1472 tests)
- 14429(요청 과다) 안내를 서버와 같은 문장 "요청이 너무 잦습니다. 잠시 후 다시 시도해주세요"로 교체 — 종전 문구는 사유를 알려주지 않아 재시도를 연속으로 누른 사용자가 같은 안내만 반복해서 봤다. FE 고정 문구 상수이며 서버 응답 문자열을 렌더하지는 않는다(MSG-488 §1-5 정책 유지)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QkrsSVoDCWaPAV45gzKpZX
브라우저 자동화 탭이 백그라운드면 rAF가 0.5~1fps로 얼어 계측이 35배까지 부풀려진다(같은 줌 전이가 숨은 탭 11,162ms · 전면 탭 316ms). 그 값이 "정규화 대기 14.2초 무피드백"이라는 리포트 문장과 후속 권장까지 만들어 냈고, 더 나쁜 것은 숨은 탭에서는 지도가 idle을 내지 않아 동작 자체가 달라진다는 점이다 — 오염된 계측이 실제 결함(14401)을 가리고 있었다. 계측 신뢰성을 검증자의 주의력이 아니라 절차의 전제 조건으로 내린다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QkrsSVoDCWaPAV45gzKpZX
codex 리뷰(push 전 게이트) 지적 2건 반영.

- [P1] 2차 요청 인스턴스에 onLoginRequired가 없어, 1차 성공과 지연된 2차 사이에 세션이 만료되면 401에도 로그인 모달이 뜨지 않았다. secondary 옵션은 onMutate만 분기하고 onError는 공통 경로라 콜백 주입 한 줄로 1·2차 동작이 같아진다. 문서에 "알려진 한계"로 적혀 있던 서술은 결함이었음을 명시하고 정정
- [P2] 목표 뷰포트 미도달 상태에서 bounds·zoom이 갱신될 때마다 이펙트가 재실행되며 상한 타이머를 처음부터 다시 걸어, 사용자가 계속 패닝하면 2차가 무한정 연기되고 패널이 로딩에 갇혔다 — §11 D13(영구 로딩 금지) 정면 위반. 지적은 2차만 짚었으나 정규화 대기도 같은 구조여서 함께 고쳤다
- 마감을 대기 사이클당 1회만 계산하는 순수 함수 advanceSettleDeadline 신설 — 이펙트 재실행은 남은 시간만 스케줄하고, remaining <= 0이면 타이머 없이 즉시 종결한다(setTimeout(fire, 0)은 다음 cleanup에 취소될 창이 남는다)
- 가시성 공존: 마감이 hiddenSince를 기억해 복귀 시 숨어 있던 만큼 뒤로 밀린다(= 가시 시간만 소모). 숨은 동안 타이머를 걸지 않는 §12 동작과 도달 후 서버 10초 창은 그대로
- 실타이머 실측: 정규화 대기 반복 패닝 3,116ms · 2차 대기 3,168ms · 숨김 복귀 후 <2,200ms 종결. RED 단계에서 패닝 3시나리오 전부 미발사(무한)로 결함 재현
- 게이트 6종 green(web 1481 tests). 브라우저 실동작 4건은 확인불가 — 검증 시점에 탭이 hidden 고정(rAF 정지)이고 로그인 세션이 만료돼 있었다

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QkrsSVoDCWaPAV45gzKpZX
검증 3회차는 탭이 hidden 고정(rAF 정지)이고 로그인 세션이 만료돼 실동작을 못 봤다. 전제(visible·121fps)를 충족시켜 재계측한 결과 4건 전건 통과.

- 정규화 315ms(8km→1km, 200)·복구 851ms·2차 간격 10,492ms(드래그 3회 방해에도 연기 없음)
- 계속 방해 시 3,088ms 종결 — recommend 0건, 로딩이 아니라 안내+"다시 시도" (P2 무한 연기 차단과 14401 가드가 같은 시나리오에서 동시 입증)
- 요청 span 전건 0.1211°x0.2966° <= 0.5°, 비200은 의도적 14429 1건뿐
- 14429 화면 문구 "요청이 너무 잦습니다. 잠시 후 다시 시도해주세요" 노출 확인

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QkrsSVoDCWaPAV45gzKpZX
@claude

claude Bot commented Aug 29, 2026

Copy link
Copy Markdown

Claude finished @gyuhochoime's task in 5m 10s —— View job


🔍 코드 리뷰 완료

작업 목록

결과: 🔴/🟡 결함 없음. codex 리뷰(P1 2차 401 로그인 누락, P2 정착 상한 무한 연기)가 코드·테스트로 확인된 대로 해결돼 있고, advanceSettleDeadline·reachedTargetViewport 등 핵심 판정 로직을 직접 추적 검증했습니다. 사소한 참고 2건(빈 문자열 조사 처리, 이펙트 내 호출 순서 코멘트 제안)만 요약 코멘트에 남겼습니다.

@github-actions

github-actions Bot commented Aug 29, 2026

Copy link
Copy Markdown

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit 55aa075.

@claude

claude Bot commented Aug 29, 2026

Copy link
Copy Markdown

🔍 코드 리뷰 요약 — MSG-489 (PR #105)

범위: route-mentioned-area·route-origin·route-request(정착 마감 advanceSettleDeadline 포함)·ai-route-store·use-route-recommend·use-ai-route-auto-move·route-error·AiRoutePage/RouteInputCard/RouteToastHost·MapShell/cluster-route-gate·geolocation 전체를 diff만이 아니라 주변 코드(스토어 전이, MapCanvasonIdle 배선, viewport-store 참조 갱신 방식 등)까지 Read로 추적해 확인했습니다.

✅ 잘한 점

  • 판정 로직 전부가 순수 함수로 분리돼 있고(route-origin·route-mentioned-area·route-request), 뷰-레이어 훅(use-ai-route-auto-move)은 "언제 무엇을 호출하는가"만 남아 있어 테스트 가능성과 RN 재사용 경계가 명확합니다.
  • advanceSettleDeadline(codex P2 후속) — 대기 사이클당 마감을 1회만 확정하고 재실행은 남은 시간만 돌려주는 설계를 직접 추적했는데, useSettleDeadline 훅에서 !visible 조기 반환 전에 remainingMs()를 호출해 hiddenSince를 먼저 기록하는 순서가 정확합니다(가시 상태 진입 시 딱 그만큼만 마감이 밀림). 관련 단위 테스트(route-request.test.ts §13)와 통합 테스트(use-ai-route-auto-move.test.tsx — 패닝 반복·탭 숨김/복귀 시나리오)가 회귀를 실제로 잡을 수 있는 형태로 잘 짜여 있습니다.
  • reachedTargetViewport의 참조 동등성 판정(bounds !== boundsAtCommand) — MapCanvasonIdle마다 toViewport(map)매번 새 객체를 만들어 setViewport에 넣는 걸 확인했습니다. 이것만으로는 "실제로 이동했는지"가 아니라 "idle이 한 번이라도 더 일어났는지"만 보장하는데, needsZoomNormalize 병행 체크로 pan 단계 idle(줌 아직 목표 아님)을 걸러내는 조합이 안전망 역할을 해 실질적으로 안전합니다. 다만 이건 네이버 지도 SDK의 idle 발화 타이밍(팬/줌 각각 애니메이션 종료 시 1회)에 대한 암묵적 가정에 기대고 있다는 점은 참고해 두시면 좋겠습니다(실측 검증에서는 정상 동작 확인됨 — 확신은 아니고 추정입니다).
  • XSS 우려 없음movedAreaName(서버 응답 값)이 RouteToastHost에서 JSX 텍스트로만 전달되고 ui-webToastdangerouslySetInnerHTML을 쓰지 않아 안전합니다. role="status"Toast 자체에 있어 접근성도 챙겨져 있습니다.
  • StrictMode 이중 실행 방어(useAiRouteStore.getState().normalizePending/secondaryPendingfire() 안에서 재확인)와 정합성 테스트(canSubmit이 참이면 buildRecommendBody도 반드시 값을 낸다는 L8↔L9 정합 테스트)처럼, 이전 리뷰(codex P1·P2)에서 나온 지적을 근본 원인까지 따라가 고친 흔적이 테스트에 잘 남아 있습니다.
  • getCurrentPositionOrNull 신설로 기존 getCurrentPosition(서면 폴백)의 오판정 문제를 깔끔하게 가산 방식으로 해결했고, 기존 소비 3곳을 건드리지 않은 점도 확인했습니다.

🟢 사소한 참고 (블로킹 아님)

  • route-mentioned-area.tseuroJosaareaName이 빈 문자열일 때 charCodeAt(-1)NaN → "로"로 안전하게 폴백합니다. 서버가 빈 이름을 줄 가능성은 낮아 보여 수정 요구는 아니고, 방어가 이미 돼 있다는 점만 확인 차 남깁니다.
  • use-ai-route-auto-move.ts의 두 useEffect(정규화·2차 대기)에서 remainingMs(Date.now()) 호출이 if (!visible) return; 에 위치해 있어("먼저 계산하고 버린다"는 흐름), 처음 보면 순서가 어색해 보일 수 있습니다. 실제로는 hiddenSince 기록을 위해 의도된 순서라 문제는 없지만, 짧은 주석 한 줄(remainingMs 호출 자체가 hiddenSince 기록 부작용)이 있으면 다음에 보는 사람이 더 빨리 납득할 것 같습니다.

🟡 / 🔴 발견된 결함

없습니다. 로직·엣지 케이스·타입 안정성·메모이제이션·재렌더링·보안 관점에서 확인했으나 반드시 수정해야 할 문제나 권장 수정 사항을 찾지 못했습니다. PR 본문에 기록된 codex 리뷰 P1(2차 401 로그인 처리 누락)·P2(정착 상한 무한 연기)는 코드·테스트로 모두 확인된 대로 해결되어 있습니다.


View job run · 브랜치 feat/ai-route-mentioned-area-auto-move-origin

PR #105 리뷰(react-doctor/query-mutation-missing-invalidation, warning) 판정.

/api/routes/recommend는 body가 필요해 POST일 뿐 읽기형이라(생성 스펙: "…보내면 지점 목록을 돌려준다") 서버 리소스를 만들지도 고치지도 않고, 응답은 쿼리 캐시가 아니라 ai-route-store로 간다 — 낡아질 캐시가 없다. 제안대로 onSuccess에 invalidateQueries를 넣으면 1차 응답 분기(결과 미게시 + 2차 예약)에서도 실행돼 무관한 격자 쿼리를 재요청하고 오버레이를 흔든다.

규칙 자체는 레포의 다른 mutation에서 유효하므로(video 5·profile 3·badge 1이 실제로 invalidate) doctor.config.json 전역 off는 하지 않는다. pre-commit 훅이 --blocking warning이라 커밋이 막혀, 공식 인라인 억제(react-doctor-disable-next-line)로 이 한 줄만 좁혀 껐다 — 판정 근거는 호출부 주석과 docs/spec/MSG-489.md §13-2에 남긴다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QkrsSVoDCWaPAV45gzKpZX
@claude

claude Bot commented Aug 29, 2026

Copy link
Copy Markdown

Claude finished @gyuhochoime's task in 3m 36s —— View job


🔍 코드 리뷰 완료

작업 목록

  • 컨텍스트 수집 (PR diff, 변경 파일 확인)
  • 요청 파악 (코드 리뷰 — 한국어)
  • 핵심 로직 파일 순차 확인 (route-mentioned-area·route-origin·route-request·ai-route-store·use-route-recommend·use-ai-route-auto-move·route-error·AiRoutePage/RouteInputCard/RouteToastHost·MapShell/cluster-route-gate·geolocation)
  • react-doctor 신규 경고(use-route-recommend.ts:52 캐시 무효화) 타당성 검토 — 기각 판정이 타당함을 독립 확인
  • 인라인 코멘트 (구체적 결함 없어 생략 — 이전 리뷰와 동일 참고 2건만 요약에 기록)
  • 전체 요약 코멘트 게시 → MSG-489 feat: [웹] AI 경로추천 — 지역 자동 이동·1km 축척 고정·2차 자동 재요청·출발지 자동 판정 #105 (comment)

결과: 🔴/🟡 결함 없음. advanceSettleDeadline(codex P2 후속)·onMutate 시점 플래그 소거·2차 mutation의 onLoginRequired 배선(codex P1)·gateRouteClusters 파생 격리를 직접 추적 검증했고, react-doctor 신규 경고(캐시 무효화) 기각 사유도 타당하다고 확인했습니다. 사소한 참고 2건(빈 문자열 조사 처리, 이펙트 내 호출 순서 주석 제안)만 요약 코멘트에 남겼습니다.
· 브랜치 feat/ai-route-mentioned-area-auto-move-origin

@claude

claude Bot commented Aug 29, 2026

Copy link
Copy Markdown

🔍 코드 리뷰 요약 — MSG-489 (PR #105)

범위: route-mentioned-area·route-origin·route-request(advanceSettleDeadline 포함)·ai-route-store·use-route-recommend·use-ai-route-auto-move·route-error·AiRoutePage/RouteInputCard/RouteToastHost·MapShell/cluster-route-gate·geolocation diff와 주변 코드를 Read/Grep으로 순차 확인했습니다(서브에이전트·병렬 도구 미사용). HEAD는 55aa075(react-doctor 지적 기각 커밋 포함) 기준입니다.

✅ 잘한 점

  • 판정 로직이 전부 순수 함수(route-origin·route-mentioned-area·route-request)로 분리돼 있고, 뷰-레이어 훅(use-ai-route-auto-move)은 "언제 호출하는가"만 남겨 RN 경계·테스트 가능성이 명확합니다.
  • advanceSettleDeadline(codex P2 후속) 를 직접 추적했습니다 — deadline === null일 때만 새 마감을 확정하고, 숨김 구간은 hiddenSince 최초 관측 시각으로 고정한 뒤 복귀 시 그 차이만큼 deadlineAt을 뒤로 미는 로직이 정확합니다. 사이클당 마감이 한 번만 서므로 "계속 패닝하면 2차가 무한 연기"되는 P2 재발 경로가 막혀 있습니다. route-request.test.ts §"advanceSettleDeadline"의 경계 테스트(마감 경과 시 0, 숨김 중 시작 등)도 구현과 일치합니다.
  • onMutate 시점 플래그 소거를 확인했습니다 — startRequest/markSecondarySentmutate() 호출과 동시에(응답 도착 전) normalizePending/secondaryPending을 내리므로, onError(fail())가 별도로 이 플래그들을 정리하지 않아도 됩니다(레이스 없음).
  • 2차 mutation의 onLoginRequired 부착(codex P1) — useRouteRecommend({ secondary: true, onLoginRequired }) 배선을 확인했고, 1차 성공 후 지연된 2차에서 세션이 만료돼도 로그인 모달이 뜨도록 고쳐져 있습니다.
  • gateRouteClustersMapShell의 파생 결과에만 적용되고 집계 훅 3종은 무수정이라, PR 설명대로 홈·도감 영향이 0입니다. pathname 선언을 게이트 사용 지점 위로 옮긴 리팩터도 부작용 없는 단순 이동입니다.
  • getCurrentPositionOrNull 신설로 기존 getCurrentPosition(서면 폴백)의 오판정 문제를 가산 방식으로 해결했고 기존 소비 3곳은 그대로입니다.
  • react-doctor 신규 경고(use-route-recommend.tsquery-mutation-missing-invalidation)는 검토 결과 기각이 타당합니다. 이 엔드포인트는 body가 필요한 POST일 뿐 읽기형이고 응답은 쿼리 캐시가 아니라 zustand 스토어로 가므로 무효화할 캐시가 없으며, 오히려 onSuccess에 넣으면 1차 응답의 "결과 미게시 + 2차 예약" 분기에서도 실행돼 무관한 격자 쿼리를 흔듭니다. 전역 off 대신 해당 줄만 공식 인라인 억제로 좁힌 처리도 적절합니다(레포 다른 mutation에서는 규칙이 유효하므로).

🟢 사소한 참고 (블로킹 아님, 이전 리뷰와 동일 관찰 재확인)

  • route-mentioned-area.tseuroJosaareaName이 빈 문자열이면 charCodeAt(-1)NaN → "로"로 안전하게 폴백합니다. 서버가 빈 이름을 줄 가능성은 낮지만 방어가 이미 있다는 점만 확인 차 남깁니다.
  • use-ai-route-auto-move.ts의 두 정착 이펙트에서 remainingMs(Date.now()) 호출이 if (!visible) return; 에 있어 처음 보면 순서가 어색합니다. hiddenSince 기록 부작용을 위한 의도된 순서이고 동작은 정확하지만, 짧은 주석 한 줄이 있으면 다음에 보는 사람이 더 빨리 납득할 것 같습니다.

🟡 / 🔴 발견된 결함

없습니다. 로직·엣지 케이스(대기 무한 연기, 탭 가시성, StrictMode 이중 실행, 2차 인증 처리)·타입 안정성·메모이제이션·보안(XSS: movedAreaName JSX 텍스트 렌더만, dangerouslySetInnerHTML 없음) 관점에서 확인했으나 반드시 수정해야 할 문제를 찾지 못했습니다. codex 리뷰 P1(2차 401 로그인 처리 누락)·P2(정착 상한 무한 연기)는 코드·테스트로 모두 해결되어 있고, react-doctor 신규 경고 1건은 기각이 타당하다고 독립적으로 확인했습니다.

View job run · 브랜치 feat/ai-route-mentioned-area-auto-move-origin

@gyuhochoime
gyuhochoime merged commit 0477869 into develop Aug 29, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant