Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
지도 렌더의 반복 페인트와 마커 할당을 줄인다
…60925 sync: data의 승격 머지를 develop에 포함한다
promote: develop의 지도 렌더 개선을 data로 올린다
…60925 sync: main의 승격 머지를 develop에 포함한다
promote: develop의 지도 렌더 개선을 data로 올린다
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4c8684be68
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| export function resolveDeviceLocationReadiness(): DeviceLocationReadiness { | ||
| return { status: 'available', reasonCode: DEVICE_LOCATION_AVAILABLE }; |
There was a problem hiding this comment.
Restore the fail-closed device-location gate
In every environment, including production with no operator or legal evidence configured, this now reports available; the readiness route consequently mints a location-use capability and lets a consenting user transmit precise coordinates to the map provider. The removed environment checks were the only release-evidence gate, so absence or invalidity of those inputs must continue to return unavailable rather than silently enabling the feature.
AGENTS.md reference: AGENTS.md:L37-L37
Useful? React with 👍 / 👎.
| ...(sort === 'popular' | ||
| ? ([ | ||
| ['order', 'weekly_search_count.desc'], | ||
| ['order', 'created_at.desc'], | ||
| ] as RestQuery) |
There was a problem hiding this comment.
Paginate with the same score used for popular ordering
When the 인기순 list spans more than one 12-item page, these offsets partition rows by weekly_search_count, but each returned page is subsequently reordered by the new composite score and DesktopLeftPanelMapHome simply concatenates the pages. A high-composite restaurant assigned to page 2 can therefore never appear ahead of a lower-composite restaurant from page 1, producing discontinuous and globally incorrect popular ordering as the user loads more items.
Useful? React with 👍 / 👎.
| restaurant.mergedYoutubeLinks?.length ?? 0, | ||
| restaurant.mergedTzuyangReviews?.length ?? 0, | ||
| restaurant.mergedRestaurants?.length ?? 0, | ||
| restaurant.tzuyang_review ? 1 : 0, | ||
| Array.isArray(restaurant.categories) ? restaurant.categories[0] ?? '' : '', | ||
| Array.isArray(restaurant.category) ? restaurant.category[0] ?? '' : restaurant.category ?? '', | ||
| restaurant.source_type ?? '', |
There was a problem hiding this comment.
Key marker visuals by the resolved marker kind
When refreshed data keeps the same restaurant ID and number of merged rows but changes a merged row's source_type to user_submission_new, this cache key is unchanged because it records only mergedRestaurants.length and the root source. resolveRestaurantMarkerKind does inspect nested sources, but it runs only after the cache lookup, so the cached category marker is reused instead of displaying the required user-submitted marker until eviction.
Useful? React with 👍 / 👎.
| const push = (value: unknown, record?: RestaurantLike) => { | ||
| if (typeof value !== 'string') { | ||
| if (Array.isArray(value)) value.forEach((entry) => push(entry, record)); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Preserve object-backed Tzuyang reviews in detail entries
When tzuyang_reviews contains the object forms already supported by collectRestaurantMergedMedia—such as { review: "..." }—this new entry collector recursively handles arrays but drops every non-string object. RestaurantDetailPanel now renders only collectTzuyangReviewEntries, so these valid reviews disappear from the detail panel even though the merged-media collector finds them.
Useful? React with 👍 / 👎.
| const videoKey = extractCanonicalYouTubeVideoId(String(record.videoId ?? record.video_id ?? '')) | ||
| ?? [ | ||
| meta.title ?? '', | ||
| String(record.publishedAt ?? record.published_at ?? ''), | ||
| String(record.viewCount ?? record.view_count ?? ''), | ||
| ].join('\u0000'); |
There was a problem hiding this comment.
Deduplicate KPI snapshots by video identity
When the live KPI enrichment appends current metrics for a video whose stored youtube_meta has an older view count, neither metadata object contains videoId, and this fallback incorporates the changing view count into the identity. The same video is consequently counted twice for views, likes, comments, and even reappearances; for example, stored 100 views plus refreshed 200 views is scored as 300. The fallback must use a stable video identity rather than a metric that changes over time.
Useful? React with 👍 / 👎.
| if (currentMarker.getAttribute('style') !== nextMarker.getAttribute('style')) return false; | ||
|
|
||
| const currentImage = currentMarker.querySelector('img'); | ||
| const nextImage = nextMarker.querySelector('img'); | ||
| if ((currentImage?.getAttribute('src') ?? '') !== (nextImage?.getAttribute('src') ?? '')) return false; |
There was a problem hiding this comment.
Restrict the DOM fast path to review-bubble-only changes
When marker content changes without changing the root style or image URL—for example, a restaurant's visit count increases from one to two and adds the visit-count badge—this check treats the update as a bubble-only change. The function then patches only the review bubble, records the new HTML in currentIcon.content, and never updates the actual marker children, leaving the visit badge and its accessible count stale indefinitely.
Useful? React with 👍 / 👎.
| const probesPlaceholder = candidateDimensions?.width === 1280; | ||
| useEffect(() => { | ||
| if (!src) return; | ||
| if (!src || !probesPlaceholder) return; |
There was a problem hiding this comment.
Probe every non-final thumbnail candidate for placeholders
When sizing chooses sddefault as the first candidate and that rendition is unavailable, YouTube can return its 120×90 placeholder with HTTP 200. This condition runs the natural-dimension probe only for maxresdefault, so the sddefault placeholder neither advances through shouldTryNextYoutubeThumbnailCandidate nor triggers onError, and it is stretched across the card instead of falling back to hqdefault or lower candidates.
Useful? React with 👍 / 👎.
| const reviewLikesById = await fetchReviewLikeTotals(restaurants.map((restaurant) => restaurant.id)); | ||
| restaurants.sort((a, b) => comparePopularRestaurants(a, b, reviewLikesById)); |
There was a problem hiding this comment.
Include merged restaurant IDs when totaling review likes
When mergeRestaurants combines duplicate restaurant rows, reviews can remain associated with any child row ID, but this query requests likes only for each merged result's primary ID. The composite score therefore omits likes on mergedRestaurants even though review_count is summed across those same rows, causing otherwise equivalent merged restaurants to receive incomplete review-like scores.
Useful? React with 👍 / 👎.
| ['weekly_search_count', 'gt.0'], | ||
| ['order', 'weekly_search_count.desc'], | ||
| ['limit', fetchLimit], |
There was a problem hiding this comment.
Select candidates using the composite ranking signals
When more than fetchLimit approved restaurants have positive weekly searches, this query admits only the first group ordered by search count before YouTube, review, and review-like signals are loaded. A restaurant just outside that search-only cutoff can have a higher composite score than every admitted row but can never reach the advertised top list, so the new multi-signal comparator is only applied to a search-biased subset rather than the actual candidate population.
Useful? React with 👍 / 👎.
| const reviewLikesById = await fetchReviewLikeTotals(restaurants.map((restaurant) => restaurant.id)); | ||
| restaurants.sort((a, b) => comparePopularRestaurants(a, b, reviewLikesById)); | ||
| restaurants.splice(limit); |
There was a problem hiding this comment.
Align rank snapshots with the new composite ranking
The current rank is now assigned after comparePopularRestaurants, but private.capture_restaurant_popular_rank_snapshot still stores ranks ordered solely by weekly_search_count. Whenever views, reviews, or likes reorder two restaurants, attachPopularRankTrends compares ranks produced by different algorithms and shows false up/down/new badges. Version the snapshot producer to the composite definition through a new migration, or stop attaching those snapshots to composite ranks; do not edit the already-applied migration.
AGENTS.md reference: AGENTS.md:L37-L37
Useful? React with 👍 / 👎.
Summary
95e598e37ff2eb86efe21951b8c79d2bc890d44a(dpl_5MC4XZygfsu8a7MRmeTKBreUDFRN,www.tzudong.app)입니다.Test plan
TZUDONG_APPROVED_PRODUCTION_SHA를 맞춘 뒤 프로덕션 배포를 읽기 확인Made with Cursor