Skip to content

chore(git): main 변경 사항을 dev로 백머지 - #121

Merged
minbros merged 10 commits into
devfrom
main
Jul 21, 2026
Merged

chore(git): main 변경 사항을 dev로 백머지#121
minbros merged 10 commits into
devfrom
main

Conversation

@minbros

@minbros minbros commented Jul 21, 2026

Copy link
Copy Markdown
Member

배경

PR #120이 dev가 아닌 main에 직접 병합되어 두 브랜치의 이력을 다시 맞춥니다.

변경 내용

  • main에 병합된 GA4 동의 제어 및 이벤트 측정 개선 사항을 dev에 반영
  • 후속 개발이 최신 main 기준에서 이어지도록 브랜치 동기화

검증

Summary by CodeRabbit

  • 새로운 기능

    • 분석 쿠키의 허용·거부 상태를 확인하고 변경할 수 있는 개인정보 설정 페이지를 추가했습니다.
    • 페이지 조회, 검색 결과, 일정, 장소, 공유 및 튜토리얼 활동의 분석 측정을 강화했습니다.
    • 검색어 원문과 민감한 URL 정보가 분석 데이터에 포함되지 않도록 보호합니다.
    • 분석 동의 전송과 철회 시 식별자 및 관련 쿠키 처리를 개선했습니다.
  • 문서

    • GA4 운영 기준, 개인정보 보호 정책 및 배포 전 검증 절차를 문서화했습니다.
  • 테스트

    • 동의 처리, 페이지 조회 중복 방지, 데이터 정제 및 분석 이벤트 검증을 보강했습니다.

song9252 and others added 10 commits July 1, 2026 20:50
fix(layout): 피드백 폼 URL을 갱신
DEV Merge : 북마크 세부 페이지 렌더링 / 초대 코드 재발급 이슈 해결
* fix(plan): 새 일차 추가 버튼 삭제

* feat(plan): 일정-북마크 추가 탭 상단 북마크 목록 사이드스크롤 기능 추가

* feat(schedule): 토글/경로표시 기능 분리 및 경로표시버튼 추가

* fix:contact@uttae.app로 공식 이메일 교체

* feat(schdule_item): schedule item 글씨체 키우기, 메모 UI 변경, 줄바꿈 기능추가

* docs: 랜딩 페이지 리디자인 설계 추가

* docs: 랜딩 페이지 구현 계획 추가

* chore: 로컬 워크트리 디렉터리 제외

* docs: CLAUDE.md에서 AGENTS.md import하는 얇은 어댑터 추가

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(landing): 랜딩 페이지 스크린샷 중심 리디자인 (#110)

* docs: 랜딩 페이지 리디자인 설계 추가

* docs: 랜딩 페이지 구현 계획 추가

* chore: 로컬 워크트리 디렉터리 제외

* docs: 랜딩 작업 기준선 기록

* test(landing): 랜딩 리디자인 계약 정의

- Vitest·Sharp 개발 의존성과 테스트 환경 추가
- 랜딩 콘텐츠·타이포그래피·스크린샷 계약 갱신

* feat: add landing hero foundation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: add landing product story sections

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: forward svg props in GithubIcon

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: rebuild landing page composition

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: update landing screenshot domain

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: stack collaboration cards vertically for larger portraits

The inner side-by-side grid capped each card's portrait screenshot at
220px and, at 768px, squeezed the text column down to ~54.5px because
the outer grid already limits card width to ~340-500px. Restack each
card's text block above its screenshot so the portrait can render at
the spec's 320-360px width without starving the copy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: patch legacy domain in map feature screenshot

02-feature-map.png still showed the legacy howaboutus.app domain in
its toolbar at the same geometry as the other patched screenshots.
Add it to the domain-patch script's TARGETS and re-run
assets:landing-domain; the other three PNGs were byte-identical after
re-processing and are not part of this change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: update stale landing comments

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: add motion-safe scroll reveal to landing sections

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: block interaction with hidden reveal sections

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(schedule_id): font size 수정

* fix(schedule): memo placeholder 문구 수정

* feat(memo): on/off 트리거 스위치화 및 삭제 버튼·확인 alert 추가

- 메모 트리거를 항상 노출되는 토글 방식으로 변경 (열려있을 때도 사라지지 않음)
- 활성 상태 (편집 중이거나 저장된 메모 있음) 는 브랜드 그린 강조
- 저장된 메모 읽기 모듈 우상단에 삭제 버튼 추가 - 클릭 시 confirm alert 후 memo 를 빈 문자열로 저장

* feat(memo): 내용 내 URL 자동 하이퍼링크 처리

- renderMemoWithLinks 유틸로 http(s):// 및 www. URL 을 <a> 로 변환
- 후행 문장부호 (. , ; : ! ? ) ]) 는 URL 에서 분리해 텍스트로 유지
- www. 로 시작하면 https:// prefix 붙여 새 탭 오픈
- 클릭 이벤트 stopPropagation 으로 카드 클릭 (지도 이동) 과 분리

* style(memo): 그레이 필 톤 리디자인 및 커스텀 스티키 아이콘

- editorModule 을 amber 톤에서 gray-100/70 필 형태 (rounded-2xl, 테두리·그림자 제거) 로 변경
- memoTextarea, timeInputCompact 도 amber → gray 로 통일
- 우측 하단 접힘 스티키 노트 커스텀 아이콘 MemoIcon 추가
- 메모 트리거 및 편집/읽기 모듈에서 lucide StickyNote → MemoIcon 교체

* fix:전역 폰트 사이즈 키우기 작업

* feat(schedule): 시간 표시를 AM/PM 시작~종료 range 로 개선

- formatScheduleTimeRange 신설 - "10:00 AM – 11:00 AM" 형식으로 시작·종료 시간 range 표시 (12시간제)
- 카드 안에 초록 필로 시간 range 표시 (contentColumn 하단 mt-auto 배치)
- 시간 트리거 버튼 라벨을 "출발-도착시간 추가/staySummary" → "시간 설정" 으로 단순화
- 기존 readOnlySummary 블록 제거 - 시간 표시가 read/edit 모드 공통 처리됨

* refactor(schedule): 카드 사이즈·padding 재조정 및 트리거 하단 이동

- 카드 padding p-3 → p-3.5, gap-x-3 → gap-x-3.5, titleRow gap-2 → 2.5 로 상향
- 썸네일·컨텐츠 컬럼 사이즈 7rem → 6rem (min-h 도 동기화), Image sizes hint 96px 로 수정
- primaryTypeBadge text-base → text-xs 로 다시 컴팩트하게
- 메모/시간 트리거 row 를 contentColumn 안 → 카드 하단 col-span-2 로 이동 (border-t 로 구분선 추가)

* style(schedule): 일차 헤더 재구성 및 sky/ivory 컬러 팔레트 정리

- 일차 헤더에서 날짜(subtitle) 를 primary 위치로 이동, 일차 번호(title) 는 sky 필로 우측 부착
- 헤더 행 전체에 아이보리 배경(#FFFFF0) 적용, 접힘 상태에 따라 rounded-2xl / rounded-t-2xl 조건부 처리
- schedule item 카드 트리거 버튼: brand-green → sky-600 (hover 시 sky-50 배경 + sky-600 텍스트), active 상태에 font-semibold 추가

* refactor(rooms): destinations 배열 계약으로 API·캐시·지도 마이그레이션

- RoomCreate/Update/Detail/ListItem 타입의 destination을 destinations: string[]로 교체
- role 타입을 "HOST" | "MEMBER"로 좁힘
- useCreateRoom·useUpdateRoom·syncRoomDetailFromServer 캐시 갱신을 destinations 기준으로 변경
- Map 부트스트랩 destination은 destinations[0] 기준으로 파생
- RoomCard 요약을 destinations.join(", ")로 표시
- 폼 상태는 임시로 destinations[0] ↔ [destination] 브릿지 유지 (다음 커밋에서 다중 입력 UI 도입)

* feat(rooms): DestinationChipsField 다중 여행지 입력 UI 도입

- DestinationChipsField: Google Places 선택 시 칩으로 추가, X로 삭제, 최대 5개
- 중복·초과 시 인라인 경고 노출, 각 항목 100자 clamp
- TripFormValues.destinations: string[]로 전환, isTripDestinationsValid·areDestinationsEqual 헬퍼 추가
- TripFormFields "목적지" 카드 헤더에 n/5 카운터 추가
- 방 생성·수정 폼 상태를 배열로 통합하고 submit 시 서버 계약대로 전송

* feat(rooms): 여행지 서버 에러 코드 한국어 매핑

- INVALID_DESTINATIONS_SIZE / DUPLICATE_DESTINATION 코드 상수·기본 메시지 추가
- messageForRoomDestinationError 헬퍼로 코드→한국어 변환
- createRoom·updateRoom이 헬퍼를 통해 사용자 표시 문구를 던지도록 조정

* refactor(plan): 일정 시간 설정 UI를 카드 하단 인라인에서 모달로 전환

- 오버레이·다이얼로그 셸(rounded-3xl, backdrop blur)로 감싸고 Esc·백드롭 클릭으로 닫히도록 변경
- 헤더에 시계 아이콘·"시간 설정" 타이틀 배치, 카드 이벤트 stopPropagation 관련 코드는 모달 격리로 정리
- 시간·체류 입력 필드와 오버랩 경고 문구는 기존 로직 유지

* refactor(plan): 프론트 인접 일정 시간 겹침 판정 로직 제거

- scheduleAdjacentConflicts 유틸 및 관련 prop/렌더 제거
- 시간 편집 모달 자동 오픈·경고 배너 제거
- 미사용 overlapWarningCompact 토큰 정리

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(place): 상세창 히어로 단일 이미지화 및 사진 탭 제거

- HeroGrid(3분할) → HeroImage(단일)로 축소
- 탭 구조 [홈][리뷰][사진] → [홈][리뷰]
- PhotosTab·PhotoLightbox 삭제 및 히어로 클릭 핸들러 제거

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* style(place): 상세 패널 폭 확장 및 내부 padding 상향

- 폭 300px → 360px, off-screen translate 350 → 400px
- 헤더/홈/리뷰 탭 padding px-4 → px-5, py-3 → py-4

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(chat): 사용자 메시지 내 URL 자동 하이퍼링크 처리

- 공용 renderTextWithLinks 유틸 추가 (http(s)://, www. 매칭 + 후행 문장부호 분리)
- ChatMarkdownContent preserveWhitespace 경로에도 URL 링크화 적용
- 메모의 renderMemoWithLinks 로컬 유틸을 공용 유틸로 치환
- chat-markdown-components 의 linkClassName 헬퍼를 export 하여 variant 별 링크 색상 공유

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* style(schedule): 여행지 제목 크기 한 단계 축소

- PLAN_PLACE_CARD_TW.titleCompact: text-lg → text-base

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(places): 장소 사진 API에 size 프리셋 파라미터 반영

- PhotoSize 타입 (THUMB|CARD|HERO|GALLERY) 및 DEFAULT_PHOTO_SIZE=CARD 도입
- requestPlacePhotoUrl / requestPlacePhotoUrlsBatch 에 size 파라미터 추가
- placePhotoUrlQueryKey 에 size 포함 → 해상도별 캐시 분리
- 컨텍스트별 프리셋 지정:
  - usePlanPlaceCardPhoto / OgPlacePreviewCard / PlaceShareCard / BookmarkPlacePreviewCard → CARD
  - HeroSection → HERO
- 시딩 경로 (usePlacesSearch, warmChatHistoryPlacePhotos, schedule-bulk-hydration) CARD 로 통일

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(schedule): 일정 항목 생성 시 프론트 기본 시간 값 제거

- API 계약 완화: RoomScheduleItemCreateRequest 의 startTime/durationMinutes optional
- 응답 타입 RoomScheduleItem 도 nullable (string|null, number|null) 로 조정
- createScheduleItemAtPlanIndex 에서 startTimeHm·durationMinutes 하드코딩 삭제
- useCreateScheduleItem startTimeHm 파라미터 및 기본값 계산 제거
- AddFromBookmarkModal / AddToScheduleModal 의 default 계산 호출 제거
- 미사용된 defaultNewItemStartTimeHm* 유틸 삭제
- PlanPlace 변환 지점(null→undefined) 정합성 반영

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Revert "feat(places): 장소 사진 API에 size 프리셋 파라미터 반영"

This reverts commit ce38e13.

* feat(schedule): 체류시간 0 을 명시적 값으로 허용

- formatScheduleStaySummary/TimeRange 가 null/undefined(미설정)과 0(명시적 0분)을 구분
- PlanPlaceCard 가 durationMinutes 를 ?? 0 없이 raw 로 전달
- 0 저장 시 카드에 "시작 – 시작" range 및 "0분" 표기가 정상 노출

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(schedule): 체류시간 0 range 표기를 "시작~" 로 변경

- formatScheduleTimeRange: dur === 0 일 때 "9:00 AM – 9:00 AM" 대신 "9:00 AM~" 반환

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* style(schedule): 일차 헤더 아이보리 배경 제거

- PlanDaySection 헤더 행에서 bg-[#FFFFF0] 삭제, 컨테이너 흰색 배경으로 통일

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(plan): 시간 설정 초기화 동작 수정

- 시간 초기화 시 시작 시각과 체류시간을 미설정 상태로 저장

- 출발시간이 없을 때 체류시간 입력과 저장을 차단

- 최초 시간 설정 중에도 초기화 버튼이 표시되도록 개선

* feat(place): Google Maps 리뷰 전체보기 링크 추가

- 장소 상세 googleMapsLinks 응답에서 리뷰 URL을 매핑
- 리뷰 수집 여부와 관계없이 외부 리뷰 링크를 조건부 표시

* feat(maps): Google Maps 장소 및 길찾기 링크 추가

- 장소 상세의 권장 googleMapsLinks.placeUri를 홈 탭에 연결
- 일정 구간의 Place ID와 이동수단으로 길찾기 링크 생성

* fix(layout): 세로 스크롤바 레이아웃 흔들림 방지

- overflow-y-auto 컨테이너에 안정적인 scrollbar gutter 적용
- textarea를 제외하고 채팅 스크롤 영역을 명시적으로 보강

* fix(place): 리뷰 평점 요약 UI 정확성 개선

- 표본 리뷰 기반 별점 분포 제거
- Google 평점과 전체 리뷰 수 및 일부 리뷰 안내 표시
- 평점 요약 UI 회귀 테스트 추가

* feat(plan): 이동 수단 공유 저장 백엔드 연동 복원 (#111)

* feat(plan): 이동 수단 공유 저장 백엔드 연동 복원

- PATCH …/items/{itemId}/travel-mode API·useUpdateScheduleItemTravelMode 복원, 선택 시 낙관적 반영 후 실패하면 서버 값으로 복귀
- 이동 수단 원천을 sessionStorage에서 서버 schedule_items.travel_mode(place.travelMode)로 전환, 모드 영속화(NS_MODE) 제거
- routes/batch 요청에서 travelMode 생략해 서버 저장값 사용, 응답의 실제 수단으로 캐시 시딩
- PlanTravelTime을 저장 수단 상시 조회 + 나머지 수단 메뉴 오픈 시 지연 조회 구조로 단순화
- SCHEDULE_ITEM_TRAVEL_MODE_UPDATED STOMP 이벤트 복원, 수신 시 일정 항목 재조회로 동기화
- usePrefetchScheduleDrivingRoutes → usePrefetchScheduleRoutes 등 DRIVING 전제 명칭 정리

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(lint): worktree 사본을 ESLint ignore에 추가

- 인자 없는 eslint 실행 시 .worktrees/ 내부 사본과 각자의 .next 산출물까지 lint되어 메모리·시간 낭비(18k 문제, 2.5GB)가 발생하던 문제 방지

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(plan): 경로 batch 시딩에 일정 지문 가드 추가

- in-flight dedup 키를 출발 항목 목록에서 일정 지문으로 교체해 목적지만 바뀐 일정이 이전 batch Promise를 공유하지 않게 함
- batch 응답 도착 시 현재 캐시의 일정 지문과 대조해 불일치하면 시딩을 버려 옛 목적지 경로가 새 구간 캐시에 남는 레이스 차단
- 가드 동작 단위 테스트 추가

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: parkjuyeong0312 <dcmo77777@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
fix(plan): 메모 첫 줄바꿈이 사라지는 문제 수정
feat: 모바일 사용성 개선 및 랜딩/인증 UI 정리
feat: 아이콘을 이미지에서 벡터이미지로 변환
feat: 카카오톡 공유 메세지 수정
fix(join): 카카오 초대 링크 대표 이미지 노출 개선
feat(plan): Routes API 기반 일정 경로 조회와 실시간 갱신 개선 (#114)
* feat(analytics): GA 이벤트와 분석 필드 확장

- 로그인·계획·초대·장소·채팅·일정 이벤트를 추가

- 원문 검색어와 URL 쿼리 대신 저카디널리티 버킷을 전송

- 프론트 기존 응답과 상태만 사용해 이벤트 컨텍스트를 구성

* feat(analytics): GA user_id 동기화

- 로그인·세션 복원 시 내부 회원 ID를 설정

- 로그아웃·세션 만료 시 user_id를 해제

- 분석 동의가 있을 때만 GA 식별 명령을 전송

* feat(analytics): GA 측정 신뢰성과 운영 기준 개선

- 자동 페이지뷰를 끄고 동적 경로와 referrer를 정규화
- GA 권장 이벤트명과 이벤트별 파라미터 타입 적용
- 디버그 런타임, 회귀 테스트, GA4 운영 체크리스트 추가

* feat(analytics): 튜토리얼 GA4 이벤트 추적 추가

* docs(analytics): GA4 검색 개인정보 보호 설계

- 검색어 원문 비수집 원칙과 이벤트 명세를 확정
- 향상된 측정 사이트 검색 비활성화 기준을 정의

* docs(analytics): GA4 검색 보호 구현 계획 추가

- 이벤트 명세 변경의 TDD 절차를 정의
- 운영 문서와 전체 검증 단계를 구체화

* feat(analytics): 검색 결과 이벤트 명세 정비

- 검색 결과 이벤트를 view_search_results로 변경
- 검색어 없는 저카디널리티 파라미터 타입을 검증

* docs(analytics): 검색어 비수집 운영 절차 추가

- 향상된 측정의 사이트 검색 비활성화를 명시
- DebugView와 수집 요청 검증 기준을 보강

* docs(analytics): 동의 설정 설계 추가

- 동의 변경과 철회 시 GA 처리 흐름 정의
- 기존 쿠키 호환과 버전 정책 및 검증 기준 명시

* docs(analytics): 동의 설정 구현 계획 추가

- 쿠키 버전과 Consent Mode 및 철회 흐름을 테스트 단위로 분해
- 공개 설정 화면과 진입점 및 운영 검증 순서 명시

* feat(analytics): 동의 쿠키 버전 관리 추가

- 기존 동의 값을 유지하며 v1 형식으로 저장\n- 철회 시 GA 쿠키를 도메인별로 만료

* feat(analytics): 분석 동의 상태 저장소 추가

- 배너와 설정 화면이 공유할 구독형 상태 제공
- 쿠키 저장 실패에도 현재 탭의 선택 유지

* feat(analytics): 분석 동의 철회 흐름 추가

- Consent Mode v2 명령 순서와 재허용 처리\n- 공용 동의 저장소로 이벤트와 페이지뷰 전송 차단

* test(analytics): 동의 세션 전송 게이트 검증

- 쿠키 저장 실패 시 페이지뷰와 일반 이벤트 차단 확인\n- User ID 전송의 동의 상태별 게이트 검증

* feat(analytics): 개인정보 동의 설정 화면 추가

- 공개 설정 페이지에서 분석 쿠키 허용과 철회 지원
- 쿠키 배너와 설정 화면이 공용 동의 상태 구독

* feat(analytics): 개인정보 설정 진입점 추가

- 홈 프로필 메뉴와 메인 사이드바에서 설정 페이지 연결

* docs(analytics): 동의 철회 운영 절차 추가

- 기본 동의 모드와 정책 버전 기준 기록
- 허용과 철회 및 재허용 배포 검증 절차 보강

* test(analytics): 동의 게이트 테스트 lint 정리

* fix(analytics): 동의 초기화 전 전송 차단

- Consent Mode 설정 완료까지 데이터 명령을 순서대로 보류
- 초기 철회 시 보류 명령 폐기와 설정 결과 메시지 연결

* fix(analytics): 승인 대기 합류 이벤트 보완

* test(analytics): 승인 상태 별칭 회귀 검증

* docs(analytics): 첫 페이지뷰 동기화 설계 추가

- 세션 확정 뒤 User-ID와 페이지뷰를 순서대로 전송하도록 정의
- 최신 경로 추적과 익명 예외 경로 검증 계획 추가

* feat(analytics): 세션 기반 페이지뷰 계획 추가

- 세션 준비와 사용자 쿼리 상태에 따라 첫 페이지뷰 전송을 대기
- 인증·비인증·오류·예외 경로의 User-ID 계획을 검증

* fix(analytics): 첫 페이지뷰와 User-ID 동기화

- 세션 확정 뒤 User-ID를 먼저 설정하고 페이지뷰를 전송
- 로그인·콜백 예외 경로를 익명으로 즉시 처리
- 빠른 경로 전환과 동일 경로 중복 방지 규칙을 공유

* docs(analytics): 첫 페이지뷰 검증 절차 추가

- 세션 확정과 User-ID 전송 순서를 운영 기준에 기록
- 인증·비인증·예외 경로와 빠른 리다이렉트 점검 항목 추가

* fix(analytics): 로그인 이탈 후 페이지뷰 대기 해소

- 최초 일반 경로와 세션 예외 경로 이탈 때만 세션을 재조정
- layout 단계에서 세션을 pending으로 전환해 stale User-ID 페이지뷰 차단
- 경로 전환과 이전 쿼리 상태 회귀 테스트 추가

* docs(analytics): 중앙 게이트와 리퍼러 보호 설계

- 데이터 명령의 런타임·동의 검사 중앙화
- 외부 리퍼러 origin-only 및 UTM 미수집 정책 정의

* fix(analytics): 데이터 전송 게이트 중앙화

- 런타임과 분석 동의를 공통 데이터 명령 경로에서 검사
- 초기화 전 보류 명령을 flush 시점에 다시 검증
- 페이지뷰, User-ID, 일반 이벤트의 중복 게이트 제거

* fix(analytics): 외부 리퍼러 정보 최소화

- 같은 출처 리퍼러만 정규화된 내부 경로 유지
- 외부 HTTP 리퍼러는 사용자 정보와 세부 경로를 제거하고 출처만 보존
- 비 HTTP URL과 전체 쿼리 파라미터 미수집 회귀 검증 추가

* docs(analytics): 전송 및 리퍼러 정책 명시

- 중앙 데이터 게이트와 제어 명령 분리 기록
- 외부 리퍼러 origin-only 및 UTM 미수집 정책 설명
- 거부 상태와 리퍼러 페이로드 배포 검증 항목 추가

* fix(analytics): gtag 명령 객체 형식 수정

- 공식 bootstrap과 동일하게 native Arguments 객체를 dataLayer에 저장
- 명령 형식 회귀 테스트와 기존 전송 assertion 정규화

* fix(analytics): 동의 쿠키 속성 구분자 수정

- HTTPS Secure 속성의 중복 세미콜론 제거
- HTTP 환경의 후행 구분자 방지 테스트 추가
@vercel

vercel Bot commented Jul 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontend-server Ready Ready Preview, Comment Jul 21, 2026 2:28pm

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

GA4 측정 런타임, 동의 저장소와 Consent Mode 전송 게이트, 세션 기반 페이지뷰, 이벤트별 파라미터 스키마, 제품 이벤트 계측, 개인정보 설정 화면 및 운영 문서가 추가·정비되었습니다.

Changes

GA4 측정 및 동의

Layer / File(s) Summary
분석 계약과 페이지뷰 컨텍스트
src/lib/analytics/context.ts, src/lib/analytics/runtime.ts, src/lib/analytics/track.ts
페이지 경로·리퍼러 정규화, 카운트 버킷, GA4 런타임 검증, 이벤트별 파라미터 타입과 튜토리얼 이벤트 생성 로직을 추가했습니다.
동의 저장소와 GA 전송
src/lib/analytics/client.ts, src/lib/analytics/consent-*, src/components/analytics/ConsentGatedAnalytics.tsx
버전 호환 동의 쿠키, 공유 동의 스토어, 허용·거부 액션, GA 초기화·FIFO 게이트·철회 처리를 추가했습니다.
세션 기반 페이지뷰
src/lib/analytics/page-view-session.ts, src/components/analytics/AnalyticsRouteTracker.tsx, src/lib/auth-session.ts
세션과 사용자 조회 상태에 따라 User-ID를 먼저 설정하고, 동일 경로 중복을 억제한 수동 page_view를 전송하도록 변경했습니다.
제품 이벤트 계측
src/app/(main)/*, src/components/*, src/hooks/*, src/lib/google-login-client.ts
검색·장소·일정·초대·채팅·튜토리얼·로그인 흐름에 버킷, 역할, 소스 기반 이벤트 파라미터를 연결했습니다.
개인정보 설정과 운영 문서
src/app/privacy-settings/page.tsx, src/components/analytics/PrivacySettingsLink.tsx, docs/analytics/*, docs/superpowers/*
개인정보 설정 페이지와 홈·사이드바 진입점을 추가하고 GA4 운영 기준 및 구현 설계 문서를 작성했습니다.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant ConsentStore
  participant ConsentGatedAnalytics
  participant GoogleAnalyticsScript
  participant AnalyticsRouteTracker
  participant GA4
  Browser->>ConsentStore: grant consent
  ConsentStore->>ConsentGatedAnalytics: update snapshot
  ConsentGatedAnalytics->>GoogleAnalyticsScript: load and initialize
  AnalyticsRouteTracker->>GA4: set user_id
  AnalyticsRouteTracker->>GA4: send one page_view
Loading

Possibly related issues

Possibly related PRs

  • uttae/frontend-server#115 — 초대 링크 공유 이벤트와 AddMemberPanel의 분석 페이로드 변경이 직접 연결됩니다.
  • uttae/frontend-server#120 — GA4 동의 게이트, 수동 페이지뷰, User-ID 철회, 검색어·리퍼러 정규화 경로가 직접 연결됩니다.

Suggested reviewers: song9252

Poem

토끼가 깡충, 동의를 눌러요
GA 쿠키는 살금살금 지워요
한 번의 페이지뷰, 길은 반듯해
검색어 원문은 당근 뒤에 숨겨요
버킷과 이벤트, 귀를 쫑긋
안전한 측정 완료! 🐇

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 main의 변경 사항을 dev로 백머지한다는 핵심 목적을 정확히 요약합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/layout/SidebarTutorial.tsx (1)

197-223: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

분석 이벤트 생성이 튜토리얼 완료 API 호출을 막을 수 있습니다.

buildTutorialExitAnalyticsEvent(reason, stepIndex)completeTutorial() 호출보다 먼저 실행됩니다. 이 함수는 skip_step이 범위를 벗어나면 RangeError를 던지도록 설계되어 있어(track.ts buildTutorialExitAnalyticsEvent), 만약 향후 TUTORIAL_STEPS 배열 크기가 변경되거나 stepIndex 계산에 회귀가 생기면 completeTutorial()이 전혀 호출되지 않고 사용자에게는 "완료 상태를 저장하지 못했어요"라는 오해성 메시지만 표시됩니다.

분석 이벤트 빌드/전송을 completeTutorial() 성공 이후로 옮기거나 별도의 try/catch로 감싸, 분석 이벤트 실패가 실제 완료 처리를 막지 않도록 하는 것을 권장합니다.

🛡️ 제안 수정
   try {
-      const analyticsEvent = buildTutorialExitAnalyticsEvent(reason, stepIndex);
       await completeTutorial();
+      let analyticsEvent: TutorialExitAnalyticsEvent | null = null;
+      try {
+        analyticsEvent = buildTutorialExitAnalyticsEvent(reason, stepIndex);
+      } catch {
+        analyticsEvent = null;
+      }

       queryClient.setQueryData<SessionUser | null>(
         sessionUserQueryKey,
         (current) =>
           current ? { ...current, tutorialCompleted: true } : current,
       );
-      if (analyticsEvent.eventName === AnalyticsEvents.tutorialSkip) {
-        trackAnalyticsEvent(analyticsEvent.eventName, analyticsEvent.params);
-      } else {
-        trackAnalyticsEvent(
-          analyticsEvent.eventName,
-          analyticsEvent.params,
-        );
-      }
+      if (analyticsEvent?.eventName === AnalyticsEvents.tutorialSkip) {
+        trackAnalyticsEvent(analyticsEvent.eventName, analyticsEvent.params);
+      } else if (analyticsEvent) {
+        trackAnalyticsEvent(analyticsEvent.eventName, analyticsEvent.params);
+      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/layout/SidebarTutorial.tsx` around lines 197 - 223, Update
finishTutorial so completeTutorial() executes before
buildTutorialExitAnalyticsEvent, or isolate analytics construction and tracking
in a separate failure-safe path after completion succeeds. Ensure analytics
errors cannot prevent the completion API call or trigger the completion-save
error message, while preserving the existing query-cache update and completion
state behavior.
🧹 Nitpick comments (2)
src/lib/join-room-workflow.ts (1)

50-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

WaitingApprovalDependencies 타입이 export되지 않아 테스트에서 재정의됨.

join-room-workflow.ts에 정의된 WaitingApprovalDependencies가 export되지 않아, 테스트 파일이 동일한 구조를 손으로 복제하고 있습니다. 소스 타입이 필드를 추가/변경해도 테스트의 로컬 복제본은 컴파일 타임에 이를 감지하지 못해 타입 안전성이 깨질 수 있습니다.

  • src/lib/join-room-workflow.ts#L50-L59: type WaitingApprovalDependenciesexport 키워드를 추가한다.
  • src/lib/join-room-completion.test.ts#L13-L22: 로컬 타입 재정의를 제거하고 @/lib/join-room-workflow에서 WaitingApprovalDependencies를 import해 사용한다.
♻️ 제안 diff
--- a/src/lib/join-room-workflow.ts
-type WaitingApprovalDependencies = {
+export type WaitingApprovalDependencies = {
   cacheRoomDetail: (roomId: string, detail: RoomDetail) => void;
   getRoomDetail: (roomId: string) => Promise<RoomDetail>;
   navigateToPlan: (path: string) => void;
   setCurrentRoomId: (roomId: string) => void;
   trackAnalyticsEvent: (
     eventName: typeof AnalyticsEvents.joinPlan,
     params: JoinPlanAnalyticsParams,
   ) => void;
 };
--- a/src/lib/join-room-completion.test.ts
-type WaitingApprovalDependencies = {
-  cacheRoomDetail: (roomId: string, detail: RoomDetail) => void;
-  getRoomDetail: (roomId: string) => Promise<RoomDetail>;
-  navigateToPlan: (path: string) => void;
-  setCurrentRoomId: (roomId: string) => void;
-  trackAnalyticsEvent: (
-    eventName: typeof AnalyticsEvents.joinPlan,
-    params: JoinPlanParams,
-  ) => void;
-};
+import { completeWaitingJoinApproval } from "`@/lib/join-room-workflow`";
+import type { WaitingApprovalDependencies } from "`@/lib/join-room-workflow`";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/join-room-workflow.ts` around lines 50 - 59, Export the
WaitingApprovalDependencies type from src/lib/join-room-workflow.ts (lines
50-59), then remove the duplicated local type definition in
src/lib/join-room-completion.test.ts (lines 13-22) and import
WaitingApprovalDependencies from `@/lib/join-room-workflow` for the test.
src/app/join/[inviteCode]/page.tsx (1)

59-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

입장 성공/대기 승인 완료 흐름을 공통 헬퍼로 묶어주세요.
getRoomDetail 조회 → 캐시 반영 → joinPlan 트래킹 → 이동 순서가 src/lib/join-room-workflow.tscompleteWaitingJoinApproval과 거의 같습니다. 지금처럼 두 곳에 나뉘어 있으면 재시도/에러 처리 정책이 바뀔 때 드리프트가 생기기 쉬우니, 이 흐름을 공통 헬퍼로 합치는 편이 좋습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/join/`[inviteCode]/page.tsx around lines 59 - 70, 입장 성공 처리와 대기 승인 완료
처리의 중복 흐름을 공통 헬퍼로 통합하세요. src/lib/join-room-workflow.ts의
completeWaitingJoinApproval을 기준으로 getRoomDetail 조회, roomDetailQueryKey 캐시 반영,
joinPlan 트래킹, 이동 순서를 하나의 헬퍼에서 처리하도록 정리하고, page.tsx에서는 해당 헬퍼를 호출해 기존 성공 및 메타 조회
실패 시 입장 진행 정책을 유지하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/`(main)/search/page.tsx:
- Around line 174-192: Update the search analytics effect around
trackAnalyticsEvent so result_count_bucket is calculated from the total search
result count rather than items.length, which only reflects the current page.
Expose or reuse the existing total-count value from the search response, include
it in the effect dependencies, and pass it to bucketResultCount while preserving
the existing tracking guards.

---

Outside diff comments:
In `@src/components/layout/SidebarTutorial.tsx`:
- Around line 197-223: Update finishTutorial so completeTutorial() executes
before buildTutorialExitAnalyticsEvent, or isolate analytics construction and
tracking in a separate failure-safe path after completion succeeds. Ensure
analytics errors cannot prevent the completion API call or trigger the
completion-save error message, while preserving the existing query-cache update
and completion state behavior.

---

Nitpick comments:
In `@src/app/join/`[inviteCode]/page.tsx:
- Around line 59-70: 입장 성공 처리와 대기 승인 완료 처리의 중복 흐름을 공통 헬퍼로 통합하세요.
src/lib/join-room-workflow.ts의 completeWaitingJoinApproval을 기준으로 getRoomDetail
조회, roomDetailQueryKey 캐시 반영, joinPlan 트래킹, 이동 순서를 하나의 헬퍼에서 처리하도록 정리하고,
page.tsx에서는 해당 헬퍼를 호출해 기존 성공 및 메타 조회 실패 시 입장 진행 정책을 유지하세요.

In `@src/lib/join-room-workflow.ts`:
- Around line 50-59: Export the WaitingApprovalDependencies type from
src/lib/join-room-workflow.ts (lines 50-59), then remove the duplicated local
type definition in src/lib/join-room-completion.test.ts (lines 13-22) and import
WaitingApprovalDependencies from `@/lib/join-room-workflow` for the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 290f9bb1-7f1c-4f28-a4bb-f5dd231e5073

📥 Commits

Reviewing files that changed from the base of the PR and between 4cd0bd2 and 188aed9.

📒 Files selected for processing (68)
  • docs/analytics/ga4-operations.md
  • docs/superpowers/plans/2026-07-20-ga4-measurement-hardening.md
  • docs/superpowers/plans/2026-07-21-ga4-consent-settings.md
  • docs/superpowers/plans/2026-07-21-ga4-page-view-user-id-sync.md
  • docs/superpowers/plans/2026-07-21-ga4-search-privacy.md
  • docs/superpowers/plans/2026-07-21-ga4-transport-referrer-hardening.md
  • docs/superpowers/specs/2026-07-21-ga4-consent-settings-design.md
  • docs/superpowers/specs/2026-07-21-ga4-page-view-user-id-sync-design.md
  • docs/superpowers/specs/2026-07-21-ga4-search-privacy-design.md
  • docs/superpowers/specs/2026-07-21-ga4-transport-referrer-hardening-design.md
  • src/app/(main)/member-settings/_components/AddMemberPanel.tsx
  • src/app/(main)/member-settings/_components/RoomMembersSection.tsx
  • src/app/(main)/plan/_components/itinerary/AddFromBookmarkModal.tsx
  • src/app/(main)/plan/_components/itinerary/PlanItinerary.tsx
  • src/app/(main)/plan/_components/itinerary/PlanPageView.tsx
  • src/app/(main)/plan/_components/itinerary/PlanPlaceCard.tsx
  • src/app/(main)/search/page.tsx
  • src/app/home/_components/HomeHeader.tsx
  • src/app/home/new/page.tsx
  • src/app/join/[inviteCode]/page.tsx
  • src/app/privacy-settings/page.tsx
  • src/app/waiting/page.tsx
  • src/components/analytics/AnalyticsConsentSettings.test.tsx
  • src/components/analytics/AnalyticsConsentSettings.tsx
  • src/components/analytics/AnalyticsRouteTracker.tsx
  • src/components/analytics/ConsentGatedAnalytics.tsx
  • src/components/analytics/GoogleAnalyticsScript.tsx
  • src/components/analytics/PrivacySettingsLink.test.tsx
  • src/components/analytics/PrivacySettingsLink.tsx
  • src/components/chat/messages/AiRecommendedPlacesList.tsx
  • src/components/chat/messages/PlaceShareCard.tsx
  • src/components/layout/SideBar.tsx
  • src/components/layout/SidebarTutorial.tsx
  • src/components/map/Map.tsx
  • src/components/map/MapBookmarkPins.tsx
  • src/components/map/MapDiscoverPlaces.tsx
  • src/components/map/PlanItineraryMapRoutes.tsx
  • src/components/place/AddToBookmarkModal.tsx
  • src/components/place/AddToScheduleModal.tsx
  • src/components/place/PlaceDetailPanel.tsx
  • src/contexts/SelectedPlaceContext.tsx
  • src/hooks/useChatActions.ts
  • src/hooks/usePlanItineraryReorder.ts
  • src/lib/analytics/client.test.ts
  • src/lib/analytics/client.ts
  • src/lib/analytics/consent-actions.test.ts
  • src/lib/analytics/consent-actions.ts
  • src/lib/analytics/consent-cookie.test.ts
  • src/lib/analytics/consent-cookie.ts
  • src/lib/analytics/consent-store.test.ts
  • src/lib/analytics/consent-store.ts
  • src/lib/analytics/context.test.ts
  • src/lib/analytics/context.ts
  • src/lib/analytics/page-view-session.test.ts
  • src/lib/analytics/page-view-session.ts
  • src/lib/analytics/paths.ts
  • src/lib/analytics/runtime.test.ts
  • src/lib/analytics/runtime.ts
  • src/lib/analytics/track.test.ts
  • src/lib/analytics/track.ts
  • src/lib/auth-session.test.ts
  • src/lib/auth-session.ts
  • src/lib/auth.ts
  • src/lib/client-storage.ts
  • src/lib/google-login-client.ts
  • src/lib/join-room-completion.test.ts
  • src/lib/join-room-workflow.ts
  • src/providers/root-providers.tsx

Comment on lines +174 to +192
useEffect(() => {
if (searchGeneration === 0 || !isSuccess || isFetching) return;
if (lastTrackedSearchGenerationRef.current === searchGeneration) return;
if (!query.trim() || searchCoords === null) return;

lastTrackedSearchGenerationRef.current = searchGeneration;
trackAnalyticsEvent(AnalyticsEvents.search, {
result_count_bucket: bucketResultCount(items.length),
search_mode: searchModeRef.current,
});
}, [
isFetching,
isSuccess,
items.length,
query,
searchCoords,
searchGeneration,
]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# usePlacesSearch가 페이지 단위 items만 반환하는지, 전체 결과 수를 별도로 노출하는지 확인
fd usePlacesSearch
rg -n "totalCount|total_count|resultCount|hasNextPage|items" src/hooks/usePlacesSearch.ts -A 3 -B 3

Repository: uttae/frontend-server

Length of output: 2070


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- search page relevant lines ---'
rg -n "PLACES_SEARCH_PAGE_SIZE|bucketResultCount|trackAnalyticsEvent|searchGeneration|items.length" src/app/(main)/search/page.tsx -A 3 -B 3

echo
echo '--- bucketResultCount definition ---'
rg -n "function bucketResultCount|const bucketResultCount" src -A 6 -B 2

echo
echo '--- places search page size ---'
rg -n "PLACES_SEARCH_PAGE_SIZE" src -A 2 -B 2

Repository: uttae/frontend-server

Length of output: 247


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- search page relevant lines ---'
rg -n "PLACES_SEARCH_PAGE_SIZE|bucketResultCount|trackAnalyticsEvent|searchGeneration|items.length" 'src/app/(main)/search/page.tsx' -A 4 -B 4

echo
echo '--- bucketResultCount definition ---'
rg -n "function bucketResultCount|const bucketResultCount" src -A 6 -B 2

echo
echo '--- places search page size ---'
rg -n "PLACES_SEARCH_PAGE_SIZE" src -A 2 -B 2

Repository: uttae/frontend-server

Length of output: 5563


result_count_bucket에는 전체 검색 결과 수를 사용하세요. items는 현재 페이지 결과만 담고 있어 bucketResultCount(items.length)는 0~10 범위에 묶입니다. 전체 결과 수를 함께 노출해 그 값으로 버킷을 계산해야 합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`(main)/search/page.tsx around lines 174 - 192, Update the search
analytics effect around trackAnalyticsEvent so result_count_bucket is calculated
from the total search result count rather than items.length, which only reflects
the current page. Expose or reuse the existing total-count value from the search
response, include it in the effect dependencies, and pass it to
bucketResultCount while preserving the existing tracking guards.

@minbros
minbros merged commit 904256b into dev Jul 21, 2026
2 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.

3 participants