@pylonsync/create-pylon 0.3.51 → 0.3.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/bin/create-pylon.js +347 -1156
  2. package/package.json +4 -3
  3. package/templates/_root/.env.example +9 -0
  4. package/templates/_root/README.md +43 -0
  5. package/templates/backend/b2b/apps/api/functions/archiveProject.ts +15 -0
  6. package/templates/backend/b2b/apps/api/functions/createOrg.ts +43 -0
  7. package/templates/backend/b2b/apps/api/functions/createProject.ts +25 -0
  8. package/templates/backend/b2b/apps/api/functions/inviteMember.ts +49 -0
  9. package/templates/backend/b2b/apps/api/functions/myOrgs.ts +37 -0
  10. package/templates/backend/b2b/apps/api/functions/orgMembers.ts +13 -0
  11. package/templates/backend/b2b/apps/api/functions/orgProjects.ts +18 -0
  12. package/templates/backend/b2b/apps/api/functions/removeMember.ts +29 -0
  13. package/templates/backend/b2b/apps/api/functions/setMemberRole.ts +38 -0
  14. package/templates/backend/b2b/apps/api/package.json +20 -0
  15. package/templates/backend/b2b/apps/api/schema.ts +171 -0
  16. package/templates/backend/b2b/apps/api/tsconfig.json +13 -0
  17. package/templates/backend/barebones/apps/api/functions/createWidget.ts +22 -0
  18. package/templates/backend/barebones/apps/api/functions/listWidgets.ts +18 -0
  19. package/templates/backend/barebones/apps/api/package.json +20 -0
  20. package/templates/backend/barebones/apps/api/schema.ts +61 -0
  21. package/templates/backend/barebones/apps/api/tsconfig.json +13 -0
  22. package/templates/backend/chat/apps/api/functions/createRoom.ts +32 -0
  23. package/templates/backend/chat/apps/api/functions/listRooms.ts +15 -0
  24. package/templates/backend/chat/apps/api/functions/roomMessages.ts +20 -0
  25. package/templates/backend/chat/apps/api/functions/sendMessage.ts +37 -0
  26. package/templates/backend/chat/apps/api/package.json +20 -0
  27. package/templates/backend/chat/apps/api/schema.ts +93 -0
  28. package/templates/backend/chat/apps/api/tsconfig.json +13 -0
  29. package/templates/backend/consumer/apps/api/functions/createPost.ts +48 -0
  30. package/templates/backend/consumer/apps/api/functions/deletePost.ts +21 -0
  31. package/templates/backend/consumer/apps/api/functions/feed.ts +57 -0
  32. package/templates/backend/consumer/apps/api/functions/myProfile.ts +17 -0
  33. package/templates/backend/consumer/apps/api/functions/profilePosts.ts +17 -0
  34. package/templates/backend/consumer/apps/api/functions/toggleLike.ts +48 -0
  35. package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +70 -0
  36. package/templates/backend/consumer/apps/api/package.json +20 -0
  37. package/templates/backend/consumer/apps/api/schema.ts +130 -0
  38. package/templates/backend/consumer/apps/api/tsconfig.json +13 -0
  39. package/templates/backend/todo/apps/api/functions/addTodo.ts +27 -0
  40. package/templates/backend/todo/apps/api/functions/deleteTodo.ts +14 -0
  41. package/templates/backend/todo/apps/api/functions/editTodo.ts +16 -0
  42. package/templates/backend/todo/apps/api/functions/listTodos.ts +24 -0
  43. package/templates/backend/todo/apps/api/functions/reorderTodo.ts +14 -0
  44. package/templates/backend/todo/apps/api/functions/toggleTodo.ts +13 -0
  45. package/templates/backend/todo/apps/api/package.json +20 -0
  46. package/templates/backend/todo/apps/api/schema.ts +85 -0
  47. package/templates/backend/todo/apps/api/tsconfig.json +13 -0
  48. package/templates/expo/barebones/apps/expo/App.tsx +166 -0
  49. package/templates/expo/barebones/apps/expo/app.json +31 -0
  50. package/templates/expo/barebones/apps/expo/babel.config.js +6 -0
  51. package/templates/expo/barebones/apps/expo/package.json +30 -0
  52. package/templates/expo/barebones/apps/expo/tsconfig.json +16 -0
  53. package/templates/expo/chat/apps/expo/App.tsx +414 -0
  54. package/templates/expo/chat/apps/expo/app.json +25 -0
  55. package/templates/expo/chat/apps/expo/babel.config.js +6 -0
  56. package/templates/expo/chat/apps/expo/package.json +30 -0
  57. package/templates/expo/chat/apps/expo/tsconfig.json +16 -0
  58. package/templates/expo/consumer/apps/expo/App.tsx +360 -0
  59. package/templates/expo/consumer/apps/expo/app.json +25 -0
  60. package/templates/expo/consumer/apps/expo/babel.config.js +6 -0
  61. package/templates/expo/consumer/apps/expo/package.json +30 -0
  62. package/templates/expo/consumer/apps/expo/tsconfig.json +16 -0
  63. package/templates/expo/todo/apps/expo/App.tsx +287 -0
  64. package/templates/expo/todo/apps/expo/app.json +25 -0
  65. package/templates/expo/todo/apps/expo/babel.config.js +6 -0
  66. package/templates/expo/todo/apps/expo/package.json +30 -0
  67. package/templates/expo/todo/apps/expo/tsconfig.json +16 -0
  68. package/templates/ios/barebones/apps/ios/Package.swift +34 -0
  69. package/templates/ios/barebones/apps/ios/Sources/__APP_NAME_PASCAL__/ContentView.swift +98 -0
  70. package/templates/ios/barebones/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +17 -0
  71. package/templates/ios/barebones/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +34 -0
  72. package/templates/ios/barebones/apps/ios/project.yml +42 -0
  73. package/templates/ios/chat/apps/ios/Package.swift +34 -0
  74. package/templates/ios/chat/apps/ios/Sources/__APP_NAME_PASCAL__/ChatRootView.swift +120 -0
  75. package/templates/ios/chat/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +26 -0
  76. package/templates/ios/chat/apps/ios/Sources/__APP_NAME_PASCAL__/RoomView.swift +137 -0
  77. package/templates/ios/chat/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +35 -0
  78. package/templates/ios/chat/apps/ios/project.yml +42 -0
  79. package/templates/ios/consumer/apps/ios/Package.swift +23 -0
  80. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +170 -0
  81. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +42 -0
  82. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +60 -0
  83. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +30 -0
  84. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +29 -0
  85. package/templates/ios/consumer/apps/ios/project.yml +42 -0
  86. package/templates/ios/todo/apps/ios/Package.swift +23 -0
  87. package/templates/ios/todo/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +18 -0
  88. package/templates/ios/todo/apps/ios/Sources/__APP_NAME_PASCAL__/TodoListView.swift +230 -0
  89. package/templates/ios/todo/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +28 -0
  90. package/templates/ios/todo/apps/ios/project.yml +32 -0
  91. package/templates/mac/b2b/apps/mac/Package.swift +22 -0
  92. package/templates/mac/b2b/apps/mac/Sources/__APP_NAME_PASCAL__/Models.swift +15 -0
  93. package/templates/mac/b2b/apps/mac/Sources/__APP_NAME_PASCAL__/OrgPickerView.swift +178 -0
  94. package/templates/mac/b2b/apps/mac/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +30 -0
  95. package/templates/mac/b2b/apps/mac/__APP_NAME_PASCAL__.entitlements +13 -0
  96. package/templates/mac/b2b/apps/mac/project.yml +34 -0
  97. package/templates/mac/barebones/apps/mac/Package.swift +33 -0
  98. package/templates/mac/barebones/apps/mac/Sources/__APP_NAME_PASCAL__/ContentView.swift +104 -0
  99. package/templates/mac/barebones/apps/mac/Sources/__APP_NAME_PASCAL__/Models.swift +17 -0
  100. package/templates/mac/barebones/apps/mac/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +30 -0
  101. package/templates/mac/barebones/apps/mac/__APP_NAME_PASCAL__.entitlements +13 -0
  102. package/templates/mac/barebones/apps/mac/project.yml +34 -0
  103. package/templates/mac/chat/apps/mac/Package.swift +33 -0
  104. package/templates/mac/chat/apps/mac/Sources/__APP_NAME_PASCAL__/ChatRootView.swift +140 -0
  105. package/templates/mac/chat/apps/mac/Sources/__APP_NAME_PASCAL__/Models.swift +26 -0
  106. package/templates/mac/chat/apps/mac/Sources/__APP_NAME_PASCAL__/RoomView.swift +137 -0
  107. package/templates/mac/chat/apps/mac/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +37 -0
  108. package/templates/mac/chat/apps/mac/__APP_NAME_PASCAL__.entitlements +13 -0
  109. package/templates/mac/chat/apps/mac/project.yml +34 -0
  110. package/templates/mac/consumer/apps/mac/Package.swift +33 -0
  111. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/FeedView.swift +170 -0
  112. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/Models.swift +42 -0
  113. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +60 -0
  114. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/RootView.swift +30 -0
  115. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +31 -0
  116. package/templates/mac/consumer/apps/mac/__APP_NAME_PASCAL__.entitlements +13 -0
  117. package/templates/mac/consumer/apps/mac/project.yml +34 -0
  118. package/templates/mac/todo/apps/mac/Package.swift +33 -0
  119. package/templates/mac/todo/apps/mac/Sources/__APP_NAME_PASCAL__/Models.swift +19 -0
  120. package/templates/mac/todo/apps/mac/Sources/__APP_NAME_PASCAL__/TodoListView.swift +244 -0
  121. package/templates/mac/todo/apps/mac/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +30 -0
  122. package/templates/mac/todo/apps/mac/__APP_NAME_PASCAL__.entitlements +13 -0
  123. package/templates/mac/todo/apps/mac/project.yml +34 -0
  124. package/templates/ui/packages/ui/package.json +26 -0
  125. package/templates/ui/packages/ui/src/button.tsx +44 -0
  126. package/templates/ui/packages/ui/src/card.tsx +39 -0
  127. package/templates/ui/packages/ui/src/cn.ts +12 -0
  128. package/templates/ui/packages/ui/src/index.ts +4 -0
  129. package/templates/ui/packages/ui/src/input.tsx +19 -0
  130. package/templates/ui/packages/ui/tsconfig.json +15 -0
  131. package/templates/web/b2b/apps/web/next-env.d.ts +2 -0
  132. package/templates/web/b2b/apps/web/next.config.ts +24 -0
  133. package/templates/web/b2b/apps/web/package.json +29 -0
  134. package/templates/web/b2b/apps/web/postcss.config.mjs +3 -0
  135. package/templates/web/b2b/apps/web/src/app/components/OrgPicker.tsx +171 -0
  136. package/templates/web/b2b/apps/web/src/app/globals.css +6 -0
  137. package/templates/web/b2b/apps/web/src/app/layout.tsx +21 -0
  138. package/templates/web/b2b/apps/web/src/app/page.tsx +39 -0
  139. package/templates/web/b2b/apps/web/src/lib/pylon.ts +5 -0
  140. package/templates/web/b2b/apps/web/tsconfig.json +26 -0
  141. package/templates/web/barebones/apps/web/next-env.d.ts +2 -0
  142. package/templates/web/barebones/apps/web/next.config.ts +40 -0
  143. package/templates/web/barebones/apps/web/package.json +29 -0
  144. package/templates/web/barebones/apps/web/postcss.config.mjs +4 -0
  145. package/templates/web/barebones/apps/web/src/app/components/WidgetList.tsx +81 -0
  146. package/templates/web/barebones/apps/web/src/app/globals.css +9 -0
  147. package/templates/web/barebones/apps/web/src/app/layout.tsx +21 -0
  148. package/templates/web/barebones/apps/web/src/app/page.tsx +43 -0
  149. package/templates/web/barebones/apps/web/src/lib/pylon.ts +14 -0
  150. package/templates/web/barebones/apps/web/tsconfig.json +26 -0
  151. package/templates/web/chat/apps/web/next-env.d.ts +2 -0
  152. package/templates/web/chat/apps/web/next.config.ts +24 -0
  153. package/templates/web/chat/apps/web/package.json +29 -0
  154. package/templates/web/chat/apps/web/postcss.config.mjs +3 -0
  155. package/templates/web/chat/apps/web/src/app/components/ChatRoom.tsx +250 -0
  156. package/templates/web/chat/apps/web/src/app/globals.css +6 -0
  157. package/templates/web/chat/apps/web/src/app/layout.tsx +21 -0
  158. package/templates/web/chat/apps/web/src/app/page.tsx +51 -0
  159. package/templates/web/chat/apps/web/src/lib/pylon.ts +5 -0
  160. package/templates/web/chat/apps/web/tsconfig.json +26 -0
  161. package/templates/web/consumer/apps/web/next-env.d.ts +2 -0
  162. package/templates/web/consumer/apps/web/next.config.ts +24 -0
  163. package/templates/web/consumer/apps/web/package.json +29 -0
  164. package/templates/web/consumer/apps/web/postcss.config.mjs +3 -0
  165. package/templates/web/consumer/apps/web/src/app/components/Feed.tsx +295 -0
  166. package/templates/web/consumer/apps/web/src/app/globals.css +6 -0
  167. package/templates/web/consumer/apps/web/src/app/layout.tsx +21 -0
  168. package/templates/web/consumer/apps/web/src/app/page.tsx +55 -0
  169. package/templates/web/consumer/apps/web/src/lib/pylon.ts +5 -0
  170. package/templates/web/consumer/apps/web/tsconfig.json +26 -0
  171. package/templates/web/todo/apps/web/next-env.d.ts +2 -0
  172. package/templates/web/todo/apps/web/next.config.ts +24 -0
  173. package/templates/web/todo/apps/web/package.json +32 -0
  174. package/templates/web/todo/apps/web/postcss.config.mjs +3 -0
  175. package/templates/web/todo/apps/web/src/app/components/TodoList.tsx +310 -0
  176. package/templates/web/todo/apps/web/src/app/globals.css +6 -0
  177. package/templates/web/todo/apps/web/src/app/layout.tsx +21 -0
  178. package/templates/web/todo/apps/web/src/app/page.tsx +36 -0
  179. package/templates/web/todo/apps/web/src/lib/pylon.ts +5 -0
  180. package/templates/web/todo/apps/web/tsconfig.json +26 -0
@@ -0,0 +1,33 @@
1
+ // swift-tools-version:5.9
2
+ import PackageDescription
3
+
4
+ // SwiftPM package for the __APP_NAME__ macOS app.
5
+ //
6
+ // The executable target runs locally with `swift run`. For a proper
7
+ // signed `.app` bundle:
8
+ //
9
+ // brew install xcodegen
10
+ // xcodegen generate
11
+ // open __APP_NAME_PASCAL__.xcodeproj
12
+ //
13
+ // The Xcode project pulls the same Sources/__APP_NAME_PASCAL__/ tree
14
+ // as `swift build`, so SwiftPM and Xcode share one source set.
15
+ let package = Package(
16
+ name: "__APP_NAME_PASCAL__",
17
+ platforms: [
18
+ .macOS(.v13),
19
+ ],
20
+ dependencies: [
21
+ .package(url: "https://github.com/pylonsync/pylon.git", from: "0.3.0"),
22
+ ],
23
+ targets: [
24
+ .executableTarget(
25
+ name: "__APP_NAME_PASCAL__",
26
+ dependencies: [
27
+ .product(name: "PylonClient", package: "pylon"),
28
+ .product(name: "PylonSwiftUI", package: "pylon"),
29
+ ],
30
+ path: "Sources/__APP_NAME_PASCAL__"
31
+ ),
32
+ ]
33
+ )
@@ -0,0 +1,170 @@
1
+ import SwiftUI
2
+ import PylonClient
3
+
4
+ struct FeedView: View {
5
+ @EnvironmentObject var session: AppSession
6
+ @State private var feed: [FeedItem] = []
7
+ @State private var loading = true
8
+ @State private var posting = false
9
+ @State private var draft = ""
10
+ @State private var errorMessage: String?
11
+
12
+ var body: some View {
13
+ NavigationStack {
14
+ List {
15
+ Section("Post") {
16
+ VStack(alignment: .leading, spacing: 8) {
17
+ TextEditor(text: $draft)
18
+ .frame(minHeight: 80)
19
+ .font(.body)
20
+ HStack {
21
+ Text("\(draft.count)/1000")
22
+ .font(.caption)
23
+ .foregroundStyle(.secondary)
24
+ Spacer()
25
+ Button(posting ? "Posting…" : "Post") {
26
+ Task { await post() }
27
+ }
28
+ .disabled(posting || draft.trimmingCharacters(in: .whitespaces).isEmpty)
29
+ }
30
+ }
31
+ }
32
+
33
+ Section("Feed") {
34
+ if loading {
35
+ ProgressView()
36
+ } else if feed.isEmpty {
37
+ Text("No posts yet.")
38
+ .foregroundStyle(.secondary)
39
+ } else {
40
+ ForEach(feed) { item in
41
+ row(item)
42
+ }
43
+ }
44
+ }
45
+
46
+ if let errorMessage {
47
+ Section {
48
+ Text(errorMessage)
49
+ .foregroundStyle(.red)
50
+ .font(.caption)
51
+ }
52
+ }
53
+ }
54
+ .navigationTitle("__APP_NAME__")
55
+ .task { await load() }
56
+ .refreshable { await load() }
57
+ }
58
+ }
59
+
60
+ @ViewBuilder
61
+ private func row(_ item: FeedItem) -> some View {
62
+ VStack(alignment: .leading, spacing: 6) {
63
+ HStack(alignment: .firstTextBaseline) {
64
+ Text(item.author?.displayName ?? "Unknown")
65
+ .font(.subheadline.weight(.medium))
66
+ Text("@\(item.author?.handle ?? "?")")
67
+ .font(.system(.caption, design: .monospaced))
68
+ .foregroundStyle(.secondary)
69
+ Spacer()
70
+ Text(item.createdAt)
71
+ .font(.caption2)
72
+ .foregroundStyle(.tertiary)
73
+ }
74
+ Text(item.body)
75
+ .font(.body)
76
+ .fixedSize(horizontal: false, vertical: true)
77
+ HStack {
78
+ Button {
79
+ Task { await toggleLike(item) }
80
+ } label: {
81
+ HStack(spacing: 4) {
82
+ Image(systemName: item.likedByMe ? "heart.fill" : "heart")
83
+ Text("\(item.likeCount)")
84
+ }
85
+ .font(.caption)
86
+ .foregroundStyle(item.likedByMe ? .pink : .secondary)
87
+ }
88
+ .buttonStyle(.plain)
89
+
90
+ if item.author?.id == session.me?.id {
91
+ Spacer()
92
+ Button("Delete", role: .destructive) {
93
+ Task { await delete(item) }
94
+ }
95
+ .font(.caption)
96
+ }
97
+ }
98
+ }
99
+ .padding(.vertical, 4)
100
+ }
101
+
102
+ // MARK: - Network
103
+
104
+ private func load() async {
105
+ loading = true
106
+ defer { loading = false }
107
+ do {
108
+ feed = try await session.pylon.callFn("feed", args: EmptyArgs())
109
+ errorMessage = nil
110
+ } catch {
111
+ errorMessage = "Load failed: \(error.localizedDescription)"
112
+ }
113
+ }
114
+
115
+ private func post() async {
116
+ let body = draft.trimmingCharacters(in: .whitespaces)
117
+ guard !body.isEmpty else { return }
118
+ posting = true
119
+ defer { posting = false }
120
+ do {
121
+ let item: FeedItem = try await session.pylon.callFn(
122
+ "createPost",
123
+ args: CreatePostArgs(body: body),
124
+ )
125
+ feed.insert(item, at: 0)
126
+ draft = ""
127
+ } catch {
128
+ errorMessage = "Post failed: \(error.localizedDescription)"
129
+ }
130
+ }
131
+
132
+ private func toggleLike(_ item: FeedItem) async {
133
+ // Optimistic
134
+ if let i = feed.firstIndex(where: { $0.id == item.id }) {
135
+ feed[i].likedByMe.toggle()
136
+ feed[i].likeCount += feed[i].likedByMe ? 1 : -1
137
+ }
138
+ do {
139
+ let result: ToggleLikeResult = try await session.pylon.callFn(
140
+ "toggleLike",
141
+ args: ToggleLikeArgs(postId: item.id),
142
+ )
143
+ if let i = feed.firstIndex(where: { $0.id == item.id }) {
144
+ feed[i].likedByMe = result.liked
145
+ feed[i].likeCount = result.likeCount
146
+ }
147
+ } catch {
148
+ // Revert
149
+ if let i = feed.firstIndex(where: { $0.id == item.id }) {
150
+ feed[i].likedByMe = item.likedByMe
151
+ feed[i].likeCount = item.likeCount
152
+ }
153
+ errorMessage = "Like failed: \(error.localizedDescription)"
154
+ }
155
+ }
156
+
157
+ private func delete(_ item: FeedItem) async {
158
+ let snapshot = feed
159
+ feed.removeAll { $0.id == item.id }
160
+ do {
161
+ let _: FeedItem = try await session.pylon.callFn(
162
+ "deletePost",
163
+ args: DeletePostArgs(id: item.id),
164
+ )
165
+ } catch {
166
+ feed = snapshot
167
+ errorMessage = "Delete failed: \(error.localizedDescription)"
168
+ }
169
+ }
170
+ }
@@ -0,0 +1,42 @@
1
+ import Foundation
2
+
3
+ struct Profile: Codable, Identifiable, Hashable {
4
+ let id: String
5
+ let userId: String
6
+ let handle: String
7
+ let displayName: String
8
+ let bio: String?
9
+ let createdAt: String
10
+ }
11
+
12
+ struct FeedItem: Codable, Identifiable, Hashable {
13
+ let id: String
14
+ let body: String
15
+ let createdAt: String
16
+ let author: AuthorCard?
17
+ var likeCount: Int
18
+ var likedByMe: Bool
19
+ }
20
+
21
+ struct AuthorCard: Codable, Hashable {
22
+ let id: String
23
+ let handle: String
24
+ let displayName: String
25
+ }
26
+
27
+ struct UpsertProfileArgs: Encodable {
28
+ let handle: String
29
+ let displayName: String
30
+ let bio: String
31
+ }
32
+
33
+ struct CreatePostArgs: Encodable { let body: String }
34
+ struct DeletePostArgs: Encodable { let id: String }
35
+ struct ToggleLikeArgs: Encodable { let postId: String }
36
+
37
+ struct ToggleLikeResult: Codable {
38
+ let liked: Bool
39
+ let likeCount: Int
40
+ }
41
+
42
+ struct EmptyArgs: Encodable {}
@@ -0,0 +1,60 @@
1
+ import SwiftUI
2
+ import PylonClient
3
+
4
+ struct ProfileSetupView: View {
5
+ @EnvironmentObject var session: AppSession
6
+ @State private var handle = ""
7
+ @State private var displayName = ""
8
+ @State private var bio = ""
9
+ @State private var saving = false
10
+ @State private var errorMessage: String?
11
+
12
+ var body: some View {
13
+ NavigationStack {
14
+ Form {
15
+ Section("Set up your profile") {
16
+ TextField("handle (lowercase, 2–20)", text: $handle)
17
+ .autocorrectionDisabled()
18
+ .textInputAutocapitalization(.never)
19
+ TextField("Display name", text: $displayName)
20
+ TextField("Bio (optional)", text: $bio)
21
+ }
22
+
23
+ if let errorMessage {
24
+ Section {
25
+ Text(errorMessage)
26
+ .foregroundStyle(.red)
27
+ .font(.caption)
28
+ }
29
+ }
30
+
31
+ Section {
32
+ Button(saving ? "Saving…" : "Save") {
33
+ Task { await save() }
34
+ }
35
+ .disabled(saving || handle.trimmingCharacters(in: .whitespaces).isEmpty
36
+ || displayName.trimmingCharacters(in: .whitespaces).isEmpty)
37
+ }
38
+ }
39
+ .navigationTitle("__APP_NAME__")
40
+ }
41
+ }
42
+
43
+ private func save() async {
44
+ saving = true
45
+ defer { saving = false }
46
+ do {
47
+ let profile: Profile = try await session.pylon.callFn(
48
+ "upsertProfile",
49
+ args: UpsertProfileArgs(
50
+ handle: handle.trimmingCharacters(in: .whitespaces).lowercased(),
51
+ displayName: displayName.trimmingCharacters(in: .whitespaces),
52
+ bio: bio.trimmingCharacters(in: .whitespaces),
53
+ ),
54
+ )
55
+ session.me = profile
56
+ } catch {
57
+ errorMessage = "Save failed: \(error.localizedDescription)"
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,30 @@
1
+ import SwiftUI
2
+ import PylonClient
3
+
4
+ struct RootView: View {
5
+ @EnvironmentObject var session: AppSession
6
+ @State private var loading = true
7
+
8
+ var body: some View {
9
+ Group {
10
+ if loading {
11
+ ProgressView()
12
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
13
+ } else if session.me == nil {
14
+ ProfileSetupView()
15
+ } else {
16
+ FeedView()
17
+ }
18
+ }
19
+ .task { await load() }
20
+ }
21
+
22
+ private func load() async {
23
+ do {
24
+ session.me = try await session.pylon.callFn("myProfile", args: EmptyArgs())
25
+ } catch {
26
+ session.me = nil
27
+ }
28
+ loading = false
29
+ }
30
+ }
@@ -0,0 +1,31 @@
1
+ import SwiftUI
2
+ import PylonClient
3
+
4
+ @main
5
+ struct __APP_NAME_PASCAL__App: App {
6
+ @StateObject private var session = AppSession()
7
+
8
+ var body: some Scene {
9
+ Window("__APP_NAME__", id: "main") {
10
+ RootView()
11
+ .environmentObject(session)
12
+ .frame(minWidth: 480, minHeight: 600)
13
+ }
14
+ .windowResizability(.contentMinSize)
15
+ }
16
+ }
17
+
18
+ @MainActor
19
+ final class AppSession: ObservableObject {
20
+ let pylon: PylonClient
21
+ @Published var me: Profile?
22
+
23
+ init() {
24
+ let baseURLString = ProcessInfo.processInfo.environment["PYLON_BASE_URL"]
25
+ ?? "http://localhost:4321"
26
+ guard let url = URL(string: baseURLString) else {
27
+ fatalError("Invalid PYLON_BASE_URL: \(baseURLString)")
28
+ }
29
+ self.pylon = PylonClient(baseURL: url, appName: "__APP_NAME_SNAKE__")
30
+ }
31
+ }
@@ -0,0 +1,13 @@
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
+ <!-- App Sandbox + outgoing-network client. Without these the
6
+ scaffolded app can't reach localhost:4321 during dev or
7
+ a remote Pylon Cloud URL in production. -->
8
+ <key>com.apple.security.app-sandbox</key>
9
+ <true/>
10
+ <key>com.apple.security.network.client</key>
11
+ <true/>
12
+ </dict>
13
+ </plist>
@@ -0,0 +1,34 @@
1
+ name: __APP_NAME_PASCAL__
2
+ options:
3
+ bundleIdPrefix: com.example
4
+ deploymentTarget:
5
+ macOS: "13.0"
6
+
7
+ packages:
8
+ pylon:
9
+ url: https://github.com/pylonsync/pylon.git
10
+ from: "0.3.0"
11
+
12
+ targets:
13
+ __APP_NAME_PASCAL__:
14
+ type: application
15
+ platform: macOS
16
+ sources:
17
+ - path: Sources/__APP_NAME_PASCAL__
18
+ settings:
19
+ base:
20
+ GENERATE_INFOPLIST_FILE: YES
21
+ INFOPLIST_KEY_CFBundleDisplayName: __APP_NAME__
22
+ INFOPLIST_KEY_LSApplicationCategoryType: "public.app-category.developer-tools"
23
+ INFOPLIST_KEY_NSHumanReadableCopyright: ""
24
+ SWIFT_VERSION: "5.9"
25
+ ENABLE_PREVIEWS: YES
26
+ # The mac app talks to the local Pylon control plane during dev.
27
+ # App Sandbox needs the network client entitlement to reach
28
+ # localhost; we declare it via codesign entitlements at build time.
29
+ CODE_SIGN_ENTITLEMENTS: __APP_NAME_PASCAL__.entitlements
30
+ dependencies:
31
+ - package: pylon
32
+ product: PylonClient
33
+ - package: pylon
34
+ product: PylonSwiftUI
@@ -0,0 +1,33 @@
1
+ // swift-tools-version:5.9
2
+ import PackageDescription
3
+
4
+ // SwiftPM package for the __APP_NAME__ macOS app.
5
+ //
6
+ // The executable target runs locally with `swift run`. For a proper
7
+ // signed `.app` bundle:
8
+ //
9
+ // brew install xcodegen
10
+ // xcodegen generate
11
+ // open __APP_NAME_PASCAL__.xcodeproj
12
+ //
13
+ // The Xcode project pulls the same Sources/__APP_NAME_PASCAL__/ tree
14
+ // as `swift build`, so SwiftPM and Xcode share one source set.
15
+ let package = Package(
16
+ name: "__APP_NAME_PASCAL__",
17
+ platforms: [
18
+ .macOS(.v13),
19
+ ],
20
+ dependencies: [
21
+ .package(url: "https://github.com/pylonsync/pylon.git", from: "0.3.0"),
22
+ ],
23
+ targets: [
24
+ .executableTarget(
25
+ name: "__APP_NAME_PASCAL__",
26
+ dependencies: [
27
+ .product(name: "PylonClient", package: "pylon"),
28
+ .product(name: "PylonSwiftUI", package: "pylon"),
29
+ ],
30
+ path: "Sources/__APP_NAME_PASCAL__"
31
+ ),
32
+ ]
33
+ )
@@ -0,0 +1,19 @@
1
+ import Foundation
2
+
3
+ /// Mirrors `Todo` from `apps/api/schema.ts`. Regenerate from the
4
+ /// schema with `pylon codegen client schema.ts --target swift` for
5
+ /// production.
6
+ struct Todo: Codable, Identifiable, Hashable {
7
+ let id: String
8
+ var title: String
9
+ var done: Bool
10
+ let createdAt: String
11
+ var position: Double?
12
+ }
13
+
14
+ struct AddTodoArgs: Encodable { let title: String }
15
+ struct ToggleTodoArgs: Encodable { let id: String; let done: Bool }
16
+ struct EditTodoArgs: Encodable { let id: String; let title: String }
17
+ struct DeleteTodoArgs: Encodable { let id: String }
18
+ struct ReorderTodoArgs: Encodable { let id: String; let position: Double }
19
+ struct EmptyArgs: Encodable {}