32 KiB
Main Page Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 홈, 콘텐츠, 채팅, 마이 4개 하단 탭을 가진 신규 메인 페이지를 SodaLive/Sources/V2/Main/** 아래에 구현하고 앱 루트에 연결한다.
Architecture: 신규 MainView를 기존 HomeView와 독립된 메인 컨테이너로 만든다. 탭 상태, 탭바, 빈 탭 화면을 V2/Main 모듈로 분리하고, 기존 HomeView의 미니 플레이어/인증/팝업 흐름은 새 컨테이너에 필요한 범위만 이관한다. 마이 탭은 기존 MyPageView를 그대로 재사용한다.
Tech Stack: SwiftUI, Combine, AppState 기반 전역 NavigationStack, Xcode project source membership, 기존 I18n 문자열 시스템
기준 문서
- PRD:
docs/prd/20260519_메인페이지신규개발_PRD.md - 검증 가이드:
docs/agent-guides/build-test-verification.md - 코드 스타일:
docs/agent-guides/code-style.md
파일 구조
생성
SodaLive/Sources/V2/Main/MainTab.swift: 4개 탭 정의, 타이틀, 선택/미선택 아이콘 매핑SodaLive/Sources/V2/Main/MainViewModel.swift: 현재 선택 탭 상태 관리SodaLive/Sources/V2/Main/MainView.swift: 신규 메인 루트 컨테이너, 탭 콘텐츠 전환, 미니 플레이어/팝업 이관 지점SodaLive/Sources/V2/Main/MainTabBarView.swift: 하단 탭바 HStack 구성SodaLive/Sources/V2/Main/MainTabBarButton.swift: 탭 버튼 단일 UI, 정렬 규칙 적용SodaLive/Sources/V2/Main/MainPlaceholderTabView.swift: 검정 배경 + 탭명 표시 빈 페이지
수정
SodaLive/Sources/ContentView.swift: 루트 화면을HomeView()에서MainView()로 변경SodaLive/Sources/I18n/I18n.swift:I18n.Main.Tab.content추가SodaLive.xcodeproj/project.pbxproj: 신규 Swift 파일 6개를SodaLive,SodaLive-dev앱 타깃 Sources에 포함
수정하지 않음
SodaLive/Sources/MyPage/MyPageView.swift: 기존 마이페이지는 재사용만 한다.SodaLive/Sources/Main/Home/HomeView.swift: 신규MainView연결이 안정화되기 전에는 삭제하거나 대규모 정리하지 않는다.SodaLive/Sources/App/AppState.swift,SodaLive/Sources/App/AppStep.swift:.main의미와 전역 라우팅 구조는 유지한다.
구현 체크리스트
Task 1: MainTab 모델 작성
Files:
-
Create:
SodaLive/Sources/V2/Main/MainTab.swift -
Step 1:
MainTab.swift생성
//
// MainTab.swift
// SodaLive
//
import Foundation
enum MainTab: CaseIterable, Hashable {
case home
case content
case chat
case my
var title: String {
switch self {
case .home:
return I18n.Main.Tab.home
case .content:
return I18n.Main.Tab.content
case .chat:
return I18n.Main.Tab.chat
case .my:
return I18n.Main.Tab.my
}
}
var selectedIconName: String {
switch self {
case .home:
return "ic_nav_home_selected"
case .content:
return "ic_nav_content_selected"
case .chat:
return "ic_nav_chat_selected"
case .my:
return "ic_tabbar_my_selected"
}
}
var unselectedIconName: String {
switch self {
case .home:
return "ic_nav_home"
case .content:
return "ic_nav_content"
case .chat:
return "ic_nav_chat"
case .my:
return "ic_nav_my"
}
}
}
- Step 2: 정적 진단 실행
Run: lsp_diagnostics("SodaLive/Sources/V2/Main/MainTab.swift")
Expected: 신규 파일 자체의 Swift 문법 오류가 없어야 한다. SourceKit이 프로젝트 컨텍스트를 완전히 잡지 못해 외부 심볼 오류를 보고하면 이후 빌드로 최종 검증한다.
Task 2: 다국어 탭 라벨 추가
Files:
-
Modify:
SodaLive/Sources/I18n/I18n.swift -
Step 1:
I18n.Main.Tab.content추가
I18n.Main.Tab에 아래 항목을 추가한다.
static var content: String { pick(ko: "콘텐츠", en: "Content", ja: "コンテンツ") }
적용 후 I18n.Main.Tab은 아래 형태가 된다.
enum Tab {
static var home: String { pick(ko: "홈", en: "Home", ja: "ホーム") }
static var content: String { pick(ko: "콘텐츠", en: "Content", ja: "コンテンツ") }
static var live: String { pick(ko: "라이브", en: "Live", ja: "ライブ") }
static var chat: String { pick(ko: "채팅", en: "Chat", ja: "チャット") }
static var my: String { pick(ko: "마이", en: "My", ja: "マイ") }
}
- Step 2: 정적 진단 실행
Run: lsp_diagnostics("SodaLive/Sources/I18n/I18n.swift")
Expected: 변경 구간의 중괄호/스코프 오류가 없어야 한다.
Task 3: 빈 탭 페이지 작성
Files:
-
Create:
SodaLive/Sources/V2/Main/MainPlaceholderTabView.swift -
Step 1: 검정 배경 + 탭명 표시 View 생성
//
// MainPlaceholderTabView.swift
// SodaLive
//
import SwiftUI
struct MainPlaceholderTabView: View {
let title: String
var body: some View {
ZStack {
Color.black
.ignoresSafeArea()
Text(title)
.appFont(size: 20, weight: .bold)
.foregroundColor(.white)
}
}
}
struct MainPlaceholderTabView_Previews: PreviewProvider {
static var previews: some View {
MainPlaceholderTabView(title: "홈")
}
}
- Step 2: 정적 진단 실행
Run: lsp_diagnostics("SodaLive/Sources/V2/Main/MainPlaceholderTabView.swift")
Expected: 문법 오류가 없어야 한다.
Task 4: 탭 버튼 UI 작성
Files:
-
Create:
SodaLive/Sources/V2/Main/MainTabBarButton.swift -
Step 1: 정렬 기준을 반영한 버튼 View 생성
//
// MainTabBarButton.swift
// SodaLive
//
import SwiftUI
struct MainTabBarButton: View {
let tab: MainTab
let isSelected: Bool
let width: CGFloat
let action: () -> Void
var body: some View {
Button(action: action) {
VStack(spacing: 4) {
ZStack(alignment: .center) {
Image(isSelected ? tab.selectedIconName : tab.unselectedIconName)
}
.frame(height: 24)
Text(tab.title)
.appFont(size: 10, weight: isSelected ? .bold : .medium)
.foregroundColor(isSelected ? Color.button : Color.graybb)
.frame(height: 12, alignment: .bottom)
}
.frame(width: width, minHeight: 50, alignment: .center)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
}
struct MainTabBarButton_Previews: PreviewProvider {
static var previews: some View {
MainTabBarButton(
tab: .home,
isSelected: true,
width: UIScreen.main.bounds.width / 4,
action: {}
)
.background(Color.gray11)
}
}
- Step 2: 정적 진단 실행
Run: lsp_diagnostics("SodaLive/Sources/V2/Main/MainTabBarButton.swift")
Expected: 문법 오류가 없어야 한다.
Task 5: 하단 탭바 작성
Files:
-
Create:
SodaLive/Sources/V2/Main/MainTabBarView.swift -
Step 1: 4분할 탭바 View 생성
//
// MainTabBarView.swift
// SodaLive
//
import SwiftUI
struct MainTabBarView: View {
let width: CGFloat
@Binding var currentTab: MainTab
var body: some View {
HStack(spacing: 0) {
let tabWidth = width / CGFloat(MainTab.allCases.count)
ForEach(MainTab.allCases, id: \.self) { tab in
MainTabBarButton(
tab: tab,
isSelected: currentTab == tab,
width: tabWidth,
action: {
currentTab = tab
}
)
}
}
.padding(.top, 8)
.padding(.bottom, 8)
.background(Color.gray11)
}
}
struct MainTabBarView_Previews: PreviewProvider {
static var previews: some View {
MainTabBarView(
width: UIScreen.main.bounds.width,
currentTab: .constant(.home)
)
}
}
- Step 2: 정적 진단 실행
Run: lsp_diagnostics("SodaLive/Sources/V2/Main/MainTabBarView.swift")
Expected: 문법 오류가 없어야 한다.
Task 6: 메인 ViewModel 작성
Files:
-
Create:
SodaLive/Sources/V2/Main/MainViewModel.swift -
Step 1: 탭 상태 ViewModel 생성
//
// MainViewModel.swift
// SodaLive
//
import Combine
import Foundation
final class MainViewModel: ObservableObject {
@Published var currentTab: MainTab = .home
}
- Step 2: 정적 진단 실행
Run: lsp_diagnostics("SodaLive/Sources/V2/Main/MainViewModel.swift")
Expected: 문법 오류가 없어야 한다.
Task 7: 신규 MainView 1차 작성
Files:
-
Create:
SodaLive/Sources/V2/Main/MainView.swift -
Step 1: 탭 전환 컨테이너 생성
//
// MainView.swift
// SodaLive
//
import SwiftUI
struct MainView: View {
@StateObject private var viewModel = MainViewModel()
var body: some View {
GeometryReader { proxy in
VStack(spacing: 0) {
contentView
MainTabBarView(
width: proxy.size.width,
currentTab: $viewModel.currentTab
)
if proxy.safeAreaInsets.bottom > 0 {
Rectangle()
.foregroundColor(Color.gray11)
.frame(width: proxy.size.width, height: 15.3)
}
}
.background(Color.black)
}
}
@ViewBuilder
private var contentView: some View {
switch viewModel.currentTab {
case .home:
MainPlaceholderTabView(title: MainTab.home.title)
case .content:
MainPlaceholderTabView(title: MainTab.content.title)
case .chat:
MainPlaceholderTabView(title: MainTab.chat.title)
case .my:
MyPageView()
}
}
}
struct MainView_Previews: PreviewProvider {
static var previews: some View {
MainView()
}
}
- Step 2: 정적 진단 실행
Run: lsp_diagnostics("SodaLive/Sources/V2/Main/MainView.swift")
Expected: 문법 오류가 없어야 한다.
Task 8: 기존 메인 주변 기능 이관
Files:
-
Modify:
SodaLive/Sources/V2/Main/MainView.swift -
Reference:
SodaLive/Sources/Main/Home/HomeView.swift -
Step 1: 미니 플레이어 상태와 매니저 이관
MainView에 기존 HomeView의 플레이어 관련 상태를 추가한다.
@StateObject private var appState = AppState.shared
@StateObject private var contentPlayManager = ContentPlayManager.shared
@StateObject private var contentPlayerPlayManager = ContentPlayerPlayManager.shared
@State private var isShowPlayer = false
VStack에서 콘텐츠와 탭바 사이에 기존 HomeView의 두 미니 플레이어 블록을 동일한 조건으로 배치한다.
if contentPlayerPlayManager.isShowingMiniPlayer {
contentPlayerMiniPlayerView
}
if contentPlayManager.isShowingMiniPlayer {
previewContentMiniPlayerView
}
contentPlayerMiniPlayerView는 HomeView.swift의 contentPlayerPlayManager 기반 블록과 동일한 동작을 유지한다. previewContentMiniPlayerView는 contentPlayManager 기반 블록과 동일하게 contentDetail 이동, 재생/일시정지, 정지를 처리한다.
- Step 2: 본인인증 관련 상태와 Bootpay payload 이관
기존 HomeView의 인증 관련 상태를 MainView에 추가한다.
@AppStorage("token") private var token: String = UserDefaults.string(forKey: UserDefaultsKey.token)
@AppStorage("auth") private var auth: Bool = UserDefaults.bool(forKey: UserDefaultsKey.auth)
@State private var isShowAuthView = false
@State private var isShowAuthConfirmView = false
@State private var pendingAction: (() -> Void)? = nil
@State private var payload = Payload()
Bootpay, BootpayUI import가 필요한 경우 MainView.swift 상단에 추가한다.
import Bootpay
import BootpayUI
onAppear에서 기존 payload 초기화 값을 유지한다.
payload.applicationId = BOOTPAY_APP_ID
payload.price = 0
payload.pg = "다날"
payload.method = "본인인증"
payload.orderName = "본인인증"
payload.authenticationId = "\(UserDefaults.string(forKey: .nickname))__\(String(NSTimeIntervalSince1970))"
- Step 3: 전역 팝업/다이얼로그 이관
기존 HomeView에서 메인 루트 차원에 표시되던 아래 UI를 MainView의 최상위 ZStack으로 옮긴다.
NotificationSettingsDialog()- 본인인증 안내
SodaDialog LivePaymentDialogLiveRoomPasswordDialogappState.eventPopup기반 이벤트 팝업
LivePaymentDialog, LiveRoomPasswordDialog 유지에 필요한 LiveViewModel은 기존처럼 MainView가 소유한다.
@StateObject private var liveViewModel = LiveViewModel()
- Step 4: 사용자 정보 갱신/이벤트 팝업 조회 이관 범위 결정대로 반영
기존 HomeView의 HomeViewModel 의존을 신규 MainViewModel로 무리하게 옮기지 않는다. 기존 메인 진입 시 필요한 부수효과가 있으면 HomeViewModel을 임시로 MainView에서 소유해 기존 메서드를 호출한다.
@StateObject private var legacyHomeViewModel = HomeViewModel()
onAppear에서 기존 호출을 유지한다.
if !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
pushTokenUpdate()
legacyHomeViewModel.getMemberInfo()
legacyHomeViewModel.getEventPopup()
legacyHomeViewModel.addAllPlaybackTracking()
}
pushTokenUpdate()는 기존 HomeView의 메서드를 동일하게 옮겨 Messaging.messaging().token을 조회하고 legacyHomeViewModel.pushTokenUpdate(pushToken:)를 호출한다.
- Step 5: 정적 진단 실행
Run: lsp_diagnostics("SodaLive/Sources/V2/Main/MainView.swift")
Expected: 문법 오류가 없어야 한다. 외부 SDK 심볼 인식 한계가 있으면 빌드로 최종 검증한다.
Task 9: 앱 루트 연결
Files:
-
Modify:
SodaLive/Sources/ContentView.swift -
Step 1: 루트 화면 교체
ContentView의 루트 렌더링을 아래처럼 변경한다.
if appState.isRestartApp {
EmptyView()
} else {
MainView()
}
- Step 2: 정적 진단 실행
Run: lsp_diagnostics("SodaLive/Sources/ContentView.swift")
Expected: MainView 참조 오류가 없어야 한다. Xcode project membership이 아직 반영되지 않아 인식 실패하면 Task 10 이후 다시 확인한다.
Task 10: Xcode project membership 반영
Files:
-
Modify:
SodaLive.xcodeproj/project.pbxproj -
Step 1: 신규 Swift 파일을 프로젝트에 포함
아래 6개 파일을 Xcode project의 SodaLive 및 SodaLive-dev Sources build phase에 포함한다.
MainTab.swiftMainViewModel.swiftMainView.swiftMainTabBarView.swiftMainTabBarButton.swiftMainPlaceholderTabView.swift
기존 project.pbxproj는 동일 파일이 두 앱 타깃에 각각 PBXBuildFile로 등록되는 패턴을 사용한다. 새 파일도 같은 패턴으로 추가한다.
- Step 2: 프로젝트 파일 참조 확인
Run: rg -n "MainTab.swift|MainViewModel.swift|MainView.swift|MainTabBarView.swift|MainTabBarButton.swift|MainPlaceholderTabView.swift" "SodaLive.xcodeproj/project.pbxproj"
Expected: 각 파일명이 PBXFileReference, PBXBuildFile, PBXSourcesBuildPhase 구간에 나타난다.
Task 11: 기능 검증
Files:
-
Verify:
SodaLive/Sources/V2/Main/*.swift -
Verify:
SodaLive/Sources/ContentView.swift -
Verify:
SodaLive/Sources/I18n/I18n.swift -
Verify:
SodaLive.xcodeproj/project.pbxproj -
Step 1: 변경 파일 정적 진단 실행
Run each:
lsp_diagnostics("SodaLive/Sources/V2/Main/MainTab.swift")
lsp_diagnostics("SodaLive/Sources/V2/Main/MainViewModel.swift")
lsp_diagnostics("SodaLive/Sources/V2/Main/MainView.swift")
lsp_diagnostics("SodaLive/Sources/V2/Main/MainTabBarView.swift")
lsp_diagnostics("SodaLive/Sources/V2/Main/MainTabBarButton.swift")
lsp_diagnostics("SodaLive/Sources/V2/Main/MainPlaceholderTabView.swift")
lsp_diagnostics("SodaLive/Sources/ContentView.swift")
lsp_diagnostics("SodaLive/Sources/I18n/I18n.swift")
Expected: 변경으로 인한 Swift 문법/타입 오류가 없어야 한다. SourceKit이 외부 모듈을 못 찾는 기존 한계는 빌드 결과로 판정한다.
- Step 2: 앱 빌드 실행
Run: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build
Expected: ** BUILD SUCCEEDED **
- Step 3: dev 앱 빌드 실행
Run: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
Expected: ** BUILD SUCCEEDED **
- Step 4: 테스트 액션 상태 확인
Run: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test
Expected: 현재 저장소 상태상 테스트 번들 타깃이 없으면 Scheme SodaLive is not currently configured for the test action.가 나올 수 있다. 이 경우 빌드 성공을 컴파일 검증 기준으로 삼고, 테스트 액션 부재를 검증 기록에 남긴다.
Task 12: 수동 확인 항목
Files:
-
Verify: 실행 앱 화면
-
Step 1: 메인 탭 표시 확인
확인 기준:
-
하단 탭 순서가
홈,콘텐츠,채팅,마이다. -
각 탭은 화면 폭의 1/4을 사용한다.
-
아이콘과 타이틀은 가로 가운데 정렬이다.
-
타이틀 하단 라인이 탭 간 흔들리지 않는다.
-
Step 2: 탭별 화면 확인
확인 기준:
-
홈탭: 검정 배경,홈텍스트 표시 -
콘텐츠탭: 검정 배경,콘텐츠텍스트 표시 -
채팅탭: 검정 배경,채팅텍스트 표시 -
마이탭: 기존MyPageView표시 -
Step 3: 아이콘 매핑 확인
확인 기준:
홈:ic_nav_home_selected/ic_nav_home콘텐츠:ic_nav_content_selected/ic_nav_content채팅:ic_nav_chat_selected/ic_nav_chat마이:ic_tabbar_my_selected/ic_nav_my
Phase 13: 메인 홈 크리에이터 생성 Floating Menu
-
Task 13.1: 내 채널 생성 메뉴를 공용 컴포넌트로 이동
- 대상 파일:
- 이동/이름 변경:
SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelFloatingActionMenu.swift - 생성 위치:
SodaLive/Sources/V2/Component/Button/CreatorContentCreationFloatingActionMenu.swift - 수정:
SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift - 확인:
SodaLive/Sources/I18n/I18n.swift
- 이동/이름 변경:
- 작업 내용:
- 내 채널 홈의
CreatorChannelFloatingActionMenu를 두 페이지에서 재사용할 수 있는 공용 컴포넌트로 이동하고 이름을 역할 중심으로 변경한다. ic_plus_no_bg,ic_new_upload_community_post,ic_new_upload_audio,ic_new_create_live,ic_new_x_black, dim, 버튼 순서, spring animation을 변경하지 않는다.- 내 채널 홈도 이동한 공용 컴포넌트를 사용해 기존 UI와 action을 유지한다.
- 내 채널 홈의
- 검증 기준:
- 실행 명령:
rg "CreatorContentCreationFloatingActionMenu|ic_plus_no_bg|ic_new_upload_community_post|ic_new_upload_audio|ic_new_create_live|ic_new_x_black|interpolatingSpring\\(mass: 1, stiffness: 256, damping: 24" SodaLive/Sources/V2/Component/Button SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift - 기대 결과: 하나의 공용 생성 메뉴 구현을 내 채널 홈에서 사용하고, 기존 아이콘과 animation 값이 유지된다.
- 실행 명령:
- 대상 파일:
-
Task 13.2: 메인 홈에 크리에이터 조건으로 생성 메뉴 표시
- 대상 파일:
- 수정:
SodaLive/Sources/V2/Main/MainView.swift - 확인:
SodaLive/Sources/Extensions/UserDefaultsExtension.swift - 확인:
SodaLive/Sources/Settings/Notification/GetMemberInfoResponse.swift
- 수정:
- 작업 내용:
UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue로 크리에이터 여부를 판별한다.- 크리에이터이고
viewModel.currentTab == .home일 때만 공용 생성 floating menu를MainView최상위ZStack에 표시한다. - 메인 홈 내부
추천,랭킹,팔로잉전환과 관계없이 메뉴를 유지한다. .content,.chat,.my로 이동하거나 일반 사용자로 앱을 사용하면 menu/dim을 표시하지 않는다.- 미니 플레이어와 하단 탭바가 표시될 때 겹치지 않도록 기존 safe area 구조를 따른다.
- 검증 기준:
- 실행 명령:
rg "MemberRole\\.CREATOR|viewModel\\.currentTab == \\.home|CreatorContentCreationFloatingActionMenu" SodaLive/Sources/V2/Main/MainView.swift - 기대 결과: 크리에이터와 메인 홈 탭 조건을 모두 만족할 때만 공용 생성 메뉴가 표시된다.
- 실행 명령:
- 대상 파일:
-
Task 13.3: 기존 등록·생성 action 연결
- 대상 파일:
- 수정:
SodaLive/Sources/V2/Main/MainView.swift - 확인:
SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift - 확인:
SodaLive/Sources/Live/LiveView.swift - 확인:
SodaLive/Sources/App/AppStep.swift
- 수정:
- 작업 내용:
- 커뮤니티 게시글 등록은 기존
AppStep.creatorCommunityWrite(onSuccess:)로 이동한다. - 콘텐츠 등록은 기존
AppStep.createContent(onSuccess:)로 이동한다. - 라이브 생성은 기존
AppStep.createLive(timeSettingMode: .NOW, onSuccess:)로 이동한다. - 라이브 생성 성공 시 내 채널 홈과
LiveView.onCreateSuccess의 기존 흐름처럼 라이브 메인 데이터를 갱신하고,response.channelName이 있으면 생성된 room에 입장한다. - 각 action 실행 전에 floating menu를 닫는다.
- 메인 홈 전용 신규 route, 작성/생성 API, 별도 화면을 추가하지 않는다.
- 커뮤니티 게시글 등록은 기존
- 검증 기준:
- 실행 명령:
rg "creatorCommunityWrite|createContent|createLive\\(timeSettingMode: \\.NOW|getLiveMain|enterRoom|is.*ActionMenuPresented = false" SodaLive/Sources/V2/Main/MainView.swift - 기대 결과: 세 버튼이 기존 route에 연결되고 라이브 생성 성공 흐름과 menu 닫힘 처리가 확인된다.
- 실행 명령:
- 대상 파일:
-
Task 13.4: 정적·빌드·수동 검증
- 대상 파일/화면:
- 확인:
SodaLive/Sources/V2/Main/MainView.swift - 확인:
SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift - 확인:
SodaLive.xcworkspace - 수동 확인: 크리에이터/일반 사용자 메인 화면과 내 채널 홈
- 확인:
- 작업 내용:
- 공용 메뉴 사용처와 지정 아이콘/action을 정적 검색한다.
SodaLive-devDebug 빌드를 실행한다.- 크리에이터 메인 홈, 일반 사용자 메인 홈, 메인 홈 외 탭, 내 채널 홈에서 표시 조건과 회귀 여부를 확인한다.
- 각 생성 버튼이 기존 등록/생성 화면으로 이동하는지 확인한다.
- 검증 기준:
- 실행 명령:
rg "CreatorContentCreationFloatingActionMenu" SodaLive/Sources/V2/Main SodaLive/Sources/V2/CreatorChannel SodaLive/Sources/V2/Component/Button - 실행 명령:
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build - 기대 결과: 메인 홈과 내 채널 홈이 같은 공용 메뉴를 사용하고 빌드가 성공하며, 크리에이터에게만 메인 홈 메뉴가 노출된다.
- 실행 명령:
- 대상 파일/화면:
검증 기록
- 계획 문서 작성 단계에서는 코드 구현을 수행하지 않았다.
- 계획 작성 전 확인한 근거 파일:
ContentView.swift,HomeView.swift,HomeViewModel.swift,BottomTabView.swift,TabButton.swift,MyPageView.swift,I18n.swift,SodaLive.xcodeproj/project.pbxproj. - 구현 완료 후 검증 기록은 이 섹션 아래에 실행 명령과 결과를 누적한다.
- 무엇/왜/어떻게: 신규
V2/MainSwift 파일 6개,I18n.Main.Tab.content,ContentView루트 연결, Xcode project membership을 계획 문서 기준으로 구현했다. - 실행 명령:
lsp_diagnostics(SodaLive/Sources/V2/Main,ContentView.swift,I18n.swift) - 결과: SourceKit 단독 컨텍스트 한계로 기존 프로젝트 심볼/외부 모듈 인식 오류가 보고되었고, 실제 컴파일 유효성은 빌드로 검증했다.
- 실행 명령:
rg -n "MainTab.swift|MainViewModel.swift|MainView.swift|MainTabBarView.swift|MainTabBarButton.swift|MainPlaceholderTabView.swift|41A00013" "SodaLive.xcodeproj/project.pbxproj" - 결과: 신규 파일 6개가
PBXFileReference,PBXBuildFile,V2/Main그룹, 두 앱 타깃PBXSourcesBuildPhase에 포함되어 있음을 확인했다. - 실행 명령:
xcodebuild -workspace "SodaLive.xcworkspace" -list - 결과: workspace/project 파싱 성공,
SodaLive,SodaLive-dev스킴 확인. - 실행 명령:
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build - 결과:
** BUILD SUCCEEDED **. - 실행 명령:
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build - 결과:
** BUILD SUCCEEDED **. - 실행 명령:
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test - 결과:
Scheme SodaLive is not currently configured for the test action. - 실행 명령:
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug -destination "platform=iOS Simulator,name=iPhone 17,OS=26.0" build - 결과:
** BUILD SUCCEEDED **. - 실행 명령:
xcrun simctl boot "iPhone 17" && xcrun simctl install "iPhone 17" ".../Debug-iphonesimulator/SodaLive-dev.app" && xcrun simctl launch "iPhone 17" "kr.co.vividnext.sodalive.debug2" - 결과: 앱 설치 및 실행 성공, launch pid
75253확인. - 실행 명령:
xcrun simctl io "iPhone 17" screenshot "/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sodalive-main.png" - 결과: 시뮬레이터 실행 화면 스크린샷 저장 완료.
- 무엇/왜/어떻게: 구현 후 코드 리뷰를 요청해 요구사항 누락과 품질 이슈를 점검했다.
- 실행 명령/도구:
task(category="deep")코드 리뷰 및 follow-up 검토 - 결과: 현재 범위는
홈/콘텐츠/채팅/마이중홈/콘텐츠/채팅이 placeholder이므로 Bootpay 인증 트리거가 없는 것은 blocking issue가 아니며, 향후 실제 라이브/성인 콘텐츠 진입 UI 연결 시isShowAuthConfirmView/pendingAction경로를 연결해야 한다는 non-blocking future integration note로 정리했다. - 2026-07-08 메인 콘텐츠 탭 title bar 요구사항 반영: Figma node
24:6738기준으로 콘텐츠 탭 상단은 홈 탭과 같은 좌측 로고/우측 알림 버튼 구성이 아니라, 좌측에 메인 탭명콘텐츠를 고정 표시하고 우측에는ic_bar_cash,ic_bar_search,ic_bar_storage를 표시하도록 수정했다. - 2026-07-08 메인 콘텐츠 탭 보관함 이동 반영:
ic_bar_storage터치 시 기존 보관함 진입 경로인AppState.shared.setAppStep(step: .myBox(currentTab: .orderlist))로 이동하도록MainContentView에onTapStorage콜백을 추가하고MainView에서 연결했다. 홈 탭의HomeTitleBar/ic_bar_bell동작은 유지한다. - 2026-07-08 검증: Figma
24:6738design context와 screenshot을 확인했고,rg/ast-grep로 콘텐츠 탭은DefaultTitleBar(title: MainTab.content.title)와ic_bar_storage, 홈 탭은 기존HomeTitleBar와ic_bar_bell을 사용하는 것을 확인했다. 구현 직후xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -destination 'generic/platform=iOS Simulator' -configuration Debug build실행 결과 빌드 성공을 확인했다. - 2026-07-20 문서 보강: 크리에이터가 메인 홈에서 라이브 생성, 콘텐츠 등록, 커뮤니티 게시글 등록을 실행할 수 있도록 Phase 13을 추가했다.
CreatorChannelFloatingActionMenu,CreatorChannelView의 세 action,MemberRole.CREATOR역할 판별 근거를 확인해 같은 아이콘·animation·기존AppSteproute를 공용화하는 범위로 기록했다. 이번 작업은 문서 수정만 수행했으며 구현/빌드는 Phase 13 실행 시 검증한다. - 2026-07-20 Phase 13 구현:
CreatorChannelFloatingActionMenu를SodaLive/Sources/V2/Component/Button/CreatorContentCreationFloatingActionMenu.swift로 이동/이름 변경하고, 내 채널 홈과 메인 홈이 같은 공용 메뉴를 사용하도록 반영했다. - 실행 명령:
rg -n "CreatorContentCreationFloatingActionMenu|ic_plus_no_bg|ic_new_upload_community_post|ic_new_upload_audio|ic_new_create_live|ic_new_x_black|interpolatingSpring\\(mass: 1, stiffness: 256, damping: 24" "SodaLive/Sources/V2/Component/Button" "SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift" - 결과: 공용 메뉴 구현과 내 채널 홈 사용처, 기존 plus/커뮤니티/오디오/라이브/닫기 아이콘 및 spring animation 값이 확인됐다.
- 실행 명령:
rg -n "MemberRole\\.CREATOR|viewModel\\.currentTab == \\.home|CreatorContentCreationFloatingActionMenu" "SodaLive/Sources/V2/Main/MainView.swift" - 결과:
UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue && viewModel.currentTab == .home조건에서만 메인 홈 생성 메뉴를 표시하는 것을 확인했다. - 실행 명령:
rg -n "creatorCommunityWrite|createContent\\(onSuccess|createLive\\(timeSettingMode: \\.NOW|getLiveMain|enterRoom|isCreatorActionMenuPresented = false" "SodaLive/Sources/V2/Main/MainView.swift" - 결과: 커뮤니티 게시글 등록, 콘텐츠 등록, 즉시 라이브 생성 route 연결과 action 실행 전 메뉴 닫힘, 라이브 생성 성공 후
getLiveMain()및enterRoom(roomId:)흐름을 확인했다. - 실행 명령:
rg -n "CreatorContentCreationFloatingActionMenu" "SodaLive/Sources/V2/Main" "SodaLive/Sources/V2/CreatorChannel" "SodaLive/Sources/V2/Component/Button" - 결과: 공용 메뉴 구현 1개와
MainView,CreatorChannelView사용처가 확인됐다. - 실행 명령:
lsp_diagnostics(CreatorContentCreationFloatingActionMenu.swift,CreatorChannelView.swift,MainView.swift) - 결과: SourceKit 단독 컨텍스트 한계로 전역 프로젝트 심볼 인식 오류/timeout이 보고되어 실제 컴파일 유효성은 빌드로 판정했다.
- 실행 명령:
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build - 결과:
** BUILD SUCCEEDED **. - 실행 명령:
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test - 결과:
Scheme SodaLive is not currently configured for the test action. - 실행 명령:
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug -destination "platform=iOS Simulator,name=iPhone 17,OS=26.0" build - 결과:
** BUILD SUCCEEDED **. - 실행 명령:
xcrun simctl spawn "iPhone 17" defaults write kr.co.vividnext.sodalive.debug2 role CREATOR,token,isViewedOnboardingView설정 후 앱 launch 및 screenshot 캡처 - 결과:
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sodalive-phase13-creator-main-retry.png에서 메인 홈 하단 오른쪽 plus floating action button 표시를 확인했다. - 정리 명령:
xcrun simctl spawn "iPhone 17" defaults delete ...및xcrun simctl shutdown "iPhone 17" - 결과: Phase 13 수동 검증용 시뮬레이터 기본값 제거와 시뮬레이터 종료를 완료했다.
- 실행 명령:
git diff --check - 결과: 공백 오류 없음.
- 실행 도구: 리뷰어 게이트
task(category="deep") - 결과: 차단 결함 없음. 자동 테스트는 test action 미구성으로 실행할 수 없고, 생성 버튼 실제 탭 이동은 정적 라우팅 검토로 확인했다는 메모만 남았다.
커밋 정책
- 이 계획 실행 중 커밋은 사용자가 명시적으로 요청한 경우에만 수행한다.
- 커밋 요청이 있으면 먼저
commit-policy스킬을 로드하고, 커밋 직후work/scripts/check-commit-message-rules.sh를 실행한다.