@pylonsync/create-pylon 0.3.54 → 0.3.55

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 (35) hide show
  1. package/package.json +1 -1
  2. package/templates/expo/chat/apps/expo/App.tsx +126 -156
  3. package/templates/expo/consumer/apps/expo/App.tsx +211 -179
  4. package/templates/ios/chat/apps/ios/Package.swift +1 -11
  5. package/templates/ios/chat/apps/ios/Sources/__APP_NAME_PASCAL__/ChatRootView.swift +26 -30
  6. package/templates/ios/chat/apps/ios/Sources/__APP_NAME_PASCAL__/RoomView.swift +35 -36
  7. package/templates/ios/chat/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +27 -4
  8. package/templates/ios/chat/apps/ios/project.yml +2 -0
  9. package/templates/ios/consumer/apps/ios/Package.swift +1 -0
  10. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +81 -52
  11. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +6 -14
  12. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +14 -6
  13. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -15
  14. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +33 -5
  15. package/templates/ios/consumer/apps/ios/project.yml +2 -0
  16. package/templates/mac/chat/apps/mac/Package.swift +1 -0
  17. package/templates/mac/chat/apps/mac/Sources/__APP_NAME_PASCAL__/ChatRootView.swift +30 -27
  18. package/templates/mac/chat/apps/mac/Sources/__APP_NAME_PASCAL__/RoomView.swift +35 -36
  19. package/templates/mac/chat/apps/mac/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +26 -5
  20. package/templates/mac/chat/apps/mac/project.yml +2 -0
  21. package/templates/mac/consumer/apps/mac/Package.swift +1 -0
  22. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/FeedView.swift +81 -52
  23. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/Models.swift +6 -14
  24. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +14 -6
  25. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -15
  26. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +34 -6
  27. package/templates/mac/consumer/apps/mac/project.yml +2 -0
  28. package/templates/web/chat/apps/web/src/app/components/ChatRoom.tsx +52 -71
  29. package/templates/web/chat/apps/web/src/app/layout.tsx +3 -2
  30. package/templates/web/chat/apps/web/src/app/page.tsx +5 -44
  31. package/templates/web/chat/apps/web/src/app/providers.tsx +25 -0
  32. package/templates/web/consumer/apps/web/src/app/components/Feed.tsx +139 -119
  33. package/templates/web/consumer/apps/web/src/app/layout.tsx +3 -2
  34. package/templates/web/consumer/apps/web/src/app/page.tsx +8 -42
  35. package/templates/web/consumer/apps/web/src/app/providers.tsx +25 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pylonsync/create-pylon",
3
- "version": "0.3.54",
3
+ "version": "0.3.55",
4
4
  "description": "Scaffold a new Pylon app — realtime backend + web/mobile/expo frontends in one command. Run via `npm create @pylonsync/pylon@latest`.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -13,7 +13,7 @@ import {
13
13
  SafeAreaView,
14
14
  } from "react-native";
15
15
  import { StatusBar } from "expo-status-bar";
16
- import { init, callFn } from "@pylonsync/react-native";
16
+ import { init, db, callFn } from "@pylonsync/react-native";
17
17
 
18
18
  const PYLON_BASE_URL =
19
19
  process.env.EXPO_PUBLIC_PYLON_BASE_URL ??
@@ -48,23 +48,9 @@ function ensureInit() {
48
48
 
49
49
  export default function App() {
50
50
  const [ready, setReady] = useState(false);
51
- const [rooms, setRooms] = useState<Room[]>([]);
52
- const [active, setActive] = useState<Room | null>(null);
53
- const [authorName, setAuthorName] = useState("anonymous");
54
-
55
51
  useEffect(() => {
56
- ensureInit().then(async () => {
57
- try {
58
- const list = await callFn<Room[]>("listRooms", {});
59
- setRooms(list);
60
- setActive(list[0] ?? null);
61
- } catch (e) {
62
- Alert.alert("Load failed", String(e));
63
- }
64
- setReady(true);
65
- });
52
+ ensureInit().then(() => setReady(true));
66
53
  }, []);
67
-
68
54
  if (!ready) {
69
55
  return (
70
56
  <View style={[styles.screen, styles.center]}>
@@ -72,159 +58,61 @@ export default function App() {
72
58
  </View>
73
59
  );
74
60
  }
75
-
76
- if (!active) {
77
- return (
78
- <RoomCreate
79
- authorName={authorName}
80
- onAuthorNameChange={setAuthorName}
81
- onCreated={(r) => {
82
- setRooms((prev) => [...prev, r]);
83
- setActive(r);
84
- }}
85
- />
86
- );
87
- }
88
-
89
- return (
90
- <RoomView
91
- room={active}
92
- rooms={rooms}
93
- onSwitch={setActive}
94
- authorName={authorName}
95
- onAuthorNameChange={setAuthorName}
96
- onCreate={(r) => {
97
- setRooms((prev) => [...prev, r]);
98
- setActive(r);
99
- }}
100
- />
101
- );
61
+ return <Chat />;
102
62
  }
103
63
 
104
- function RoomCreate({
105
- authorName,
106
- onAuthorNameChange,
107
- onCreated,
108
- }: {
109
- authorName: string;
110
- onAuthorNameChange: (s: string) => void;
111
- onCreated: (r: Room) => void;
112
- }) {
113
- const [name, setName] = useState("General");
114
- const [slug, setSlug] = useState("general");
115
- const [creating, setCreating] = useState(false);
64
+ function Chat() {
65
+ // Live subscriptions — sync engine pushes diffs over WebSocket.
66
+ // New rooms / new messages from any other device or device update
67
+ // re-render this component without polling.
68
+ const { data: rooms = [] } = db.useQuery<Room>("Room", {
69
+ orderBy: { createdAt: "asc" },
70
+ });
71
+ const [activeId, setActiveId] = useState<string | null>(null);
72
+ useEffect(() => {
73
+ if (!activeId && rooms.length > 0) setActiveId(rooms[0].id);
74
+ }, [rooms, activeId]);
116
75
 
117
- async function create() {
118
- setCreating(true);
119
- try {
120
- const r = await callFn<Room>("createRoom", {
121
- slug: slug.toLowerCase(),
122
- name,
123
- });
124
- onCreated(r);
125
- } catch (e) {
126
- Alert.alert("Create failed", String(e));
127
- } finally {
128
- setCreating(false);
129
- }
130
- }
76
+ const active = rooms.find((r) => r.id === activeId) ?? null;
131
77
 
132
- return (
133
- <SafeAreaView style={styles.screen}>
134
- <StatusBar style="auto" />
135
- <View style={styles.content}>
136
- <Text style={styles.title}>__APP_NAME__</Text>
137
- <Text style={styles.subtitle}>No rooms yet — create one.</Text>
138
- <TextInput
139
- style={styles.input}
140
- placeholder="Your display name"
141
- value={authorName}
142
- onChangeText={onAuthorNameChange}
143
- />
144
- <TextInput
145
- style={styles.input}
146
- placeholder="Room name"
147
- value={name}
148
- onChangeText={setName}
149
- />
150
- <TextInput
151
- style={styles.input}
152
- placeholder="Slug"
153
- value={slug}
154
- onChangeText={(s) => setSlug(s.toLowerCase())}
155
- autoCapitalize="none"
156
- autoCorrect={false}
157
- />
158
- <Pressable
159
- onPress={create}
160
- disabled={creating || !name.trim() || !slug.trim()}
161
- style={({ pressed }) => [
162
- styles.button,
163
- (creating || !name.trim() || !slug.trim()) && styles.buttonDisabled,
164
- pressed && styles.buttonPressed,
165
- ]}
166
- >
167
- <Text style={styles.buttonLabel}>
168
- {creating ? "Creating…" : "Create room"}
169
- </Text>
170
- </Pressable>
171
- </View>
172
- </SafeAreaView>
173
- );
174
- }
78
+ const { data: messages = [] } = db.useQuery<Message>("Message", {
79
+ where: activeId ? { roomId: activeId } : undefined,
80
+ orderBy: { createdAt: "asc" },
81
+ limit: 200,
82
+ });
175
83
 
176
- function RoomView({
177
- room,
178
- rooms,
179
- onSwitch,
180
- authorName,
181
- onAuthorNameChange,
182
- onCreate,
183
- }: {
184
- room: Room;
185
- rooms: Room[];
186
- onSwitch: (r: Room) => void;
187
- authorName: string;
188
- onAuthorNameChange: (s: string) => void;
189
- onCreate: (r: Room) => void;
190
- }) {
191
- const [messages, setMessages] = useState<Message[]>([]);
192
84
  const [draft, setDraft] = useState("");
193
85
  const [sending, setSending] = useState(false);
86
+ const [authorName, setAuthorName] = useState("anonymous");
194
87
  const listRef = useRef<FlatList<Message>>(null);
195
88
 
196
- useEffect(() => {
197
- void load();
198
- const t = setInterval(load, 1500);
199
- return () => clearInterval(t);
200
- async function load() {
201
- try {
202
- const m = await callFn<Message[]>("roomMessages", { roomId: room.id });
203
- setMessages(m);
204
- } catch {
205
- // ignore — will retry on next tick
206
- }
207
- }
208
- }, [room.id]);
209
-
210
89
  useEffect(() => {
211
90
  if (messages.length > 0) {
212
91
  listRef.current?.scrollToEnd({ animated: true });
213
92
  }
214
- }, [messages.length]);
93
+ }, [messages.length, activeId]);
94
+
95
+ if (!active) {
96
+ return (
97
+ <RoomCreate
98
+ authorName={authorName}
99
+ onAuthorNameChange={setAuthorName}
100
+ onCreated={(r) => setActiveId(r.id)}
101
+ />
102
+ );
103
+ }
215
104
 
216
105
  async function send() {
217
106
  const body = draft.trim();
218
107
  if (!body) return;
219
- setSending(true);
220
108
  setDraft("");
109
+ setSending(true);
221
110
  try {
222
- const msg = await callFn<Message>("sendMessage", {
223
- roomId: room.id,
111
+ await callFn("sendMessage", {
112
+ roomId: active.id,
224
113
  body,
225
114
  authorName,
226
115
  });
227
- setMessages((prev) => [...prev, msg]);
228
116
  } catch (e) {
229
117
  setDraft(body);
230
118
  Alert.alert("Send failed", String(e));
@@ -241,8 +129,8 @@ function RoomView({
241
129
  .replace(/[^a-z0-9]+/g, "-")
242
130
  .replace(/^-|-$/g, "");
243
131
  try {
244
- const r = await callFn<Room>("createRoom", { slug, name });
245
- onCreate(r);
132
+ const r = (await callFn("createRoom", { slug, name })) as Room;
133
+ setActiveId(r.id);
246
134
  } catch (e) {
247
135
  Alert.alert("Create failed", String(e));
248
136
  }
@@ -258,15 +146,14 @@ function RoomView({
258
146
  >
259
147
  <View style={styles.header}>
260
148
  <View>
261
- <Text style={styles.title}>{room.name}</Text>
262
- <Text style={styles.handle}>#{room.slug}</Text>
149
+ <Text style={styles.title}>{active.name}</Text>
150
+ <Text style={styles.handle}>#{active.slug}</Text>
263
151
  </View>
264
152
  {rooms.length > 1 && (
265
153
  <Pressable
266
154
  onPress={() => {
267
- // Cycle to next room simplest "switch room" UX without a sidebar.
268
- const idx = rooms.findIndex((r) => r.id === room.id);
269
- onSwitch(rooms[(idx + 1) % rooms.length]);
155
+ const idx = rooms.findIndex((r) => r.id === active.id);
156
+ setActiveId(rooms[(idx + 1) % rooms.length].id);
270
157
  }}
271
158
  >
272
159
  <Text style={styles.switchBtn}>Next room →</Text>
@@ -279,7 +166,7 @@ function RoomView({
279
166
  <TextInput
280
167
  style={styles.nameinput}
281
168
  value={authorName}
282
- onChangeText={onAuthorNameChange}
169
+ onChangeText={setAuthorName}
283
170
  autoCapitalize="none"
284
171
  />
285
172
  </View>
@@ -313,7 +200,7 @@ function RoomView({
313
200
  style={styles.composerInput}
314
201
  value={draft}
315
202
  onChangeText={setDraft}
316
- placeholder={`Message ${room.name}…`}
203
+ placeholder={`Message ${active.name}…`}
317
204
  multiline
318
205
  editable={!sending}
319
206
  />
@@ -329,11 +216,87 @@ function RoomView({
329
216
  <Text style={styles.buttonLabel}>Send</Text>
330
217
  </Pressable>
331
218
  </View>
219
+
220
+ <Pressable onPress={createRoom} style={styles.newRoomBar}>
221
+ <Text style={styles.newRoomLabel}>+ New room</Text>
222
+ </Pressable>
332
223
  </KeyboardAvoidingView>
333
224
  </SafeAreaView>
334
225
  );
335
226
  }
336
227
 
228
+ function RoomCreate({
229
+ authorName,
230
+ onAuthorNameChange,
231
+ onCreated,
232
+ }: {
233
+ authorName: string;
234
+ onAuthorNameChange: (s: string) => void;
235
+ onCreated: (r: Room) => void;
236
+ }) {
237
+ const [name, setName] = useState("General");
238
+ const [slug, setSlug] = useState("general");
239
+ const [creating, setCreating] = useState(false);
240
+
241
+ async function create() {
242
+ setCreating(true);
243
+ try {
244
+ const r = (await callFn("createRoom", {
245
+ slug: slug.toLowerCase(),
246
+ name,
247
+ })) as Room;
248
+ onCreated(r);
249
+ } catch (e) {
250
+ Alert.alert("Create failed", String(e));
251
+ } finally {
252
+ setCreating(false);
253
+ }
254
+ }
255
+
256
+ return (
257
+ <SafeAreaView style={styles.screen}>
258
+ <StatusBar style="auto" />
259
+ <View style={styles.content}>
260
+ <Text style={styles.title}>__APP_NAME__</Text>
261
+ <Text style={styles.subtitle}>No rooms yet — create one.</Text>
262
+ <TextInput
263
+ style={styles.input}
264
+ placeholder="Your display name"
265
+ value={authorName}
266
+ onChangeText={onAuthorNameChange}
267
+ />
268
+ <TextInput
269
+ style={styles.input}
270
+ placeholder="Room name"
271
+ value={name}
272
+ onChangeText={setName}
273
+ />
274
+ <TextInput
275
+ style={styles.input}
276
+ placeholder="Slug"
277
+ value={slug}
278
+ onChangeText={(s) => setSlug(s.toLowerCase())}
279
+ autoCapitalize="none"
280
+ autoCorrect={false}
281
+ />
282
+ <Pressable
283
+ onPress={create}
284
+ disabled={creating || !name.trim() || !slug.trim()}
285
+ style={({ pressed }) => [
286
+ styles.button,
287
+ (creating || !name.trim() || !slug.trim()) && styles.buttonDisabled,
288
+ pressed && styles.buttonPressed,
289
+ ]}
290
+ >
291
+ <Text style={styles.buttonLabel}>
292
+ {creating ? "Creating…" : "Create room"}
293
+ </Text>
294
+ </Pressable>
295
+ </View>
296
+ </SafeAreaView>
297
+ );
298
+ }
299
+
337
300
  const styles = StyleSheet.create({
338
301
  screen: { flex: 1, backgroundColor: "#fff" },
339
302
  flex: { flex: 1 },
@@ -411,4 +374,11 @@ const styles = StyleSheet.create({
411
374
  paddingVertical: 8,
412
375
  fontSize: 14,
413
376
  },
377
+ newRoomBar: {
378
+ paddingVertical: 10,
379
+ alignItems: "center",
380
+ borderTopWidth: 1,
381
+ borderColor: "#e5e5e5",
382
+ },
383
+ newRoomLabel: { color: "#3b82f6", fontSize: 13 },
414
384
  });