@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,244 @@
1
+ import SwiftUI
2
+ import PylonClient
3
+
4
+ /// macOS Todo list. List with inline checkbox + edit-on-double-click +
5
+ /// delete on hover. macOS doesn't ship `EditButton`, so reordering is
6
+ /// drag-and-drop via `.onMove(perform:)` which works in editable List.
7
+ struct TodoListView: View {
8
+ @EnvironmentObject var session: AppSession
9
+ @State private var todos: [Todo] = []
10
+ @State private var draftTitle: String = ""
11
+ @State private var loading = true
12
+ @State private var pending = false
13
+ @State private var errorMessage: String?
14
+ @State private var editingId: String?
15
+ @State private var editingDraft: String = ""
16
+ @State private var hoveredId: String?
17
+
18
+ var body: some View {
19
+ VStack(spacing: 0) {
20
+ HStack(spacing: 8) {
21
+ TextField("What needs doing?", text: $draftTitle)
22
+ .textFieldStyle(.roundedBorder)
23
+ .onSubmit { Task { await add() } }
24
+ Button("Add") { Task { await add() } }
25
+ .keyboardShortcut(.defaultAction)
26
+ .disabled(draftTitle.trimmingCharacters(in: .whitespaces).isEmpty || pending)
27
+ }
28
+ .padding(16)
29
+
30
+ Divider()
31
+
32
+ if loading {
33
+ ProgressView().padding()
34
+ Spacer()
35
+ } else if todos.isEmpty {
36
+ Spacer()
37
+ Text("No todos yet.")
38
+ .foregroundStyle(.secondary)
39
+ Spacer()
40
+ } else {
41
+ List {
42
+ ForEach(todos) { todo in
43
+ row(todo)
44
+ }
45
+ .onMove { source, destination in
46
+ Task { await reorder(from: source, to: destination) }
47
+ }
48
+ }
49
+ .listStyle(.inset)
50
+ }
51
+
52
+ if let errorMessage {
53
+ Divider()
54
+ Text(errorMessage)
55
+ .foregroundStyle(.red)
56
+ .font(.caption)
57
+ .padding(8)
58
+ }
59
+ }
60
+ .task { await load() }
61
+ .toolbar {
62
+ ToolbarItem(placement: .primaryAction) {
63
+ Button { Task { await load() } } label: {
64
+ Image(systemName: "arrow.clockwise")
65
+ }
66
+ .disabled(loading)
67
+ }
68
+ }
69
+ }
70
+
71
+ @ViewBuilder
72
+ private func row(_ todo: Todo) -> some View {
73
+ HStack(spacing: 10) {
74
+ Button { Task { await toggle(todo) } } label: {
75
+ Image(systemName: todo.done ? "checkmark.circle.fill" : "circle")
76
+ .foregroundStyle(todo.done ? .green : .secondary)
77
+ .imageScale(.large)
78
+ }
79
+ .buttonStyle(.plain)
80
+
81
+ if editingId == todo.id {
82
+ TextField("Title", text: $editingDraft)
83
+ .textFieldStyle(.roundedBorder)
84
+ .onSubmit { Task { await commitEdit(todo) } }
85
+ Button("Save") { Task { await commitEdit(todo) } }
86
+ .buttonStyle(.bordered)
87
+ Button("Cancel") {
88
+ editingId = nil
89
+ editingDraft = ""
90
+ }
91
+ .buttonStyle(.bordered)
92
+ } else {
93
+ Text(todo.title)
94
+ .strikethrough(todo.done, color: .secondary)
95
+ .foregroundStyle(todo.done ? .secondary : .primary)
96
+ .onTapGesture(count: 2) {
97
+ editingId = todo.id
98
+ editingDraft = todo.title
99
+ }
100
+ Spacer()
101
+ if hoveredId == todo.id {
102
+ Button("Edit") {
103
+ editingId = todo.id
104
+ editingDraft = todo.title
105
+ }
106
+ .buttonStyle(.borderless)
107
+ .font(.caption)
108
+ Button("Delete") { Task { await delete(todo) } }
109
+ .buttonStyle(.borderless)
110
+ .font(.caption)
111
+ .foregroundStyle(.red)
112
+ }
113
+ }
114
+ }
115
+ .padding(.vertical, 4)
116
+ .contentShape(Rectangle())
117
+ .onHover { hovering in
118
+ hoveredId = hovering ? todo.id : (hoveredId == todo.id ? nil : hoveredId)
119
+ }
120
+ }
121
+
122
+ // MARK: - Network
123
+
124
+ private func load() async {
125
+ loading = true
126
+ defer { loading = false }
127
+ do {
128
+ let rows: [Todo] = try await session.pylon.callFn("listTodos", args: EmptyArgs())
129
+ todos = rows
130
+ errorMessage = nil
131
+ } catch {
132
+ errorMessage = "Load failed: \(error.localizedDescription)"
133
+ }
134
+ }
135
+
136
+ private func add() async {
137
+ let trimmed = draftTitle.trimmingCharacters(in: .whitespaces)
138
+ guard !trimmed.isEmpty else { return }
139
+ pending = true
140
+ defer { pending = false }
141
+ do {
142
+ let todo: Todo = try await session.pylon.callFn(
143
+ "addTodo",
144
+ args: AddTodoArgs(title: trimmed),
145
+ )
146
+ todos.append(todo)
147
+ draftTitle = ""
148
+ } catch {
149
+ errorMessage = "Add failed: \(error.localizedDescription)"
150
+ }
151
+ }
152
+
153
+ private func toggle(_ todo: Todo) async {
154
+ let nextDone = !todo.done
155
+ if let i = todos.firstIndex(where: { $0.id == todo.id }) {
156
+ todos[i].done = nextDone
157
+ }
158
+ do {
159
+ let _: Todo = try await session.pylon.callFn(
160
+ "toggleTodo",
161
+ args: ToggleTodoArgs(id: todo.id, done: nextDone),
162
+ )
163
+ } catch {
164
+ if let i = todos.firstIndex(where: { $0.id == todo.id }) {
165
+ todos[i].done = todo.done
166
+ }
167
+ errorMessage = "Toggle failed: \(error.localizedDescription)"
168
+ }
169
+ }
170
+
171
+ private func commitEdit(_ todo: Todo) async {
172
+ let trimmed = editingDraft.trimmingCharacters(in: .whitespaces)
173
+ guard !trimmed.isEmpty, trimmed != todo.title else {
174
+ editingId = nil
175
+ editingDraft = ""
176
+ return
177
+ }
178
+ let originalTitle = todo.title
179
+ if let i = todos.firstIndex(where: { $0.id == todo.id }) {
180
+ todos[i].title = trimmed
181
+ }
182
+ editingId = nil
183
+ editingDraft = ""
184
+ do {
185
+ let _: Todo = try await session.pylon.callFn(
186
+ "editTodo",
187
+ args: EditTodoArgs(id: todo.id, title: trimmed),
188
+ )
189
+ } catch {
190
+ if let i = todos.firstIndex(where: { $0.id == todo.id }) {
191
+ todos[i].title = originalTitle
192
+ }
193
+ errorMessage = "Rename failed: \(error.localizedDescription)"
194
+ }
195
+ }
196
+
197
+ private func delete(_ todo: Todo) async {
198
+ let snapshot = todos
199
+ todos.removeAll { $0.id == todo.id }
200
+ do {
201
+ let _: Todo = try await session.pylon.callFn(
202
+ "deleteTodo",
203
+ args: DeleteTodoArgs(id: todo.id),
204
+ )
205
+ } catch {
206
+ todos = snapshot
207
+ errorMessage = "Delete failed: \(error.localizedDescription)"
208
+ }
209
+ }
210
+
211
+ private func reorder(from source: IndexSet, to destination: Int) async {
212
+ var moved = todos
213
+ moved.move(fromOffsets: source, toOffset: destination)
214
+ guard let movedIndex = source.first else { return }
215
+ let newIndex = destination > movedIndex ? destination - 1 : destination
216
+ guard newIndex < moved.count else { return }
217
+ let movedTodo = moved[newIndex]
218
+ let prev = newIndex > 0 ? moved[newIndex - 1] : nil
219
+ let next = newIndex + 1 < moved.count ? moved[newIndex + 1] : nil
220
+ let prevPos = prev?.position ?? 0
221
+ let nextPos = next?.position ?? 0
222
+ let position: Double
223
+ if prev != nil && next != nil {
224
+ position = (prevPos + nextPos) / 2
225
+ } else if prev != nil {
226
+ position = prevPos + 1024
227
+ } else if next != nil {
228
+ position = nextPos - 1024
229
+ } else {
230
+ position = 1024
231
+ }
232
+ let snapshot = todos
233
+ todos = moved
234
+ do {
235
+ let _: Todo = try await session.pylon.callFn(
236
+ "reorderTodo",
237
+ args: ReorderTodoArgs(id: movedTodo.id, position: position),
238
+ )
239
+ } catch {
240
+ todos = snapshot
241
+ errorMessage = "Reorder failed: \(error.localizedDescription)"
242
+ }
243
+ }
244
+ }
@@ -0,0 +1,30 @@
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
+ TodoListView()
11
+ .environmentObject(session)
12
+ .frame(minWidth: 480, minHeight: 360)
13
+ }
14
+ .windowResizability(.contentMinSize)
15
+ }
16
+ }
17
+
18
+ @MainActor
19
+ final class AppSession: ObservableObject {
20
+ let pylon: PylonClient
21
+
22
+ init() {
23
+ let baseURLString = ProcessInfo.processInfo.environment["PYLON_BASE_URL"]
24
+ ?? "http://localhost:4321"
25
+ guard let url = URL(string: baseURLString) else {
26
+ fatalError("Invalid PYLON_BASE_URL: \(baseURLString)")
27
+ }
28
+ self.pylon = PylonClient(baseURL: url, appName: "__APP_NAME_SNAKE__")
29
+ }
30
+ }
@@ -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,26 @@
1
+ {
2
+ "name": "@__APP_NAME_KEBAB__/ui",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "types": "src/index.ts",
8
+ "exports": {
9
+ ".": "./src/index.ts",
10
+ "./button": "./src/button.tsx",
11
+ "./input": "./src/input.tsx",
12
+ "./card": "./src/card.tsx",
13
+ "./cn": "./src/cn.ts"
14
+ },
15
+ "dependencies": {
16
+ "clsx": "^2.1.0",
17
+ "tailwind-merge": "^2.5.0"
18
+ },
19
+ "peerDependencies": {
20
+ "react": "^19.0.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/react": "^19.0.0",
24
+ "typescript": "^5.5.0"
25
+ }
26
+ }
@@ -0,0 +1,44 @@
1
+ import * as React from "react";
2
+ import { cn } from "./cn";
3
+
4
+ type Variant = "default" | "primary" | "ghost";
5
+ type Size = "sm" | "md";
6
+
7
+ const variants: Record<Variant, string> = {
8
+ default:
9
+ "bg-neutral-100 hover:bg-neutral-200 text-neutral-900 dark:bg-neutral-800 dark:hover:bg-neutral-700 dark:text-neutral-100",
10
+ primary:
11
+ "bg-neutral-900 hover:bg-neutral-800 text-white dark:bg-white dark:hover:bg-neutral-200 dark:text-neutral-900",
12
+ ghost:
13
+ "bg-transparent hover:bg-neutral-100 text-neutral-700 dark:hover:bg-neutral-800 dark:text-neutral-300",
14
+ };
15
+
16
+ const sizes: Record<Size, string> = {
17
+ sm: "h-8 px-3 text-[13px]",
18
+ md: "h-9 px-4 text-sm",
19
+ };
20
+
21
+ export interface ButtonProps
22
+ extends React.ButtonHTMLAttributes<HTMLButtonElement> {
23
+ variant?: Variant;
24
+ size?: Size;
25
+ }
26
+
27
+ export function Button({
28
+ className,
29
+ variant = "default",
30
+ size = "md",
31
+ ...props
32
+ }: ButtonProps) {
33
+ return (
34
+ <button
35
+ className={cn(
36
+ "inline-flex items-center justify-center gap-1.5 rounded-md font-medium transition-colors disabled:opacity-50 disabled:pointer-events-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
37
+ variants[variant],
38
+ sizes[size],
39
+ className,
40
+ )}
41
+ {...props}
42
+ />
43
+ );
44
+ }
@@ -0,0 +1,39 @@
1
+ import * as React from "react";
2
+ import { cn } from "./cn";
3
+
4
+ export function Card({
5
+ className,
6
+ ...props
7
+ }: React.HTMLAttributes<HTMLDivElement>) {
8
+ return (
9
+ <div
10
+ className={cn(
11
+ "rounded-lg border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900",
12
+ className,
13
+ )}
14
+ {...props}
15
+ />
16
+ );
17
+ }
18
+
19
+ export function CardHeader({
20
+ className,
21
+ ...props
22
+ }: React.HTMLAttributes<HTMLDivElement>) {
23
+ return (
24
+ <div
25
+ className={cn(
26
+ "p-5 border-b border-neutral-200 dark:border-neutral-800",
27
+ className,
28
+ )}
29
+ {...props}
30
+ />
31
+ );
32
+ }
33
+
34
+ export function CardContent({
35
+ className,
36
+ ...props
37
+ }: React.HTMLAttributes<HTMLDivElement>) {
38
+ return <div className={cn("p-5", className)} {...props} />;
39
+ }
@@ -0,0 +1,12 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ /**
5
+ * Tailwind-aware class merger. Last-class-wins semantics so a
6
+ * caller's `className` reliably overrides a default in a UI
7
+ * primitive (e.g. <Button className="bg-red-500"> beats the
8
+ * primitive's bg-neutral-900 base).
9
+ */
10
+ export function cn(...inputs: ClassValue[]): string {
11
+ return twMerge(clsx(inputs));
12
+ }
@@ -0,0 +1,4 @@
1
+ export { cn } from "./cn";
2
+ export { Button, type ButtonProps } from "./button";
3
+ export { Input, type InputProps } from "./input";
4
+ export { Card, CardHeader, CardContent } from "./card";
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+ import { cn } from "./cn";
3
+
4
+ export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
5
+
6
+ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
7
+ function Input({ className, ...props }, ref) {
8
+ return (
9
+ <input
10
+ ref={ref}
11
+ className={cn(
12
+ "flex h-9 w-full rounded-md border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-2 text-sm placeholder:text-neutral-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 disabled:opacity-50",
13
+ className,
14
+ )}
15
+ {...props}
16
+ />
17
+ );
18
+ },
19
+ );
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["dom", "esnext"],
5
+ "jsx": "preserve",
6
+ "module": "ESNext",
7
+ "moduleResolution": "Bundler",
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "noEmit": true,
11
+ "esModuleInterop": true,
12
+ "allowSyntheticDefaultImports": true
13
+ },
14
+ "include": ["src/**/*.ts", "src/**/*.tsx"]
15
+ }
@@ -0,0 +1,2 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
@@ -0,0 +1,24 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const PYLON_API_URL = process.env.PYLON_API_URL ?? "http://localhost:4321";
4
+
5
+ const config: NextConfig = {
6
+ transpilePackages: [
7
+ "@__APP_NAME_KEBAB__/ui",
8
+ "@pylonsync/sdk",
9
+ "@pylonsync/react",
10
+ "@pylonsync/next",
11
+ "@pylonsync/functions",
12
+ "@pylonsync/sync",
13
+ ],
14
+ async rewrites() {
15
+ return [
16
+ { source: "/api/fn/:path*", destination: `${PYLON_API_URL}/api/fn/:path*` },
17
+ { source: "/api/auth/:path*", destination: `${PYLON_API_URL}/api/auth/:path*` },
18
+ { source: "/api/sync/:path*", destination: `${PYLON_API_URL}/api/sync/:path*` },
19
+ { source: "/api/:path*", destination: `${PYLON_API_URL}/api/:path*` },
20
+ ];
21
+ },
22
+ };
23
+
24
+ export default config;
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@__APP_NAME_KEBAB__/web",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "next dev --port 3000",
8
+ "build": "next build",
9
+ "start": "next start",
10
+ "lint": "next lint"
11
+ },
12
+ "dependencies": {
13
+ "@__APP_NAME_KEBAB__/ui": "__WORKSPACE_DEP__",
14
+ "@pylonsync/sdk": "^__PYLON_VERSION__",
15
+ "@pylonsync/react": "^__PYLON_VERSION__",
16
+ "@pylonsync/next": "^__PYLON_VERSION__",
17
+ "next": "^16.0.0",
18
+ "react": "^19.0.0",
19
+ "react-dom": "^19.0.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^20.0.0",
23
+ "@types/react": "^19.0.0",
24
+ "@types/react-dom": "^19.0.0",
25
+ "@tailwindcss/postcss": "^4.0.0",
26
+ "tailwindcss": "^4.0.0",
27
+ "typescript": "^5.5.0"
28
+ }
29
+ }
@@ -0,0 +1,3 @@
1
+ export default {
2
+ plugins: { "@tailwindcss/postcss": {} },
3
+ };