Add macOS app target
Seto Elkahfi committed
May 15, 2026 at 07:36 UTC
28d6a89c904b81b1d95856b643a63044ace4fb16
17 files changed
+591
-40
.github/workflows/ci.yml
+25
@@ -33,3 +33,28 @@ jobs:
33
-destination 'platform=iOS Simulator,OS=26.2,name=iPhone 17' \
34
-only-testing:KleponSnapshotTests \
35
CODE_SIGNING_ALLOWED=NO
36
+
37
+ macos-build:
38
+ name: macOS app build
39
+ runs-on: macos-26
40
+
41
+ steps:
42
+ - name: Checkout
43
+ uses: actions/checkout@v6
44
+
45
+ - name: Install XcodeGen
46
+ run: brew install xcodegen
47
+
48
+ - name: Generate project
49
+ run: xcodegen generate
50
+
51
+ - name: Resolve Swift packages
52
+ run: xcodebuild -project Klepon.xcodeproj -scheme 'Klepon Mac' -resolvePackageDependencies
53
+
54
+ - name: Build macOS app
55
+ run: |
56
+ xcodebuild build \
57
+ -project Klepon.xcodeproj \
58
+ -scheme 'Klepon Mac' \
59
+ -destination 'platform=macOS' \
60
+ CODE_SIGNING_ALLOWED=NO
Klepon.xcodeproj/project.pbxproj
+258
@@ -8,49 +8,90 @@
8
9
/* Begin PBXBuildFile section */
10
019B658F26ABE2706231EC9E /* GuideEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A0935AE4B398B281758834 /* GuideEntry.swift */; };
11
+ 032AFB30CAB4E8A31CDC897D /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7E886BAB66E828950DBED52 /* SearchView.swift */; };
12
03D16A2386EB68FED8D92D52 /* GuideEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A0935AE4B398B281758834 /* GuideEntry.swift */; };
13
05BF0404B7030EDCD3D8C357 /* GuideArtworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2850226B4B05FF758C1F93 /* GuideArtworkView.swift */; };
14
+ 0709F0B92923D97F6859275A /* SearchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6235AD6792F25476E2A508F0 /* SearchService.swift */; };
15
0977CD6BFFDBDBE3B7795924 /* collections.json in Resources */ = {isa = PBXBuildFile; fileRef = 1FDDB0F88FF430ABD679AD89 /* collections.json */; };
16
0CE2632DF6FB02163D55BB65 /* Klepon Watch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 9E76510CC840CE068B0FC381 /* Klepon Watch.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
17
0D685E35EE50C926DC6FBE4C /* KleponChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83C4F3C8688072191AA16C6 /* KleponChip.swift */; };
18
0E69E79C5FADF1633FE3D63F /* KleponColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C675ABBBA25BBA25A8DE29BE /* KleponColor.swift */; };
19
14B7CE6E6A0EB12B709F7B55 /* KleponTypography.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD520C870387E82E9A1533C2 /* KleponTypography.swift */; };
20
1704DDB60B1E23057C0E1094 /* GuideAnswerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3719031080B59898133810F7 /* GuideAnswerService.swift */; };
21
+ 1879460ECC35F3386E8E6109 /* KleponChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83C4F3C8688072191AA16C6 /* KleponChip.swift */; };
22
19E282A6347C588E17778195 /* DiscoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4545DC84863788D55B94D31A /* DiscoverView.swift */; };
23
1E7AAEA8007BFE4ED6FE4892 /* WatchContentLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C77AF5EB4B17D087FA522F6 /* WatchContentLoader.swift */; };
24
1F95A454A641C829BD304A89 /* GuideCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17C0DB3BD620AD9F55B7631 /* GuideCollection.swift */; };
25
1FF490E985C31BB08DA29AFF /* guide_entries.json in Resources */ = {isa = PBXBuildFile; fileRef = 248F359109A08A60F792C42D /* guide_entries.json */; };
26
200AE94E7E7E73B5C83869DD /* FavoritesStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC0C4C22A5134DB058E41F4 /* FavoritesStore.swift */; };
27
+ 215C4D8A2B2ABF2C3C11C163 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8870F09A11EA6D3F0108DA4D /* SystemConfiguration.framework */; };
28
23E22EC56BE48A20BD1229F7 /* AnswerCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = B02B0EA4B8BA09604D62A62B /* AnswerCard.swift */; };
29
2AB57009245E1BABD1156AFA /* GuideDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7541983FF7CE190C0663294D /* GuideDetailView.swift */; };
30
+ 2AD05B0B7ED109134F013862 /* EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E7B5E3D9A1906C2FED3032 /* EmptyStateView.swift */; };
31
+ 2F3047F5078C4526133CB848 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13418896CA28250CEEE7592E /* OnboardingView.swift */; };
32
3349FB46691C944112401D6E /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42E152344B39D1D77F1B3943 /* SettingsView.swift */; };
33
+ 35B5508906432B3EFE26F4FF /* PlatformViewSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = F51D6F97615398D511673B98 /* PlatformViewSupport.swift */; };
34
+ 35D43509610D0E2CB3DBD2BB /* testEmptyStateView.saved-empty-state.png in Resources */ = {isa = PBXBuildFile; fileRef = 943670F01925DE3065B92636 /* testEmptyStateView.saved-empty-state.png */; };
35
36AA193A3B7EF7CB25B6E0BE /* KleponWatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFBF98118F066D3C7FA14B2 /* KleponWatchApp.swift */; };
36
+ 37A723A15D19B7DE3DD92B25 /* PlatformViewSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = F51D6F97615398D511673B98 /* PlatformViewSupport.swift */; };
37
+ 37BD5B2A275426143819B70D /* OndeGuideEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0D53A5F9D5897E15506C20 /* OndeGuideEngine.swift */; };
38
+ 3A6F67743964C4D04893C321 /* RecentlyViewedStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45070309D2C7058BAA44CD2 /* RecentlyViewedStore.swift */; };
39
+ 3F999E04650931C2019BDA17 /* KleponColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C675ABBBA25BBA25A8DE29BE /* KleponColor.swift */; };
40
41F8E173B82809AF87E1B297 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13418896CA28250CEEE7592E /* OnboardingView.swift */; };
41
+ 43ABAA3C08695B7C8FEE7324 /* KleponMetadataRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7E1C50BFBE2EF5F37941590 /* KleponMetadataRow.swift */; };
42
4609C70D9FB74C55B9DBE832 /* KleponActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52DD909BB2EF68103CB46EDA /* KleponActionButton.swift */; };
43
478FFEBDAA7729189B429A6D /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7E886BAB66E828950DBED52 /* SearchView.swift */; };
44
+ 47B632AF6BF3E25AF628CC01 /* Onde in Frameworks */ = {isa = PBXBuildFile; productRef = 35BDA28E3F43D5441C5FE932 /* Onde */; };
45
4D63E231AC9630B2C225DF22 /* WatchHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDBB4F5D4C98D036AC52E193 /* WatchHomeView.swift */; };
46
+ 5692BBCE950885C5C1193DDF /* collections.json in Resources */ = {isa = PBXBuildFile; fileRef = 1FDDB0F88FF430ABD679AD89 /* collections.json */; };
47
+ 5808C4539EBB98D822BDE99F /* KleponActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52DD909BB2EF68103CB46EDA /* KleponActionButton.swift */; };
48
67368394088AA3045EBA14A5 /* OndeEnvironmentBootstrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B499D3BF7AC80D31907D87F /* OndeEnvironmentBootstrap.swift */; };
49
+ 687418866BCC5554A0F4BFDC /* AnswerCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = B02B0EA4B8BA09604D62A62B /* AnswerCard.swift */; };
50
+ 72A88C81BE80344130E0EC0C /* GuideAnswerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3719031080B59898133810F7 /* GuideAnswerService.swift */; };
51
+ 7C90532B70C1C4840E49A92B /* GuideDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7541983FF7CE190C0663294D /* GuideDetailView.swift */; };
52
7F1E744AAE60BF1FB132D66C /* KleponCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 868C58098969278A542893F3 /* KleponCard.swift */; };
53
8401F9C0FA140DC2CF8BA248 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AF291E9C2E66209377138E6 /* AppState.swift */; };
54
8804CFBAD707E1FED63BF214 /* SearchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6235AD6792F25476E2A508F0 /* SearchService.swift */; };
55
88984B6A8AA504AD200A7F39 /* collections.json in Resources */ = {isa = PBXBuildFile; fileRef = 1FDDB0F88FF430ABD679AD89 /* collections.json */; };
56
+ 889CBF2AF84AABF8012EE737 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AF79D8DD27B9CA485140C934 /* Assets.xcassets */; };
57
+ 895B5369E9CF1E1B26CDF40E /* KleponTypography.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD520C870387E82E9A1533C2 /* KleponTypography.swift */; };
58
+ 8EBCC9FA074785FDD82B02E5 /* KleponApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEDE2A1E73EB15970A17A894 /* KleponApp.swift */; };
59
910A84B3F28B3F79871FF60E /* SectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC438ECC335175B18778C682 /* SectionHeader.swift */; };
60
926AB007EBCD5315EE77E2A6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AF79D8DD27B9CA485140C934 /* Assets.xcassets */; };
61
+ 93E938AA978ABF5665737887 /* GuideEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A0935AE4B398B281758834 /* GuideEntry.swift */; };
62
+ 955D039F4B5D14FCA4E7F2B2 /* GuideArtworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2850226B4B05FF758C1F93 /* GuideArtworkView.swift */; };
63
99071E0CB9F44857C0BFC8C9 /* SavedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98AA2E425D5454602F78D628 /* SavedView.swift */; };
64
+ 9E16F5C37F30B58441780260 /* DiscoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4545DC84863788D55B94D31A /* DiscoverView.swift */; };
65
9ECA989E9DCDF08A2D7C29A3 /* AppRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C82874C90A5FAA32D1B52EB7 /* AppRootView.swift */; };
66
+ 9ECEB59572D3B9D08D1DA896 /* GuideCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17C0DB3BD620AD9F55B7631 /* GuideCollection.swift */; };
67
A54D028D8F35FE002B0ACA90 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 054656155630B064F785B0D7 /* Assets.xcassets */; };
68
A7001D58E0F2B1BD5F52FF8A /* Onde in Frameworks */ = {isa = PBXBuildFile; productRef = 855EE8DF62A7820E77C05D1A /* Onde */; };
69
B1F1D1A453150D3BBB0146EF /* RecentlyViewedStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45070309D2C7058BAA44CD2 /* RecentlyViewedStore.swift */; };
70
B34377720179454F9B9184C5 /* OndeGuideEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0D53A5F9D5897E15506C20 /* OndeGuideEngine.swift */; };
71
+ B4467AA7214B4990D2FC19DA /* KleponCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 868C58098969278A542893F3 /* KleponCard.swift */; };
72
+ B768150B02B19417B81FE651 /* guide_entries.json in Resources */ = {isa = PBXBuildFile; fileRef = 248F359109A08A60F792C42D /* guide_entries.json */; };
73
B7DE9EC8BE998E6719C88E83 /* guide_entries.json in Resources */ = {isa = PBXBuildFile; fileRef = 248F359109A08A60F792C42D /* guide_entries.json */; };
74
+ BB70FAD944544125AF261F7C /* SavedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98AA2E425D5454602F78D628 /* SavedView.swift */; };
75
+ C15131992039B3AC7DB74DAB /* testGuideArtworkView.guide-artwork.png in Resources */ = {isa = PBXBuildFile; fileRef = 9D6EFA021D4524508DA8E78C /* testGuideArtworkView.guide-artwork.png */; };
76
C2EA2682A6545802C06E00CC /* WatchBrandSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217E7D4050D471955FF3AB13 /* WatchBrandSystem.swift */; };
77
C709C82E7F0BCF398BCD9C2F /* ContentRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2891A1AFBB59928FC5B1E66 /* ContentRepository.swift */; };
78
+ D6D8A42930EA9A0EA6622108 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AF291E9C2E66209377138E6 /* AppState.swift */; };
79
D99AAE1CDE51139840A52607 /* KleponMetadataRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7E1C50BFBE2EF5F37941590 /* KleponMetadataRow.swift */; };
80
DA915BA8B2F732E7D62CF936 /* EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E7B5E3D9A1906C2FED3032 /* EmptyStateView.swift */; };
81
E45F257E3D35DC565B21B299 /* KleponColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C675ABBBA25BBA25A8DE29BE /* KleponColor.swift */; };
82
E599CCDB1FE5331C4A1EB6C6 /* KleponSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 759E92BBAF958F5882F17C37 /* KleponSnapshotTests.swift */; };
83
+ E87F8A08D6922557A5DBD198 /* testOnboardingView_defaultState.default.png in Resources */ = {isa = PBXBuildFile; fileRef = 414FD1A8945B820DCC4ED192 /* testOnboardingView_defaultState.default.png */; };
84
+ ECDD606D8AFA776765AA9A31 /* SectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC438ECC335175B18778C682 /* SectionHeader.swift */; };
85
+ EE2103530984735318E469FD /* AskSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEC6E7DC56DD8E1459EFC892 /* AskSheetView.swift */; };
86
EE88AEC7059BB2639687FECF /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 35214C3FF433B08C1C06C3F7 /* SnapshotTesting */; };
87
+ F0694A813D1CB57F0B5C0F65 /* FavoritesStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC0C4C22A5134DB058E41F4 /* FavoritesStore.swift */; };
88
F1431179B54CEB23FFFCB2AE /* KleponApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEDE2A1E73EB15970A17A894 /* KleponApp.swift */; };
89
+ F2D905B433B0B76CE6B4001F /* OndeEnvironmentBootstrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B499D3BF7AC80D31907D87F /* OndeEnvironmentBootstrap.swift */; };
90
+ F46EB4EDFFF125ED88506D89 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6B67BCC5A459288FB240C6E7 /* Info.plist */; };
91
+ F5B35C6C771073CE32B4BFD1 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42E152344B39D1D77F1B3943 /* SettingsView.swift */; };
92
+ F87EF3C291B7D3FB9BE8E43A /* ContentRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2891A1AFBB59928FC5B1E66 /* ContentRepository.swift */; };
93
+ F88972525510344E63D2983C /* AppRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C82874C90A5FAA32D1B52EB7 /* AppRootView.swift */; };
94
+ F9D6FC75A11BE98BD28BBDC2 /* RecentSearchStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643D52BCEDF66CC8FE8A1234 /* RecentSearchStore.swift */; };
95
FA423A76BE098A9911044246 /* AskSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEC6E7DC56DD8E1459EFC892 /* AskSheetView.swift */; };
96
FD0E1BA888E3CE73875D58FF /* RecentSearchStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643D52BCEDF66CC8FE8A1234 /* RecentSearchStore.swift */; };
97
/* End PBXBuildFile section */
@@ -96,6 +137,7 @@
137
248F359109A08A60F792C42D /* guide_entries.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = guide_entries.json; sourceTree = "<group>"; };
138
2B499D3BF7AC80D31907D87F /* OndeEnvironmentBootstrap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OndeEnvironmentBootstrap.swift; sourceTree = "<group>"; };
139
3719031080B59898133810F7 /* GuideAnswerService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuideAnswerService.swift; sourceTree = "<group>"; };
140
+ 414FD1A8945B820DCC4ED192 /* testOnboardingView_defaultState.default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = testOnboardingView_defaultState.default.png; sourceTree = "<group>"; };
141
42E152344B39D1D77F1B3943 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
142
4545DC84863788D55B94D31A /* DiscoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoverView.swift; sourceTree = "<group>"; };
143
4C77AF5EB4B17D087FA522F6 /* WatchContentLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchContentLoader.swift; sourceTree = "<group>"; };
@@ -112,7 +154,10 @@
154
7F2850226B4B05FF758C1F93 /* GuideArtworkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuideArtworkView.swift; sourceTree = "<group>"; };
155
85E7B5E3D9A1906C2FED3032 /* EmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStateView.swift; sourceTree = "<group>"; };
156
868C58098969278A542893F3 /* KleponCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KleponCard.swift; sourceTree = "<group>"; };
157
+ 8870F09A11EA6D3F0108DA4D /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
158
+ 943670F01925DE3065B92636 /* testEmptyStateView.saved-empty-state.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testEmptyStateView.saved-empty-state.png"; sourceTree = "<group>"; };
159
98AA2E425D5454602F78D628 /* SavedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedView.swift; sourceTree = "<group>"; };
160
+ 9D6EFA021D4524508DA8E78C /* testGuideArtworkView.guide-artwork.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testGuideArtworkView.guide-artwork.png"; sourceTree = "<group>"; };
161
9E76510CC840CE068B0FC381 /* Klepon Watch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Klepon Watch.app"; sourceTree = BUILT_PRODUCTS_DIR; };
162
A4400AB0AB8D245325A00BA3 /* Klepon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Klepon.entitlements; sourceTree = "<group>"; };
163
A48AC0E39277DEFCE5D103CC /* KleponSnapshotTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = KleponSnapshotTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -125,6 +170,7 @@
170
C675ABBBA25BBA25A8DE29BE /* KleponColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KleponColor.swift; sourceTree = "<group>"; };
171
C82874C90A5FAA32D1B52EB7 /* AppRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRootView.swift; sourceTree = "<group>"; };
172
CD520C870387E82E9A1533C2 /* KleponTypography.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KleponTypography.swift; sourceTree = "<group>"; };
173
+ D53EA049112C7D59177F9878 /* Klepon Mac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Klepon Mac.app"; sourceTree = BUILT_PRODUCTS_DIR; };
174
DEDE2A1E73EB15970A17A894 /* KleponApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KleponApp.swift; sourceTree = "<group>"; };
175
E45070309D2C7058BAA44CD2 /* RecentlyViewedStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentlyViewedStore.swift; sourceTree = "<group>"; };
176
E7E886BAB66E828950DBED52 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
@@ -132,10 +178,20 @@
178
EEC6E7DC56DD8E1459EFC892 /* AskSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AskSheetView.swift; sourceTree = "<group>"; };
179
F17C0DB3BD620AD9F55B7631 /* GuideCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuideCollection.swift; sourceTree = "<group>"; };
180
F2891A1AFBB59928FC5B1E66 /* ContentRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentRepository.swift; sourceTree = "<group>"; };
181
+ F51D6F97615398D511673B98 /* PlatformViewSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformViewSupport.swift; sourceTree = "<group>"; };
182
FC438ECC335175B18778C682 /* SectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeader.swift; sourceTree = "<group>"; };
183
/* End PBXFileReference section */
184
185
/* Begin PBXFrameworksBuildPhase section */
186
+ B0052635A882F0E996B81154 /* Frameworks */ = {
187
+ isa = PBXFrameworksBuildPhase;
188
+ buildActionMask = 2147483647;
189
+ files = (
190
+ 47B632AF6BF3E25AF628CC01 /* Onde in Frameworks */,
191
+ 215C4D8A2B2ABF2C3C11C163 /* SystemConfiguration.framework in Frameworks */,
192
+ );
193
+ runOnlyForDeploymentPostprocessing = 0;
194
+ };
195
B161A429683CBAD2A984A5AE /* Frameworks */ = {
196
isa = PBXFrameworksBuildPhase;
197
buildActionMask = 2147483647;
@@ -171,9 +227,18 @@
227
path = Settings;
228
sourceTree = "<group>";
229
};
230
+ 2D223E83C424037A62CA358F /* __Snapshots__ */ = {
231
+ isa = PBXGroup;
232
+ children = (
233
+ 89DF9DC044BB33376803C6F5 /* KleponSnapshotTests */,
234
+ );
235
+ path = __Snapshots__;
236
+ sourceTree = "<group>";
237
+ };
238
3F9369D339C14126AB44D720 /* Products */ = {
239
isa = PBXGroup;
240
children = (
241
+ D53EA049112C7D59177F9878 /* Klepon Mac.app */,
242
9E76510CC840CE068B0FC381 /* Klepon Watch.app */,
243
5DE91827A635C0B0B72690EC /* Klepon.app */,
244
A48AC0E39277DEFCE5D103CC /* KleponSnapshotTests.xctest */,
@@ -246,6 +311,7 @@
311
C82874C90A5FAA32D1B52EB7 /* AppRootView.swift */,
312
5AF291E9C2E66209377138E6 /* AppState.swift */,
313
DEDE2A1E73EB15970A17A894 /* KleponApp.swift */,
314
+ F51D6F97615398D511673B98 /* PlatformViewSupport.swift */,
315
);
316
path = App;
317
sourceTree = "<group>";
@@ -254,6 +320,7 @@
320
isa = PBXGroup;
321
children = (
322
759E92BBAF958F5882F17C37 /* KleponSnapshotTests.swift */,
323
+ 2D223E83C424037A62CA358F /* __Snapshots__ */,
324
);
325
path = KleponSnapshotTests;
326
sourceTree = "<group>";
@@ -286,6 +353,16 @@
353
path = Domain;
354
sourceTree = "<group>";
355
};
356
+ 89DF9DC044BB33376803C6F5 /* KleponSnapshotTests */ = {
357
+ isa = PBXGroup;
358
+ children = (
359
+ 943670F01925DE3065B92636 /* testEmptyStateView.saved-empty-state.png */,
360
+ 9D6EFA021D4524508DA8E78C /* testGuideArtworkView.guide-artwork.png */,
361
+ 414FD1A8945B820DCC4ED192 /* testOnboardingView_defaultState.default.png */,
362
+ );
363
+ path = KleponSnapshotTests;
364
+ sourceTree = "<group>";
365
+ };
366
8FCB4D289A8BA31F1213435D /* Resources */ = {
367
isa = PBXGroup;
368
children = (
@@ -346,6 +423,7 @@
423
EA4DFA44FE8BB01979ADB187 /* Klepon */,
424
825A58FCE091C3FE902D3131 /* KleponSnapshotTests */,
425
472BF5D47BEE4AF1EC80B645 /* KleponWatch */,
426
+ EC85AFC077F4445CA6E5B876 /* Frameworks */,
427
3F9369D339C14126AB44D720 /* Products */,
428
);
429
sourceTree = "<group>";
@@ -367,6 +445,14 @@
445
path = Klepon;
446
sourceTree = "<group>";
447
};
448
+ EC85AFC077F4445CA6E5B876 /* Frameworks */ = {
449
+ isa = PBXGroup;
450
+ children = (
451
+ 8870F09A11EA6D3F0108DA4D /* SystemConfiguration.framework */,
452
+ );
453
+ name = Frameworks;
454
+ sourceTree = "<group>";
455
+ };
456
/* End PBXGroup section */
457
458
/* Begin PBXNativeTarget section */
@@ -393,6 +479,27 @@
479
productReference = 5DE91827A635C0B0B72690EC /* Klepon.app */;
480
productType = "com.apple.product-type.application";
481
};
482
+ 676CA86C78E7DB801DEEAF12 /* Klepon Mac */ = {
483
+ isa = PBXNativeTarget;
484
+ buildConfigurationList = 78899F7C62A9675700D42C5A /* Build configuration list for PBXNativeTarget "Klepon Mac" */;
485
+ buildPhases = (
486
+ 625CD4834F7E62F4BCE58404 /* Sources */,
487
+ 667705EEBEDEBBCA43E94EAC /* Resources */,
488
+ B0052635A882F0E996B81154 /* Frameworks */,
489
+ 38259E5F7F0F32D684AE509A /* Update CFBundleVersion */,
490
+ );
491
+ buildRules = (
492
+ );
493
+ dependencies = (
494
+ );
495
+ name = "Klepon Mac";
496
+ packageProductDependencies = (
497
+ 35BDA28E3F43D5441C5FE932 /* Onde */,
498
+ );
499
+ productName = "Klepon Mac";
500
+ productReference = D53EA049112C7D59177F9878 /* Klepon Mac.app */;
501
+ productType = "com.apple.product-type.application";
502
+ };
503
E8CEBF52617721AEE450A7CF /* Klepon Watch */ = {
504
isa = PBXNativeTarget;
505
buildConfigurationList = 07A758477E97CF9254DDBA82 /* Build configuration list for PBXNativeTarget "Klepon Watch" */;
@@ -417,6 +524,7 @@
524
buildConfigurationList = 116221FC6D50F33334387509 /* Build configuration list for PBXNativeTarget "KleponSnapshotTests" */;
525
buildPhases = (
526
C2B1B8ADEE7B8E2628B90D1F /* Sources */,
527
+ 6F8A4C98AF649CB41F0837B3 /* Resources */,
528
B161A429683CBAD2A984A5AE /* Frameworks */,
529
);
530
buildRules = (
@@ -444,6 +552,9 @@
552
2ADB11F6C1D51B9F9A9DB15F = {
553
ProvisioningStyle = Automatic;
554
};
555
+ 676CA86C78E7DB801DEEAF12 = {
556
+ ProvisioningStyle = Automatic;
557
+ };
558
E8CEBF52617721AEE450A7CF = {
559
ProvisioningStyle = Automatic;
560
};
@@ -471,6 +582,7 @@
582
projectRoot = "";
583
targets = (
584
2ADB11F6C1D51B9F9A9DB15F /* Klepon */,
585
+ 676CA86C78E7DB801DEEAF12 /* Klepon Mac */,
586
E8CEBF52617721AEE450A7CF /* Klepon Watch */,
587
F10E9562BA0BE60A7E000DB0 /* KleponSnapshotTests */,
588
);
@@ -488,6 +600,27 @@
600
);
601
runOnlyForDeploymentPostprocessing = 0;
602
};
603
+ 667705EEBEDEBBCA43E94EAC /* Resources */ = {
604
+ isa = PBXResourcesBuildPhase;
605
+ buildActionMask = 2147483647;
606
+ files = (
607
+ 889CBF2AF84AABF8012EE737 /* Assets.xcassets in Resources */,
608
+ F46EB4EDFFF125ED88506D89 /* Info.plist in Resources */,
609
+ 5692BBCE950885C5C1193DDF /* collections.json in Resources */,
610
+ B768150B02B19417B81FE651 /* guide_entries.json in Resources */,
611
+ );
612
+ runOnlyForDeploymentPostprocessing = 0;
613
+ };
614
+ 6F8A4C98AF649CB41F0837B3 /* Resources */ = {
615
+ isa = PBXResourcesBuildPhase;
616
+ buildActionMask = 2147483647;
617
+ files = (
618
+ 35D43509610D0E2CB3DBD2BB /* testEmptyStateView.saved-empty-state.png in Resources */,
619
+ C15131992039B3AC7DB74DAB /* testGuideArtworkView.guide-artwork.png in Resources */,
620
+ E87F8A08D6922557A5DBD198 /* testOnboardingView_defaultState.default.png in Resources */,
621
+ );
622
+ runOnlyForDeploymentPostprocessing = 0;
623
+ };
624
BC5A6BBAFB4A1B61000065D1 /* Resources */ = {
625
isa = PBXResourcesBuildPhase;
626
buildActionMask = 2147483647;
@@ -521,6 +654,26 @@
654
shellScript = "set -eu\n\nSHARED_BUNDLE_VERSION_FILE=\"${BUILD_DIR}/klepon-cfbundleversion.txt\"\nif [ -f \"${SHARED_BUNDLE_VERSION_FILE}\" ]; then\n BUNDLE_VERSION=\"$(cat \"${SHARED_BUNDLE_VERSION_FILE}\")\"\nelse\n BUNDLE_VERSION=\"$(date -u +\"%Y%m%d.%H%M\")\"\n printf '%s' \"${BUNDLE_VERSION}\" > \"${SHARED_BUNDLE_VERSION_FILE}\"\nfi\n\nINFO_PLIST=\"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\n\nif [ ! -f \"${INFO_PLIST}\" ]; then\n echo \"error: Info.plist not found at ${INFO_PLIST}\"\n exit 1\nfi\n\nif ! /usr/libexec/PlistBuddy -c \"Set :CFBundleVersion ${BUNDLE_VERSION}\" \"${INFO_PLIST}\" >/dev/null 2>&1; then\n /usr/libexec/PlistBuddy -c \"Add :CFBundleVersion string ${BUNDLE_VERSION}\" \"${INFO_PLIST}\"\nfi\n\necho \"Updated CFBundleVersion to ${BUNDLE_VERSION}\"\n";
655
showEnvVarsInLog = 0;
656
};
657
+ 38259E5F7F0F32D684AE509A /* Update CFBundleVersion */ = {
658
+ isa = PBXShellScriptBuildPhase;
659
+ alwaysOutOfDate = 1;
660
+ buildActionMask = 2147483647;
661
+ files = (
662
+ );
663
+ inputFileListPaths = (
664
+ );
665
+ inputPaths = (
666
+ );
667
+ name = "Update CFBundleVersion";
668
+ outputFileListPaths = (
669
+ );
670
+ outputPaths = (
671
+ );
672
+ runOnlyForDeploymentPostprocessing = 0;
673
+ shellPath = /bin/sh;
674
+ shellScript = "set -eu\n\nSHARED_BUNDLE_VERSION_FILE=\"${BUILD_DIR}/klepon-cfbundleversion.txt\"\nif [ -f \"${SHARED_BUNDLE_VERSION_FILE}\" ]; then\n BUNDLE_VERSION=\"$(cat \"${SHARED_BUNDLE_VERSION_FILE}\")\"\nelse\n BUNDLE_VERSION=\"$(date -u +\"%Y%m%d.%H%M\")\"\n printf '%s' \"${BUNDLE_VERSION}\" > \"${SHARED_BUNDLE_VERSION_FILE}\"\nfi\n\nINFO_PLIST=\"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\n\nif [ ! -f \"${INFO_PLIST}\" ]; then\n echo \"error: Info.plist not found at ${INFO_PLIST}\"\n exit 1\nfi\n\nif ! /usr/libexec/PlistBuddy -c \"Set :CFBundleVersion ${BUNDLE_VERSION}\" \"${INFO_PLIST}\" >/dev/null 2>&1; then\n /usr/libexec/PlistBuddy -c \"Add :CFBundleVersion string ${BUNDLE_VERSION}\" \"${INFO_PLIST}\"\nfi\n\necho \"Updated CFBundleVersion to ${BUNDLE_VERSION}\"\n";
675
+ showEnvVarsInLog = 0;
676
+ };
677
4CD2E0A71BF112F74656BFFD /* Update CFBundleVersion */ = {
678
isa = PBXShellScriptBuildPhase;
679
alwaysOutOfDate = 1;
@@ -571,6 +724,7 @@
724
41F8E173B82809AF87E1B297 /* OnboardingView.swift in Sources */,
725
67368394088AA3045EBA14A5 /* OndeEnvironmentBootstrap.swift in Sources */,
726
B34377720179454F9B9184C5 /* OndeGuideEngine.swift in Sources */,
727
+ 37A723A15D19B7DE3DD92B25 /* PlatformViewSupport.swift in Sources */,
728
FD0E1BA888E3CE73875D58FF /* RecentSearchStore.swift in Sources */,
729
B1F1D1A453150D3BBB0146EF /* RecentlyViewedStore.swift in Sources */,
730
99071E0CB9F44857C0BFC8C9 /* SavedView.swift in Sources */,
@@ -594,6 +748,44 @@
748
);
749
runOnlyForDeploymentPostprocessing = 0;
750
};
751
+ 625CD4834F7E62F4BCE58404 /* Sources */ = {
752
+ isa = PBXSourcesBuildPhase;
753
+ buildActionMask = 2147483647;
754
+ files = (
755
+ 687418866BCC5554A0F4BFDC /* AnswerCard.swift in Sources */,
756
+ F88972525510344E63D2983C /* AppRootView.swift in Sources */,
757
+ D6D8A42930EA9A0EA6622108 /* AppState.swift in Sources */,
758
+ EE2103530984735318E469FD /* AskSheetView.swift in Sources */,
759
+ F87EF3C291B7D3FB9BE8E43A /* ContentRepository.swift in Sources */,
760
+ 9E16F5C37F30B58441780260 /* DiscoverView.swift in Sources */,
761
+ 2AD05B0B7ED109134F013862 /* EmptyStateView.swift in Sources */,
762
+ F0694A813D1CB57F0B5C0F65 /* FavoritesStore.swift in Sources */,
763
+ 72A88C81BE80344130E0EC0C /* GuideAnswerService.swift in Sources */,
764
+ 955D039F4B5D14FCA4E7F2B2 /* GuideArtworkView.swift in Sources */,
765
+ 9ECEB59572D3B9D08D1DA896 /* GuideCollection.swift in Sources */,
766
+ 7C90532B70C1C4840E49A92B /* GuideDetailView.swift in Sources */,
767
+ 93E938AA978ABF5665737887 /* GuideEntry.swift in Sources */,
768
+ 5808C4539EBB98D822BDE99F /* KleponActionButton.swift in Sources */,
769
+ 8EBCC9FA074785FDD82B02E5 /* KleponApp.swift in Sources */,
770
+ B4467AA7214B4990D2FC19DA /* KleponCard.swift in Sources */,
771
+ 1879460ECC35F3386E8E6109 /* KleponChip.swift in Sources */,
772
+ 3F999E04650931C2019BDA17 /* KleponColor.swift in Sources */,
773
+ 43ABAA3C08695B7C8FEE7324 /* KleponMetadataRow.swift in Sources */,
774
+ 895B5369E9CF1E1B26CDF40E /* KleponTypography.swift in Sources */,
775
+ 2F3047F5078C4526133CB848 /* OnboardingView.swift in Sources */,
776
+ F2D905B433B0B76CE6B4001F /* OndeEnvironmentBootstrap.swift in Sources */,
777
+ 37BD5B2A275426143819B70D /* OndeGuideEngine.swift in Sources */,
778
+ 35B5508906432B3EFE26F4FF /* PlatformViewSupport.swift in Sources */,
779
+ F9D6FC75A11BE98BD28BBDC2 /* RecentSearchStore.swift in Sources */,
780
+ 3A6F67743964C4D04893C321 /* RecentlyViewedStore.swift in Sources */,
781
+ BB70FAD944544125AF261F7C /* SavedView.swift in Sources */,
782
+ 0709F0B92923D97F6859275A /* SearchService.swift in Sources */,
783
+ 032AFB30CAB4E8A31CDC897D /* SearchView.swift in Sources */,
784
+ ECDD606D8AFA776765AA9A31 /* SectionHeader.swift in Sources */,
785
+ F5B35C6C771073CE32B4BFD1 /* SettingsView.swift in Sources */,
786
+ );
787
+ runOnlyForDeploymentPostprocessing = 0;
788
+ };
789
C2B1B8ADEE7B8E2628B90D1F /* Sources */ = {
790
isa = PBXSourcesBuildPhase;
791
buildActionMask = 2147483647;
@@ -684,6 +876,56 @@
876
};
877
name = Debug;
878
};
879
+ 73320483BA240362017C25C8 /* Debug */ = {
880
+ isa = XCBuildConfiguration;
881
+ baseConfigurationReference = 13B09D700AA047E07E91F6A2 /* Signing.xcconfig */;
882
+ buildSettings = {
883
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
884
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
885
+ CODE_SIGN_ENTITLEMENTS = "";
886
+ COMBINE_HIDPI_IMAGES = YES;
887
+ CURRENT_PROJECT_VERSION = 1;
888
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
889
+ "EXCLUDED_ARCHS[sdk=macosx*]" = x86_64;
890
+ INFOPLIST_FILE = KleponMac/Info.plist;
891
+ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Splitfire AB";
892
+ LD_RUNPATH_SEARCH_PATHS = (
893
+ "$(inherited)",
894
+ "@executable_path/../Frameworks",
895
+ );
896
+ MARKETING_VERSION = 1.0.0;
897
+ PRODUCT_BUNDLE_IDENTIFIER = ai.splitfire.klepon.mac;
898
+ PRODUCT_NAME = Klepon;
899
+ SDKROOT = macosx;
900
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
901
+ };
902
+ name = Debug;
903
+ };
904
+ 812F1D8D209CC3C18A9DC330 /* Release */ = {
905
+ isa = XCBuildConfiguration;
906
+ baseConfigurationReference = 13B09D700AA047E07E91F6A2 /* Signing.xcconfig */;
907
+ buildSettings = {
908
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
909
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
910
+ CODE_SIGN_ENTITLEMENTS = "";
911
+ COMBINE_HIDPI_IMAGES = YES;
912
+ CURRENT_PROJECT_VERSION = 1;
913
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
914
+ "EXCLUDED_ARCHS[sdk=macosx*]" = x86_64;
915
+ INFOPLIST_FILE = KleponMac/Info.plist;
916
+ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Splitfire AB";
917
+ LD_RUNPATH_SEARCH_PATHS = (
918
+ "$(inherited)",
919
+ "@executable_path/../Frameworks",
920
+ );
921
+ MARKETING_VERSION = 1.0.0;
922
+ PRODUCT_BUNDLE_IDENTIFIER = ai.splitfire.klepon.mac;
923
+ PRODUCT_NAME = Klepon;
924
+ SDKROOT = macosx;
925
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
926
+ };
927
+ name = Release;
928
+ };
929
850753164F664131E0E37EAE /* Release */ = {
930
isa = XCBuildConfiguration;
931
buildSettings = {
@@ -730,6 +972,7 @@
972
GCC_WARN_UNUSED_FUNCTION = YES;
973
GCC_WARN_UNUSED_VARIABLE = YES;
974
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
975
+ MACOSX_DEPLOYMENT_TARGET = 15.0;
976
MTL_ENABLE_DEBUG_INFO = NO;
977
MTL_FAST_MATH = YES;
978
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -842,6 +1085,7 @@
1085
GCC_WARN_UNUSED_FUNCTION = YES;
1086
GCC_WARN_UNUSED_VARIABLE = YES;
1087
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
1088
+ MACOSX_DEPLOYMENT_TARGET = 15.0;
1089
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
1090
MTL_FAST_MATH = YES;
1091
ONLY_ACTIVE_ARCH = YES;
@@ -898,6 +1142,15 @@
1142
defaultConfigurationIsVisible = 0;
1143
defaultConfigurationName = Debug;
1144
};
1145
+ 78899F7C62A9675700D42C5A /* Build configuration list for PBXNativeTarget "Klepon Mac" */ = {
1146
+ isa = XCConfigurationList;
1147
+ buildConfigurations = (
1148
+ 73320483BA240362017C25C8 /* Debug */,
1149
+ 812F1D8D209CC3C18A9DC330 /* Release */,
1150
+ );
1151
+ defaultConfigurationIsVisible = 0;
1152
+ defaultConfigurationName = Debug;
1153
+ };
1154
8911E6C6F25C3A4F99870749 /* Build configuration list for PBXProject "Klepon" */ = {
1155
isa = XCConfigurationList;
1156
buildConfigurations = (
@@ -943,6 +1196,11 @@
1196
package = 85B622113A5DC5A1BD2D123B /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */;
1197
productName = SnapshotTesting;
1198
};
1199
+ 35BDA28E3F43D5441C5FE932 /* Onde */ = {
1200
+ isa = XCSwiftPackageProductDependency;
1201
+ package = AD8C734FB461604D5793769C /* XCRemoteSwiftPackageReference "onde-swift" */;
1202
+ productName = Onde;
1203
+ };
1204
855EE8DF62A7820E77C05D1A /* Onde */ = {
1205
isa = XCSwiftPackageProductDependency;
1206
package = AD8C734FB461604D5793769C /* XCRemoteSwiftPackageReference "onde-swift" */;
Klepon.xcodeproj/xcshareddata/xcschemes/Klepon Mac.xcscheme
new
+87
@@ -0,0 +1,87 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<Scheme
3
+ LastUpgradeVersion = "2600"
4
+ version = "1.3">
5
+ <BuildAction
6
+ parallelizeBuildables = "YES"
7
+ buildImplicitDependencies = "YES">
8
+ <BuildActionEntries>
9
+ <BuildActionEntry
10
+ buildForTesting = "YES"
11
+ buildForRunning = "YES"
12
+ buildForProfiling = "YES"
13
+ buildForArchiving = "YES"
14
+ buildForAnalyzing = "YES">
15
+ <BuildableReference
16
+ BuildableIdentifier = "primary"
17
+ BlueprintIdentifier = "676CA86C78E7DB801DEEAF12"
18
+ BuildableName = "Klepon.app"
19
+ BlueprintName = "Klepon Mac"
20
+ ReferencedContainer = "container:Klepon.xcodeproj">
21
+ </BuildableReference>
22
+ </BuildActionEntry>
23
+ </BuildActionEntries>
24
+ </BuildAction>
25
+ <TestAction
26
+ buildConfiguration = "Debug"
27
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
28
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29
+ shouldUseLaunchSchemeArgsEnv = "YES">
30
+ <MacroExpansion>
31
+ <BuildableReference
32
+ BuildableIdentifier = "primary"
33
+ BlueprintIdentifier = "676CA86C78E7DB801DEEAF12"
34
+ BuildableName = "Klepon.app"
35
+ BlueprintName = "Klepon Mac"
36
+ ReferencedContainer = "container:Klepon.xcodeproj">
37
+ </BuildableReference>
38
+ </MacroExpansion>
39
+ <Testables>
40
+ </Testables>
41
+ </TestAction>
42
+ <LaunchAction
43
+ buildConfiguration = "Debug"
44
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
45
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
46
+ launchStyle = "0"
47
+ useCustomWorkingDirectory = "NO"
48
+ ignoresPersistentStateOnLaunch = "NO"
49
+ debugDocumentVersioning = "YES"
50
+ debugServiceExtension = "internal"
51
+ allowLocationSimulation = "YES">
52
+ <BuildableProductRunnable
53
+ runnableDebuggingMode = "0">
54
+ <BuildableReference
55
+ BuildableIdentifier = "primary"
56
+ BlueprintIdentifier = "676CA86C78E7DB801DEEAF12"
57
+ BuildableName = "Klepon.app"
58
+ BlueprintName = "Klepon Mac"
59
+ ReferencedContainer = "container:Klepon.xcodeproj">
60
+ </BuildableReference>
61
+ </BuildableProductRunnable>
62
+ </LaunchAction>
63
+ <ProfileAction
64
+ buildConfiguration = "Release"
65
+ shouldUseLaunchSchemeArgsEnv = "YES"
66
+ savedToolIdentifier = ""
67
+ useCustomWorkingDirectory = "NO"
68
+ debugDocumentVersioning = "YES">
69
+ <BuildableProductRunnable
70
+ runnableDebuggingMode = "0">
71
+ <BuildableReference
72
+ BuildableIdentifier = "primary"
73
+ BlueprintIdentifier = "676CA86C78E7DB801DEEAF12"
74
+ BuildableName = "Klepon.app"
75
+ BlueprintName = "Klepon Mac"
76
+ ReferencedContainer = "container:Klepon.xcodeproj">
77
+ </BuildableReference>
78
+ </BuildableProductRunnable>
79
+ </ProfileAction>
80
+ <AnalyzeAction
81
+ buildConfiguration = "Debug">
82
+ </AnalyzeAction>
83
+ <ArchiveAction
84
+ buildConfiguration = "Release"
85
+ revealArchiveInOrganizer = "YES">
86
+ </ArchiveAction>
87
+</Scheme>
Klepon/AI/GuideAnswerService.swift
+1
-1
@@ -57,7 +57,7 @@ final class GuideAnswerService {
57
let previousContext = previousConversationContext(from: previousCards)
58
59
return """
60
- You are answering inside Klepon, a private iPhone guide to Indonesian food.
60
+ You are answering inside Klepon, a private local guide to Indonesian food.
61
Use only the guide notes below.
62
If the notes are not enough, say the guide does not have enough detail yet.
63
Keep the tone warm, respectful, and concise.
Klepon/AI/OndeGuideEngine.swift
+4
-4
@@ -15,11 +15,11 @@ enum PrivateGuideAvailability: Equatable {
15
case .notInstalled:
16
return "Private guide not ready yet"
17
case .downloaded:
18
- return "Private guide downloaded on this iPhone"
18
+ return "Private guide downloaded on this device"
19
case .preparing:
20
return "Preparing your private guide"
21
case .ready:
22
- return "Private guide ready on this iPhone"
22
+ return "Private guide ready on this device"
23
case .answering:
24
return "Answering privately"
25
case .failed:
@@ -34,10 +34,10 @@ enum PrivateGuideAvailability: Equatable {
34
"You can browse everything first, then add a one-time private guide download whenever you want deeper follow-up answers."
35
case .downloaded:
36
return
37
- "The private guide is already stored on this iPhone. Finish preparing it when you want faster follow-up answers in the app."
37
+ "The private guide is already stored on this device. Finish preparing it when you want faster follow-up answers in the app."
38
case .preparing:
39
return
40
- "The first run can take a while because Klepon needs to download and load the private guide locally on your iPhone."
40
+ "The first run can take a while because Klepon needs to download and load the private guide locally on your device."
41
case .ready:
42
return
43
"Klepon can now answer follow-up questions privately without turning the app into a generic chat shell."
Klepon/App/AppRootView.swift
+10
-1
@@ -45,8 +45,11 @@ struct AppRootView: View {
45
NavigationStack {
46
SettingsView()
47
}
48
+ #if os(macOS)
49
+ .frame(minWidth: 480, minHeight: 620)
50
+ #endif
51
}
49
- .fullScreenCover(isPresented: $showingOnboarding) {
52
+ .kleponOnboardingPresentation(isPresented: $showingOnboarding) {
53
OnboardingView(
54
onBrowseFirst: {
55
appState.completeOnboarding()
@@ -55,6 +58,9 @@ struct AppRootView: View {
58
appState.completeOnboarding()
59
}
60
)
61
+ #if os(macOS)
62
+ .frame(minWidth: 640, minHeight: 760)
63
+ #endif
64
}
65
.task {
66
showingOnboarding = !appState.hasCompletedOnboarding
@@ -63,5 +69,8 @@ struct AppRootView: View {
69
.onChange(of: appState.hasCompletedOnboarding) { _, newValue in
70
showingOnboarding = !newValue
71
}
72
+ #if os(macOS)
73
+ .frame(minWidth: 980, minHeight: 720)
74
+ #endif
75
}
76
}
Klepon/App/KleponApp.swift
+6
-1
@@ -12,7 +12,12 @@ struct KleponApp: App {
12
.environmentObject(appState.recentSearchStore)
13
.environmentObject(appState.recentlyViewedStore)
14
.environmentObject(appState.guideEngine)
15
- .preferredColorScheme(.light)
15
+ #if os(iOS)
16
+ .preferredColorScheme(.light)
17
+ #endif
18
}
19
+ #if os(macOS)
20
+ .defaultSize(width: 1100, height: 760)
21
+ #endif
22
}
23
}
Klepon/App/PlatformViewSupport.swift
new
+43
@@ -0,0 +1,43 @@
1
+import SwiftUI
2
+
3
+extension ToolbarItemPlacement {
4
+ static var kleponPrimaryAction: ToolbarItemPlacement {
5
+ #if os(macOS)
6
+ return .primaryAction
7
+ #else
8
+ return .topBarTrailing
9
+ #endif
10
+ }
11
+}
12
+
13
+extension View {
14
+ @ViewBuilder
15
+ func kleponInlineNavigationTitle() -> some View {
16
+ #if os(iOS)
17
+ self.navigationBarTitleDisplayMode(.inline)
18
+ #else
19
+ self
20
+ #endif
21
+ }
22
+
23
+ @ViewBuilder
24
+ func kleponLargeNavigationTitle() -> some View {
25
+ #if os(iOS)
26
+ self.navigationBarTitleDisplayMode(.large)
27
+ #else
28
+ self
29
+ #endif
30
+ }
31
+
32
+ @ViewBuilder
33
+ func kleponOnboardingPresentation<Content: View>(
34
+ isPresented: Binding<Bool>,
35
+ @ViewBuilder content: @escaping () -> Content
36
+ ) -> some View {
37
+ #if os(macOS)
38
+ self.sheet(isPresented: isPresented, content: content)
39
+ #else
40
+ self.fullScreenCover(isPresented: isPresented, content: content)
41
+ #endif
42
+ }
43
+}
Klepon/Features/Ask/AskSheetView.swift
+3
-3
@@ -158,9 +158,9 @@ struct AskSheetView: View {
158
}
159
.background(KleponColor.background.ignoresSafeArea())
160
.navigationTitle("Ask privately")
161
- .navigationBarTitleDisplayMode(.inline)
161
+ .kleponInlineNavigationTitle()
162
.toolbar {
163
- ToolbarItem(placement: .topBarTrailing) {
163
+ ToolbarItem(placement: .kleponPrimaryAction) {
164
Button("Done") {
165
dismiss()
166
}
@@ -212,7 +212,7 @@ private struct AnswerCardView: View {
212
Spacer(minLength: 12)
213
214
if card.isGeneratedOnDevice {
215
- KleponChip(title: "On this iPhone", icon: "lock")
215
+ KleponChip(title: "On this device", icon: "lock")
216
} else {
217
KleponChip(title: "Curated fallback", icon: "book")
218
}
Klepon/Features/Detail/GuideDetailView.swift
+2
-2
@@ -90,9 +90,9 @@ struct GuideDetailView: View {
90
}
91
.background(KleponColor.background.ignoresSafeArea())
92
.navigationTitle(entry.title)
93
- .navigationBarTitleDisplayMode(.inline)
93
+ .kleponInlineNavigationTitle()
94
.toolbar {
95
- ToolbarItem(placement: .topBarTrailing) {
95
+ ToolbarItem(placement: .kleponPrimaryAction) {
96
Button {
97
favoritesStore.toggle(entry.id)
98
} label: {
Klepon/Features/Discover/DiscoverView.swift
+4
-4
@@ -19,7 +19,7 @@ struct DiscoverView: View {
19
}
20
21
private var featuredTasteNotes: [String] {
22
- ["Private on your iPhone", "Curated first", "Warm follow-up answers"]
22
+ ["Private on your device", "Curated first", "Warm follow-up answers"]
23
}
24
25
private var recentlyViewedEntries: [GuideEntry] {
@@ -104,9 +104,9 @@ struct DiscoverView: View {
104
}
105
.background(KleponColor.background.ignoresSafeArea())
106
.navigationTitle("Klepon")
107
- .navigationBarTitleDisplayMode(.large)
107
+ .kleponLargeNavigationTitle()
108
.toolbar {
109
- ToolbarItem(placement: .topBarTrailing) {
109
+ ToolbarItem(placement: .kleponPrimaryAction) {
110
Button {
111
showingSettings = true
112
} label: {
@@ -125,7 +125,7 @@ struct DiscoverView: View {
125
.foregroundStyle(KleponColor.textPrimary)
126
127
Text(
128
- "Explore dishes, ingredients, and food traditions with a guide that feels calm, personal, and private on your iPhone."
128
+ "Explore dishes, ingredients, and food traditions with a guide that feels calm, personal, and private on your device."
129
)
130
.font(KleponTypography.body)
131
.foregroundStyle(KleponColor.textSecondary)
Klepon/Features/Onboarding/OnboardingView.swift
+35
-22
@@ -47,7 +47,7 @@ struct OnboardingView: View {
47
48
KleponCard {
49
onboardingPoint(
50
- title: "Private on your iPhone",
50
+ title: "Private on your device",
51
detail:
52
"Private answers use a one-time on-device guide download. You can browse first and add it later.",
53
systemImage: "lock"
@@ -94,31 +94,44 @@ struct OnboardingView: View {
94
.padding(.bottom, 180)
95
}
96
.background(KleponColor.background.ignoresSafeArea())
97
- .safeAreaInset(edge: .bottom) {
98
- VStack(spacing: 12) {
99
- KleponActionButton(
100
- title: privateGuideButtonTitle,
101
- systemImage: "lock",
102
- isLoading: guideEngine.availability.isBusy,
103
- isDisabled: guideEngine.availability == .ready
104
- ) {
105
- Task {
106
- await guideEngine.prepareIfNeeded(
107
- forceReload: guideEngine.availability.isFailure)
108
- if guideEngine.availability == .ready {
109
- onComplete()
110
- }
97
+ #if os(macOS)
98
+ .safeAreaInset(edge: .bottom) {
99
+ onboardingActions
100
+ .padding(.horizontal, 24)
101
+ .padding(.vertical, 16)
102
+ .background(KleponColor.background)
103
+ }
104
+ #else
105
+ .safeAreaInset(edge: .bottom) {
106
+ onboardingActions
107
+ .padding(.horizontal, 24)
108
+ .padding(.top, 12)
109
+ .padding(.bottom, 16)
110
+ .background(.ultraThinMaterial)
111
+ }
112
+ #endif
113
+ }
114
+
115
+ private var onboardingActions: some View {
116
+ VStack(spacing: 12) {
117
+ KleponActionButton(
118
+ title: privateGuideButtonTitle,
119
+ systemImage: "lock",
120
+ isLoading: guideEngine.availability.isBusy,
121
+ isDisabled: guideEngine.availability == .ready
122
+ ) {
123
+ Task {
124
+ await guideEngine.prepareIfNeeded(
125
+ forceReload: guideEngine.availability.isFailure)
126
+ if guideEngine.availability == .ready {
127
+ onComplete()
128
}
129
}
130
+ }
131
114
- KleponActionButton(title: "Browse first", tone: .secondary) {
115
- onBrowseFirst()
116
- }
132
+ KleponActionButton(title: "Browse first", tone: .secondary) {
133
+ onBrowseFirst()
134
}
118
- .padding(.horizontal, 24)
119
- .padding(.top, 12)
120
- .padding(.bottom, 16)
121
- .background(.ultraThinMaterial)
135
}
136
}
137
Klepon/Features/Settings/SettingsView.swift
+2
-2
@@ -57,7 +57,7 @@ struct SettingsView: View {
57
58
if guideEngine.storageUsedDescription != "Not downloaded yet" {
59
KleponActionButton(
60
- title: "Remove private guide from this iPhone",
60
+ title: "Remove private guide from this device",
61
systemImage: "trash",
62
tone: .secondary
63
) {
@@ -169,7 +169,7 @@ struct SettingsView: View {
169
.background(KleponColor.background.ignoresSafeArea())
170
.navigationTitle("Settings")
171
.toolbar {
172
- ToolbarItem(placement: .topBarTrailing) {
172
+ ToolbarItem(placement: .kleponPrimaryAction) {
173
Button("Done") {
174
dismiss()
175
}
KleponMac/Info.plist
new
+24
@@ -0,0 +1,24 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+<plist version="1.0">
4
+<dict>
5
+ <key>CFBundleDevelopmentRegion</key>
6
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
7
+ <key>CFBundleExecutable</key>
8
+ <string>$(EXECUTABLE_NAME)</string>
9
+ <key>CFBundleIdentifier</key>
10
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11
+ <key>CFBundleInfoDictionaryVersion</key>
12
+ <string>6.0</string>
13
+ <key>CFBundleName</key>
14
+ <string>$(PRODUCT_NAME)</string>
15
+ <key>CFBundlePackageType</key>
16
+ <string>APPL</string>
17
+ <key>CFBundleShortVersionString</key>
18
+ <string>$(MARKETING_VERSION)</string>
19
+ <key>CFBundleVersion</key>
20
+ <string>$(CURRENT_PROJECT_VERSION)</string>
21
+ <key>LSApplicationCategoryType</key>
22
+ <string>public.app-category.food-and-drink</string>
23
+</dict>
24
+</plist>
KleponSnapshotTests/KleponSnapshotTests.swift
+10
@@ -6,6 +6,16 @@ import XCTest
6
7
@MainActor
8
final class KleponSnapshotTests: XCTestCase {
9
+ override func invokeTest() {
10
+ if ProcessInfo.processInfo.environment["RECORD_SNAPSHOTS"] == "1" {
11
+ withSnapshotTesting(record: .all) {
12
+ super.invokeTest()
13
+ }
14
+ } else {
15
+ super.invokeTest()
16
+ }
17
+ }
18
+
19
override func setUp() {
20
super.setUp()
21
continueAfterFailure = false
KleponSnapshotTests/__Snapshots__/KleponSnapshotTests/testOnboardingView_defaultState.default.png
Binary files a/KleponSnapshotTests/__Snapshots__/KleponSnapshotTests/testOnboardingView_defaultState.default.png and b/KleponSnapshotTests/__Snapshots__/KleponSnapshotTests/testOnboardingView_defaultState.default.png differ
project.yml
+77
@@ -2,6 +2,7 @@ name: Klepon
2
options:
3
deploymentTarget:
4
iOS: "18.5"
5
+ macOS: "15.0"
6
watchOS: "9.0"
7
xcodeVersion: "26.0"
8
generateEmptyDirectories: false
@@ -48,6 +49,18 @@ schemes:
49
config: Release
50
profile:
51
config: Release
52
+ Klepon Mac:
53
+ build:
54
+ targets:
55
+ Klepon Mac: all
56
+ run:
57
+ config: Debug
58
+ analyze:
59
+ config: Debug
60
+ archive:
61
+ config: Release
62
+ profile:
63
+ config: Release
64
65
targets:
66
Klepon:
@@ -151,6 +164,70 @@ targets:
164
Release:
165
SWIFT_OPTIMIZATION_LEVEL: -O
166
167
+ KleponMac:
168
+ name: Klepon Mac
169
+ type: application
170
+ platform: macOS
171
+ sources:
172
+ - path: Klepon
173
+ info:
174
+ path: KleponMac/Info.plist
175
+ properties:
176
+ CFBundleShortVersionString: $(MARKETING_VERSION)
177
+ CFBundleVersion: $(CURRENT_PROJECT_VERSION)
178
+ LSApplicationCategoryType: public.app-category.food-and-drink
179
+ configFiles:
180
+ Debug: Configs/Signing.xcconfig
181
+ Release: Configs/Signing.xcconfig
182
+ dependencies:
183
+ - package: Onde
184
+ product: Onde
185
+ - sdk: SystemConfiguration.framework
186
+ postBuildScripts:
187
+ - name: Update CFBundleVersion
188
+ shell: /bin/sh
189
+ basedOnDependencyAnalysis: false
190
+ showEnvVars: false
191
+ script: |
192
+ set -eu
193
+
194
+ SHARED_BUNDLE_VERSION_FILE="${BUILD_DIR}/klepon-cfbundleversion.txt"
195
+ if [ -f "${SHARED_BUNDLE_VERSION_FILE}" ]; then
196
+ BUNDLE_VERSION="$(cat "${SHARED_BUNDLE_VERSION_FILE}")"
197
+ else
198
+ BUNDLE_VERSION="$(date -u +"%Y%m%d.%H%M")"
199
+ printf '%s' "${BUNDLE_VERSION}" > "${SHARED_BUNDLE_VERSION_FILE}"
200
+ fi
201
+
202
+ INFO_PLIST="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"
203
+
204
+ if [ ! -f "${INFO_PLIST}" ]; then
205
+ echo "error: Info.plist not found at ${INFO_PLIST}"
206
+ exit 1
207
+ fi
208
+
209
+ if ! /usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${BUNDLE_VERSION}" "${INFO_PLIST}" >/dev/null 2>&1; then
210
+ /usr/libexec/PlistBuddy -c "Add :CFBundleVersion string ${BUNDLE_VERSION}" "${INFO_PLIST}"
211
+ fi
212
+
213
+ echo "Updated CFBundleVersion to ${BUNDLE_VERSION}"
214
+ settings:
215
+ base:
216
+ PRODUCT_BUNDLE_IDENTIFIER: ai.splitfire.klepon.mac
217
+ PRODUCT_NAME: Klepon
218
+ MARKETING_VERSION: "1.0.0"
219
+ CURRENT_PROJECT_VERSION: 1
220
+ ENABLE_USER_SCRIPT_SANDBOXING: NO
221
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor
222
+ CODE_SIGN_ENTITLEMENTS: ""
223
+ INFOPLIST_KEY_NSHumanReadableCopyright: "Copyright © 2026 Splitfire AB"
224
+ "EXCLUDED_ARCHS[sdk=macosx*]": x86_64
225
+ configs:
226
+ Debug:
227
+ SWIFT_OPTIMIZATION_LEVEL: -Onone
228
+ Release:
229
+ SWIFT_OPTIMIZATION_LEVEL: -O
230
+
231
KleponWatch:
232
name: Klepon Watch
233
type: application