Skip to content

#114 후속 — dev 프리뷰 라우트가 프로덕션에 실리고, /places/suggestions 계약이 문서에 없다 #116

Description

@TrossYou

#114(S15P11A705-323, 이미지 기반 장소·맥락 제안)를 머지 후 살펴보다 발견한 후속 항목입니다. 기능 자체는 잘 동작하고, 아래는 전부 "붙여야 할 마감" 성격입니다. 우선순위 순으로 적었습니다.

기능 전체를 @ghkim1632 님이 맡고 계셔서 배정합니다. 3번은 백엔드 확인이 필요할 수 있습니다.


1. /dev/place-record 프리뷰 라우트가 프로덕션 번들에 그대로 실린다 🔴

src/app/router.tsxplaceRecordPreviewRouteimport.meta.env.DEV 가드도, 다른 라우트가 쓰는 beforeLoad 인증 가드도 없습니다. 배포된 앱에서 URL만 알면 누구나 열립니다.

문제는 라우트 존재 자체보다 그 안의 버튼입니다. PlaceRecordPreviewPage.tsx의 "테스트 세션 발급"이 이걸 호출합니다.

await fetch('/api/core/v1/dev/auth/login', {
  method: 'GET',
  credentials: 'include',
});

credentials: 'include'로 세션 쿠키를 받고 memberId를 화면에 띄웁니다. 운영 백엔드가 /dev/auth/login을 노출하고 있다면 로그인 없이 세션을 발급받는 경로가 열려 있는 것입니다. 프론트만으로는 판단할 수 없으니 확인이 필요합니다.

  • 운영 백엔드에 GET /api/core/v1/dev/auth/login이 살아 있는지 확인 (살아 있으면 백엔드 이슈로 승격)
  • 라우트를 import.meta.env.DEV로 감싸거나 프로덕션 빌드에서 제외
  • docs/conventions.md에 개발 전용 라우트를 다루는 규칙 한 줄 추가 (규칙이 없어서 재발하기 쉬운 자리입니다)

2. 서버가 보낸 warnings·kakaoSearch.status를 아무도 읽지 않는다 🟡

suggestPlacesFromImage.ts가 Zod로 파싱은 하는데, 소비하는 코드가 한 줄도 없습니다(grep으로 확인).

kakaoSearch: { status: 'SUCCESS' | 'NO_RESULTS' | 'FAILED', query, items }
warnings: [{ code, message, candidateId }]

PlaceRecordSheetcandidate.kakaoSearch.items만 꺼내 쓰고 status로 분기하지 않습니다. 서버가 "이 후보는 카카오 검색이 실패했다"(FAILED)고 알려줘도 UI는 items: []와 구분하지 못하고, 사용자에게는 그냥 후보가 비어 보입니다. warnings도 통째로 버려집니다.

keywords: []를 오류로 처리하지 말라는 규칙의 반대편 실수입니다 — 정상이 아닌 신호를 정상처럼 넘기고 있습니다.

  • NO_RESULTSFAILED를 구분해 안내 (전자는 "검색 결과가 없어요", 후자는 재시도 여지가 있는 오류)
  • warnings를 노출하거나, 노출하지 않기로 정했다면 스키마 주석에 그 이유를 남기기

3. POST /places/suggestions 계약이 어느 문서에도 없다 🟡

docs/api-contract.md에도, 원본 docs/reference/08_API_명세.md에도 이 엔드포인트가 없습니다(grep으로 확인). 현재 근거는 #109 이슈뿐입니다.

AGENTS.md가 "문서에 없는 정책·엔드포인트를 추측해서 구현하지 않는다"를 절대 금지로 두고 있어서, 지금 상태로는 다른 사람이 이 코드를 만졌을 때 기댈 근거가 없습니다. 이미 머지됐으니 사후에 계약을 문서로 고정하는 쪽이 맞겠습니다.

전례가 있습니다 — 표지 생성 API(/image/api/*)는 원본 명세 §7.7이 front#99 가이드로 위임하고, api-contract.md가 "이 절만 근거가 원본 docs가 아니라 front#99"라고 명시하는 형태입니다. 같은 패턴으로 절을 하나 만들면 됩니다.

스키마는 코드에서 읽어낼 수 있으니 제가 초안을 쓸 수 있습니다. 다만 아래는 추측하면 안 되는 항목이라 확인이 필요합니다.

  • kakaoSearch.statusNO_RESULTS/FAILED가 오류인지 정상 응답인지 (2번 처리 방향이 여기서 갈립니다)
  • warnings[].code의 값 목록과 의미
  • 인증 필요 여부와 rate limit — Gemini Vision 비용이 발생하는 경로입니다. 표지 생성 API도 같은 이유로 api-contract.md "협의 필요"에 §3.2로 올라가 있습니다
  • 이미지 크기·형식의 서버 측 제한. 프론트는 5MB·JPG/PNG로 자체 제한 중인데(PlaceRecordSheet.tsx#L28-L29) 서버 기준과 같은지 불명입니다
  • 위가 정해지면 docs/api-contract.md에 절 추가 (미확정 항목은 "협의 필요"로)

4. httpClient multipart 관용구가 문서에 없다 🟢

httpClient가 기본 헤더로 Content-Type: application/json을 박고 있어서, multipart를 보내려면 이렇게 지워야 브라우저가 boundary를 붙입니다.

httpClient.post('/places/suggestions', formData, {
  headers: { 'Content-Type': undefined },
});

이 레포에서 처음 등장한 패턴이고, 모르면 boundary 누락으로 400이 납니다. 다음에 파일 업로드를 붙이는 사람이 같은 데서 막힐 자리라 한 줄 남길 가치가 있습니다.

  • docs/conventions.md 또는 docs/api-contract.md에 한 줄 추가

확인했지만 문제 없었던 것

공개 범위는 위반이 없습니다. extracted.evidence(대화 캡처에서 뽑은 근거 문장)는 읽기 전용 표시에만 쓰이고 저장되지 않습니다. contextSuggestion만 Context 본문 초안으로 들어가는데 Context는 본인 것이고 타인 응답에서는 null이라 기존 규칙 그대로입니다.

다만 한 가지는 docs/privacy-rules.md에 남길 만합니다 — Context 원문이 이제 사용자가 직접 쓴 메모가 아니라 AI가 사적 대화 캡처에서 추출한 문장일 수 있습니다. "Context 원문 비공개" 규칙의 무게가 그만큼 커졌습니다. 이건 제 문서 쪽이라 제가 반영하겠습니다.

VITE_KAKAO_REST_KEY가 브라우저에 노출되는 건 docs/frontend-image-runtime-contract.md에 이미 "공개 입력"으로 명시돼 있어 새로 할 게 없습니다.

제가 맡을 것

docs/architecture.md 2장 폴더 목록이 실제와 어긋난 것(places·map·layout 누락, followfollows)과 위 privacy-rules 한 줄은 제 문서라 제가 반영하겠습니다. 4번도 원하시면 제가 쓰겠습니다.

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentation

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions