Skip to content

feat: add PWA install onboarding guide#9

Merged
seoJing merged 7 commits into
mainfrom
feature/spot-api-contract-sync
May 25, 2026
Merged

feat: add PWA install onboarding guide#9
seoJing merged 7 commits into
mainfrom
feature/spot-api-contract-sync

Conversation

@seoJing
Copy link
Copy Markdown
Member

@seoJing seoJing commented May 25, 2026

Summary

  • Add a PWA install guide step to the onboarding flow
  • Add manifest shortcuts for map, offer creation, and chat
  • Generate PWA, maskable, and Apple touch icons from the real Spot logo

Checks

  • pnpm test -- src/features/onboarding/model/pwa-install.test.ts
  • pnpm lint (passes with existing warnings only)
  • pnpm build
  • Notjing final gate: pass

Notes

  • This PR includes the existing tested commits already on feature/spot-api-contract-sync plus the PWA onboarding/icon commit.

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • PWA 설치 가이드가 온보딩에 추가되었습니다.
    • 채팅 드로어에 필터 바가 추가되어 대화를 카테고리별로 정렬할 수 있습니다.
    • 피드 항목이 지도 마커로 표시됩니다.
    • PWA 홈 화면 바로가기가 추가되었습니다.
  • 개선 사항

    • 온보딩 단계가 4단계로 확장되었습니다.
    • 피드 위치 정보 처리가 개선되었습니다.
    • 폼 입력 UI가 통일되었습니다.
    • 지도 클러스터 시각 표현이 개선되었습니다.

Review Change Stack

@vercel
Copy link
Copy Markdown

vercel Bot commented May 25, 2026

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

Project Deployment Actions Updated (UTC)
frontend Ready Ready Preview, Comment May 25, 2026 6:02am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 2026

Warning

Review limit reached

@seoJing, we couldn't start this review because you've used your available PR reviews for now.

Your plan includes 1 review of capacity. Refill in 36 minutes and 6 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more review capacity refills, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8cc7f28e-346f-4caf-b8f6-abbf3529f32a

📥 Commits

Reviewing files that changed from the base of the PR and between d93cd38 and 44e25b8.

📒 Files selected for processing (10)
  • src/features/chat/ui/ChatDrawer.test.tsx
  • src/features/chat/ui/ChatDrawer.tsx
  • src/features/feed/model/feed-map.test.ts
  • src/features/feed/ui/FeedBottomSheet.tsx
  • src/features/onboarding/model/pwa-install.test.ts
  • src/features/onboarding/model/pwa-install.ts
  • src/features/onboarding/ui/use-pwa-install-prompt.ts
  • src/features/post/ui/FormField.tsx
  • src/shared/model/auth-store.test.ts
  • src/shared/model/auth-store.ts
📝 Walkthrough

Walkthrough

이 PR은 PWA 설치 온보딩 기능, 피드 좌표 시스템, 지도 통합, 채팅 필터링 UI, 폼 컨트롤 라이브러리, 인증 영속화를 포함하는 대규모 기능 추가 및 리팩토링을 수행합니다.

Changes

PWA 설치 온보딩 기능

계층 / 파일 설명
PWA 매니페스트 및 앱 메타데이터
public/manifest.json, src/app/layout.tsx
PWA 시작 URL을 /map?source=pwa로 변경하고, 아이콘에 purpose 필드를 추가하며, 단축키 배열(지도/오퍼/채팅)을 정의합니다. Apple 웹 앱 메타데이터에 아이콘 설정을 추가합니다.
PWA 설치 감지 및 가이드 로직
src/features/onboarding/model/pwa-install.ts
사용자 브라우저를 감지하여 iOS/Android/데스크톱 플랫폼을 분류하고, 플랫폼별 설치 안내 단계와 단축키를 제공하는 함수를 구현합니다.
온보딩 단계 확장 및 가이드 UI
src/features/onboarding/model/types.ts, src/features/onboarding/client/OnboardingPageClient.tsx, src/features/onboarding/ui/PwaInstallGuide.tsx
온보딩 단계에 INSTALL을 추가하고, PWA 설치 안내를 렌더링하는 클라이언트 컴포넌트를 구현합니다.
PWA 설치 프롬프트 관리 훅
src/features/onboarding/ui/use-pwa-install-prompt.ts
브라우저의 beforeinstallprompt 이벤트를 감지하고 설치 상태를 관리하며, 플랫폼별 가이드를 메모이제이션하는 훅을 구현합니다.
PWA 설치 로직 테스트
src/features/onboarding/model/pwa-install.test.ts
플랫폼 감지, 가이드 조회, 단축키 제공, 독립 실행 판정 함수의 동작을 검증합니다.

피드 데이터, 맵 통합, 채팅, 폼, 인증

계층 / 파일 설명
피드 좌표 해석 및 검증
src/features/feed/model/feed-location.ts, src/features/feed/model/types.ts
FeedItem에서 좌표 데이터를 추출하여 정규화하는 resolveFeedCoordinate 함수를 구현하고, FeedItem 타입에 coord, lat, lng 필드를 추가합니다.
피드 API 타입 및 계층 인식 쿼리
src/features/feed/api/feed-api.ts, src/features/feed/model/feed-layer-filter.ts, src/features/feed/model/use-feed.ts
BackendFeedItem 타입을 좌표 필드 기준으로 확장하고, isAi 필터 파라미터를 추가하며, useLayerAwareFeedList 훅으로 활성 계층에 따라 피드 쿼리를 자동 조정합니다.
피드 필터링 로직
src/features/feed/model/feed-filter.ts, src/features/feed/model/feed-map.test.ts
피드 유형, 카테고리, 검색어 기준으로 항목을 필터링하고, 계층 값에 따라 AI 포함 여부를 결정하는 함수와 테스트를 구현합니다.
피드 조회 컴포넌트 통합
src/features/feed/ui/FeedBottomSheet.tsx, src/features/feed/ui/MapFeedCardPager.tsx
FeedBottomSheet와 MapFeedCardPager가 useLayerAwareFeedList와 필터링 헬퍼를 사용하도록 업데이트됩니다.
채팅 룸 타입 및 API 매핑
src/features/chat/model/types.ts, src/features/chat/api/chat-api.ts, src/features/chat/api/chat-api.test.ts
SpotChatRoom에 선택적 unreadCount를 추가하고, BackendRoom에 feedId/spotId 필드를 추가하며, 룸 카테고리 판정을 개선합니다.
채팅 드로어 필터링 UI 재설계
src/features/chat/ui/ChatDrawer.tsx, src/features/chat/ui/ChatDrawer.test.tsx
개인/피드/스팟 섹션 기반 UI를 상단 필터 바와 단일 목록으로 재설계하고, 필터 선택에 따라 룸을 표시/숨기는 동작을 검증하는 테스트를 추가합니다.
폼 입력 컴포넌트 라이브러리
src/features/post/ui/FormControls.tsx, src/features/post/ui/FormCard.tsx, src/features/post/ui/FormField.tsx
PostTextInput, PostTextarea, PostAddButton, PostRemoveButton, PostErrorMessage 등 표준화된 폼 컴포넌트를 구현하고, FormCard/FormField를 확장합니다.
포스트 폼 컨트롤 통합
src/features/post/client/*.tsx, src/features/post/ui/post-form/*.tsx
오퍼/요청/상세정보/계획/준비/가격 입력 섹션에서 기존 HTML 입력을 새로운 폼 컴포넌트로 교체합니다.
영수증 카드 가격 미리보기 재설계
src/features/post/ui/ReceiptCard.tsx
참여 인원별 누적 목록 기반 미리보기에서 1인당 예상 금액 단일 미리보기로 변경합니다.
이미지 업로드 UI 단순화
src/features/post/ui/ImageUploadGrid.tsx, src/features/post/ui/ImageUploadSlot.tsx
이미지 업로드 슬롯을 아이콘 중심에서 텍스트 중심 UI로 변경합니다.
맵 클러스터 타입 확장
src/features/map/model/types.ts
ActivityCluster와 ClusterInput의 variant를 discovery/ai-feed/user-feed/mine 조합으로 확장합니다.
클러스터 Blob 시각화 변형
src/features/map/ui/ClusterBlob.tsx
클러스터 variant에 따라 SVG 스타일, 애니메이션, 렌더링 조건을 분기하는 getVariantTone 헬퍼를 구현합니다.
맵에 피드 마커 및 카드 추가
src/features/map/client/MapClient.tsx, src/features/map/ui/MapFeedInfoCard.tsx, src/features/map/ui/SpotInfoCard.tsx
필터링된 피드 항목을 지도 오버레이로 변환하고, 피드 마커 선택 시 정보 카드를 렌더링하며, SpotInfoCard에 discovery 변형을 추가합니다.
온보딩 페르소나 영속화
src/shared/model/auth-store.ts, src/shared/model/auth-store.test.ts
사용자별 온보딩 페르소나를 localStorage에 저장/복원하고, 세션 변경 시 영속화를 관리합니다.

Estimated code review effort

🎯 4 (복잡함) | ⏱️ ~60분

Possibly related PRs

  • spot-platform/frontend#6: 채팅 및 피드 API 어댑터 변경(src/features/chat/api/chat-api.ts, src/features/feed/api/feed-api.ts)과 인증 영속화(src/shared/model/auth-store.ts)가 해당 PR의 백엔드 계약 정렬 작업과 동일한 모듈에서 겹칩니다.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목 'feat: add PWA install onboarding guide'는 PWA 설치 온보딩 가이드 기능 추가라는 주요 변경사항을 명확하고 간결하게 요약하고 있으며, 실제 변경사항과 완벽하게 일치합니다.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/spot-api-contract-sync

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
src/shared/model/auth-store.ts (1)

116-117: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

resetPersona()가 영속 저장소를 지우지 않아 리셋이 유지되지 않습니다.

Line 112에서 사용자별 persona를 localStorage에 저장했는데, 여기서는 메모리 상태만 비웁니다. 그래서 resetPersona() 뒤 같은 사용자가 다시 setSession()되면 Line 73-89 경로로 이전 persona가 바로 복원되고, src/features/auth/model/use-login-form.ts:52-57 기준으로도 다시 /onboarding을 밟지 못하게 됩니다.

🔧 예시 수정
+function removeStoredOnboardingPersona(userId: string) {
+    if (!canUseLocalStorage()) return;
+
+    try {
+        const { [userId]: _removed, ...rest } = readStoredOnboardingPersonas();
+        window.localStorage.setItem(
+            ONBOARDING_PERSONA_STORAGE_KEY,
+            JSON.stringify(rest),
+        );
+    } catch {
+        // localStorage 접근 실패는 reset 동작을 막지 않는다.
+    }
+}
+
             resetPersona: () => {
-                set({ userPersona: null, hasCompletedOnboarding: false });
+                set((state) => {
+                    const targetUserId = state.userPersona?.userId ?? state.userId;
+                    if (targetUserId) {
+                        removeStoredOnboardingPersona(targetUserId);
+                    }
+
+                    return {
+                        userPersona: null,
+                        hasCompletedOnboarding: false,
+                    };
+                });
             },
🤖 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/shared/model/auth-store.ts` around lines 116 - 117, resetPersona()
currently only clears in-memory state (userPersona, hasCompletedOnboarding) but
does not remove the persisted persona in localStorage, so the persona is
immediately restored when setSession() runs; update resetPersona to also remove
the same localStorage key used to persist userPersona (the key written where
userPersona is saved), e.g. call localStorage.removeItem(...) for that key
inside resetPersona, ensuring the persisted persona is cleared along with the
in-memory state so onboarding can be re-triggered.
🧹 Nitpick comments (4)
src/features/chat/ui/ChatDrawer.tsx (1)

241-259: ⚡ Quick win

필터별 목록도 동일한 최신순 정렬을 적용해 주세요.

Line 254-Line 258에서 allRooms만 정렬되고, personal/feed/spot 탭은 원본 순서로 보여서 탭 전환 시 정렬 기준이 달라집니다.

정렬 일관성 개선 예시
     const { personalRooms, feedRooms, spotRooms, allRooms } = useMemo(() => {
+        const byUpdatedAtDesc = (left: ChatRoom, right: ChatRoom) =>
+            new Date(right.updatedAt).getTime() -
+            new Date(left.updatedAt).getTime();
+
         const personal: PersonalChatRoom[] = [];
         const feed: SpotChatRoom[] = [];
         const spot: SpotChatRoom[] = [];
         for (const room of rooms) {
             if (room.category === 'personal') personal.push(room);
             else if (room.sourceFeedId) feed.push(room);
             else spot.push(room);
         }
         return {
-            personalRooms: personal,
-            feedRooms: feed,
-            spotRooms: spot,
-            allRooms: [...personal, ...feed, ...spot].sort(
-                (left, right) =>
-                    new Date(right.updatedAt).getTime() -
-                    new Date(left.updatedAt).getTime(),
-            ),
+            personalRooms: [...personal].sort(byUpdatedAtDesc),
+            feedRooms: [...feed].sort(byUpdatedAtDesc),
+            spotRooms: [...spot].sort(byUpdatedAtDesc),
+            allRooms: [...personal, ...feed, ...spot].sort(byUpdatedAtDesc),
         };
     }, [rooms]);

Also applies to: 277-282

🤖 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/features/chat/ui/ChatDrawer.tsx` around lines 241 - 259, The per-tab
arrays (personalRooms, feedRooms, spotRooms) are left in original order while
only allRooms is sorted; update the useMemo block so each collection is sorted
by updatedAt in the same descending order as allRooms (e.g., create or apply a
sort-by-updatedAt comparator and call it on personal, feed, spot before
returning them, keeping the existing allRooms construction intact) — look for
the useMemo, variables personalRooms/feedRooms/spotRooms/allRooms, rooms and the
updatedAt fields to apply the change.
src/features/post/ui/FormField.tsx (1)

6-7: 🏗️ Heavy lift

FormFieldhtmlFor 연결 경로를 추가해 접근성 연동을 보강해주세요.

Line 23의 <label>이 현재 htmlFor 없이 렌더링되어, 라벨-입력 연결을 호출부에서 만들 수 없습니다. FormField에서 htmlFor를 받아 전달할 수 있게 열어두는 편이 안전합니다.

제안 diff
 interface FormFieldProps {
     label: string;
     required?: boolean;
     labelSize?: 'display' | 'compact';
+    htmlFor?: string;
     children: ReactNode;
 }

 export function FormField({
     label,
     required,
     labelSize = 'display',
+    htmlFor,
     children,
 }: FormFieldProps) {
@@
-            <label className={labelClass}>
+            <label htmlFor={htmlFor} className={labelClass}>
                 {label}
                 {required && <span className="text-red-400 ml-0.5">*</span>}
             </label>

Also applies to: 10-15, 23-23

🤖 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/features/post/ui/FormField.tsx` around lines 6 - 7, The FormField
component is rendering a <label> without an htmlFor, preventing callers from
linking labels to inputs; update the FormField props (e.g., add htmlFor?: string
alongside labelSize and children) and pass that prop into the rendered <label>
element (label htmlFor={htmlFor}) so callers can provide the input id and
restore accessibility linkage; adjust any TypeScript types or prop forwarding in
the FormField function to accept and forward htmlFor.
src/features/feed/model/feed-map.test.ts (1)

22-91: ⚡ Quick win

resolveFeedCoordinate의 문자열 좌표/coordinate 경로도 테스트에 포함해 주세요.

현재 테스트는 핵심 폴백 순서는 잘 검증하지만, 실제 백엔드 응답에서 들어올 수 있는 coordinate: { lat: "37.5", lng: "127.0" } 형태를 놓치고 있습니다. 이 케이스를 추가하면 좌표 정규화 회귀를 더 빨리 잡을 수 있습니다.

🤖 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/features/feed/model/feed-map.test.ts` around lines 22 - 91, 테스트에 문자열 좌표와
coordinate 경로 케이스를 추가해 resolveFeedCoordinate가 "coordinate: { lat: '37.5', lng:
'127.0' }" 또는 coordinate 경로(예: coord vs coordinate)에서 문자열 값을 숫자로 정상 변환하는지 검증하세요;
구체적으로 feed-map.test.ts에 makeFeed를 사용해 item에 coordinate: { lat: '37.2636', lng:
'127.0286' }(또는 coord/primaryPin 혼합 케이스) 를 넣고 resolveFeedCoordinate(item) 결과가 숫자
타입의 { lat: 37.2636, lng: 127.0286 }인지 기대값으로 assert하도록 추가하십시오.
src/features/feed/ui/FeedBottomSheet.tsx (1)

60-84: ⚡ Quick win

필터 로직을 filterVisibleFeedItems로 통합해 중복을 제거해 주세요.

여기 필터 구현이 src/features/feed/model/feed-filter.ts와 사실상 동일해서, 조건 변경 시 바텀이랑 맵이 쉽게 불일치할 수 있습니다. 공용 함수 호출로 통일하는 편이 안전합니다.

제안 diff
 import { isSearchExcludedFeedItem, type FeedItem } from '../model/types';
+import { filterVisibleFeedItems } from '../model/feed-filter';
@@
-    const filtered = feedItems.filter((item) => {
-        if (feedType === 'offer' && item.type !== 'OFFER') return false;
-        if (feedType === 'request' && item.type !== 'REQUEST') return false;
-        if (
-            categories.length > 0 &&
-            (!item.category ||
-                !categories.includes(item.category as SpotCategory))
-        )
-            return false;
-        if (normalizedQuery.length > 0) {
-            if (isSearchExcludedFeedItem(item)) return false;
-
-            const haystack = [
-                item.title,
-                item.description ?? '',
-                item.category ?? '',
-                item.location,
-                item.authorNickname,
-            ]
-                .join(' ')
-                .toLowerCase();
-            if (!haystack.includes(normalizedQuery)) return false;
-        }
-        return true;
-    });
+    const filtered = filterVisibleFeedItems(feedItems, {
+        feedType,
+        categories,
+        searchQuery: normalizedQuery,
+    });
🤖 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/features/feed/ui/FeedBottomSheet.tsx` around lines 60 - 84, The inline
filter logic that computes filtered duplicates the shared logic in
filterVisibleFeedItems; replace the manual .filter callback on feedItems with a
call to filterVisibleFeedItems(feedItems, { feedType, categories, query:
normalizedQuery }) (or the correct parameter shape used in
src/features/feed/model/feed-filter.ts), remove the duplicated checks (including
references to isSearchExcludedFeedItem, category/feedType checks and haystack
search), and add the necessary import for filterVisibleFeedItems at the top of
FeedBottomSheet.tsx so the bottom sheet and map use the single shared filter
implementation.
🤖 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/features/onboarding/model/pwa-install.ts`:
- Around line 35-42: The current device detection uses the normalized UA and
flags isIOS, isAndroid, isChromium, but iPadOS UA that contains "Macintosh" +
"Mobile" is misclassified as desktop; update the isIOS logic to also treat a UA
as iOS when normalized includes 'macintosh' AND 'mobile' (e.g., change isIOS to:
/iphone|ipad|ipod/.test(normalized) || (normalized.includes('macintosh') &&
normalized.includes('mobile'))), then keep the existing branching (use isIOS,
isAndroid, isChromium) so iPadOS returns 'ios-safari' instead of 'desktop'.

In `@src/features/onboarding/ui/use-pwa-install-prompt.ts`:
- Around line 91-96: The install flow can get stuck in 'prompting' because
installPrompt.prompt() / installPrompt.userChoice can throw; update the effect
that calls setInstallState('prompting') and awaits
installPrompt.prompt()/installPrompt.userChoice (functions referenced:
setInstallState, installPrompt.prompt, installPrompt.userChoice,
setInstallPrompt) to wrap the awaits in try/catch/finally: on error catch and
setInstallState back to a safe state (e.g., 'idle' or 'dismissed') and log or
handle the error, and in finally ensure setInstallPrompt(null) is always called
so state is cleaned up regardless of success or failure.

In `@src/shared/model/auth-store.ts`:
- Around line 9-10: canUseLocalStorage currently reads window.localStorage and
can throw in restricted environments; change it to only check typeof window !==
'undefined' (do not access window.localStorage) and move any actual localStorage
access into try/catch blocks inside readStoredOnboardingPersonas and
persistOnboardingPersona so exceptions are handled where they occur. Also update
resetPersona to remove the per-user persona from persisted storage (the
"spot-onboarding-personas" entry for the current userId) and update the
in-memory persona state (and call persistOnboardingPersona or the same
persistence routine) so getStoredOnboardingPersona(userId) cannot recover an old
completed state after reset; reference functions: canUseLocalStorage,
readStoredOnboardingPersonas, persistOnboardingPersona, resetPersona,
setSession, getStoredOnboardingPersona.

---

Outside diff comments:
In `@src/shared/model/auth-store.ts`:
- Around line 116-117: resetPersona() currently only clears in-memory state
(userPersona, hasCompletedOnboarding) but does not remove the persisted persona
in localStorage, so the persona is immediately restored when setSession() runs;
update resetPersona to also remove the same localStorage key used to persist
userPersona (the key written where userPersona is saved), e.g. call
localStorage.removeItem(...) for that key inside resetPersona, ensuring the
persisted persona is cleared along with the in-memory state so onboarding can be
re-triggered.

---

Nitpick comments:
In `@src/features/chat/ui/ChatDrawer.tsx`:
- Around line 241-259: The per-tab arrays (personalRooms, feedRooms, spotRooms)
are left in original order while only allRooms is sorted; update the useMemo
block so each collection is sorted by updatedAt in the same descending order as
allRooms (e.g., create or apply a sort-by-updatedAt comparator and call it on
personal, feed, spot before returning them, keeping the existing allRooms
construction intact) — look for the useMemo, variables
personalRooms/feedRooms/spotRooms/allRooms, rooms and the updatedAt fields to
apply the change.

In `@src/features/feed/model/feed-map.test.ts`:
- Around line 22-91: 테스트에 문자열 좌표와 coordinate 경로 케이스를 추가해 resolveFeedCoordinate가
"coordinate: { lat: '37.5', lng: '127.0' }" 또는 coordinate 경로(예: coord vs
coordinate)에서 문자열 값을 숫자로 정상 변환하는지 검증하세요; 구체적으로 feed-map.test.ts에 makeFeed를 사용해
item에 coordinate: { lat: '37.2636', lng: '127.0286' }(또는 coord/primaryPin 혼합
케이스) 를 넣고 resolveFeedCoordinate(item) 결과가 숫자 타입의 { lat: 37.2636, lng: 127.0286
}인지 기대값으로 assert하도록 추가하십시오.

In `@src/features/feed/ui/FeedBottomSheet.tsx`:
- Around line 60-84: The inline filter logic that computes filtered duplicates
the shared logic in filterVisibleFeedItems; replace the manual .filter callback
on feedItems with a call to filterVisibleFeedItems(feedItems, { feedType,
categories, query: normalizedQuery }) (or the correct parameter shape used in
src/features/feed/model/feed-filter.ts), remove the duplicated checks (including
references to isSearchExcludedFeedItem, category/feedType checks and haystack
search), and add the necessary import for filterVisibleFeedItems at the top of
FeedBottomSheet.tsx so the bottom sheet and map use the single shared filter
implementation.

In `@src/features/post/ui/FormField.tsx`:
- Around line 6-7: The FormField component is rendering a <label> without an
htmlFor, preventing callers from linking labels to inputs; update the FormField
props (e.g., add htmlFor?: string alongside labelSize and children) and pass
that prop into the rendered <label> element (label htmlFor={htmlFor}) so callers
can provide the input id and restore accessibility linkage; adjust any
TypeScript types or prop forwarding in the FormField function to accept and
forward htmlFor.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d7bd02a9-e306-4665-ab01-33070bdcb84b

📥 Commits

Reviewing files that changed from the base of the PR and between daecca4 and d93cd38.

⛔ Files ignored due to path filters (5)
  • public/apple-touch-icon.png is excluded by !**/*.png
  • public/brand/spot-logo.png is excluded by !**/*.png
  • public/icons/icon-192x192.png is excluded by !**/*.png
  • public/icons/icon-512x512.png is excluded by !**/*.png
  • public/icons/maskable-icon-512x512.png is excluded by !**/*.png
📒 Files selected for processing (43)
  • public/manifest.json
  • src/app/layout.tsx
  • src/features/chat/api/chat-api.test.ts
  • src/features/chat/api/chat-api.ts
  • src/features/chat/model/types.ts
  • src/features/chat/ui/ChatDrawer.test.tsx
  • src/features/chat/ui/ChatDrawer.tsx
  • src/features/feed/api/feed-api.ts
  • src/features/feed/model/feed-filter.ts
  • src/features/feed/model/feed-layer-filter.ts
  • src/features/feed/model/feed-location.ts
  • src/features/feed/model/feed-map.test.ts
  • src/features/feed/model/types.ts
  • src/features/feed/model/use-feed.ts
  • src/features/feed/ui/FeedBottomSheet.tsx
  • src/features/feed/ui/MapFeedCardPager.tsx
  • src/features/map/client/MapClient.tsx
  • src/features/map/model/types.ts
  • src/features/map/ui/ClusterBlob.tsx
  • src/features/map/ui/MapFeedInfoCard.tsx
  • src/features/map/ui/SpotInfoCard.tsx
  • src/features/onboarding/client/OnboardingPageClient.tsx
  • src/features/onboarding/model/pwa-install.test.ts
  • src/features/onboarding/model/pwa-install.ts
  • src/features/onboarding/model/types.ts
  • src/features/onboarding/ui/PwaInstallGuide.tsx
  • src/features/onboarding/ui/use-pwa-install-prompt.ts
  • src/features/post/client/OfferFormClient.tsx
  • src/features/post/client/RequestFormClient.tsx
  • src/features/post/ui/FormCard.tsx
  • src/features/post/ui/FormControls.tsx
  • src/features/post/ui/FormField.tsx
  • src/features/post/ui/ImageUploadGrid.tsx
  • src/features/post/ui/ImageUploadSlot.tsx
  • src/features/post/ui/ReceiptCard.tsx
  • src/features/post/ui/post-form/OfferDetailsSection.tsx
  • src/features/post/ui/post-form/PlanInputSection.tsx
  • src/features/post/ui/post-form/PostBaseInfoSection.tsx
  • src/features/post/ui/post-form/PreparationInputSection.tsx
  • src/features/post/ui/post-form/PriceInputSection.tsx
  • src/features/post/ui/post-form/RequestDetailsSection.tsx
  • src/shared/model/auth-store.test.ts
  • src/shared/model/auth-store.ts

Comment thread src/features/onboarding/model/pwa-install.ts Outdated
Comment thread src/features/onboarding/ui/use-pwa-install-prompt.ts
Comment thread src/shared/model/auth-store.ts Outdated
@seoJing seoJing merged commit 7195354 into main May 25, 2026
5 checks passed
@seoJing seoJing deleted the feature/spot-api-contract-sync branch May 25, 2026 06:56
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