Skip to content

MSG-490 feat: [웹] AI 경로추천 실보행 경로 — walk-paths 점진 렌더·세그먼트 폴백·실거리 표기 - #106

Merged
gyuhochoime merged 5 commits into
developfrom
feat/ai-route-walk-paths
Aug 30, 2026
Merged

MSG-490 feat: [웹] AI 경로추천 실보행 경로 — walk-paths 점진 렌더·세그먼트 폴백·실거리 표기#106
gyuhochoime merged 5 commits into
developfrom
feat/ai-route-walk-paths

Conversation

@gyuhochoime

@gyuhochoime gyuhochoime commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

🎫 관련 티켓

📌 작업 내용

경로 추천 결과의 지점 사이 선을 직선에서 실제 걷는 길로 교체합니다 (POST /api/routes/walk-paths).

  • 점진 렌더: 결과 도착 시 직선 폴리라인을 먼저 그린 채 이웃 좌표쌍(≤8)을 1회 요청 → resolved:true 세그먼트만 path 좌표열로 교체, 커넥터 "도보 약 Nm"도 서버 distanceMeters로 갱신(같은 formatWalkDistance 규칙)
  • 세그먼트 단위 폴백: resolved:false·요청 실패(400/503/네트워크/401)는 직선·직선 거리 유지의 조용한 폴백 — 에러 UI 0, retry: false
  • 경합 차단: 서버 응답에 결과 id가 없어 세그먼트 좌표 자체를 queryKey로 사용 — 새 추천 = 다른 키라 늦게 온 이전 응답이 화면에 앉을 수 없고, staleTime/gcTime: Infinity가 세션 유지 + 섹션 왕복 재요청 0회를 겸함(로그아웃 시 QueryProvider.clear()로 세션 경계 일치)
  • 렌더 0줄: Figma route-line-walkpath 실측이 #34C759 4px 실선 = 현재 직선 스타일과 동일 → MapCanvas·map-overlay-store·MapShell·RouteWalkConnector 미접촉. alignWalkSegments(개수 불일치 시 통째 폐기 + originOffset)를 legs·overlay가 공유해 "거리는 실거리·선은 직선" 불일치 차단
  • MSG-489와 교차 파일 0건(MSG-488.md §4-2) — 승인된 표 이탈 1건만(use-ai-route-overlay-publish.ts, 489도 미접촉). 489 머지(0477869)를 이 브랜치에 머지 완료 — 코드 충돌 0, 문서 2개만 append 경합 해소, 머지 후 전 게이트 재통과
  • R2 정정: "489 머지 후 originOffset 배선 필수"는 티켓 초안 가정 — 489 실구현이 출발지 구간을 UI에 그리지 않아 현재 배선 대상 없음(파라미터는 예비 기반 존치)

✅ 체크리스트

  • pnpm lint / pnpm typecheck / pnpm --filter web test run 통과
  • 수용 기준 검증 완료 (검증 리포트 요약을 아래에 첨부)
  • 필요한 경우 문서(README, docs/) 업데이트 — docs/STATUS.md · docs/spec/MSG-490.md · docs/decisions/DECISIONS.md

🔍 검증 요약

최종 판정: 수용 기준 25개 전건 통과 (로직 17 + 화면 8), 실패 0, 확인불가 0

자동 검증 결과
vitest 489 머지 후 web 198파일/1508케이스 · mobile 156파일/982케이스 전건 통과
typecheck 6개 워크스페이스 통과
lint (oxlint) 에러·경고 0, 신규 비활성 주석 0
format:check 통과
check:duplication 신규 패밀리 0, nose.baseline.json 0줄
생성물 드리프트 openapi-ts 후 diff 0줄 (웨이브 0 스냅샷에 walk-paths 기포함, 재생성 0회)
pnpm build 통과

로직 (L1~L17 + R6) — 전건 통과. 검증자가 뮤테이션 검증(구현을 일부러 깨뜨려 테스트가 FAIL하는지)을 수행: 최초 회차에서 L14(enabled 가드)·L15(retry: false)가 뮤테이션에도 통과해 테스트가 회귀를 못 잡는 것을 적발 → 재작업 1회(프로덕션 0줄, 테스트 단정만 교체) 후 재검증에서 뮤테이션 A/B/B2 각각 해당 케이스만 국소 FAIL 확인.

화면 (S1~S8, 3-A 브라우저 실측) — 전건 통과:

  • S1 점진 렌더: 150ms 샘플러로 상태 정확히 3개(없음 → 직선 정점 2·450m → 굽은 선 정점 12·650m), 중간 빈 상태 0회
  • S2 실거리 교체: 450m(직선 근사) → 650m(서버 647m)
  • S3 부분 폴백: 세그먼트 1개만 resolved:false 조작 → 단일 폴리라인에 직선·도로 구간 공존
  • S4 조용한 폴백: 503·네트워크 오류 모두 직선 유지·[role="alert"] 0·재시도 0
  • S5 경합: 이전 응답을 9초 늦게 도착시켜도 새 결과 유지(식별 데이터 DOM 미출현)
  • S6 섹션 왕복: 42정점 실경로 복원 + walk-paths 재요청 0회
  • S7 Figma 대조: DOM stroke:#34C759; stroke-width:4px 실선 path 정확히 1개
  • S8 요청 수: 추천 1건당 1회, body segments 길이 N−1 3회 일치(6→5·7→6·8→7)
  • a11y: 콘솔 에러·경고 0, 커넥터 낭독 회귀 없음. R4 전제(TMap 쿼터 미소진, resolved:true 존재) 확인 후 판정

codex 리뷰(push 전): P2 1건("walk 도착 시 clear→재게시 깜빡임") — 기각. S1 실측(빈 상태 0회) + React 18 배칭(cleanup·setup 같은 flush, 한 커밋) + 488 선례(selectedOrder 변경마다 동일 패턴, 잔상 0)로 반증. 근거는 docs/spec/MSG-490.md 작업 로그.

💡 추가 논의할 사항

  • 실사용에서 같은 장소에 팝업이 몰리면 0m 세그먼트가 생겨 TMap이 resolved:false를 돌려줍니다("도보 약 0m" 표기 — 사양). 실거리/추정 구분 표기(Q2, 디자인에 변형 노드 없음) 후속 논의 시 실측 근거.
  • 출발지 구간을 지도에 그리는 티켓이 생기면 originOffset 파라미터·L7 테스트가 예비 기반입니다(origin 좌표 보관부터 필요).

🤖 Generated with Claude Code

https://claude.ai/code/session_012uAZNzH4J6LYbwRSqGLxnf

@claude

claude Bot commented Aug 30, 2026

Copy link
Copy Markdown

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


✅ 코드 리뷰 완료

작업 목록

  • 컨텍스트 수집 (PR body, 변경 파일 목록 확인)
  • git diff origin/develop...HEAD 확인
  • 변경 파일 Read (주변 코드 포함)
    • use-walk-paths-query.ts / .test.tsx
    • route-legs.ts / .test.ts
    • route-overlay.ts / .test.ts
    • use-ai-route-overlay-publish.ts
    • use-route-legs.ts
    • 생성 타입(types.gen.ts, sdk.gen.ts)까지 대조
  • 버그/로직, React/TS, 성능, 가독성, 보안 관점 분석
  • 인라인 코멘트 작성 (2건: 🟡 1, 🟢 1)
  • 요약 코멘트 게시 (gh pr comment)

결과: 🔴(반드시 수정) 없음. route-overlay.tsresolved:true+빈 path 방어 누락(🟡)과 접점 부동소수점 dedup(🟢, 추측)만 지적했습니다. 상세 내용은 인라인 코멘트와 요약 코멘트를 참고해 주세요.

@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit e89a925.

ordered.slice(1).forEach((to, index) => {
const from = ordered[index];
const segment = segments[index];
if (segment?.resolved === true && segment.path !== null) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 권장: segment.resolved === true인데 segment.path가 빈 배열([])인 경우를 방어하지 않습니다.

route-legs.ts:117-120의 동일한 판정 지점은 resolved === true여도 distanceMeters !== null까지 함께 확인해 방어적으로 폴백합니다:

const walked =
  segment?.resolved === true && segment.distanceMeters !== null
    ? segment.distanceMeters
    : null;

반면 여기는 segment.path !== null만 확인하고 길이는 보지 않습니다. 서버가 resolved: true인데 path: []를 내려주는 경우(계약 위반이지만 완전히 배제할 수는 없음) for...of가 아무것도 push하지 않아 해당 구간이 통째로 빠지고, 앞뒤 구간이 직접 이어지며 지도에 눈에 띄는 "끊김/직선 점프"가 생깁니다. 다른 모든 폴백 경로(미해결, 개수 불일치 등)는 "무조건 뭔가 그려진다"를 보장하는데 이 경로만 예외입니다.

제안:

if (segment?.resolved === true && segment.path !== null && segment.path.length > 0) {
  for (const { lat, lng } of segment.path) pushPoint(path, { lat, lng });
  return;
}

확신은 못 하지만(서버가 실제로 이 값을 보낼지는 추측), route-legs.ts와의 방어 수준을 맞추는 게 안전할 것 같습니다.

/** 마지막 점과 같은 좌표는 싣지 않는다 — 세그먼트 접점 중복 1개를 합치는 자리 (Q8) */
const pushPoint = (path: LatLng[], point: LatLng) => {
const last = path[path.length - 1];
if (last && last.lat === point.lat && last.lng === point.lng) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 사소한 제안 (추측): pushPoint의 접점 중복 제거가 부동소수점 완전 일치(===)에 의존합니다.

세그먼트 경계(이전 세그먼트의 마지막 좌표 vs 다음 세그먼트의 시작 좌표, 또는 resolved→unresolved 전환 시 from 좌표)가 실수 연산을 거쳐 미세하게 달라지면(예: 서버가 TMap 스냅 좌표를 돌려주는 경우) 중복 제거가 실패해 두 점이 겹치듯 아주 가깝게 찍히는 정도의 영향이라 실사용상 육안으로는 거의 티가 안 날 수 있습니다. 다만 요청 좌표를 그대로 에코하는 것이 아니라 서버가 도로에 스냅한 좌표를 돌려주는 계약이라면, 접점에서 미세한 지그재그가 남을 수 있습니다. S7 검증에서 실측으로 확인된 부분이라 실제로는 문제없을 가능성이 높다고 봅니다 — 참고용으로만 남깁니다.

@claude

claude Bot commented Aug 30, 2026

Copy link
Copy Markdown

🔍 코드 리뷰 요약 — MSG-490 walk-paths 실보행 경로

전체 평가: 완성도가 높은 PR입니다. alignWalkSegments(개수 불일치 시 통째 폐기)를 route-legs·route-overlay 양쪽이 공유해 "거리는 실거리인데 선은 직선"류 불일치를 구조적으로 차단했고, queryKey를 세그먼트 좌표 자체로 삼아 결과 id 없이도 경합을 차단한 설계가 깔끔합니다. 뮤테이션 검증(L14/L15 회귀 미검출 적발 → 테스트 보강)까지 수행한 점도 눈에 띕니다. 🔴(반드시 수정) 항목은 발견하지 못했습니다.

잘한 점

  • 경합 차단 설계: walkPathsQueryKey가 세그먼트 좌표를 그대로 키로 써서, 새 추천 결과는 자동으로 다른 캐시 엔트리가 됨 (use-walk-paths-query.ts:39) — 별도의 "최신 요청 id 추적" 로직 없이 경합 문제를 원천 차단.
  • 일관된 폴백 계약: route-legs.tsalignWalkSegmentsroute-legs·route-overlay가 공유해서, 거리 표기와 폴리라인이 항상 같은 판정 기준(개수 일치 여부)으로 실보행/직선 여부를 결정함 (route-legs.ts:92-101, route-overlay.ts:51).
  • 훅 재사용을 통한 중복 요청 방지: use-route-legs.tsuse-ai-route-overlay-publish.ts가 동일 points로 각자 useWalkPathsQuery를 호출하지만 queryKey가 동일해 react-query 캐시가 요청을 1회로 합침 — 확인 결과 두 훅의 정렬 로직(order 기준 정렬)도 동일해 세그먼트 인덱스 대응이 어긋나지 않음.
  • 테스트 커버리지: route-legs.test.ts/route-overlay.test.ts에 경계값(개수 불일치, 부분 해결, distanceMeters null, originOffset)이 꼼꼼히 커버되어 있고, use-walk-paths-query.test.tsx는 "요청이 안 나가는" 케이스를 대조군 렌더로 검증하는 등 실효성 있는 단정이 많습니다.
  • Hermes 미구현 API(toSorted 등) 금지 규칙을 신규 코드에서도 잘 지켰습니다([...points].sort(...) 패턴 유지).

발견한 이슈 (인라인 코멘트 참고)

  1. 🟡 route-overlay.ts:60 (권장)segment.resolved === true인데 segment.path가 빈 배열([])인 경우를 방어하지 않습니다. 같은 판정을 하는 route-legs.ts:117-120resolved === true여도 distanceMeters !== null까지 확인하는데, route-overlay.tspath !== null만 보고 길이는 확인하지 않아 방어 수준이 다릅니다. 서버가 계약을 어기고 resolved:true, path:[]를 보내면 그 구간이 통째로 빠지면서 지도에 눈에 띄는 직선 점프가 생깁니다. route-legs.ts와 동일하게 path.length > 0까지 확인하는 편이 안전해 보입니다.
  2. 🟢 route-overlay.ts:39 (사소한 제안, 추측)pushPoint의 접점 중복 제거가 부동소수점 완전 일치(===)에 의존합니다. 서버가 요청 좌표를 그대로 에코하지 않고 도로 스냅 좌표를 돌려주는 계약이라면 접점에서 미세한 중복이 남을 수 있습니다. S7 실측에서 문제없이 확인된 부분이라 실사용 영향은 낮아 보이지만 참고로 남깁니다.

확인했으나 문제없다고 판단한 부분 (추측 아님, 직접 추적 확인)

  • alignWalkSegmentsoriginOffset 미배선 상태(PR 설명의 R2 정정)는 코드상으로도 모든 소비처에서 originOffset을 넘기지 않아 기본값 0으로 일관되게 동작함을 확인했습니다.
  • use-ai-route-overlay-publish.tsoverlay 변경 시 clear→재게시 cleanup 패턴(codex P2 기각 건)은 onWaypointSelectAiRoutePage.tsx:81-84에서 useCallback으로 안정화돼 있어 불필요한 재실행 트리거는 아님을 확인했습니다.
  • MAX_WALK_SEGMENTS(8) 절단 로직이 buildWalkSegments(요청 생성)와 alignWalkSegments(응답 정렬) 양쪽에서 일관되게 계산되어 인덱스 대응이 깨지지 않음을 확인했습니다.

전반적으로 프로덕션 반영에 무리가 없어 보이는 PR입니다. 위 🟡 항목은 서버 계약이 실제로 깨질 가능성이 낮다면 선택적으로 반영해도 무방합니다.

@gyuhochoime

Copy link
Copy Markdown
Contributor Author

리뷰 반영 (e89a925)

  • 🟡 route-overlay.ts resolved:true + path: []채택. 중간 구간은 앞뒤 정점이 직접 이어져 사실상 직선 폴백이 되지만, 첫/마지막 구간이 빈 path면 0번/N번 지점이 push되지 않아 선이 끝점 마커에 닿지 않는 결함이 실제로 있었습니다. segment.path.length > 0까지 확인해 route-legsdistanceMeters !== null 방어와 수준을 맞췄고, RED 확인 후 수정한 회귀 테스트를 추가했습니다(web 1509케이스 통과).
  • 🟢 pushPoint 부동소수점 ===미채택(참고 유지). 미해결 구간 접점은 같은 RoutePointDto 숫자를 양쪽에 쓰므로 구성상 완전 일치가 보장되고, 해결↔해결(TMap 스냅) 접점이 미세하게 다르면 sub-meter 정점 2개가 남을 뿐 어떤 줌에서도 보이지 않습니다(S7 실측). 엡실론은 근거 있는 임계값이 없어 복잡도만 늘립니다.

근거 상세는 docs/spec/MSG-490.md 작업 로그 "PR #106 리뷰 반영".

@claude

claude Bot commented Aug 30, 2026

Copy link
Copy Markdown

Claude finished @gyuhochoime's task in 2m 7s —— View job


🔍 코드 리뷰 완료 — MSG-490 walk-paths (후속 커밋 e89a925)

작업 목록

  • 컨텍스트 수집 (PR body, 이전 리뷰 코멘트 2건 확인)
  • git diff origin/develop...HEAD 확인 (신규 커밋 e89a925 포함)
  • 이전 리뷰(🟡 route-overlay.ts 빈 path 방어)에 대한 후속 수정 검증
  • 변경 파일 전체 Read (주변 코드 포함)
  • 버그/로직, React/TS, 성능, 가독성, 보안 관점 분석
  • 인라인 코멘트 검토 (신규 이슈 없음 — 추가 코멘트 없음)
  • 요약 코멘트 게시

이번 트리거는 이전 리뷰 이후 추가된 커밋 e89a925 1개(resolved:true+빈 path 세그먼트 방어)에 대한 후속 확인입니다.

✅ 이전 🟡 지적 사항 — 정확히 반영 확인

route-overlay.ts:62-66에서 segment.path.length > 0까지 확인하도록 고쳐, route-legs.ts:117-120distanceMeters !== null 방어와 같은 수준으로 맞춰졌습니다.

if (
  segment?.resolved === true &&
  segment.path !== null &&
  segment.path.length > 0
) {
  • 테스트(route-overlay.test.ts:134-150)가 "resolved:true + path:[]" 케이스에서 마지막 구간이 두 끝점 직선으로 폴백하는지 정확히 단정하고 있고, 커밋 메시지·docs/spec/MSG-490.md 작업 로그에 test-first(RED 확인 후 1줄 수정) 절차가 기록되어 있습니다.
  • 로직도 직접 추적 확인: 첫/마지막 구간이 빈 path였을 때 이전 방식으로는 해당 끝점이 pushPoint로 한 번도 push되지 않아 마커까지 선이 안 닿는 문제였는데, 수정 후에는 else 분기(두 끝점 직선 push)로 빠지므로 끝점 도달이 보장됩니다.
  • 수정 범위가 정확히 1줄(조건 추가)이라 사이드이펙트 표면이 없고, pushPoint의 dedup 로직·다른 분기에는 영향이 없습니다.

🟢 이전 지적 사항 — 기각 근거 확인 (문제없음)

접점 부동소수점 dedup(pushPoint === 비교) 건은 docs/spec/MSG-490.md에 "해결→해결 접점의 sub-meter 오차는 어떤 줌에서도 안 보임(S7 실측), 해결→미해결 전환의 정점 추가는 오히려 바람직함, 엡실론 임계값 근거 없음"으로 기각 사유가 문서화되어 있습니다. 사소한 제안이었고 실사용 영향이 낮다고 판단해 기각에 동의합니다.

재검토 결과

use-walk-paths-query.ts, use-ai-route-overlay-publish.ts, use-route-legs.ts 등 나머지 변경 파일은 이번 커밋에서 손대지 않았고, 이전 리뷰에서 이미 전건 확인했던 내용과 코드가 동일함을 재확인했습니다. 신규 🔴/🟡/🟢 이슈는 발견하지 못했습니다.

⚠️ 실행 제약

이번 잡 환경에서 pnpm vitest / gh 등 셸 명령이 승인 요구로 차단되어 직접 실행하지 못했습니다 — 테스트 통과 여부는 PR 설명의 검증 리포트(vitest 전건 통과)와 코드/테스트 정독으로 대체 확인했습니다. 실행 검증이 필요하면 --allowedTools에 해당 명령을 추가해 주세요.

결론: 🔴 없음, 신규 🟡/🟢 없음. 이전 리뷰 지적사항 처리가 적절합니다. 프로덕션 반영에 무리 없어 보입니다.

@gyuhochoime
gyuhochoime merged commit 0b2c355 into develop Aug 30, 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