diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7affcdee..ce81e7b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -229,7 +229,7 @@ jobs: - name: Persistence schemes: "Persistence" - name: Presentation - schemes: "Presentation HomeTab" + schemes: "Entry PresentationShared HomeTab" - name: Widget schemes: "Widget WidgetCore" steps: diff --git a/.hermes/skills/devlog-architecture-harness/references/devlog-architecture-flow.md b/.hermes/skills/devlog-architecture-harness/references/devlog-architecture-flow.md index 82ae3335..734399fd 100644 --- a/.hermes/skills/devlog-architecture-harness/references/devlog-architecture-flow.md +++ b/.hermes/skills/devlog-architecture-harness/references/devlog-architecture-flow.md @@ -130,6 +130,15 @@ flowchart TD | `WidgetCore` | widget snapshot models, factories, app-group keys/defaults store, deep links, pure snapshot logic | Core | Adding Domain, Data, Infra, Persistence, Presentation, App, or Widget dependency | | `WidgetExtension` | WidgetKit rendering and timeline plumbing | WidgetCore | Calling app/domain services directly | +## Presentation target structure + +- `Presentation` preserves `App -> Presentation` imports and re-exports the entry API through `Application/Presentation/Sources/**/*.swift`. +- `Entry` owns root, auth, login, main tab shell, window, and global route responsibilities. It owns the `Domain` references needed by those flows. +- `EntryTests` validates `Entry` through `Application/Presentation/Entry/Tests/**/*.swift`. +- `HomeTab`, `TodayTab`, `NotificationTab`, and `ProfileTab` remain tab-specific feature targets and each target owns the `Domain` references it needs. +- `PresentationShared` owns shared Todo, Search, Loading UI, and presentation contracts. +- `App` owns composition root, lifecycle, and assembler wiring. It must not take ownership of presentation feature or root flows. + ## Layer-internal dependency injection Do not inject dependencies between types that belong to the same layer. diff --git a/.hermes/skills/devlog-architecture-harness/references/devlog-workflow-rules.md b/.hermes/skills/devlog-architecture-harness/references/devlog-workflow-rules.md index 9f0e7a1b..a078209d 100644 --- a/.hermes/skills/devlog-architecture-harness/references/devlog-workflow-rules.md +++ b/.hermes/skills/devlog-architecture-harness/references/devlog-workflow-rules.md @@ -74,7 +74,7 @@ This reference holds DevLog-specific working rules that should live with the pro - Prefer a Data-side boundary value plus repository mapping when Infra should not import Domain. - For example, keep the app-facing Domain query separate from an Infra-facing Data query when that avoids Domain coupling in service protocols. - Firebase-specific error detection belongs in Infra; Data should handle domain-level errors after mapping. -- Data and Presentation currently keep narrow social-login cancellation classification in `DataLayerError` and `Error+SocialLogin`. Do not expand that into concrete login implementation or broader SDK ownership without explicit approval. +- Infra currently keeps narrow social-login cancellation classification in `InfraLayerError`. Do not expand that into concrete login implementation or broader SDK ownership outside Infra without explicit approval. ## Presentation StorePattern diff --git a/Application/Presentation/Entry/Sources/.swiftlint.yml b/Application/Presentation/Entry/Sources/.swiftlint.yml new file mode 100644 index 00000000..6a9d7380 --- /dev/null +++ b/Application/Presentation/Entry/Sources/.swiftlint.yml @@ -0,0 +1 @@ +parent_config: ../../../../.swiftlint.yml diff --git a/Application/Presentation/Sources/Common/Component/LoginButton.swift b/Application/Presentation/Entry/Sources/Common/Component/LoginButton.swift similarity index 97% rename from Application/Presentation/Sources/Common/Component/LoginButton.swift rename to Application/Presentation/Entry/Sources/Common/Component/LoginButton.swift index ef991dd4..d393b60f 100644 --- a/Application/Presentation/Sources/Common/Component/LoginButton.swift +++ b/Application/Presentation/Entry/Sources/Common/Component/LoginButton.swift @@ -1,12 +1,11 @@ // // LoginButton.swift -// Presentation +// Entry // // Created by opfic on 4/25/25. // import SwiftUI -import Domain struct LoginButton: View { @State private var logo: Image? @@ -26,7 +25,7 @@ struct LoginButton: View { self.showsProgressView = showsProgressView self.action = action } - + var body: some View { Button { action() diff --git a/Application/Presentation/Sources/Login/LoginFeature.swift b/Application/Presentation/Entry/Sources/Login/LoginFeature.swift similarity index 99% rename from Application/Presentation/Sources/Login/LoginFeature.swift rename to Application/Presentation/Entry/Sources/Login/LoginFeature.swift index 17287f1f..f06e764d 100644 --- a/Application/Presentation/Sources/Login/LoginFeature.swift +++ b/Application/Presentation/Entry/Sources/Login/LoginFeature.swift @@ -1,12 +1,12 @@ // // LoginFeature.swift -// Presentation +// Entry // // Created by opfic on 6/5/26. // -import Domain import Foundation +import Domain import PresentationShared @Reducer diff --git a/Application/Presentation/Sources/Login/LoginView.swift b/Application/Presentation/Entry/Sources/Login/LoginView.swift similarity index 99% rename from Application/Presentation/Sources/Login/LoginView.swift rename to Application/Presentation/Entry/Sources/Login/LoginView.swift index 0ef446ee..781a2f0c 100644 --- a/Application/Presentation/Sources/Login/LoginView.swift +++ b/Application/Presentation/Entry/Sources/Login/LoginView.swift @@ -1,6 +1,6 @@ // // LoginView.swift -// Presentation +// Entry // // Created by opfic on 12/30/24. // diff --git a/Application/Presentation/Sources/Main/MainFeature.swift b/Application/Presentation/Entry/Sources/Main/MainFeature.swift similarity index 99% rename from Application/Presentation/Sources/Main/MainFeature.swift rename to Application/Presentation/Entry/Sources/Main/MainFeature.swift index a6af2af5..aa4df388 100644 --- a/Application/Presentation/Sources/Main/MainFeature.swift +++ b/Application/Presentation/Entry/Sources/Main/MainFeature.swift @@ -1,6 +1,6 @@ // // MainFeature.swift -// Presentation +// Entry // // Created by opfic on 6/16/26. // diff --git a/Application/Presentation/Sources/Main/MainView.swift b/Application/Presentation/Entry/Sources/Main/MainView.swift similarity index 99% rename from Application/Presentation/Sources/Main/MainView.swift rename to Application/Presentation/Entry/Sources/Main/MainView.swift index 43bb12b0..b80772ee 100644 --- a/Application/Presentation/Sources/Main/MainView.swift +++ b/Application/Presentation/Entry/Sources/Main/MainView.swift @@ -1,6 +1,6 @@ // // MainView.swift -// Presentation +// Entry // // Created by opfic on 5/8/25. // diff --git a/Application/Presentation/Sources/Root/RootFeature.swift b/Application/Presentation/Entry/Sources/Root/RootFeature.swift similarity index 99% rename from Application/Presentation/Sources/Root/RootFeature.swift rename to Application/Presentation/Entry/Sources/Root/RootFeature.swift index db950f84..692b4ce2 100644 --- a/Application/Presentation/Sources/Root/RootFeature.swift +++ b/Application/Presentation/Entry/Sources/Root/RootFeature.swift @@ -1,6 +1,6 @@ // // RootFeature.swift -// Presentation +// Entry // // Created by opfic on 6/17/26. // diff --git a/Application/Presentation/Sources/Root/RootView.swift b/Application/Presentation/Entry/Sources/Root/RootView.swift similarity index 99% rename from Application/Presentation/Sources/Root/RootView.swift rename to Application/Presentation/Entry/Sources/Root/RootView.swift index 77636d36..4140fe39 100644 --- a/Application/Presentation/Sources/Root/RootView.swift +++ b/Application/Presentation/Entry/Sources/Root/RootView.swift @@ -1,6 +1,6 @@ // // RootView.swift -// Presentation +// Entry // // Created by opfic on 5/2/25. // diff --git a/Application/Presentation/Sources/Routing/MainTab.swift b/Application/Presentation/Entry/Sources/Routing/MainTab.swift similarity index 90% rename from Application/Presentation/Sources/Routing/MainTab.swift rename to Application/Presentation/Entry/Sources/Routing/MainTab.swift index c2d0a250..ed041251 100644 --- a/Application/Presentation/Sources/Routing/MainTab.swift +++ b/Application/Presentation/Entry/Sources/Routing/MainTab.swift @@ -1,6 +1,6 @@ // // MainTab.swift -// Presentation +// Entry // // Created by opfic on 4/30/26. // diff --git a/Application/Presentation/Entry/Sources/SharedExports.swift b/Application/Presentation/Entry/Sources/SharedExports.swift new file mode 100644 index 00000000..129e6931 --- /dev/null +++ b/Application/Presentation/Entry/Sources/SharedExports.swift @@ -0,0 +1,8 @@ +// +// SharedExports.swift +// Entry +// +// Created by opfic on 7/2/26. +// + +@_exported import PresentationShared diff --git a/Application/Presentation/Sources/WindowGroup/TodoEditorWindowView.swift b/Application/Presentation/Entry/Sources/WindowGroup/TodoEditorWindowView.swift similarity index 99% rename from Application/Presentation/Sources/WindowGroup/TodoEditorWindowView.swift rename to Application/Presentation/Entry/Sources/WindowGroup/TodoEditorWindowView.swift index ca7b2c9c..75e1ffb6 100644 --- a/Application/Presentation/Sources/WindowGroup/TodoEditorWindowView.swift +++ b/Application/Presentation/Entry/Sources/WindowGroup/TodoEditorWindowView.swift @@ -1,6 +1,6 @@ // // TodoEditorWindowView.swift -// Presentation +// Entry // // Created by opfic on 5/31/26. // diff --git a/Application/Presentation/Sources/WindowGroup/TodoWindowCoordinator.swift b/Application/Presentation/Entry/Sources/WindowGroup/TodoWindowCoordinator.swift similarity index 99% rename from Application/Presentation/Sources/WindowGroup/TodoWindowCoordinator.swift rename to Application/Presentation/Entry/Sources/WindowGroup/TodoWindowCoordinator.swift index d17d0738..ac8c4a0a 100644 --- a/Application/Presentation/Sources/WindowGroup/TodoWindowCoordinator.swift +++ b/Application/Presentation/Entry/Sources/WindowGroup/TodoWindowCoordinator.swift @@ -1,6 +1,6 @@ // // TodoWindowCoordinator.swift -// Presentation +// Entry // // Created by opfic on 5/31/26. // diff --git a/Application/Presentation/Entry/Tests/.swiftlint.yml b/Application/Presentation/Entry/Tests/.swiftlint.yml new file mode 100644 index 00000000..7bb38c07 --- /dev/null +++ b/Application/Presentation/Entry/Tests/.swiftlint.yml @@ -0,0 +1 @@ +parent_config: ../../../../.swiftlint-tests.yml diff --git a/Application/Presentation/Tests/Login/LoginFeatureTests.swift b/Application/Presentation/Entry/Tests/Login/LoginFeatureTests.swift similarity index 99% rename from Application/Presentation/Tests/Login/LoginFeatureTests.swift rename to Application/Presentation/Entry/Tests/Login/LoginFeatureTests.swift index d1c641ad..0159b467 100644 --- a/Application/Presentation/Tests/Login/LoginFeatureTests.swift +++ b/Application/Presentation/Entry/Tests/Login/LoginFeatureTests.swift @@ -1,6 +1,6 @@ // // LoginFeatureTests.swift -// PresentationTests +// EntryTests // // Created by opfic on 6/5/26. // @@ -9,7 +9,7 @@ import Testing import PresentationShared import Foundation import Domain -@testable import Presentation +@testable import Entry @MainActor struct LoginFeatureTests { diff --git a/Application/Presentation/Tests/Main/MainFeatureTests.swift b/Application/Presentation/Entry/Tests/Main/MainFeatureTests.swift similarity index 99% rename from Application/Presentation/Tests/Main/MainFeatureTests.swift rename to Application/Presentation/Entry/Tests/Main/MainFeatureTests.swift index ef2d6df8..404fab2a 100644 --- a/Application/Presentation/Tests/Main/MainFeatureTests.swift +++ b/Application/Presentation/Entry/Tests/Main/MainFeatureTests.swift @@ -1,6 +1,6 @@ // // MainFeatureTests.swift -// PresentationTests +// EntryTests // // Created by opfic on 6/16/26. // @@ -10,7 +10,7 @@ import PresentationShared import Domain import Foundation import Testing -@testable import Presentation +@testable import Entry @MainActor struct MainFeatureTests { diff --git a/Application/Presentation/Tests/Root/RootFeatureTestSupport.swift b/Application/Presentation/Entry/Tests/Root/RootFeatureTestSupport.swift similarity index 99% rename from Application/Presentation/Tests/Root/RootFeatureTestSupport.swift rename to Application/Presentation/Entry/Tests/Root/RootFeatureTestSupport.swift index 6d999159..14c15d6b 100644 --- a/Application/Presentation/Tests/Root/RootFeatureTestSupport.swift +++ b/Application/Presentation/Entry/Tests/Root/RootFeatureTestSupport.swift @@ -1,6 +1,6 @@ // // RootFeatureTestSupport.swift -// PresentationTests +// EntryTests // // Created by opfic on 6/17/26. // @@ -11,7 +11,7 @@ import Core import Domain import Foundation import Testing -@testable import Presentation +@testable import Entry @MainActor protocol RootStateDriving { diff --git a/Application/Presentation/Tests/Root/RootFeatureTests.swift b/Application/Presentation/Entry/Tests/Root/RootFeatureTests.swift similarity index 99% rename from Application/Presentation/Tests/Root/RootFeatureTests.swift rename to Application/Presentation/Entry/Tests/Root/RootFeatureTests.swift index 29a23fb3..d7a841c4 100644 --- a/Application/Presentation/Tests/Root/RootFeatureTests.swift +++ b/Application/Presentation/Entry/Tests/Root/RootFeatureTests.swift @@ -1,6 +1,6 @@ // // RootFeatureTests.swift -// PresentationTests +// EntryTests // // Created by opfic on 6/17/26. // diff --git a/Application/Presentation/Entry/Tests/Support/EntryTestSupport.swift b/Application/Presentation/Entry/Tests/Support/EntryTestSupport.swift new file mode 100644 index 00000000..da6280ea --- /dev/null +++ b/Application/Presentation/Entry/Tests/Support/EntryTestSupport.swift @@ -0,0 +1,66 @@ +// +// EntryTestSupport.swift +// EntryTests +// +// Created by opfic on 7/6/26. +// + +import Domain + +@MainActor +func waitUntil( + timeout: Duration = .seconds(1), + pollInterval: Duration = .milliseconds(20), + _ condition: @escaping () -> Bool +) async { + let continuousClock = ContinuousClock() + let deadline = continuousClock.now + timeout + + while !condition() && continuousClock.now < deadline { + try? await Task.sleep(for: pollInterval) + } +} + +final class SignInUseCaseSpy: SignInUseCase { + var error: Error? + var signedIn = true + var shouldSuspend = false + private(set) var calledProviders = [AuthProvider]() + private(set) var successfulProviders = [AuthProvider]() + private var continuation: CheckedContinuation? + private var shouldResume = false + + func execute(_ provider: AuthProvider) async throws -> Bool { + calledProviders.append(provider) + + if shouldSuspend { + await withCheckedContinuation { continuation in + if shouldResume { + shouldResume = false + continuation.resume() + } else { + self.continuation = continuation + } + } + } + + if let error { + throw error + } + + if signedIn { + successfulProviders.append(provider) + } + + return signedIn + } + + func resume() { + guard let continuation else { + shouldResume = true + return + } + self.continuation = nil + continuation.resume() + } +} diff --git a/Application/Presentation/Sources/Common/LoadingState.swift b/Application/Presentation/PresentationShared/Sources/Common/LoadingState.swift similarity index 98% rename from Application/Presentation/Sources/Common/LoadingState.swift rename to Application/Presentation/PresentationShared/Sources/Common/LoadingState.swift index a9a9dda9..d23dd01f 100644 --- a/Application/Presentation/Sources/Common/LoadingState.swift +++ b/Application/Presentation/PresentationShared/Sources/Common/LoadingState.swift @@ -1,12 +1,11 @@ // // LoadingState.swift -// Presentation +// PresentationShared // // Created by opfic on 3/16/26. // import Foundation -import Domain @MainActor public final class LoadingState { @@ -26,7 +25,7 @@ public final class LoadingState { private var visibleDelayedTargets = Set() private var visibleTargets = Set() - nonisolated init(delay: Duration = .seconds(0.3)) { + public nonisolated init(delay: Duration = .seconds(0.3)) { self.delay = delay } diff --git a/Application/Presentation/PresentationShared/Tests/.swiftlint.yml b/Application/Presentation/PresentationShared/Tests/.swiftlint.yml new file mode 100644 index 00000000..7bb38c07 --- /dev/null +++ b/Application/Presentation/PresentationShared/Tests/.swiftlint.yml @@ -0,0 +1 @@ +parent_config: ../../../../.swiftlint-tests.yml diff --git a/Application/Presentation/Tests/Common/LoadingFeatureTests.swift b/Application/Presentation/PresentationShared/Tests/Common/LoadingFeatureTests.swift similarity index 96% rename from Application/Presentation/Tests/Common/LoadingFeatureTests.swift rename to Application/Presentation/PresentationShared/Tests/Common/LoadingFeatureTests.swift index e517420f..e7fc9874 100644 --- a/Application/Presentation/Tests/Common/LoadingFeatureTests.swift +++ b/Application/Presentation/PresentationShared/Tests/Common/LoadingFeatureTests.swift @@ -1,13 +1,12 @@ // // LoadingFeatureTests.swift -// PresentationTests +// PresentationSharedTests // // Created by opfic on 6/11/26. // import Testing -import PresentationShared -@testable import Presentation +@testable import PresentationShared @MainActor struct LoadingFeatureTests { diff --git a/Application/Presentation/Tests/Search/SearchFeatureTestDoubles.swift b/Application/Presentation/PresentationShared/Tests/Search/SearchFeatureTestDoubles.swift similarity index 99% rename from Application/Presentation/Tests/Search/SearchFeatureTestDoubles.swift rename to Application/Presentation/PresentationShared/Tests/Search/SearchFeatureTestDoubles.swift index 60da83e8..8fe47f42 100644 --- a/Application/Presentation/Tests/Search/SearchFeatureTestDoubles.swift +++ b/Application/Presentation/PresentationShared/Tests/Search/SearchFeatureTestDoubles.swift @@ -1,15 +1,14 @@ // // SearchFeatureTestDoubles.swift -// PresentationTests +// PresentationSharedTests // // Created by opfic on 6/12/26. // import Foundation -import PresentationShared import Core import Domain -@testable import Presentation +@testable import PresentationShared @MainActor struct SearchStoreTestAdapter { diff --git a/Application/Presentation/Tests/Search/SearchFeatureTests.swift b/Application/Presentation/PresentationShared/Tests/Search/SearchFeatureTests.swift similarity index 98% rename from Application/Presentation/Tests/Search/SearchFeatureTests.swift rename to Application/Presentation/PresentationShared/Tests/Search/SearchFeatureTests.swift index 572a09d7..e75cd85e 100644 --- a/Application/Presentation/Tests/Search/SearchFeatureTests.swift +++ b/Application/Presentation/PresentationShared/Tests/Search/SearchFeatureTests.swift @@ -1,16 +1,15 @@ // // SearchFeatureTests.swift -// PresentationTests +// PresentationSharedTests // // Created by opfic on 6/12/26. // import Testing -import PresentationShared import Foundation import Core import Domain -@testable import Presentation +@testable import PresentationShared @MainActor struct SearchFeatureTests { diff --git a/Application/Presentation/Tests/Support/TestSupport.swift b/Application/Presentation/PresentationShared/Tests/Support/TestSupport.swift similarity index 99% rename from Application/Presentation/Tests/Support/TestSupport.swift rename to Application/Presentation/PresentationShared/Tests/Support/TestSupport.swift index 565c94f2..64c56022 100644 --- a/Application/Presentation/Tests/Support/TestSupport.swift +++ b/Application/Presentation/PresentationShared/Tests/Support/TestSupport.swift @@ -1,6 +1,6 @@ // // TestSupport.swift -// PresentationTests +// PresentationSharedTests // // Created by opfic on 4/6/26. // @@ -10,7 +10,7 @@ import Foundation import Combine import Core import Domain -@testable import Presentation +@testable import PresentationShared @MainActor func waitUntil( diff --git a/Application/Presentation/Tests/Todo/TodoDetailFeatureTests.swift b/Application/Presentation/PresentationShared/Tests/Todo/TodoDetailFeatureTests.swift similarity index 99% rename from Application/Presentation/Tests/Todo/TodoDetailFeatureTests.swift rename to Application/Presentation/PresentationShared/Tests/Todo/TodoDetailFeatureTests.swift index e305e719..cd58ae8e 100644 --- a/Application/Presentation/Tests/Todo/TodoDetailFeatureTests.swift +++ b/Application/Presentation/PresentationShared/Tests/Todo/TodoDetailFeatureTests.swift @@ -1,15 +1,14 @@ // // TodoDetailFeatureTests.swift -// PresentationTests +// PresentationSharedTests // // Created by opfic on 6/11/26. // import Testing -import PresentationShared import Foundation import Domain -@testable import Presentation +@testable import PresentationShared @MainActor struct TodoDetailFeatureTests { diff --git a/Application/Presentation/Tests/Todo/TodoEditorFeatureTestDoubles.swift b/Application/Presentation/PresentationShared/Tests/Todo/TodoEditorFeatureTestDoubles.swift similarity index 99% rename from Application/Presentation/Tests/Todo/TodoEditorFeatureTestDoubles.swift rename to Application/Presentation/PresentationShared/Tests/Todo/TodoEditorFeatureTestDoubles.swift index dfc339e3..91239621 100644 --- a/Application/Presentation/Tests/Todo/TodoEditorFeatureTestDoubles.swift +++ b/Application/Presentation/PresentationShared/Tests/Todo/TodoEditorFeatureTestDoubles.swift @@ -1,15 +1,14 @@ // // TodoEditorFeatureTestDoubles.swift -// PresentationTests +// PresentationSharedTests // // Created by opfic on 6/12/26. // import Testing -import PresentationShared import Foundation import Domain -@testable import Presentation +@testable import PresentationShared let todoEditorNow = Date(timeIntervalSince1970: 1_000) diff --git a/Application/Presentation/Tests/Todo/TodoEditorFeatureTests.swift b/Application/Presentation/PresentationShared/Tests/Todo/TodoEditorFeatureTests.swift similarity index 99% rename from Application/Presentation/Tests/Todo/TodoEditorFeatureTests.swift rename to Application/Presentation/PresentationShared/Tests/Todo/TodoEditorFeatureTests.swift index 9de1fcec..27dfb63d 100644 --- a/Application/Presentation/Tests/Todo/TodoEditorFeatureTests.swift +++ b/Application/Presentation/PresentationShared/Tests/Todo/TodoEditorFeatureTests.swift @@ -1,6 +1,6 @@ // // TodoEditorFeatureTests.swift -// PresentationTests +// PresentationSharedTests // // Created by opfic on 6/12/26. // @@ -8,7 +8,7 @@ import Testing import Foundation import Domain -@testable import Presentation +@testable import PresentationShared @MainActor struct TodoEditorFeatureTests { diff --git a/Application/Presentation/Tests/Todo/TodoListFeatureTestDoubles.swift b/Application/Presentation/PresentationShared/Tests/Todo/TodoListFeatureTestDoubles.swift similarity index 99% rename from Application/Presentation/Tests/Todo/TodoListFeatureTestDoubles.swift rename to Application/Presentation/PresentationShared/Tests/Todo/TodoListFeatureTestDoubles.swift index fa82d7fb..5e1ca72e 100644 --- a/Application/Presentation/Tests/Todo/TodoListFeatureTestDoubles.swift +++ b/Application/Presentation/PresentationShared/Tests/Todo/TodoListFeatureTestDoubles.swift @@ -1,15 +1,14 @@ // // TodoListFeatureTestDoubles.swift -// PresentationTests +// PresentationSharedTests // // Created by opfic on 6/12/26. // import Foundation -import PresentationShared import Core import Domain -@testable import Presentation +@testable import PresentationShared @MainActor final class TodoListStoreTestAdapter { diff --git a/Application/Presentation/Tests/Todo/TodoListFeatureTests.swift b/Application/Presentation/PresentationShared/Tests/Todo/TodoListFeatureTests.swift similarity index 99% rename from Application/Presentation/Tests/Todo/TodoListFeatureTests.swift rename to Application/Presentation/PresentationShared/Tests/Todo/TodoListFeatureTests.swift index 4b9f50fe..382f3100 100644 --- a/Application/Presentation/Tests/Todo/TodoListFeatureTests.swift +++ b/Application/Presentation/PresentationShared/Tests/Todo/TodoListFeatureTests.swift @@ -1,16 +1,15 @@ // // TodoListFeatureTests.swift -// PresentationTests +// PresentationSharedTests // // Created by opfic on 6/12/26. // import Testing import Foundation -import PresentationShared import Core import Domain -@testable import Presentation +@testable import PresentationShared @MainActor struct TodoListFeatureTests { diff --git a/Application/Presentation/Project.swift b/Application/Presentation/Project.swift index 3a59b11b..fe45b5c8 100644 --- a/Application/Presentation/Project.swift +++ b/Application/Presentation/Project.swift @@ -53,6 +53,31 @@ let project = Project( ], settings: frameworkBuildSettings ), + .target( + name: "PresentationSharedTests", + destinations: .iOS, + product: .unitTests, + bundleId: "com.opfic.DevLog.PresentationSharedTests", + infoPlist: .file(path: testsInfoPlistPath), + sources: ["PresentationShared/Tests/**/*.swift"], + scripts: [ + DevLogScripts.swiftLint( + sourcePath: "PresentationShared/Tests", + configPath: "PresentationShared/Tests/.swiftlint.yml" + ) + ], + dependencies: [ + .project(target: "Domain", path: "../Domain"), + .project(target: "Core", path: "../Core"), + .target(name: "PresentationShared") + ], + settings: .devlog( + base: [ + "ENABLE_USER_SCRIPT_SANDBOXING": "NO", + "TEST_TARGET_NAME": "PresentationShared" + ] + ) + ), .target( name: "HomeTab", destinations: .iOS, @@ -234,16 +259,16 @@ let project = Project( ) ), .target( - name: "Presentation", + name: "Entry", destinations: .iOS, product: .staticFramework, - bundleId: "com.opfic.DevLog.Presentation", + bundleId: "com.opfic.DevLog.Entry", infoPlist: .file(path: frameworkInfoPlistPath), - sources: ["Sources/**/*.swift"], + sources: ["Entry/Sources/**/*.swift"], scripts: [ DevLogScripts.swiftLint( - sourcePath: "Sources", - configPath: "Sources/.swiftlint.yml" + sourcePath: "Entry/Sources", + configPath: "Entry/Sources/.swiftlint.yml" ) ], dependencies: [ @@ -258,27 +283,49 @@ let project = Project( settings: frameworkBuildSettings ), .target( - name: "PresentationTests", + name: "EntryTests", destinations: .iOS, product: .unitTests, - bundleId: "com.opfic.DevLog.PresentationTests", + bundleId: "com.opfic.DevLog.EntryTests", infoPlist: .file(path: testsInfoPlistPath), - sources: ["Tests/**/*.swift"], + sources: ["Entry/Tests/**/*.swift"], scripts: [ DevLogScripts.swiftLint( - sourcePath: "Tests", - configPath: "Tests/.swiftlint.yml" + sourcePath: "Entry/Tests", + configPath: "Entry/Tests/.swiftlint.yml" ) ], dependencies: [ - .target(name: "Presentation") + .project(target: "Domain", path: "../Domain"), + .project(target: "Core", path: "../Core"), + .target(name: "Entry"), + .target(name: "PresentationShared") ], settings: .devlog( base: [ "ENABLE_USER_SCRIPT_SANDBOXING": "NO", - "TEST_TARGET_NAME": "Presentation" + "TEST_TARGET_NAME": "Entry" ] ) + ), + .target( + name: "Presentation", + destinations: .iOS, + product: .staticFramework, + bundleId: "com.opfic.DevLog.Presentation", + infoPlist: .file(path: frameworkInfoPlistPath), + sources: ["Sources/**/*.swift"], + scripts: [ + DevLogScripts.swiftLint( + sourcePath: "Sources", + configPath: "Sources/.swiftlint.yml" + ) + ], + dependencies: [ + .target(name: "Entry"), + .target(name: "PresentationShared") + ], + settings: frameworkBuildSettings ) ] ) diff --git a/Application/Presentation/Sources/EntryExports.swift b/Application/Presentation/Sources/EntryExports.swift new file mode 100644 index 00000000..cef7895c --- /dev/null +++ b/Application/Presentation/Sources/EntryExports.swift @@ -0,0 +1,9 @@ +// +// EntryExports.swift +// Presentation +// +// Created by opfic on 7/6/26. +// + +@_exported import Entry +@_exported import PresentationShared diff --git a/Application/Presentation/Sources/Extension/Error+SocialLogin.swift b/Application/Presentation/Sources/Extension/Error+SocialLogin.swift deleted file mode 100644 index 34fda8b6..00000000 --- a/Application/Presentation/Sources/Extension/Error+SocialLogin.swift +++ /dev/null @@ -1,6 +0,0 @@ -// -// Error+SocialLogin.swift -// Presentation -// -// Created by opfic on 5/15/26. -// diff --git a/Application/Presentation/Sources/SharedExports.swift b/Application/Presentation/Sources/SharedExports.swift deleted file mode 100644 index c81fc3dc..00000000 --- a/Application/Presentation/Sources/SharedExports.swift +++ /dev/null @@ -1,10 +0,0 @@ -// -// SharedExports.swift -// Presentation -// -// Created by opfic on 7/2/26. -// - -// App의 window group에서 사용하는 shared presentation API를 노출하면서 -// App은 PresentationShared가 아닌 Presentation에만 의존하도록 유지합니다. -@_exported import PresentationShared diff --git a/Application/Presentation/Sources/Version.xcconfig b/Application/Presentation/Sources/Version.xcconfig deleted file mode 100644 index 30a0c309..00000000 --- a/Application/Presentation/Sources/Version.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "../../Shared/Version.xcconfig" diff --git a/Application/Presentation/Tests/.swiftlint.yml b/Application/Presentation/Tests/.swiftlint.yml deleted file mode 100644 index b2c5c38d..00000000 --- a/Application/Presentation/Tests/.swiftlint.yml +++ /dev/null @@ -1 +0,0 @@ -parent_config: ../../../.swiftlint-tests.yml diff --git a/README.md b/README.md index 6eca3aaf..ece1a99d 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Todo, 저장 링크, 오늘 할 일, 받은 알림, 누적 활동을 하나의 ## 아키텍처 `DevLog.xcworkspace` 안에서 Application, Widget 모듈을 분리하고 화면, 상태, 비즈니스 로직, 외부 의존성 경계를 나눈 `Clean Architecture` 기반 구성 -`Presentation`은 tab shell/root composition을 유지하고, 공통 UI 흐름은 `PresentationShared`, 탭 단위 흐름은 `HomeTab`, `TodayTab`, `NotificationTab` target이 소유함 +`Presentation` target은 `App`의 기존 import를 유지하는 re-export 역할을 하고, root/auth/tab shell/window 흐름은 `Entry`, 공통 Todo/Search/Loading 흐름은 `PresentationShared`, 탭 단위 흐름은 `HomeTab`, `TodayTab`, `NotificationTab`, `ProfileTab` target이 소유함 @@ -238,12 +238,14 @@ DevLog_iOS/ │ ├── Data/ # Repository 구현, DTO, Mapper, Data 계층 Protocol │ ├── Infra/ # Firebase, 소셜 로그인, 네트워크, 메타데이터 서비스 구현 │ ├── Persistence/ # UserDefaults, 이미지 저장소, 앱 로컬 영속성 처리 -│ ├── Presentation/ # SwiftUI tab shell, root composition -│ │ ├── PresentationShared/ # 공통 UI 흐름, 공통 presentation structure +│ ├── Presentation/ # Presentation project, re-export/shared/entry/tab target 구성 +│ │ ├── Sources/ # Presentation target re-export source +│ │ ├── Entry/ # root/auth/tab shell/window target +│ │ ├── PresentationShared/ # 공통 Todo/Search/Loading UI, 공통 presentation structure │ │ ├── HomeTab/ # Home 탭 화면, feature, coordinator, 테스트 │ │ ├── TodayTab/ # Today 탭 화면, feature, coordinator, 테스트 │ │ ├── NotificationTab/ # Notification 탭 화면, feature, coordinator, 테스트 -│ │ └── Sources/ # Main, Root, Settings 등 앱 presentation shell +│ │ └── ProfileTab/ # Profile/Settings 탭 화면, feature, coordinator, 테스트 │ └── Widget/ # 앱-위젯 브릿지, 위젯 동기화 이벤트, 스냅샷 갱신 ├── Widget/ │ ├── WidgetCore/ # 위젯 스냅샷 모델, Factory, App Group 상수 diff --git a/docs/graph.png b/docs/graph.png index 7e980b7a..44d44cbb 100644 Binary files a/docs/graph.png and b/docs/graph.png differ