Skip to content

promote: data의 지도 렌더 개선을 main으로 올린다 - #3052

Merged
twoimo merged 8 commits into
mainfrom
data
Sep 24, 2026
Merged

twoimo merged 8 commits into
mainfrom
data

Conversation

@twoimo

@twoimo twoimo commented Sep 24, 2026

Copy link
Copy Markdown
Owner

Summary

  • 지도 렌더의 반복 페인트와 마커 할당 감소, 스탬프 카드 리뷰 문장 제거, 관리자 지도 오버레이 제거를 프로덕션 브랜치로 올립니다.
  • 롤백 대상은 현재 프로덕션 95e598e37ff2eb86efe21951b8c79d2bc890d44a (dpl_5MC4XZygfsu8a7MRmeTKBreUDFRN, www.tzudong.app)입니다.

Test plan

  • Promotion Path와 Release가 통과하는지 확인
  • 머지 커밋에 TZUDONG_APPROVED_PRODUCTION_SHA를 맞춘 뒤 프로덕션 배포를 읽기 확인

Made with Cursor

twoimo and others added 8 commits September 25, 2026 00:31
지도 렌더의 반복 페인트와 마커 할당을 줄인다
…60925

sync: data의 승격 머지를 develop에 포함한다
promote: develop의 지도 렌더 개선을 data로 올린다
…60925

sync: main의 승격 머지를 develop에 포함한다
promote: develop의 지도 렌더 개선을 data로 올린다
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-24T16:00:09.680789Z 4c8684b PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@twoimo
twoimo merged commit 9b408de into main Sep 24, 2026
15 of 22 checks passed

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment on lines +8 to +9
export function resolveDeviceLocationReadiness(): DeviceLocationReadiness {
return { status: 'available', reasonCode: DEVICE_LOCATION_AVAILABLE };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +535 to +539
...(sort === 'popular'
? ([
['order', 'weekly_search_count.desc'],
['order', 'created_at.desc'],
] as RestQuery)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +203 to +209
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 ?? '',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +151 to +155
const push = (value: unknown, record?: RestaurantLike) => {
if (typeof value !== 'string') {
if (Array.isArray(value)) value.forEach((entry) => push(entry, record));
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.

P2 Badge 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 👍 / 👎.

Comment on lines +71 to +76
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');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +47 to +51
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +74 to +76
const probesPlaceholder = candidateDimensions?.width === 1280;
useEffect(() => {
if (!src) return;
if (!src || !probesPlaceholder) 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.

P2 Badge 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 👍 / 👎.

Comment on lines +487 to +488
const reviewLikesById = await fetchReviewLikeTotals(restaurants.map((restaurant) => restaurant.id));
restaurants.sort((a, b) => comparePopularRestaurants(a, b, reviewLikesById));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +470 to +472
['weekly_search_count', 'gt.0'],
['order', 'weekly_search_count.desc'],
['limit', fetchLimit],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +487 to +489
const reviewLikesById = await fetchReviewLikeTotals(restaurants.map((restaurant) => restaurant.id));
restaurants.sort((a, b) => comparePopularRestaurants(a, b, reviewLikesById));
restaurants.splice(limit);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

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