@pylonsync/create-pylon 0.3.53 → 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 (127) hide show
  1. package/bin/create-pylon.js +98 -42
  2. package/package.json +1 -1
  3. package/templates/backend/b2b/apps/api/functions/archiveProject.ts +15 -0
  4. package/templates/backend/b2b/apps/api/functions/createOrg.ts +43 -0
  5. package/templates/backend/b2b/apps/api/functions/createProject.ts +25 -0
  6. package/templates/backend/b2b/apps/api/functions/inviteMember.ts +49 -0
  7. package/templates/backend/b2b/apps/api/functions/myOrgs.ts +37 -0
  8. package/templates/backend/b2b/apps/api/functions/orgMembers.ts +13 -0
  9. package/templates/backend/b2b/apps/api/functions/orgProjects.ts +18 -0
  10. package/templates/backend/b2b/apps/api/functions/removeMember.ts +29 -0
  11. package/templates/backend/b2b/apps/api/functions/setMemberRole.ts +38 -0
  12. package/templates/backend/b2b/apps/api/package.json +20 -0
  13. package/templates/backend/b2b/apps/api/schema.ts +171 -0
  14. package/templates/backend/b2b/apps/api/tsconfig.json +13 -0
  15. package/templates/backend/chat/apps/api/functions/createRoom.ts +32 -0
  16. package/templates/backend/chat/apps/api/functions/listRooms.ts +15 -0
  17. package/templates/backend/chat/apps/api/functions/roomMessages.ts +20 -0
  18. package/templates/backend/chat/apps/api/functions/sendMessage.ts +37 -0
  19. package/templates/backend/chat/apps/api/package.json +20 -0
  20. package/templates/backend/chat/apps/api/schema.ts +93 -0
  21. package/templates/backend/chat/apps/api/tsconfig.json +13 -0
  22. package/templates/backend/consumer/apps/api/functions/createPost.ts +48 -0
  23. package/templates/backend/consumer/apps/api/functions/deletePost.ts +21 -0
  24. package/templates/backend/consumer/apps/api/functions/feed.ts +57 -0
  25. package/templates/backend/consumer/apps/api/functions/myProfile.ts +17 -0
  26. package/templates/backend/consumer/apps/api/functions/profilePosts.ts +17 -0
  27. package/templates/backend/consumer/apps/api/functions/toggleLike.ts +48 -0
  28. package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +70 -0
  29. package/templates/backend/consumer/apps/api/package.json +20 -0
  30. package/templates/backend/consumer/apps/api/schema.ts +130 -0
  31. package/templates/backend/consumer/apps/api/tsconfig.json +13 -0
  32. package/templates/expo/chat/apps/expo/App.tsx +414 -0
  33. package/templates/expo/chat/apps/expo/app.json +25 -0
  34. package/templates/expo/chat/apps/expo/babel.config.js +6 -0
  35. package/templates/expo/chat/apps/expo/package.json +30 -0
  36. package/templates/expo/chat/apps/expo/tsconfig.json +16 -0
  37. package/templates/expo/consumer/apps/expo/App.tsx +360 -0
  38. package/templates/expo/consumer/apps/expo/app.json +25 -0
  39. package/templates/expo/consumer/apps/expo/babel.config.js +6 -0
  40. package/templates/expo/consumer/apps/expo/package.json +30 -0
  41. package/templates/expo/consumer/apps/expo/tsconfig.json +16 -0
  42. package/templates/ios/chat/apps/ios/Package.swift +34 -0
  43. package/templates/ios/chat/apps/ios/Sources/__APP_NAME_PASCAL__/ChatRootView.swift +120 -0
  44. package/templates/ios/chat/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +26 -0
  45. package/templates/ios/chat/apps/ios/Sources/__APP_NAME_PASCAL__/RoomView.swift +137 -0
  46. package/templates/ios/chat/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +35 -0
  47. package/templates/ios/chat/apps/ios/project.yml +42 -0
  48. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +170 -0
  49. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +42 -0
  50. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +60 -0
  51. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +30 -0
  52. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +29 -0
  53. package/templates/ios/consumer/apps/ios/project.yml +42 -0
  54. package/templates/ios/todo/apps/ios/Package.swift +23 -0
  55. package/templates/mac/b2b/apps/mac/Package.swift +22 -0
  56. package/templates/mac/b2b/apps/mac/Sources/__APP_NAME_PASCAL__/Models.swift +15 -0
  57. package/templates/mac/b2b/apps/mac/Sources/__APP_NAME_PASCAL__/OrgPickerView.swift +178 -0
  58. package/templates/mac/b2b/apps/mac/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +30 -0
  59. package/templates/mac/b2b/apps/mac/__APP_NAME_PASCAL__.entitlements +13 -0
  60. package/templates/mac/b2b/apps/mac/project.yml +34 -0
  61. package/templates/mac/barebones/apps/mac/Package.swift +33 -0
  62. package/templates/mac/barebones/apps/mac/Sources/__APP_NAME_PASCAL__/ContentView.swift +104 -0
  63. package/templates/mac/barebones/apps/mac/Sources/__APP_NAME_PASCAL__/Models.swift +17 -0
  64. package/templates/mac/barebones/apps/mac/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +30 -0
  65. package/templates/mac/barebones/apps/mac/__APP_NAME_PASCAL__.entitlements +13 -0
  66. package/templates/mac/barebones/apps/mac/project.yml +34 -0
  67. package/templates/mac/chat/apps/mac/Package.swift +33 -0
  68. package/templates/mac/chat/apps/mac/Sources/__APP_NAME_PASCAL__/ChatRootView.swift +140 -0
  69. package/templates/mac/chat/apps/mac/Sources/__APP_NAME_PASCAL__/Models.swift +26 -0
  70. package/templates/mac/chat/apps/mac/Sources/__APP_NAME_PASCAL__/RoomView.swift +137 -0
  71. package/templates/mac/chat/apps/mac/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +37 -0
  72. package/templates/mac/chat/apps/mac/__APP_NAME_PASCAL__.entitlements +13 -0
  73. package/templates/mac/chat/apps/mac/project.yml +34 -0
  74. package/templates/mac/consumer/apps/mac/Package.swift +33 -0
  75. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/FeedView.swift +170 -0
  76. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/Models.swift +42 -0
  77. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +60 -0
  78. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/RootView.swift +30 -0
  79. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +31 -0
  80. package/templates/mac/consumer/apps/mac/__APP_NAME_PASCAL__.entitlements +13 -0
  81. package/templates/mac/consumer/apps/mac/project.yml +34 -0
  82. package/templates/mac/todo/apps/mac/Package.swift +33 -0
  83. package/templates/mac/todo/apps/mac/Sources/__APP_NAME_PASCAL__/Models.swift +19 -0
  84. package/templates/mac/todo/apps/mac/Sources/__APP_NAME_PASCAL__/TodoListView.swift +244 -0
  85. package/templates/mac/todo/apps/mac/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +30 -0
  86. package/templates/mac/todo/apps/mac/__APP_NAME_PASCAL__.entitlements +13 -0
  87. package/templates/mac/todo/apps/mac/project.yml +34 -0
  88. package/templates/web/b2b/apps/web/next-env.d.ts +2 -0
  89. package/templates/web/b2b/apps/web/next.config.ts +24 -0
  90. package/templates/web/b2b/apps/web/package.json +29 -0
  91. package/templates/web/b2b/apps/web/postcss.config.mjs +3 -0
  92. package/templates/web/b2b/apps/web/src/app/components/OrgPicker.tsx +171 -0
  93. package/templates/web/b2b/apps/web/src/app/globals.css +6 -0
  94. package/templates/web/b2b/apps/web/src/app/layout.tsx +21 -0
  95. package/templates/web/b2b/apps/web/src/app/page.tsx +39 -0
  96. package/templates/web/b2b/apps/web/src/lib/pylon.ts +5 -0
  97. package/templates/web/b2b/apps/web/tsconfig.json +26 -0
  98. package/templates/web/chat/apps/web/next-env.d.ts +2 -0
  99. package/templates/web/chat/apps/web/next.config.ts +24 -0
  100. package/templates/web/chat/apps/web/package.json +29 -0
  101. package/templates/web/chat/apps/web/postcss.config.mjs +3 -0
  102. package/templates/web/chat/apps/web/src/app/components/ChatRoom.tsx +250 -0
  103. package/templates/web/chat/apps/web/src/app/globals.css +6 -0
  104. package/templates/web/chat/apps/web/src/app/layout.tsx +21 -0
  105. package/templates/web/chat/apps/web/src/app/page.tsx +51 -0
  106. package/templates/web/chat/apps/web/src/lib/pylon.ts +5 -0
  107. package/templates/web/chat/apps/web/tsconfig.json +26 -0
  108. package/templates/web/consumer/apps/web/next-env.d.ts +2 -0
  109. package/templates/web/consumer/apps/web/next.config.ts +24 -0
  110. package/templates/web/consumer/apps/web/package.json +29 -0
  111. package/templates/web/consumer/apps/web/postcss.config.mjs +3 -0
  112. package/templates/web/consumer/apps/web/src/app/components/Feed.tsx +295 -0
  113. package/templates/web/consumer/apps/web/src/app/globals.css +6 -0
  114. package/templates/web/consumer/apps/web/src/app/layout.tsx +21 -0
  115. package/templates/web/consumer/apps/web/src/app/page.tsx +55 -0
  116. package/templates/web/consumer/apps/web/src/lib/pylon.ts +5 -0
  117. package/templates/web/consumer/apps/web/tsconfig.json +26 -0
  118. /package/templates/{mobile/barebones/apps/mobile → ios/barebones/apps/ios}/Package.swift +0 -0
  119. /package/templates/{mobile/barebones/apps/mobile → ios/barebones/apps/ios}/Sources/__APP_NAME_PASCAL__/ContentView.swift +0 -0
  120. /package/templates/{mobile/barebones/apps/mobile → ios/barebones/apps/ios}/Sources/__APP_NAME_PASCAL__/Models.swift +0 -0
  121. /package/templates/{mobile/barebones/apps/mobile → ios/barebones/apps/ios}/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +0 -0
  122. /package/templates/{mobile/barebones/apps/mobile → ios/barebones/apps/ios}/project.yml +0 -0
  123. /package/templates/{mobile/todo/apps/mobile → ios/consumer/apps/ios}/Package.swift +0 -0
  124. /package/templates/{mobile/todo/apps/mobile → ios/todo/apps/ios}/Sources/__APP_NAME_PASCAL__/Models.swift +0 -0
  125. /package/templates/{mobile/todo/apps/mobile → ios/todo/apps/ios}/Sources/__APP_NAME_PASCAL__/TodoListView.swift +0 -0
  126. /package/templates/{mobile/todo/apps/mobile → ios/todo/apps/ios}/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +0 -0
  127. /package/templates/{mobile/todo/apps/mobile → ios/todo/apps/ios}/project.yml +0 -0
@@ -0,0 +1,295 @@
1
+ "use client";
2
+
3
+ import { useState, useTransition } from "react";
4
+ import { Button, Input, Card, CardHeader, CardContent } from "@__APP_NAME_KEBAB__/ui";
5
+
6
+ type FeedItem = {
7
+ id: string;
8
+ body: string;
9
+ createdAt: string;
10
+ author: { id: string; handle: string; displayName: string } | null;
11
+ likeCount: number;
12
+ likedByMe: boolean;
13
+ };
14
+
15
+ type Profile = {
16
+ id: string;
17
+ userId: string;
18
+ handle: string;
19
+ displayName: string;
20
+ bio?: string | null;
21
+ createdAt: string;
22
+ };
23
+
24
+ export function Feed({
25
+ initialFeed,
26
+ initialMe,
27
+ }: {
28
+ initialFeed: FeedItem[];
29
+ initialMe: Profile | null;
30
+ }) {
31
+ const [feed, setFeed] = useState(initialFeed);
32
+ const [me, setMe] = useState(initialMe);
33
+ const [body, setBody] = useState("");
34
+ const [pending, startTransition] = useTransition();
35
+
36
+ if (!me) {
37
+ return <ProfileSetup onSaved={(p) => setMe(p)} />;
38
+ }
39
+
40
+ async function post() {
41
+ const trimmed = body.trim();
42
+ if (!trimmed) return;
43
+ setBody("");
44
+ startTransition(async () => {
45
+ const res = await fetch("/api/fn/createPost", {
46
+ method: "POST",
47
+ headers: { "Content-Type": "application/json" },
48
+ body: JSON.stringify({ body: trimmed }),
49
+ });
50
+ if (res.ok) {
51
+ const item = (await res.json()) as FeedItem;
52
+ setFeed((prev) => [item, ...prev]);
53
+ } else {
54
+ setBody(trimmed);
55
+ }
56
+ });
57
+ }
58
+
59
+ async function toggleLike(item: FeedItem) {
60
+ // Optimistic
61
+ setFeed((prev) =>
62
+ prev.map((p) =>
63
+ p.id === item.id
64
+ ? {
65
+ ...p,
66
+ likedByMe: !p.likedByMe,
67
+ likeCount: p.likeCount + (p.likedByMe ? -1 : 1),
68
+ }
69
+ : p,
70
+ ),
71
+ );
72
+ startTransition(async () => {
73
+ const res = await fetch("/api/fn/toggleLike", {
74
+ method: "POST",
75
+ headers: { "Content-Type": "application/json" },
76
+ body: JSON.stringify({ postId: item.id }),
77
+ });
78
+ if (!res.ok) {
79
+ // Revert
80
+ setFeed((prev) =>
81
+ prev.map((p) =>
82
+ p.id === item.id
83
+ ? {
84
+ ...p,
85
+ likedByMe: item.likedByMe,
86
+ likeCount: item.likeCount,
87
+ }
88
+ : p,
89
+ ),
90
+ );
91
+ }
92
+ });
93
+ }
94
+
95
+ async function remove(item: FeedItem) {
96
+ const snapshot = feed;
97
+ setFeed((prev) => prev.filter((p) => p.id !== item.id));
98
+ startTransition(async () => {
99
+ const res = await fetch("/api/fn/deletePost", {
100
+ method: "POST",
101
+ headers: { "Content-Type": "application/json" },
102
+ body: JSON.stringify({ id: item.id }),
103
+ });
104
+ if (!res.ok) setFeed(snapshot);
105
+ });
106
+ }
107
+
108
+ return (
109
+ <div className="space-y-6">
110
+ <Card>
111
+ <CardHeader>
112
+ <div className="flex items-center justify-between">
113
+ <div>
114
+ <div className="text-sm font-medium">{me.displayName}</div>
115
+ <div className="text-xs text-neutral-400 font-mono">
116
+ @{me.handle}
117
+ </div>
118
+ </div>
119
+ <button
120
+ className="text-xs text-neutral-500 hover:text-neutral-800 dark:hover:text-neutral-200"
121
+ onClick={() => setMe(null)}
122
+ >
123
+ Edit profile
124
+ </button>
125
+ </div>
126
+ </CardHeader>
127
+ <CardContent>
128
+ <form
129
+ onSubmit={(e) => {
130
+ e.preventDefault();
131
+ post();
132
+ }}
133
+ className="space-y-2"
134
+ >
135
+ <textarea
136
+ value={body}
137
+ onChange={(e) => setBody(e.target.value)}
138
+ placeholder={`What's on your mind, ${me.displayName.split(" ")[0]}?`}
139
+ rows={3}
140
+ maxLength={1000}
141
+ disabled={pending}
142
+ className="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 resize-none"
143
+ />
144
+ <div className="flex items-center justify-between">
145
+ <span className="text-xs text-neutral-400">
146
+ {body.length}/1000
147
+ </span>
148
+ <Button
149
+ type="submit"
150
+ variant="primary"
151
+ disabled={pending || !body.trim()}
152
+ size="sm"
153
+ >
154
+ Post
155
+ </Button>
156
+ </div>
157
+ </form>
158
+ </CardContent>
159
+ </Card>
160
+
161
+ {feed.length === 0 ? (
162
+ <p className="text-sm text-neutral-500 text-center py-8">
163
+ No posts yet. Be the first.
164
+ </p>
165
+ ) : (
166
+ <ul className="space-y-3">
167
+ {feed.map((item) => (
168
+ <li key={item.id}>
169
+ <Card>
170
+ <CardContent className="space-y-3">
171
+ <div className="flex items-baseline justify-between">
172
+ <div className="text-sm">
173
+ <span className="font-medium">
174
+ {item.author?.displayName ?? "Unknown"}
175
+ </span>{" "}
176
+ <span className="text-neutral-400 font-mono text-xs">
177
+ @{item.author?.handle ?? "?"}
178
+ </span>
179
+ </div>
180
+ <span className="text-xs text-neutral-400">
181
+ {new Date(item.createdAt).toLocaleString(undefined, {
182
+ month: "short",
183
+ day: "numeric",
184
+ hour: "numeric",
185
+ minute: "2-digit",
186
+ })}
187
+ </span>
188
+ </div>
189
+ <p className="text-sm whitespace-pre-wrap break-words">
190
+ {item.body}
191
+ </p>
192
+ <div className="flex items-center gap-2">
193
+ <button
194
+ onClick={() => toggleLike(item)}
195
+ className={`text-xs font-mono px-2 py-1 rounded border transition-colors ${
196
+ item.likedByMe
197
+ ? "border-pink-300 dark:border-pink-700 text-pink-600 dark:text-pink-300 bg-pink-50 dark:bg-pink-950"
198
+ : "border-neutral-200 dark:border-neutral-800 text-neutral-500 hover:bg-neutral-50 dark:hover:bg-neutral-900"
199
+ }`}
200
+ >
201
+ {item.likedByMe ? "♥" : "♡"} {item.likeCount}
202
+ </button>
203
+ {item.author?.id === me.id && (
204
+ <button
205
+ onClick={() => remove(item)}
206
+ className="text-xs text-neutral-400 hover:text-red-500"
207
+ >
208
+ Delete
209
+ </button>
210
+ )}
211
+ </div>
212
+ </CardContent>
213
+ </Card>
214
+ </li>
215
+ ))}
216
+ </ul>
217
+ )}
218
+ </div>
219
+ );
220
+ }
221
+
222
+ function ProfileSetup({ onSaved }: { onSaved: (p: Profile) => void }) {
223
+ const [handle, setHandle] = useState("");
224
+ const [displayName, setDisplayName] = useState("");
225
+ const [bio, setBio] = useState("");
226
+ const [pending, startTransition] = useTransition();
227
+ const [error, setError] = useState<string | null>(null);
228
+
229
+ async function save(e: React.FormEvent) {
230
+ e.preventDefault();
231
+ setError(null);
232
+ startTransition(async () => {
233
+ const res = await fetch("/api/fn/upsertProfile", {
234
+ method: "POST",
235
+ headers: { "Content-Type": "application/json" },
236
+ body: JSON.stringify({ handle, displayName, bio }),
237
+ });
238
+ if (res.ok) {
239
+ const profile = (await res.json()) as Profile;
240
+ onSaved(profile);
241
+ } else {
242
+ const body = await res.json().catch(() => ({}));
243
+ setError(body?.message ?? "save failed");
244
+ }
245
+ });
246
+ }
247
+
248
+ return (
249
+ <Card>
250
+ <CardHeader>
251
+ <h2 className="text-sm font-medium">Set up your profile</h2>
252
+ </CardHeader>
253
+ <CardContent>
254
+ <form onSubmit={save} className="space-y-3">
255
+ <div>
256
+ <label className="text-xs text-neutral-500 block mb-1">
257
+ Handle
258
+ </label>
259
+ <Input
260
+ value={handle}
261
+ onChange={(e) => setHandle(e.target.value.toLowerCase())}
262
+ placeholder="lowercase letters, digits, underscore"
263
+ pattern="[a-z0-9_]{2,20}"
264
+ required
265
+ />
266
+ </div>
267
+ <div>
268
+ <label className="text-xs text-neutral-500 block mb-1">
269
+ Display name
270
+ </label>
271
+ <Input
272
+ value={displayName}
273
+ onChange={(e) => setDisplayName(e.target.value)}
274
+ required
275
+ />
276
+ </div>
277
+ <div>
278
+ <label className="text-xs text-neutral-500 block mb-1">
279
+ Bio
280
+ </label>
281
+ <Input
282
+ value={bio}
283
+ onChange={(e) => setBio(e.target.value)}
284
+ placeholder="(optional)"
285
+ />
286
+ </div>
287
+ {error && <p className="text-xs text-red-500">{error}</p>}
288
+ <Button type="submit" variant="primary" disabled={pending}>
289
+ {pending ? "Saving…" : "Save"}
290
+ </Button>
291
+ </form>
292
+ </CardContent>
293
+ </Card>
294
+ );
295
+ }
@@ -0,0 +1,6 @@
1
+ @import "tailwindcss";
2
+ @source "../../../../packages/ui/src/**/*.{ts,tsx}";
3
+
4
+ :root { color-scheme: light dark; }
5
+ html, body { height: 100%; }
6
+ body { font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; }
@@ -0,0 +1,21 @@
1
+ import type { Metadata } from "next";
2
+ import "./globals.css";
3
+
4
+ export const metadata: Metadata = {
5
+ title: "__APP_NAME__",
6
+ description: "Multi-tenant SaaS scaffold powered by Pylon",
7
+ };
8
+
9
+ export default function RootLayout({
10
+ children,
11
+ }: {
12
+ children: React.ReactNode;
13
+ }) {
14
+ return (
15
+ <html lang="en">
16
+ <body className="antialiased min-h-screen bg-white dark:bg-neutral-950 text-neutral-900 dark:text-neutral-100">
17
+ {children}
18
+ </body>
19
+ </html>
20
+ );
21
+ }
@@ -0,0 +1,55 @@
1
+ import { pylon } from "@/lib/pylon";
2
+ import { Feed } from "./components/Feed";
3
+
4
+ export const dynamic = "force-dynamic";
5
+
6
+ type FeedItem = {
7
+ id: string;
8
+ body: string;
9
+ createdAt: string;
10
+ author: { id: string; handle: string; displayName: string } | null;
11
+ likeCount: number;
12
+ likedByMe: boolean;
13
+ };
14
+
15
+ type Profile = {
16
+ id: string;
17
+ userId: string;
18
+ handle: string;
19
+ displayName: string;
20
+ bio?: string | null;
21
+ createdAt: string;
22
+ };
23
+
24
+ export default async function HomePage() {
25
+ const [feed, me] = await Promise.all([
26
+ pylon
27
+ .json<FeedItem[]>("/api/fn/feed", {
28
+ method: "POST",
29
+ body: "{}",
30
+ headers: { "Content-Type": "application/json" },
31
+ })
32
+ .catch(() => [] as FeedItem[]),
33
+ pylon
34
+ .json<Profile | null>("/api/fn/myProfile", {
35
+ method: "POST",
36
+ body: "{}",
37
+ headers: { "Content-Type": "application/json" },
38
+ })
39
+ .catch(() => null),
40
+ ]);
41
+
42
+ return (
43
+ <main className="mx-auto max-w-2xl px-6 py-12 space-y-8">
44
+ <header className="space-y-2">
45
+ <h1 className="text-3xl font-semibold tracking-tight">__APP_NAME__</h1>
46
+ <p className="text-sm text-neutral-500 dark:text-neutral-400">
47
+ Public feed scaffold. Profile, Post, Like — wide-open reads, owner-
48
+ only writes (enforced by Pylon row-level policies).
49
+ </p>
50
+ </header>
51
+
52
+ <Feed initialFeed={feed} initialMe={me} />
53
+ </main>
54
+ );
55
+ }
@@ -0,0 +1,5 @@
1
+ import { createPylonServer } from "@pylonsync/next/server";
2
+
3
+ export const pylon = createPylonServer({
4
+ cookieName: "__APP_NAME_SNAKE___session",
5
+ });
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [{ "name": "next" }],
17
+ "paths": { "@/*": ["./src/*"] }
18
+ },
19
+ "include": [
20
+ "next-env.d.ts",
21
+ "src/**/*.ts",
22
+ "src/**/*.tsx",
23
+ ".next/types/**/*.ts"
24
+ ],
25
+ "exclude": ["node_modules"]
26
+ }