@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,287 @@
1
+ import { useEffect, useState } from "react";
2
+ import {
3
+ View,
4
+ Text,
5
+ TextInput,
6
+ Pressable,
7
+ FlatList,
8
+ ActivityIndicator,
9
+ StyleSheet,
10
+ Platform,
11
+ Alert,
12
+ } from "react-native";
13
+ import { StatusBar } from "expo-status-bar";
14
+ import { init, db, callFn } from "@pylonsync/react-native";
15
+
16
+ const PYLON_BASE_URL =
17
+ process.env.EXPO_PUBLIC_PYLON_BASE_URL ??
18
+ (Platform.OS === "android" ? "http://10.0.2.2:4321" : "http://localhost:4321");
19
+
20
+ type Todo = {
21
+ id: string;
22
+ title: string;
23
+ done: boolean;
24
+ createdAt: string;
25
+ position?: number;
26
+ };
27
+
28
+ let initPromise: Promise<void> | null = null;
29
+ function ensureInit() {
30
+ if (!initPromise) {
31
+ initPromise = init({
32
+ baseUrl: PYLON_BASE_URL,
33
+ appName: "__APP_NAME_SNAKE__",
34
+ });
35
+ }
36
+ return initPromise;
37
+ }
38
+
39
+ export default function App() {
40
+ const [ready, setReady] = useState(false);
41
+ useEffect(() => {
42
+ ensureInit().then(() => setReady(true));
43
+ }, []);
44
+
45
+ if (!ready) {
46
+ return (
47
+ <View style={[styles.screen, styles.center]}>
48
+ <ActivityIndicator />
49
+ </View>
50
+ );
51
+ }
52
+ return <TodoApp />;
53
+ }
54
+
55
+ function TodoApp() {
56
+ const { data: todos, loading } = db.useQuery<Todo>("Todo", {});
57
+ const [draft, setDraft] = useState("");
58
+ const [busy, setBusy] = useState(false);
59
+ const [editingId, setEditingId] = useState<string | null>(null);
60
+ const [editingDraft, setEditingDraft] = useState("");
61
+
62
+ async function add() {
63
+ const trimmed = draft.trim();
64
+ if (!trimmed) return;
65
+ setBusy(true);
66
+ try {
67
+ await callFn("addTodo", { title: trimmed });
68
+ setDraft("");
69
+ } catch (e) {
70
+ Alert.alert("Add failed", String(e));
71
+ } finally {
72
+ setBusy(false);
73
+ }
74
+ }
75
+
76
+ async function toggle(t: Todo) {
77
+ try {
78
+ await callFn("toggleTodo", { id: t.id, done: !t.done });
79
+ } catch (e) {
80
+ Alert.alert("Toggle failed", String(e));
81
+ }
82
+ }
83
+
84
+ async function remove(t: Todo) {
85
+ try {
86
+ await callFn("deleteTodo", { id: t.id });
87
+ } catch (e) {
88
+ Alert.alert("Delete failed", String(e));
89
+ }
90
+ }
91
+
92
+ async function commitEdit(t: Todo) {
93
+ const trimmed = editingDraft.trim();
94
+ if (!trimmed || trimmed === t.title) {
95
+ setEditingId(null);
96
+ setEditingDraft("");
97
+ return;
98
+ }
99
+ try {
100
+ await callFn("editTodo", { id: t.id, title: trimmed });
101
+ } catch (e) {
102
+ Alert.alert("Rename failed", String(e));
103
+ } finally {
104
+ setEditingId(null);
105
+ setEditingDraft("");
106
+ }
107
+ }
108
+
109
+ const sorted = (todos ?? [])
110
+ .slice()
111
+ .sort((a, b) => {
112
+ const ap = typeof a.position === "number" ? a.position : Date.parse(a.createdAt) || 0;
113
+ const bp = typeof b.position === "number" ? b.position : Date.parse(b.createdAt) || 0;
114
+ return ap - bp;
115
+ });
116
+
117
+ return (
118
+ <View style={styles.screen}>
119
+ <StatusBar style="auto" />
120
+ <Text style={styles.title}>__APP_NAME__</Text>
121
+ <Text style={styles.subtitle}>
122
+ Tap to toggle. Long-press to edit. Swipe-style buttons for delete.
123
+ </Text>
124
+
125
+ <View style={styles.row}>
126
+ <TextInput
127
+ style={styles.input}
128
+ placeholder="What needs doing?"
129
+ value={draft}
130
+ onChangeText={setDraft}
131
+ onSubmitEditing={add}
132
+ editable={!busy}
133
+ autoCorrect={false}
134
+ />
135
+ <Pressable
136
+ onPress={add}
137
+ disabled={busy || !draft.trim()}
138
+ style={({ pressed }) => [
139
+ styles.button,
140
+ (busy || !draft.trim()) && styles.buttonDisabled,
141
+ pressed && styles.buttonPressed,
142
+ ]}
143
+ >
144
+ <Text style={styles.buttonLabel}>Add</Text>
145
+ </Pressable>
146
+ </View>
147
+
148
+ {loading ? (
149
+ <ActivityIndicator style={{ marginTop: 24 }} />
150
+ ) : sorted.length === 0 ? (
151
+ <Text style={styles.empty}>No todos yet.</Text>
152
+ ) : (
153
+ <FlatList
154
+ data={sorted}
155
+ keyExtractor={(t) => t.id}
156
+ contentContainerStyle={{ paddingTop: 16 }}
157
+ ItemSeparatorComponent={() => <View style={styles.separator} />}
158
+ renderItem={({ item }) => (
159
+ <Row
160
+ todo={item}
161
+ editing={editingId === item.id}
162
+ editingDraft={editingDraft}
163
+ onEditingDraftChange={setEditingDraft}
164
+ onStartEdit={() => {
165
+ setEditingId(item.id);
166
+ setEditingDraft(item.title);
167
+ }}
168
+ onCommitEdit={() => commitEdit(item)}
169
+ onCancelEdit={() => {
170
+ setEditingId(null);
171
+ setEditingDraft("");
172
+ }}
173
+ onToggle={() => toggle(item)}
174
+ onDelete={() => remove(item)}
175
+ />
176
+ )}
177
+ />
178
+ )}
179
+ </View>
180
+ );
181
+ }
182
+
183
+ function Row({
184
+ todo,
185
+ editing,
186
+ editingDraft,
187
+ onEditingDraftChange,
188
+ onStartEdit,
189
+ onCommitEdit,
190
+ onCancelEdit,
191
+ onToggle,
192
+ onDelete,
193
+ }: {
194
+ todo: Todo;
195
+ editing: boolean;
196
+ editingDraft: string;
197
+ onEditingDraftChange: (v: string) => void;
198
+ onStartEdit: () => void;
199
+ onCommitEdit: () => void;
200
+ onCancelEdit: () => void;
201
+ onToggle: () => void;
202
+ onDelete: () => void;
203
+ }) {
204
+ return (
205
+ <View style={styles.item}>
206
+ <Pressable onPress={onToggle} hitSlop={8}>
207
+ <Text style={[styles.checkbox, todo.done && styles.checkboxDone]}>
208
+ {todo.done ? "☑" : "☐"}
209
+ </Text>
210
+ </Pressable>
211
+ {editing ? (
212
+ <TextInput
213
+ style={[styles.itemText, styles.itemEditInput]}
214
+ value={editingDraft}
215
+ onChangeText={onEditingDraftChange}
216
+ onSubmitEditing={onCommitEdit}
217
+ onBlur={onCommitEdit}
218
+ autoFocus
219
+ blurOnSubmit
220
+ />
221
+ ) : (
222
+ <Pressable onLongPress={onStartEdit} style={{ flex: 1 }}>
223
+ <Text
224
+ style={[styles.itemText, todo.done && styles.itemTextDone]}
225
+ numberOfLines={1}
226
+ >
227
+ {todo.title}
228
+ </Text>
229
+ </Pressable>
230
+ )}
231
+ {editing ? (
232
+ <Pressable onPress={onCancelEdit} hitSlop={8}>
233
+ <Text style={styles.actionLabel}>Cancel</Text>
234
+ </Pressable>
235
+ ) : (
236
+ <Pressable onPress={onDelete} hitSlop={8}>
237
+ <Text style={[styles.actionLabel, styles.actionDelete]}>Delete</Text>
238
+ </Pressable>
239
+ )}
240
+ </View>
241
+ );
242
+ }
243
+
244
+ const styles = StyleSheet.create({
245
+ screen: { flex: 1, paddingTop: 64, paddingHorizontal: 20, backgroundColor: "#fff" },
246
+ center: { alignItems: "center", justifyContent: "center" },
247
+ title: { fontSize: 28, fontWeight: "600" },
248
+ subtitle: { color: "#666", marginTop: 4, marginBottom: 20 },
249
+ row: { flexDirection: "row", gap: 8 },
250
+ input: {
251
+ flex: 1,
252
+ borderWidth: 1,
253
+ borderColor: "#d4d4d8",
254
+ borderRadius: 6,
255
+ paddingHorizontal: 12,
256
+ paddingVertical: 8,
257
+ fontSize: 14,
258
+ },
259
+ button: {
260
+ backgroundColor: "#171717",
261
+ borderRadius: 6,
262
+ paddingHorizontal: 16,
263
+ justifyContent: "center",
264
+ },
265
+ buttonDisabled: { opacity: 0.5 },
266
+ buttonPressed: { opacity: 0.8 },
267
+ buttonLabel: { color: "#fff", fontWeight: "600" },
268
+ empty: { textAlign: "center", color: "#999", marginTop: 32 },
269
+ item: {
270
+ flexDirection: "row",
271
+ alignItems: "center",
272
+ gap: 12,
273
+ paddingVertical: 12,
274
+ },
275
+ checkbox: { fontSize: 22, color: "#9ca3af" },
276
+ checkboxDone: { color: "#16a34a" },
277
+ itemText: { flex: 1, fontSize: 15 },
278
+ itemTextDone: { color: "#9ca3af", textDecorationLine: "line-through" },
279
+ itemEditInput: {
280
+ borderBottomWidth: 1,
281
+ borderColor: "#d4d4d8",
282
+ paddingVertical: 4,
283
+ },
284
+ actionLabel: { fontSize: 13, color: "#6b7280" },
285
+ actionDelete: { color: "#ef4444" },
286
+ separator: { height: 1, backgroundColor: "#e5e5e5" },
287
+ });
@@ -0,0 +1,25 @@
1
+ {
2
+ "expo": {
3
+ "name": "__APP_NAME__",
4
+ "slug": "__APP_NAME_KEBAB__",
5
+ "version": "0.0.1",
6
+ "orientation": "portrait",
7
+ "userInterfaceStyle": "automatic",
8
+ "ios": {
9
+ "supportsTablet": true,
10
+ "bundleIdentifier": "com.example.__APP_NAME_SNAKE__",
11
+ "infoPlist": {
12
+ "NSAppTransportSecurity": {
13
+ "NSAllowsLocalNetworking": true
14
+ }
15
+ }
16
+ },
17
+ "android": {
18
+ "package": "com.example.__APP_NAME_SNAKE__",
19
+ "usesCleartextTraffic": true
20
+ },
21
+ "web": {
22
+ "bundler": "metro"
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,6 @@
1
+ module.exports = function (api) {
2
+ api.cache(true);
3
+ return {
4
+ presets: ["babel-preset-expo"],
5
+ };
6
+ };
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@__APP_NAME_KEBAB__/expo",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "main": "node_modules/expo/AppEntry.js",
6
+ "scripts": {
7
+ "start": "expo start",
8
+ "android": "expo start --android",
9
+ "ios": "expo start --ios",
10
+ "web": "expo start --web"
11
+ },
12
+ "dependencies": {
13
+ "@pylonsync/sdk": "^__PYLON_VERSION__",
14
+ "@pylonsync/react": "^__PYLON_VERSION__",
15
+ "@pylonsync/react-native": "^__PYLON_VERSION__",
16
+ "@pylonsync/sync": "^__PYLON_VERSION__",
17
+ "@react-native-async-storage/async-storage": "1.23.1",
18
+ "@react-native-community/netinfo": "11.3.1",
19
+ "expo": "~51.0.0",
20
+ "expo-status-bar": "~1.12.1",
21
+ "react": "19.0.0",
22
+ "react-dom": "19.0.0",
23
+ "react-native": "0.74.5"
24
+ },
25
+ "devDependencies": {
26
+ "@babel/core": "^7.20.0",
27
+ "@types/react": "^19.0.0",
28
+ "typescript": "^5.5.0"
29
+ }
30
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "esnext",
4
+ "module": "esnext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["esnext", "dom"],
7
+ "jsx": "react-native",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "isolatedModules": true,
12
+ "resolveJsonModule": true,
13
+ "noEmit": true
14
+ },
15
+ "include": ["App.tsx", "src/**/*.ts", "src/**/*.tsx"]
16
+ }
@@ -0,0 +1,34 @@
1
+ // swift-tools-version:5.9
2
+ import PackageDescription
3
+
4
+ // SwiftPM package for the __APP_NAME__ mobile app.
5
+ //
6
+ // The executable target runs on macOS via `swift run`. For an iOS
7
+ // build, generate an Xcode project from `project.yml`:
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 iOS + macOS share one source set.
15
+ let package = Package(
16
+ name: "__APP_NAME_PASCAL__",
17
+ platforms: [
18
+ .iOS(.v16),
19
+ .macOS(.v13),
20
+ ],
21
+ dependencies: [
22
+ .package(url: "https://github.com/pylonsync/pylon.git", from: "0.3.0"),
23
+ ],
24
+ targets: [
25
+ .executableTarget(
26
+ name: "__APP_NAME_PASCAL__",
27
+ dependencies: [
28
+ .product(name: "PylonClient", package: "pylon"),
29
+ .product(name: "PylonSwiftUI", package: "pylon"),
30
+ ],
31
+ path: "Sources/__APP_NAME_PASCAL__"
32
+ ),
33
+ ]
34
+ )
@@ -0,0 +1,98 @@
1
+ import SwiftUI
2
+ import PylonClient
3
+
4
+ /// Lists every Widget and lets the user create a new one. Uses
5
+ /// PylonClient's HTTP API directly — no offline mirror, no realtime
6
+ /// subscription. Plenty for a barebones starter; upgrade to
7
+ /// `PylonQuery` from `PylonSwiftUI` when you need live updates.
8
+ struct ContentView: View {
9
+ @EnvironmentObject var session: AppSession
10
+
11
+ @State private var widgets: [Widget] = []
12
+ @State private var newName: String = ""
13
+ @State private var loading = true
14
+ @State private var creating = false
15
+ @State private var errorMessage: String?
16
+
17
+ var body: some View {
18
+ NavigationStack {
19
+ List {
20
+ Section("Create") {
21
+ HStack {
22
+ TextField("Name a widget…", text: $newName)
23
+ .textFieldStyle(.roundedBorder)
24
+ .autocorrectionDisabled()
25
+ Button("Add") { Task { await create() } }
26
+ .buttonStyle(.borderedProminent)
27
+ .disabled(newName.trimmingCharacters(in: .whitespaces).isEmpty || creating)
28
+ }
29
+ }
30
+
31
+ Section("Widgets") {
32
+ if loading {
33
+ ProgressView()
34
+ } else if widgets.isEmpty {
35
+ Text("No widgets yet.")
36
+ .foregroundStyle(.secondary)
37
+ } else {
38
+ ForEach(widgets) { w in
39
+ HStack {
40
+ Text(w.name).font(.body)
41
+ Spacer()
42
+ Text("count: \(w.count)")
43
+ .font(.system(.caption, design: .monospaced))
44
+ .foregroundStyle(.secondary)
45
+ }
46
+ }
47
+ }
48
+ }
49
+
50
+ if let errorMessage {
51
+ Section {
52
+ Text(errorMessage)
53
+ .foregroundStyle(.red)
54
+ .font(.caption)
55
+ }
56
+ }
57
+ }
58
+ .navigationTitle("__APP_NAME__")
59
+ .task { await load() }
60
+ .refreshable { await load() }
61
+ }
62
+ }
63
+
64
+ private func load() async {
65
+ loading = true
66
+ defer { loading = false }
67
+ do {
68
+ let rows: [Widget] = try await session.pylon.callFn(
69
+ "listWidgets",
70
+ args: EmptyArgs(),
71
+ )
72
+ widgets = rows
73
+ errorMessage = nil
74
+ } catch {
75
+ errorMessage = "Load failed: \(error.localizedDescription)"
76
+ }
77
+ }
78
+
79
+ private func create() async {
80
+ let trimmed = newName.trimmingCharacters(in: .whitespaces)
81
+ guard !trimmed.isEmpty else { return }
82
+ creating = true
83
+ defer { creating = false }
84
+ do {
85
+ let widget: Widget = try await session.pylon.callFn(
86
+ "createWidget",
87
+ args: CreateWidgetArgs(name: trimmed),
88
+ )
89
+ widgets.insert(widget, at: 0)
90
+ newName = ""
91
+ errorMessage = nil
92
+ } catch {
93
+ errorMessage = "Create failed: \(error.localizedDescription)"
94
+ }
95
+ }
96
+ }
97
+
98
+ private struct EmptyArgs: Encodable {}
@@ -0,0 +1,17 @@
1
+ import Foundation
2
+
3
+ /// Mirrors `Widget` from `apps/api/schema.ts`. Kept hand-written here
4
+ /// for clarity in the scaffold; for production, run
5
+ /// `pylon codegen client schema.ts --target swift --out Models.swift`
6
+ /// from `apps/api/` to regenerate this file from the schema.
7
+ struct Widget: Codable, Identifiable, Hashable {
8
+ let id: String
9
+ let name: String
10
+ let count: Int
11
+ let createdAt: String
12
+ }
13
+
14
+ /// Argument shape for `createWidget`.
15
+ struct CreateWidgetArgs: Encodable {
16
+ let name: String
17
+ }
@@ -0,0 +1,34 @@
1
+ import SwiftUI
2
+ import PylonClient
3
+
4
+ /// SwiftUI entry point. Bootstraps a single shared PylonClient pointed
5
+ /// at the local Pylon control plane (`pylon dev`'s default port). For
6
+ /// production, override `PYLON_BASE_URL` via the build environment or
7
+ /// edit the URL inline.
8
+ @main
9
+ struct __APP_NAME_PASCAL__App: App {
10
+ @StateObject private var session = AppSession()
11
+
12
+ var body: some Scene {
13
+ WindowGroup {
14
+ ContentView()
15
+ .environmentObject(session)
16
+ }
17
+ }
18
+ }
19
+
20
+ /// Shared app state. The PylonClient is held here so every view in
21
+ /// the tree gets the same instance via `@EnvironmentObject`.
22
+ @MainActor
23
+ final class AppSession: ObservableObject {
24
+ let pylon: PylonClient
25
+
26
+ init() {
27
+ let baseURLString = ProcessInfo.processInfo.environment["PYLON_BASE_URL"]
28
+ ?? "http://localhost:4321"
29
+ guard let url = URL(string: baseURLString) else {
30
+ fatalError("Invalid PYLON_BASE_URL: \(baseURLString)")
31
+ }
32
+ self.pylon = PylonClient(baseURL: url, appName: "__APP_NAME_SNAKE__")
33
+ }
34
+ }
@@ -0,0 +1,42 @@
1
+ # xcodegen spec for the iOS build of __APP_NAME__.
2
+ #
3
+ # Materialize the Xcode project once with:
4
+ # brew install xcodegen
5
+ # xcodegen generate
6
+ # Then open __APP_NAME_PASCAL__.xcodeproj.
7
+
8
+ name: __APP_NAME_PASCAL__
9
+ options:
10
+ bundleIdPrefix: com.example
11
+ deploymentTarget:
12
+ iOS: "16.0"
13
+
14
+ packages:
15
+ pylon:
16
+ url: https://github.com/pylonsync/pylon.git
17
+ from: "0.3.0"
18
+
19
+ targets:
20
+ __APP_NAME_PASCAL__:
21
+ type: application
22
+ platform: iOS
23
+ sources:
24
+ - path: Sources/__APP_NAME_PASCAL__
25
+ settings:
26
+ base:
27
+ GENERATE_INFOPLIST_FILE: YES
28
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation: YES
29
+ INFOPLIST_KEY_UILaunchScreen_Generation: YES
30
+ INFOPLIST_KEY_UISupportedInterfaceOrientations: "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"
31
+ TARGETED_DEVICE_FAMILY: "1,2"
32
+ SWIFT_VERSION: "5.9"
33
+ ENABLE_PREVIEWS: YES
34
+ # __APP_NAME__ talks to a Pylon backend on localhost during dev.
35
+ # The simulator can reach localhost directly; on a physical device
36
+ # set PYLON_BASE_URL to your machine's LAN IP.
37
+ INFOPLIST_KEY_NSAppTransportSecurity: "{NSAllowsLocalNetworking = YES;}"
38
+ dependencies:
39
+ - package: pylon
40
+ product: PylonClient
41
+ - package: pylon
42
+ product: PylonSwiftUI
@@ -0,0 +1,34 @@
1
+ // swift-tools-version:5.9
2
+ import PackageDescription
3
+
4
+ // SwiftPM package for the __APP_NAME__ mobile app.
5
+ //
6
+ // The executable target runs on macOS via `swift run`. For an iOS
7
+ // build, generate an Xcode project from `project.yml`:
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 iOS + macOS share one source set.
15
+ let package = Package(
16
+ name: "__APP_NAME_PASCAL__",
17
+ platforms: [
18
+ .iOS(.v16),
19
+ .macOS(.v13),
20
+ ],
21
+ dependencies: [
22
+ .package(url: "https://github.com/pylonsync/pylon.git", from: "0.3.0"),
23
+ ],
24
+ targets: [
25
+ .executableTarget(
26
+ name: "__APP_NAME_PASCAL__",
27
+ dependencies: [
28
+ .product(name: "PylonClient", package: "pylon"),
29
+ .product(name: "PylonSwiftUI", package: "pylon"),
30
+ ],
31
+ path: "Sources/__APP_NAME_PASCAL__"
32
+ ),
33
+ ]
34
+ )