Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
00fd426
Replace Combine with AsyncSequence and @Observable, bump to iOS 17
g-enius Feb 25, 2026
9a479ea
Fix stale Combine and UIKit references in descriptions and CLAUDE.md
g-enius Feb 25, 2026
776dc7f
Orange app icon and unique bundle ID for async branch
g-enius Feb 25, 2026
8e5f35c
Update featured items for async branch: AsyncSequence, @Observable, i…
g-enius Feb 25, 2026
798dbfd
Add concurrency patterns featured item
g-enius Feb 25, 2026
196facb
Update concurrency description to match real implementations
g-enius Feb 25, 2026
00715f7
Fix AppIcon size from 2048x2048 to 1024x1024
g-enius Feb 26, 2026
50c0687
Rename app display name to Fun Async
g-enius Feb 26, 2026
aec2002
Fix search tests to work with debounce timing
g-enius Feb 26, 2026
89948e3
Use polling instead of fixed sleep in search tests
g-enius Feb 26, 2026
36403f9
Adapt config for async-sequence branch
g-enius Feb 27, 2026
c8225fd
Remove polling from search tests
g-enius Feb 27, 2026
9b879f8
Trigger CI
g-enius Feb 27, 2026
03eecb6
Fix dark mode not applied on app launch
g-enius Feb 27, 2026
0475f4b
Extract named navigation methods on AppCoordinator
g-enius Feb 28, 2026
0c63d0f
Split TechnologyDescriptions to fix type_body_length warning
g-enius Feb 28, 2026
cefcbf3
Register toast service in LoginSession and observe via serviceDidRegi…
g-enius Feb 28, 2026
1ae45c6
Move routing table to AppCoordinator.destinationView(for:)
g-enius Feb 28, 2026
78a5462
Add @ViewBuilder to destinationView for future routing
g-enius Feb 28, 2026
d2146fe
Document ownership wrapper pattern and add anti-pattern rule
g-enius Feb 28, 2026
97d2def
Restore general rules deleted during async-sequence migration
g-enius Feb 28, 2026
dbc2667
Split toastObservation into registrationObservation + toastObservation
g-enius Feb 28, 2026
69be639
Unify service registration observation for toast and dark mode
g-enius Feb 28, 2026
91c7804
Remove redundant toastObservation cancel in transitionToLoginFlow
g-enius Feb 28, 2026
f5de172
Add @Bindable vs plain property rule and code comments
g-enius Mar 1, 2026
4f21e82
Remove stale migration guide comments
g-enius Mar 1, 2026
9ae20b5
Add Sendable doc comment to ServiceKey
g-enius Mar 1, 2026
60b9eac
Remove non-AsyncSequence-specific iOS 17 unlocks from deploymentTarge…
github-actions[bot] Mar 1, 2026
2991460
Restore base branch content in TechnologyDescriptions+Extended.swift
github-actions[bot] Mar 1, 2026
18ba6ab
Restore TaskGroup example in concurrencyPatternsDescription to match …
github-actions[bot] Mar 1, 2026
d0cd4f2
Rename TechnologyItem.combine → .asyncSequence across codebase
github-actions[bot] Mar 1, 2026
951f3cc
Replace HomeViewModel favorites example with ItemsViewModel debounce …
github-actions[bot] Mar 1, 2026
0c2984f
Sync AsyncStream example with TaskGroup logic in concurrencyPatternsD…
github-actions[bot] Mar 1, 2026
5b3a2bc
Clarify AsyncStream vs TaskGroup tradeoffs in concurrency patterns
g-enius Mar 1, 2026
065a4e6
Use continuation.finish() instead of break in AsyncStream example
g-enius Mar 1, 2026
234dca8
Fix remaining nanoseconds in ItemsViewModelTests
g-enius Mar 1, 2026
23e7097
Revert Combine sink example to match base branch
github-actions[bot] Mar 1, 2026
f9d333e
Rename *Changes stream properties to *Stream across all call sites
github-actions[bot] Mar 1, 2026
610e836
Rename toastEvents → toastStream for consistent *Stream naming
g-enius Mar 1, 2026
a440ee7
Remove stale onPop/onShare from navigation closures list
g-enius Mar 1, 2026
b0cd7a4
Restore navigationDestination chaining comments after rebase
g-enius Mar 1, 2026
b0817a6
Fix StreamBroadcaster race: use eager AsyncStream continuation
g-enius Mar 1, 2026
e2f81ed
Remove unnecessary @Bindable from ItemsView outer struct
github-actions[bot] Mar 1, 2026
7fcc3d8
Remove redundant [weak self] from inner Task in StreamBroadcaster
github-actions[bot] Mar 1, 2026
c7a8a94
Clarify iOS 17 requirement in README and PR description
g-enius Mar 1, 2026
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
11 changes: 8 additions & 3 deletions .claude/agents/change-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ Review all recent code changes thoroughly and provide a structured, actionable a

## Project Context

- **Branch**: feature/navigation-stack — Pure SwiftUI, NavigationPath, single AppCoordinator (ObservableObject), Combine
- **Branch**: feature/async-sequence — Pure SwiftUI, @Observable, AsyncSequence + StreamBroadcaster, zero Combine
- **Packages**: `FunCore` → `FunModel` → `FunViewModel` / `FunServices` → `FunUI` → `FunCoordinator`
- **Dependency direction**: Never import upward. ViewModel must NOT import UI or Coordinator.
- **UIKit**: Zero UIKit in this branch — flag any `import UIKit` as a critical issue
- **Combine**: Zero Combine in this branch — flag any `import Combine` as critical
- **DI**: ServiceLocator with `@Service` property wrapper, session-scoped (LoginSession / AuthenticatedSession)
- **Testing**: Swift Testing framework, mocks in FunModelTestSupport
- **Lint**: SwiftLint with custom rules (no_print, weak_coordinator_in_viewmodel, no_direct_userdefaults)
Expand All @@ -38,19 +39,23 @@ Review all recent code changes thoroughly and provide a structured, actionable a
### Step 3: Architecture Check
- Package dependency direction respected?
- No `import UIKit` — pure SwiftUI branch
- No `import Combine` — pure AsyncSequence branch
- No coordinator references in ViewModels (except weak closures)
- No `print()` — use LoggerService
- No `UserDefaults.standard` outside Services
- Navigation logic only in Coordinators (AppCoordinator)
- NavigationPath mutations only in coordinator, not in Views
Copy link
Owner Author

Choose a reason for hiding this comment

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

this should be the same as the navigation-stack branch. If change, should change in the previous PR.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Restored. NavigationPath rule still applies — added it back.

- Protocols in Core (reusable) or Model (domain), never in Services/ViewModel/UI/Coordinator
- Reactive pattern: Combine (`@Published`, `@StateObject`, `@ObservedObject`, `.sink`)
- Reactive pattern: `@Observable`, `AsyncStream`, `StreamBroadcaster`, `for await`, `Task`
- `@ObservationIgnored` on services and non-UI state
- `@State` (not `@StateObject`) for owning @Observable objects

### Step 4: Correctness Check
Comment on lines 46 to 49
Copy link
Owner Author

Choose a reason for hiding this comment

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

why deleting this general rules?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Restored the general memory management wording. Now extends it with async-specific guard let self instead of replacing.

- **Logic errors**: Algorithms, conditions, control flow
- **Type safety**: Force unwraps, force casts, unsafe assumptions
- **Concurrency**: `@MainActor` isolation, `Sendable` conformance, Swift 6 strict
- **Memory management**: `[weak self]` and `[weak coordinator]` in closures
- **Memory management**: `[weak self]` and `[weak coordinator]` in closures, `guard let self` inside `for await` loops
- **Stream lifecycle**: Tasks stored for cancellation? Cleaned up properly?
- **API contracts**: Public interfaces used correctly

### Step 5: Quality Check
Expand Down
3 changes: 1 addition & 2 deletions .claude/skills/pull-request/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ Create a draft PR following the team's quality standards.
2. **Review changes**
- `git diff main...HEAD` to review all changes
- Verify package dependency direction isn't violated
- Check for any `print()`, `UserDefaults.standard`, or other anti-patterns
- Verify zero UIKit imports (this branch is pure SwiftUI)
- Check for any `print()`, `UserDefaults.standard`, `import Combine`, or `import UIKit`

3. **Accessibility checklist** (for UI changes)
- Dynamic Type: Do text elements scale with user font size preference?
Expand Down
4 changes: 3 additions & 1 deletion .claude/skills/review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ Review all recent code changes for completeness, correctness, and consistency wi
3. **Architecture check**
- Verify package dependency direction: `Coordinator → UI → ViewModel → Model → Core`, `Services → Model → Core`
- No `import UIKit` anywhere — this branch is pure SwiftUI
- No `import Combine` anywhere — this branch uses AsyncSequence, zero Combine
- No coordinator references in ViewModels (except weak closures)
- No `print()` — use LoggerService
- No `UserDefaults.standard` outside Services
- Navigation logic only in Coordinators
- Protocols in Core (reusable) or Model (domain), never in Services/ViewModel/UI/Coordinator
- Branch-specific: Combine + NavigationPath + single AppCoordinator (ObservableObject)
- Branch-specific: @Observable + AsyncStream + StreamBroadcaster (no Combine, no ObservableObject)

4. **Similar pattern search**
- Search the codebase for code that follows the same pattern as what changed
Expand All @@ -35,6 +36,7 @@ Review all recent code changes for completeness, correctness, and consistency wi
5. **Correctness check**
- Logic errors, type safety, concurrency (Swift 6 strict), memory management (`[weak self]`, `[weak coordinator]`)
- Verify `@MainActor` isolation, `Sendable` conformance where needed
- Check `@ObservationIgnored` on properties that shouldn't trigger view updates

6. **Cross-platform parity**
- Compare with `~/Documents/Source/Fun-Android/` for the same feature
Expand Down
23 changes: 12 additions & 11 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,42 +46,43 @@ Never import upward. ViewModel must NOT import UI or Coordinator. Model must NOT

## Anti-Patterns (Red Flags)
- `import UIKit` anywhere — this branch is pure SwiftUI, zero UIKit
- `import Combine` anywhere — this branch uses AsyncSequence, zero Combine
- Coordinator references in ViewModels (except weak optional closures) — retain cycle risk
- `print()` anywhere — use LoggerService
- `UserDefaults.standard` outside Services — use FeatureToggleService
- Adding `fatalError()` for missing services — ServiceLocator.resolve() already crashes with `fatalError` if a service isn't registered; don't add redundant guards
- Navigation logic in Views — all navigation (push, pop, tab switch, modal present/dismiss) must go through named AppCoordinator methods (`showDetail`, `selectTab`, `showProfile`, etc.), never inline property manipulation like `coordinator.homePath.append(item)` or `coordinator.isProfilePresented = true`
- Protocol definitions in Services — domain protocols go in Model, reusable abstractions in Core
- Wrong ownership annotations — tab content wrappers must use `@StateObject` to own ViewModels (not `@ObservedObject`). `@ObservedObject` on a ViewModel means it gets recreated on every re-render. Conversely, the coordinator must be `let` or `@ObservedObject` (not `@StateObject`) since the wrapper doesn't own it.
- Wrong ownership annotations — tab content wrappers must use `@State` to own ViewModels (not bare `var`). `@State` ensures the ViewModel survives re-renders. The coordinator must be `let` (not `@Bindable` or `@State`) since the wrapper doesn't own it.

## Architecture (this branch: feature/navigation-stack)
## Architecture (this branch: feature/async-sequence)
- **Entry point**: SwiftUI `@main App` struct (`FunApp.swift`) — no AppDelegate or SceneDelegate
- **Navigation**: Single `AppCoordinator: ObservableObject` with per-tab `NavigationPath`
- **Navigation**: Single `@Observable AppCoordinator` with per-tab `NavigationPath`
- **Views**: Pure SwiftUI views, no UIHostingController or UIViewControllers
- **Reactive**: Combine (`@Published`, `@StateObject`, `@ObservedObject`, `.sink`)
- **Reactive**: AsyncSequence + `StreamBroadcaster` (zero Combine). Services yield events via `StreamBroadcaster.yield()`, consumers iterate with `for await event in stream`
- **Observation**: `@Observable` (not ObservableObject), `@ObservationIgnored` for non-observed state, `@State` (not @StateObject) in app entry
- **ViewModel → Coordinator**: Optional closures wired in tab content wrappers via `.task { viewModel.onShowDetail = { ... } }`
- **Tab bar**: SwiftUI `TabView(selection: $coordinator.selectedTab)`
- **Push nav**: `coordinator.showDetail(item, in: .home)` — named methods on AppCoordinator
- **Modals**: `.sheet(isPresented: $coordinator.isProfilePresented)`
- **DI**: ServiceLocator with `@Service` property wrapper, session-scoped (LoginSession / AuthenticatedSession)
- **Coordinator-owned views**: `AppRootView`, `MainTabView`, and tab content wrappers live in `Coordinator` (not `FunUI`) because they depend on `AppCoordinator`. Moving them to `FunUI` would create a circular dependency (`Coordinator → UI → Coordinator`). Pure reusable views (`HomeView`, `DetailView`, etc.) stay in `FunUI`.
- **Ownership wrappers**: Tab content wrappers (`HomeTabContent`, `ItemsTabContent`, etc.) use `@StateObject` to **own** their ViewModel and `@ObservedObject` (or `let`) for the coordinator passed from the parent. `@StateObject` ensures the ViewModel survives re-renders; `@ObservedObject` means the wrapper doesn't own the coordinator. Pure views in `FunUI` take `@ObservedObject var viewModel` since the wrapper owns it.
- **Ownership wrappers**: Tab content wrappers (`HomeTabContent`, `ItemsTabContent`, etc.) use `@State` to **own** their ViewModel and `let` for the coordinator passed from the parent. `@State` ensures the ViewModel survives re-renders; `let` means the wrapper doesn't own the coordinator. Pure views in `FunUI` take the ViewModel as a parameter since the wrapper owns it.

## Rule Index
Consult these files for detailed guidance (not auto-loaded — read on demand):
- `ai-rules/general.md` — Architecture deep-dive, MVVM-C patterns, DI, sessions, testing
- `ai-rules/swift-style.md` — Swift 6 concurrency, naming, Combine patterns, SwiftLint rules
- `ai-rules/swift-style.md` — Swift 6 concurrency, naming, AsyncSequence patterns, SwiftLint rules
- `ai-rules/ci-cd.md` — GitHub Actions CI workflow patterns

## Code Style
- Swift 6 strict concurrency, iOS 17+
- Pure SwiftUI (NavigationStack), MVVM-C with Combine
- Single AppCoordinator: ObservableObject with @Published NavigationPath per tab
- ViewModels use closures for navigation, wired in tab content wrappers
- Pure SwiftUI (NavigationStack), MVVM-C with AsyncSequence + @Observable
- Zero Combine — AsyncStream + StreamBroadcaster for reactive service events, @Observable for ViewModel state
- Navigation closures on ViewModels, wired by single AppCoordinator
- Navigation logic ONLY in Coordinators, never in Views
- Protocol placement: Core = reusable abstractions, Model = domain-specific
- ServiceLocator with @Service property wrapper
- Combine over NotificationCenter for reactive state
- ServiceLocator with @Service property wrapper (assertionFailure, not fatalError)

## Testing
- Swift Testing framework (`import Testing`, `@Test`, `#expect`, `@Suite`)
Expand Down
4 changes: 2 additions & 2 deletions Coordinator/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import PackageDescription
let package = Package(
name: "Coordinator",
platforms: [
.iOS(.v16),
.macCatalyst(.v16),
.iOS(.v17),
.macCatalyst(.v17),
],
products: [
.library(name: "FunCoordinator", targets: ["FunCoordinator"]),
Expand Down
104 changes: 60 additions & 44 deletions Coordinator/Sources/Coordinator/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,64 +5,74 @@
// SwiftUI-based coordinator managing navigation state and app flow
//

import Combine
import Observation
import SwiftUI

import FunCore
import FunModel

@MainActor
public final class AppCoordinator: ObservableObject {
@Observable
public final class AppCoordinator {

// MARK: - Services

@Service(.logger) private var logger: LoggerService
@Service(.featureToggles) private var featureToggleService: FeatureToggleServiceProtocol
@Service(.toast) private var toastService: ToastServiceProtocol
@ObservationIgnored @Service(.logger) private var logger: LoggerService
@ObservationIgnored @Service(.featureToggles) private var featureToggleService: FeatureToggleServiceProtocol
@ObservationIgnored @Service(.toast) private var toastService: ToastServiceProtocol

// MARK: - Session Management

private let sessionFactory: SessionFactory
private var currentSession: Session?
@ObservationIgnored private let sessionFactory: SessionFactory
@ObservationIgnored private var currentSession: Session?

// MARK: - App Flow State

@Published public var currentFlow: AppFlow = .login
public var currentFlow: AppFlow = .login

// MARK: - Navigation State

@Published public var selectedTab: TabIndex = .home
@Published public var homePath = NavigationPath()
@Published public var itemsPath = NavigationPath()
@Published public var settingsPath = NavigationPath()
@Published public var isProfilePresented = false
public var selectedTab: TabIndex = .home
public var homePath = NavigationPath()
public var itemsPath = NavigationPath()
public var settingsPath = NavigationPath()
public var isProfilePresented = false

// MARK: - Deep Link

private var pendingDeepLink: DeepLink?
@ObservationIgnored private var pendingDeepLink: DeepLink?

// MARK: - Service Observation

@ObservationIgnored private var registrationObservation: Task<Void, Never>?
@ObservationIgnored private var toastObservation: Task<Void, Never>?
@ObservationIgnored private var darkModeObservation: Task<Void, Never>?

// MARK: - Toast

@Published public var activeToast: ToastEvent?
private var cancellables = Set<AnyCancellable>()
public var activeToast: ToastEvent?

// MARK: - Dark Mode

@Published public var appearanceMode: AppearanceMode = .system
private var darkModeCancellable: AnyCancellable?
public var appearanceMode: AppearanceMode = .system

// MARK: - Init

public init(sessionFactory: SessionFactory) {
self.sessionFactory = sessionFactory
}

deinit {
registrationObservation?.cancel()
toastObservation?.cancel()
darkModeObservation?.cancel()
}

// MARK: - Start

public func start() {
activateSession(for: currentFlow)
observeToastEvents()
observeDarkMode()
observeServiceRegistrations()
}

// MARK: - Session Lifecycle
Expand Down Expand Up @@ -171,23 +181,34 @@ public final class AppCoordinator: ObservableObject {
}
}

// MARK: - Toast
// MARK: - Service Registration Observation

private func observeToastEvents() {
ServiceLocator.shared.serviceDidRegisterPublisher
.filter { $0 == .toast }
.sink { [weak self] _ in
self?.subscribeToToasts()
private func observeServiceRegistrations() {
registrationObservation?.cancel()
let registrations = ServiceLocator.shared.serviceRegistrations
registrationObservation = Task { [weak self] in
for await key in registrations {
guard let self else { break }
switch key {
case .toast: self.subscribeToToasts()
case .featureToggles: self.subscribeToDarkMode()
default: break
}
}
.store(in: &cancellables)
}
}

// MARK: - Toast

private func subscribeToToasts() {
toastService.toastPublisher
.sink { [weak self] event in
self?.activeToast = event
toastObservation?.cancel()
let stream = toastService.toastStream
toastObservation = Task { [weak self] in
for await event in stream {
guard let self else { break }
self.activeToast = event
}
.store(in: &cancellables)
}
}

public func dismissToast() {
Expand All @@ -196,20 +217,15 @@ public final class AppCoordinator: ObservableObject {

// MARK: - Dark Mode Observation

private func observeDarkMode() {
ServiceLocator.shared.serviceDidRegisterPublisher
.filter { $0 == .featureToggles }
.sink { [weak self] _ in
self?.subscribeToDarkMode()
}
.store(in: &cancellables)
}

private func subscribeToDarkMode() {
darkModeCancellable?.cancel()
darkModeCancellable = featureToggleService.appearanceModePublisher
.sink { [weak self] mode in
self?.appearanceMode = mode
darkModeObservation?.cancel()
appearanceMode = featureToggleService.appearanceMode
let stream = featureToggleService.appearanceModeStream
darkModeObservation = Task { [weak self] in
for await mode in stream {
guard let self else { break }
self.appearanceMode = mode
}
}
}
}
3 changes: 2 additions & 1 deletion Coordinator/Sources/Coordinator/AppRootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import FunUI
import FunViewModel

public struct AppRootView: View {
@ObservedObject var coordinator: AppCoordinator
// Plain var — only reads coordinator properties, no $ bindings needed
var coordinator: AppCoordinator

public init(coordinator: AppCoordinator) {
self.coordinator = coordinator
Expand Down
17 changes: 9 additions & 8 deletions Coordinator/Sources/Coordinator/MainTabView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import FunUI
import FunViewModel

struct MainTabView: View {
@ObservedObject var coordinator: AppCoordinator
// @Bindable — needs $ bindings for TabView selection, NavigationStack paths, .sheet
@Bindable var coordinator: AppCoordinator

var body: some View {
TabView(selection: $coordinator.selectedTab) {
Expand Down Expand Up @@ -88,7 +89,7 @@ struct MainTabView: View {
/// Wrapper that creates HomeViewModel with navigation closures wired to coordinator
struct HomeTabContent: View {
let coordinator: AppCoordinator
@StateObject private var viewModel = HomeViewModel()
@State private var viewModel = HomeViewModel()

var body: some View {
HomeView(viewModel: viewModel)
Expand All @@ -106,7 +107,7 @@ struct HomeTabContent: View {
/// Wrapper that creates ItemsViewModel with navigation closures wired to coordinator
struct ItemsTabContent: View {
let coordinator: AppCoordinator
@StateObject private var viewModel = ItemsViewModel()
@State private var viewModel = ItemsViewModel()

var body: some View {
ItemsView(viewModel: viewModel)
Expand All @@ -120,7 +121,7 @@ struct ItemsTabContent: View {

/// Wrapper that creates SettingsViewModel
struct SettingsTabContent: View {
@StateObject private var viewModel = SettingsViewModel()
@State private var viewModel = SettingsViewModel()

var body: some View {
SettingsView(viewModel: viewModel)
Expand All @@ -129,10 +130,10 @@ struct SettingsTabContent: View {

/// Wrapper that creates DetailViewModel for a pushed item
struct DetailTabContent: View {
@StateObject private var viewModel: DetailViewModel
@State private var viewModel: DetailViewModel

init(item: FeaturedItem) {
_viewModel = StateObject(wrappedValue: DetailViewModel(item: item))
_viewModel = State(initialValue: DetailViewModel(item: item))
}

var body: some View {
Expand All @@ -143,7 +144,7 @@ struct DetailTabContent: View {
/// Wrapper that creates ProfileViewModel with navigation closures
struct ProfileTabContent: View {
let coordinator: AppCoordinator
@StateObject private var viewModel = ProfileViewModel()
@State private var viewModel = ProfileViewModel()

var body: some View {
ProfileView(viewModel: viewModel)
Expand All @@ -166,7 +167,7 @@ struct ProfileTabContent: View {
/// Wrapper that creates LoginViewModel with login success closure
struct LoginTabContent: View {
let coordinator: AppCoordinator
@StateObject private var viewModel = LoginViewModel()
@State private var viewModel = LoginViewModel()

var body: some View {
LoginView(viewModel: viewModel)
Expand Down
4 changes: 2 additions & 2 deletions Core/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ let package = Package(
name: "Core",
defaultLocalization: "en",
platforms: [
.iOS(.v16),
.macCatalyst(.v16),
.iOS(.v17),
.macCatalyst(.v17),
],
products: [
.library(name: "FunCore", targets: ["FunCore"]),
Expand Down
Loading