Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 1 addition & 17 deletions app/(tabs)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ import { useUserProfileStore } from '@/store/userProfileStore';
import { FeaturedPlaylist, MoodRecommendation, MusicLogItem, TravelMode } from '@/types/domain';
import { requestForegroundLocationWithStatus } from '@/utils/location';
import { getMoodTagsFromFilter } from '@/utils/moodTags';
import { getTrackExternalLink, openMusicPlatformUrl } from '@/utils/musicPlatformLinks';
import { createRecommendationEventContext } from '@/utils/recommendationEventContext';

const moodFilterToMlMood: Record<string, PlaylistMlMood> = {
Expand Down Expand Up @@ -184,23 +183,8 @@ function HomeContent() {
return;
}

const externalLink = getTrackExternalLink(item.track);

setTrack(item.track);

try {
await openMusicPlatformUrl(externalLink);
syncRecommendationEvent(
addRecommendationEvent({
context: createRecommendationEventContext(),
trackId: item.track.id,
type: 'track_external_open',
value: externalLink.platformId,
}),
);
} catch {
setActionMessage('음악 링크를 열지 못했어요. 다시 시도해주세요.');
}
setActionMessage('이 곡을 SoundLog 음악으로 선택했어요. 하단 패널에서 저장하거나 순간 기록에 담을 수 있어요.');
};
const handleSelectFeaturedPlaylist = useCallback(
async (playlist: FeaturedPlaylist) => {
Expand Down
2 changes: 1 addition & 1 deletion app/legal/privacy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const privacySections = [
},
{
title: '제3자 서비스',
body: '로그인에는 Soundlog 자체 계정이 사용되고, 장소 정보에는 공공 관광 데이터 API가 사용될 수 있습니다. 외부 음악 앱으로 이동하는 경우 해당 서비스의 개인정보 처리방침이 적용됩니다.',
body: '로그인에는 Soundlog 자체 계정이 사용되고, 장소 정보에는 공공 관광 데이터 API가 사용될 수 있습니다. Soundlog는 음악 추천과 기록 UI를 앱 안에서 제공하며, 음원 재생 서비스 계정 정보는 수집하지 않습니다.',
},
{
title: '보관과 삭제',
Expand Down
2 changes: 1 addition & 1 deletion app/legal/terms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const termsSections = [
},
{
title: '외부 서비스',
body: '외부 음악 앱이나 관광 데이터 제공자의 서비스로 이동하거나 연동되는 경우 해당 서비스의 약관과 정책이 함께 적용될 수 있습니다.',
body: '장소 정보 등 공공 관광 데이터 제공자의 서비스가 사용될 수 있으며, 해당 데이터 제공자의 정책이 함께 적용될 수 있습니다. Soundlog는 앱 안에서 음악 추천과 기록 UI를 제공하며 외부 음원 재생을 보장하지 않습니다.',
},
{
title: '제한 사항',
Expand Down
10 changes: 5 additions & 5 deletions docs/frontend/MAIN_PAGE_RN_BUILD_DOC.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Figma 기준 화면은 `390 x 844` 모바일 화면에 맞춰 설계되어 있
- `Music Playlist` 대표 플레이리스트 캐러셀
- `나의 무드에 맞는 음악 추천` 섹션
- `Music Log` 섹션
- 하단 외부 링크용 미니 플레이어
- 하단 Soundlog 음악 컨텍스트 패널
- 하단 탭바 및 중앙 카메라 버튼

React Native 구현 시 Figma의 절대 좌표를 그대로 옮기기보다, 모바일 앱에서 안정적으로 동작하도록 **세로 스크롤 콘텐츠 + 가로 스크롤 리스트 + 하단 고정 미니 링크 패널/탭바** 구조로 재설계한다.
Expand All @@ -26,7 +26,7 @@ React Native 구현 시 Figma의 절대 좌표를 그대로 옮기기보다, 모

1. 현재 위치 기반 추천 플레이리스트를 빠르게 보여준다.
2. 사용자의 상황/무드 필터를 통해 추천 방향을 조정한다.
3. 현재 선택한 음악과 외부 음악 앱 링크를 항상 접근 가능하게 유지한다.
3. 현재 선택한 음악과 Soundlog 자체 저장/기록 액션을 항상 접근 가능하게 유지한다.
4. 여행 순간 저장 액션을 화면 어디서든 명확하게 제공한다.
5. Music Log를 통해 여행 중 쌓이는 음악 기록을 보여준다.

Expand Down Expand Up @@ -345,7 +345,7 @@ CTA:

### 역할

현재 선택한 음악과 외부 음악 앱 링크를 화면 하단에서 항상 확인할 수 있게 한다.
현재 선택한 음악과 Soundlog 자체 저장/기록 액션을 화면 하단에서 항상 확인할 수 있게 한다.

### Figma 기준 요소

Expand Down Expand Up @@ -391,7 +391,7 @@ type PlayerState = {
| ---------------- | --------------------------------------- |
| 미니 플레이어 탭 | 풀 플레이어 화면 또는 bottom sheet 열기 |
| 이전 | 이전 곡으로 이동하고 외부 링크 열기 |
| 외부 링크 | 현재 곡의 외부 음악 앱 검색 링크 열기 |
| 순간 기록 | 현재 곡을 유지한 채 Soundlog 카메라로 이동 |
| 다음 | 다음 곡으로 이동하고 피드백 이벤트 기록 |

---
Expand Down Expand Up @@ -645,7 +645,7 @@ Figma는 390px 기준이므로 작은 기기와 큰 기기를 모두 고려해

- 칩 터치 영역은 최소 44px 높이를 권장한다.
- 카메라 버튼은 명확한 accessibility label을 가진다.
- 미니 플레이어 버튼은 `이전 `, `음악 링크 열기`, `다음 ` label을 제공한다.
- 미니 플레이어 버튼은 `이전 추천 곡 선택`, `선택한 음악 상세 보기`, `다음 추천 곡 선택` label을 제공한다.
- 색상만으로 선택 상태를 구분하지 않고, 선택 칩에는 텍스트/테두리 차이를 함께 준다.

예시:
Expand Down
12 changes: 6 additions & 6 deletions docs/frontend/MUSIC_CURATION_PAGE_RN_BUILD_DOC.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Figma 기준 화면은 사용자의 현재 위치를 기반으로 추천된 플

1. 현재 위치 또는 선택 장소 기반 플레이리스트를 보여준다.
2. 왜 이 플레이리스트가 추천되었는지 장소 맥락을 전달한다.
3. 곡 리스트를 탐색하고 외부 음악 앱 검색 링크를 바로 열 수 있게 한다.
3. 곡 리스트를 탐색하고 선택한 곡을 Soundlog 안의 현재 여행 음악으로 유지한다.
4. 곡별 더보기, 저장, 좋아요 같은 음악 액션의 진입점을 제공한다.
5. 현재 선택한 음악은 미니 플레이어로 유지한다.

Expand Down Expand Up @@ -110,7 +110,7 @@ bg-black/40 overlay

- 지역명: `Seoul`
- 추천 근거: `Based on your location`
- 대표곡 외부 음악 링크 버튼
- 대표곡 Soundlog 음악 선택 버튼

### RN 컴포넌트 구조

Expand Down Expand Up @@ -209,7 +209,7 @@ type CuratedTrack = {

| 액션 | 동작 |
| --- | --- |
| Row 탭 | 현재 선택 곡으로 지정하고 외부 음악 링크 열기 |
| Row 탭 | 현재 선택 곡으로 지정하고 Soundlog 음악 패널 표시 |
| 더보기 탭 | BottomSheet 메뉴 열기 |
| 길게 누르기 | 좋아요/저장 quick action, 후순위 |

Expand All @@ -219,19 +219,19 @@ type CuratedTrack = {
좋아요
저장하기
이 곡으로 순간 저장
외부 음악 앱에서 열기
이 곡으로 순간 저장
이 곡 숨기기
```

MVP에서는 `좋아요`, `저장하기`, `외부 음악 앱에서 열기`만 구현해도 충분하다.
MVP에서는 `좋아요`, `저장하기`, `이 곡으로 순간 저장` 흐름만 구현해도 충분하다.

---

## 6.3 MiniPlayer

### 역할

현재 선택한 곡과 외부 음악 앱 링크를 하단에서 확인한다.
현재 선택한 곡과 Soundlog 자체 저장/기록 액션을 하단에서 확인한다.

### Figma 기준

Expand Down
6 changes: 3 additions & 3 deletions docs/frontend/RN_FRONTEND_PLANNING_POINTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ Soundlog는 위치, 음악, 카메라, 여행 로그, Recap을 함께 다루는

### 2.1 사용자가 여행 중 화면을 오래 보지 않아도 되는 앱

Soundlog의 핵심 UX는 Eyes-free 여행 몰입이다. 따라서 프론트엔드는 많은 정보를 보여주는 것보다, 사용자가 최소 조작으로 외부 음악 링크를 열고 순간을 저장할 수 있도록 설계해야 한다.
Soundlog의 핵심 UX는 Eyes-free 여행 몰입이다. 따라서 프론트엔드는 많은 정보를 보여주는 것보다, 사용자가 최소 조작으로 앱 안에서 음악 맥락을 선택하고 순간을 저장할 수 있도록 설계해야 한다.

핵심 기준은 다음과 같다.

- 홈에서 현재 장소, 추천 플레이리스트, 선택한 음악 링크 상태, 순간 저장 버튼이 즉시 보여야 한다.
- 홈에서 현재 장소, 추천 플레이리스트, 선택한 음악 상태, 순간 저장 버튼이 즉시 보여야 한다.
- 여행 중 주요 액션은 1~2탭 안에 끝나야 한다.
- 음악 추천 결과가 마음에 들지 않을 때 검색 화면으로 보내지 않고 무드 조정 버튼으로 해결해야 한다.
- 카메라 버튼은 항상 접근 가능한 중심 액션으로 유지한다.
Expand Down Expand Up @@ -200,7 +200,7 @@ MVP에서 미니/풀 링크 패널은 복잡한 음악 앱 수준의 재생 제
- 외부 음악 앱 검색 링크, 다음/이전 곡 이동, 좋아요/저장을 우선 제공한다.
- 다음/이전 이동과 외부 링크 열기 이벤트는 추천 피드백으로 수집한다.
- 백그라운드 음원 제어, 인앱 음원 제공, 외부 플랫폼 상태 제어는 MVP 범위에서 제외한다.
- 실제 음악 감상은 YouTube Music 또는 Melon 검색 링크로 넘긴다.
- MVP에서는 실제 음원 감상이나 외부 음악 앱 이동을 제공하지 않고, 선택한 곡 정보를 Soundlog 기록 UI에 유지한다.

### 5.7 순간 저장 카메라

Expand Down
20 changes: 20 additions & 0 deletions docs/implementation/2026-07-05-internal-music-ui-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Internal music UI policy

## Context

Music actions must stay inside SoundLog. The app does not stream audio and should not send users to YouTube Music, Melon, Spotify, or generated search URLs from the main recommendation, playlist, library, or mini-player flows.

## UI contract

- Tapping a recommended track selects it as the current SoundLog music context.
- The mini player shows the selected track as a SoundLog context panel, not as playback or an external link panel.
- The full music panel offers SoundLog-native actions: previous/next recommendation, like, save, and moment capture with the selected track.
- Track action menus should not include external music app actions.
- Server-provided `externalUrl` and `platformUrls` must be stripped from frontend runtime track state.
- Web export and deployed-web checks must fail if YouTube Music URLs or external platform open helpers are bundled.

## Verification

- `npm run typecheck`
- `npm run check:server-web-export`
- `npm run check:deployed-web -- https://soundlog.shop` after deployment
4 changes: 2 additions & 2 deletions docs/product/SOUNDLOG_APP_PLANNING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Soundlog는 이 문제를 다음 세 가지 관점에서 해결한다.

### 4.2 Eyes-free 여행 몰입 UX

Soundlog는 여행 중 화면 조작을 최소화하는 방향으로 설계된다. 추천된 플레이리스트는 앱 안에서 장소와 곡 맥락을 보여주고, 사용자는 하단 미니 플레이어에서 현재 선택한 곡을 확인한 뒤 외부 음악 앱 검색 링크로 이동한다. Soundlog MVP는 음원을 직접 스트리밍하거나 외부 플랫폼 재생 상태를 제어하지 않는다.
Soundlog는 여행 중 화면 조작을 최소화하는 방향으로 설계된다. 추천된 플레이리스트는 앱 안에서 장소와 곡 맥락을 보여주고, 사용자는 하단 미니 플레이어에서 현재 선택한 곡을 확인한 뒤 좋아요, 저장, 순간 기록 같은 Soundlog 자체 액션으로 이어진다. Soundlog MVP는 음원을 직접 스트리밍하거나 외부 플랫폼 재생 상태를 제어하지 않는다.

추천이 현재 분위기와 맞지 않을 경우 복잡한 검색을 다시 하지 않아도 된다. 사용자는 “더 신나게”, “더 잔잔하게”, “더 감성적으로”, “더 로컬하게” 같은 무드 조정 옵션을 선택해 추천 방향을 즉시 바꿀 수 있다.

Expand Down Expand Up @@ -108,7 +108,7 @@ Music Log는 여행 중 선택하거나 저장한 음악을 장소와 함께 보
주요 기능은 다음과 같다.

- 추천 사유 제공: “광안리 해변 산책과 어울리는 청량한 플레이리스트”처럼 추천 맥락을 짧게 설명한다.
- 외부 음악 링크: 앱 안에서 직접 재생하지 않고 YouTube Music 또는 Melon 검색 링크로 이동한다.
- 음악 컨텍스트 UI: 앱 안에서 직접 재생하거나 외부 음악 앱으로 이동하지 않고, 선택한 곡 정보를 Soundlog의 여행 기록 맥락으로 유지한다.
- 무드 조정: 더 신나게, 더 잔잔하게, 더 감성적으로, 더 로컬하게 옵션을 제공한다.
- 좋아요/저장: 마음에 드는 곡과 플레이리스트를 저장한다.

Expand Down
6 changes: 6 additions & 0 deletions scripts/check-deployed-web.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,12 @@ async function verifyBundle() {
['track_skip', 'Deployed bundle must not include fake skip playback events.'],
['spotify-auth', 'Deployed bundle must not include the removed Spotify auth route.'],
['open.spotify.com', 'Deployed bundle must not include Spotify external search URLs.'],
['music.youtube.com', 'Deployed bundle must not include YouTube Music URLs.'],
['YouTube Music', 'Deployed bundle must not include YouTube Music action copy.'],
[
'openMusicPlatformUrl',
'Deployed bundle must not include external music platform open helpers.',
],
[
'playSelectedSpotifyOrFallback',
'Deployed bundle must not include removed Spotify playback helpers.',
Expand Down
22 changes: 21 additions & 1 deletion scripts/check-server-web-export.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,22 @@ function verifyHonestMusicActionSourceFiles() {
].filter((dir) => fs.existsSync(dir));
const blockedSourceMarkers = [
['NOW PLAYING', 'User-facing source must not imply in-app streaming playback.'],
['track_play', 'Frontend source must record track_external_open, not fake play events.'],
['track_play', 'Frontend source must not record fake play events.'],
['track_pause', 'Frontend source must not include fake pause events.'],
['track_resume', 'Frontend source must not include fake resume events.'],
['track_skip', 'Frontend source must not include fake skip playback events.'],
['spotify-auth', 'Frontend source must not include the removed Spotify auth route.'],
['open.spotify.com', 'Frontend source must not include Spotify external search URLs.'],
['playSelectedSpotifyOrFallback', 'Frontend source must not include Spotify playback helpers.'],
['openMusicPlatformUrl', 'Frontend source must not open external music platform URLs.'],
['musicPlatformLinks', 'Frontend source must not import external music platform link helpers.'],
['music.youtube.com', 'Frontend source must not include YouTube Music URLs.'],
['YouTube Music', 'Frontend source must not mention YouTube Music actions.'],
];

sourceDirs
.flatMap((dir) => readTextFiles(dir))
.filter(({ filePath }) => !isAllowedMockReferencePath(filePath))
.forEach(({ filePath, text }) => {
blockedSourceMarkers.forEach(([marker, message]) => {
if (text.includes(marker)) {
Expand Down Expand Up @@ -242,6 +247,21 @@ function verifyBundle(bundleText) {
'open.spotify.com',
'Server web export must not include Spotify external search URLs.',
);
assertExcludes(
bundleText,
'music.youtube.com',
'Server web export must not include YouTube Music URLs.',
);
assertExcludes(
bundleText,
'YouTube Music',
'Server web export must not include YouTube Music action copy.',
);
assertExcludes(
bundleText,
'openMusicPlatformUrl',
'Server web export must not include external music platform open helpers.',
);
}

try {
Expand Down
Loading
Loading