almostnode 0.1.0

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 (216) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +731 -0
  3. package/dist/__sw__.js +394 -0
  4. package/dist/ai-chatbot-demo-entry.d.ts +6 -0
  5. package/dist/ai-chatbot-demo-entry.d.ts.map +1 -0
  6. package/dist/ai-chatbot-demo.d.ts +42 -0
  7. package/dist/ai-chatbot-demo.d.ts.map +1 -0
  8. package/dist/assets/runtime-worker-D9x_Ddwz.js +60543 -0
  9. package/dist/assets/runtime-worker-D9x_Ddwz.js.map +1 -0
  10. package/dist/convex-app-demo-entry.d.ts +6 -0
  11. package/dist/convex-app-demo-entry.d.ts.map +1 -0
  12. package/dist/convex-app-demo.d.ts +68 -0
  13. package/dist/convex-app-demo.d.ts.map +1 -0
  14. package/dist/cors-proxy.d.ts +46 -0
  15. package/dist/cors-proxy.d.ts.map +1 -0
  16. package/dist/create-runtime.d.ts +42 -0
  17. package/dist/create-runtime.d.ts.map +1 -0
  18. package/dist/demo.d.ts +6 -0
  19. package/dist/demo.d.ts.map +1 -0
  20. package/dist/dev-server.d.ts +97 -0
  21. package/dist/dev-server.d.ts.map +1 -0
  22. package/dist/frameworks/next-dev-server.d.ts +202 -0
  23. package/dist/frameworks/next-dev-server.d.ts.map +1 -0
  24. package/dist/frameworks/vite-dev-server.d.ts +85 -0
  25. package/dist/frameworks/vite-dev-server.d.ts.map +1 -0
  26. package/dist/index.cjs +14965 -0
  27. package/dist/index.cjs.map +1 -0
  28. package/dist/index.d.ts +71 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.mjs +14867 -0
  31. package/dist/index.mjs.map +1 -0
  32. package/dist/next-demo.d.ts +49 -0
  33. package/dist/next-demo.d.ts.map +1 -0
  34. package/dist/npm/index.d.ts +71 -0
  35. package/dist/npm/index.d.ts.map +1 -0
  36. package/dist/npm/registry.d.ts +66 -0
  37. package/dist/npm/registry.d.ts.map +1 -0
  38. package/dist/npm/resolver.d.ts +52 -0
  39. package/dist/npm/resolver.d.ts.map +1 -0
  40. package/dist/npm/tarball.d.ts +29 -0
  41. package/dist/npm/tarball.d.ts.map +1 -0
  42. package/dist/runtime-interface.d.ts +90 -0
  43. package/dist/runtime-interface.d.ts.map +1 -0
  44. package/dist/runtime.d.ts +103 -0
  45. package/dist/runtime.d.ts.map +1 -0
  46. package/dist/sandbox-helpers.d.ts +43 -0
  47. package/dist/sandbox-helpers.d.ts.map +1 -0
  48. package/dist/sandbox-runtime.d.ts +65 -0
  49. package/dist/sandbox-runtime.d.ts.map +1 -0
  50. package/dist/server-bridge.d.ts +89 -0
  51. package/dist/server-bridge.d.ts.map +1 -0
  52. package/dist/shims/assert.d.ts +51 -0
  53. package/dist/shims/assert.d.ts.map +1 -0
  54. package/dist/shims/async_hooks.d.ts +37 -0
  55. package/dist/shims/async_hooks.d.ts.map +1 -0
  56. package/dist/shims/buffer.d.ts +20 -0
  57. package/dist/shims/buffer.d.ts.map +1 -0
  58. package/dist/shims/child_process-browser.d.ts +92 -0
  59. package/dist/shims/child_process-browser.d.ts.map +1 -0
  60. package/dist/shims/child_process.d.ts +93 -0
  61. package/dist/shims/child_process.d.ts.map +1 -0
  62. package/dist/shims/chokidar.d.ts +55 -0
  63. package/dist/shims/chokidar.d.ts.map +1 -0
  64. package/dist/shims/cluster.d.ts +52 -0
  65. package/dist/shims/cluster.d.ts.map +1 -0
  66. package/dist/shims/crypto.d.ts +122 -0
  67. package/dist/shims/crypto.d.ts.map +1 -0
  68. package/dist/shims/dgram.d.ts +34 -0
  69. package/dist/shims/dgram.d.ts.map +1 -0
  70. package/dist/shims/diagnostics_channel.d.ts +80 -0
  71. package/dist/shims/diagnostics_channel.d.ts.map +1 -0
  72. package/dist/shims/dns.d.ts +87 -0
  73. package/dist/shims/dns.d.ts.map +1 -0
  74. package/dist/shims/domain.d.ts +25 -0
  75. package/dist/shims/domain.d.ts.map +1 -0
  76. package/dist/shims/esbuild.d.ts +105 -0
  77. package/dist/shims/esbuild.d.ts.map +1 -0
  78. package/dist/shims/events.d.ts +37 -0
  79. package/dist/shims/events.d.ts.map +1 -0
  80. package/dist/shims/fs.d.ts +115 -0
  81. package/dist/shims/fs.d.ts.map +1 -0
  82. package/dist/shims/fsevents.d.ts +67 -0
  83. package/dist/shims/fsevents.d.ts.map +1 -0
  84. package/dist/shims/http.d.ts +217 -0
  85. package/dist/shims/http.d.ts.map +1 -0
  86. package/dist/shims/http2.d.ts +81 -0
  87. package/dist/shims/http2.d.ts.map +1 -0
  88. package/dist/shims/https.d.ts +36 -0
  89. package/dist/shims/https.d.ts.map +1 -0
  90. package/dist/shims/inspector.d.ts +25 -0
  91. package/dist/shims/inspector.d.ts.map +1 -0
  92. package/dist/shims/module.d.ts +22 -0
  93. package/dist/shims/module.d.ts.map +1 -0
  94. package/dist/shims/net.d.ts +100 -0
  95. package/dist/shims/net.d.ts.map +1 -0
  96. package/dist/shims/os.d.ts +159 -0
  97. package/dist/shims/os.d.ts.map +1 -0
  98. package/dist/shims/path.d.ts +72 -0
  99. package/dist/shims/path.d.ts.map +1 -0
  100. package/dist/shims/perf_hooks.d.ts +50 -0
  101. package/dist/shims/perf_hooks.d.ts.map +1 -0
  102. package/dist/shims/process.d.ts +93 -0
  103. package/dist/shims/process.d.ts.map +1 -0
  104. package/dist/shims/querystring.d.ts +23 -0
  105. package/dist/shims/querystring.d.ts.map +1 -0
  106. package/dist/shims/readdirp.d.ts +52 -0
  107. package/dist/shims/readdirp.d.ts.map +1 -0
  108. package/dist/shims/readline.d.ts +62 -0
  109. package/dist/shims/readline.d.ts.map +1 -0
  110. package/dist/shims/rollup.d.ts +34 -0
  111. package/dist/shims/rollup.d.ts.map +1 -0
  112. package/dist/shims/sentry.d.ts +163 -0
  113. package/dist/shims/sentry.d.ts.map +1 -0
  114. package/dist/shims/stream.d.ts +181 -0
  115. package/dist/shims/stream.d.ts.map +1 -0
  116. package/dist/shims/tls.d.ts +53 -0
  117. package/dist/shims/tls.d.ts.map +1 -0
  118. package/dist/shims/tty.d.ts +30 -0
  119. package/dist/shims/tty.d.ts.map +1 -0
  120. package/dist/shims/url.d.ts +64 -0
  121. package/dist/shims/url.d.ts.map +1 -0
  122. package/dist/shims/util.d.ts +106 -0
  123. package/dist/shims/util.d.ts.map +1 -0
  124. package/dist/shims/v8.d.ts +73 -0
  125. package/dist/shims/v8.d.ts.map +1 -0
  126. package/dist/shims/vfs-adapter.d.ts +126 -0
  127. package/dist/shims/vfs-adapter.d.ts.map +1 -0
  128. package/dist/shims/vm.d.ts +45 -0
  129. package/dist/shims/vm.d.ts.map +1 -0
  130. package/dist/shims/worker_threads.d.ts +66 -0
  131. package/dist/shims/worker_threads.d.ts.map +1 -0
  132. package/dist/shims/ws.d.ts +66 -0
  133. package/dist/shims/ws.d.ts.map +1 -0
  134. package/dist/shims/zlib.d.ts +161 -0
  135. package/dist/shims/zlib.d.ts.map +1 -0
  136. package/dist/transform.d.ts +24 -0
  137. package/dist/transform.d.ts.map +1 -0
  138. package/dist/virtual-fs.d.ts +226 -0
  139. package/dist/virtual-fs.d.ts.map +1 -0
  140. package/dist/vite-demo.d.ts +35 -0
  141. package/dist/vite-demo.d.ts.map +1 -0
  142. package/dist/vite-sw.js +132 -0
  143. package/dist/worker/runtime-worker.d.ts +8 -0
  144. package/dist/worker/runtime-worker.d.ts.map +1 -0
  145. package/dist/worker-runtime.d.ts +50 -0
  146. package/dist/worker-runtime.d.ts.map +1 -0
  147. package/package.json +85 -0
  148. package/src/ai-chatbot-demo-entry.ts +244 -0
  149. package/src/ai-chatbot-demo.ts +509 -0
  150. package/src/convex-app-demo-entry.ts +1107 -0
  151. package/src/convex-app-demo.ts +1316 -0
  152. package/src/cors-proxy.ts +81 -0
  153. package/src/create-runtime.ts +147 -0
  154. package/src/demo.ts +304 -0
  155. package/src/dev-server.ts +274 -0
  156. package/src/frameworks/next-dev-server.ts +2224 -0
  157. package/src/frameworks/vite-dev-server.ts +702 -0
  158. package/src/index.ts +101 -0
  159. package/src/next-demo.ts +1784 -0
  160. package/src/npm/index.ts +347 -0
  161. package/src/npm/registry.ts +152 -0
  162. package/src/npm/resolver.ts +385 -0
  163. package/src/npm/tarball.ts +209 -0
  164. package/src/runtime-interface.ts +103 -0
  165. package/src/runtime.ts +1046 -0
  166. package/src/sandbox-helpers.ts +173 -0
  167. package/src/sandbox-runtime.ts +252 -0
  168. package/src/server-bridge.ts +426 -0
  169. package/src/shims/assert.ts +664 -0
  170. package/src/shims/async_hooks.ts +86 -0
  171. package/src/shims/buffer.ts +75 -0
  172. package/src/shims/child_process-browser.ts +217 -0
  173. package/src/shims/child_process.ts +463 -0
  174. package/src/shims/chokidar.ts +313 -0
  175. package/src/shims/cluster.ts +67 -0
  176. package/src/shims/crypto.ts +830 -0
  177. package/src/shims/dgram.ts +47 -0
  178. package/src/shims/diagnostics_channel.ts +196 -0
  179. package/src/shims/dns.ts +172 -0
  180. package/src/shims/domain.ts +58 -0
  181. package/src/shims/esbuild.ts +805 -0
  182. package/src/shims/events.ts +195 -0
  183. package/src/shims/fs.ts +803 -0
  184. package/src/shims/fsevents.ts +63 -0
  185. package/src/shims/http.ts +904 -0
  186. package/src/shims/http2.ts +96 -0
  187. package/src/shims/https.ts +86 -0
  188. package/src/shims/inspector.ts +30 -0
  189. package/src/shims/module.ts +82 -0
  190. package/src/shims/net.ts +359 -0
  191. package/src/shims/os.ts +195 -0
  192. package/src/shims/path.ts +199 -0
  193. package/src/shims/perf_hooks.ts +92 -0
  194. package/src/shims/process.ts +346 -0
  195. package/src/shims/querystring.ts +97 -0
  196. package/src/shims/readdirp.ts +228 -0
  197. package/src/shims/readline.ts +110 -0
  198. package/src/shims/rollup.ts +80 -0
  199. package/src/shims/sentry.ts +133 -0
  200. package/src/shims/stream.ts +1126 -0
  201. package/src/shims/tls.ts +95 -0
  202. package/src/shims/tty.ts +64 -0
  203. package/src/shims/url.ts +171 -0
  204. package/src/shims/util.ts +312 -0
  205. package/src/shims/v8.ts +113 -0
  206. package/src/shims/vfs-adapter.ts +402 -0
  207. package/src/shims/vm.ts +83 -0
  208. package/src/shims/worker_threads.ts +111 -0
  209. package/src/shims/ws.ts +382 -0
  210. package/src/shims/zlib.ts +289 -0
  211. package/src/transform.ts +313 -0
  212. package/src/types/external.d.ts +67 -0
  213. package/src/virtual-fs.ts +903 -0
  214. package/src/vite-demo.ts +577 -0
  215. package/src/worker/runtime-worker.ts +128 -0
  216. package/src/worker-runtime.ts +145 -0
@@ -0,0 +1,1316 @@
1
+ /**
2
+ * Realistic Next.js + Convex App Demo
3
+ *
4
+ * This demo creates a more realistic Next.js application structure
5
+ * with Radix UI components, Tailwind CSS, and a mocked Convex backend.
6
+ */
7
+
8
+ import { VirtualFS } from './virtual-fs';
9
+ import { Runtime } from './runtime';
10
+ import { NextDevServer } from './frameworks/next-dev-server';
11
+ import { getServerBridge } from './server-bridge';
12
+ import { Buffer } from './shims/stream';
13
+ import { PackageManager, InstallOptions, InstallResult } from './npm';
14
+
15
+ /**
16
+ * Package.json for a realistic Next.js + Convex app
17
+ */
18
+ const PACKAGE_JSON = {
19
+ name: "convex-app-demo",
20
+ version: "0.1.0",
21
+ private: true,
22
+ scripts: {
23
+ dev: "next dev",
24
+ build: "next build",
25
+ start: "next start",
26
+ },
27
+ dependencies: {
28
+ // Core
29
+ "next": "^14.0.0",
30
+ "react": "^18.2.0",
31
+ "react-dom": "^18.2.0",
32
+ // UI
33
+ "clsx": "^2.1.1",
34
+ "tailwind-merge": "^3.1.0",
35
+ "lucide-react": "^0.400.0",
36
+ // Forms
37
+ "zod": "^3.24.2",
38
+ // Date
39
+ "date-fns": "^3.6.0",
40
+ },
41
+ devDependencies: {
42
+ "@types/node": "^20",
43
+ "@types/react": "^19",
44
+ "@types/react-dom": "^19",
45
+ "typescript": "^5.9.3",
46
+ }
47
+ };
48
+
49
+ /**
50
+ * Minimal packages to install for demo (others loaded from CDN)
51
+ */
52
+ const DEMO_PACKAGES = [
53
+ 'clsx',
54
+ 'tailwind-merge',
55
+ 'zod',
56
+ 'date-fns',
57
+ ];
58
+
59
+ /**
60
+ * Create the project structure in the virtual filesystem
61
+ */
62
+ export function createConvexAppProject(vfs: VirtualFS): void {
63
+ // Create package.json
64
+ vfs.writeFileSync('/package.json', JSON.stringify(PACKAGE_JSON, null, 2));
65
+
66
+ // Create directories - App Router structure
67
+ vfs.mkdirSync('/app', { recursive: true });
68
+ vfs.mkdirSync('/app/api', { recursive: true });
69
+ vfs.mkdirSync('/app/tasks', { recursive: true });
70
+ vfs.mkdirSync('/components', { recursive: true });
71
+ vfs.mkdirSync('/components/ui', { recursive: true });
72
+ vfs.mkdirSync('/lib', { recursive: true });
73
+ vfs.mkdirSync('/convex', { recursive: true });
74
+ vfs.mkdirSync('/public', { recursive: true });
75
+
76
+ // Create convex.json configuration (required by Convex CLI)
77
+ vfs.writeFileSync('/convex.json', JSON.stringify({
78
+ functions: "convex/"
79
+ }, null, 2));
80
+
81
+ // Create TypeScript config
82
+ vfs.writeFileSync('/tsconfig.json', JSON.stringify({
83
+ compilerOptions: {
84
+ target: "es5",
85
+ lib: ["dom", "dom.iterable", "esnext"],
86
+ allowJs: true,
87
+ skipLibCheck: true,
88
+ strict: true,
89
+ noEmit: true,
90
+ esModuleInterop: true,
91
+ module: "esnext",
92
+ moduleResolution: "bundler",
93
+ resolveJsonModule: true,
94
+ isolatedModules: true,
95
+ jsx: "preserve",
96
+ incremental: true,
97
+ paths: {
98
+ "@/*": ["./*"]
99
+ }
100
+ },
101
+ include: ["**/*.ts", "**/*.tsx"],
102
+ exclude: ["node_modules"]
103
+ }, null, 2));
104
+
105
+ // Create Tailwind config
106
+ vfs.writeFileSync('/tailwind.config.js', `/** @type {import('tailwindcss').Config} */
107
+ module.exports = {
108
+ darkMode: ["class"],
109
+ content: [
110
+ './app/**/*.{js,ts,jsx,tsx,mdx}',
111
+ './components/**/*.{js,ts,jsx,tsx,mdx}',
112
+ ],
113
+ theme: {
114
+ extend: {
115
+ colors: {
116
+ border: "hsl(var(--border))",
117
+ background: "hsl(var(--background))",
118
+ foreground: "hsl(var(--foreground))",
119
+ primary: {
120
+ DEFAULT: "hsl(var(--primary))",
121
+ foreground: "hsl(var(--primary-foreground))",
122
+ },
123
+ secondary: {
124
+ DEFAULT: "hsl(var(--secondary))",
125
+ foreground: "hsl(var(--secondary-foreground))",
126
+ },
127
+ muted: {
128
+ DEFAULT: "hsl(var(--muted))",
129
+ foreground: "hsl(var(--muted-foreground))",
130
+ },
131
+ accent: {
132
+ DEFAULT: "hsl(var(--accent))",
133
+ foreground: "hsl(var(--accent-foreground))",
134
+ },
135
+ destructive: {
136
+ DEFAULT: "hsl(var(--destructive))",
137
+ foreground: "hsl(var(--destructive-foreground))",
138
+ },
139
+ },
140
+ borderRadius: {
141
+ lg: "var(--radius)",
142
+ md: "calc(var(--radius) - 2px)",
143
+ sm: "calc(var(--radius) - 4px)",
144
+ },
145
+ },
146
+ },
147
+ plugins: [],
148
+ }
149
+ `);
150
+
151
+ // Create global CSS with Tailwind and shadcn/ui CSS variables
152
+ vfs.writeFileSync('/app/globals.css', `@tailwind base;
153
+ @tailwind components;
154
+ @tailwind utilities;
155
+
156
+ @layer base {
157
+ :root {
158
+ --background: 0 0% 100%;
159
+ --foreground: 222.2 84% 4.9%;
160
+ --card: 0 0% 100%;
161
+ --card-foreground: 222.2 84% 4.9%;
162
+ --popover: 0 0% 100%;
163
+ --popover-foreground: 222.2 84% 4.9%;
164
+ --primary: 222.2 47.4% 11.2%;
165
+ --primary-foreground: 210 40% 98%;
166
+ --secondary: 210 40% 96.1%;
167
+ --secondary-foreground: 222.2 47.4% 11.2%;
168
+ --muted: 210 40% 96.1%;
169
+ --muted-foreground: 215.4 16.3% 46.9%;
170
+ --accent: 210 40% 96.1%;
171
+ --accent-foreground: 222.2 47.4% 11.2%;
172
+ --destructive: 0 84.2% 60.2%;
173
+ --destructive-foreground: 210 40% 98%;
174
+ --border: 214.3 31.8% 91.4%;
175
+ --input: 214.3 31.8% 91.4%;
176
+ --ring: 222.2 84% 4.9%;
177
+ --radius: 0.5rem;
178
+ }
179
+
180
+ .dark {
181
+ --background: 222.2 84% 4.9%;
182
+ --foreground: 210 40% 98%;
183
+ --card: 222.2 84% 4.9%;
184
+ --card-foreground: 210 40% 98%;
185
+ --popover: 222.2 84% 4.9%;
186
+ --popover-foreground: 210 40% 98%;
187
+ --primary: 210 40% 98%;
188
+ --primary-foreground: 222.2 47.4% 11.2%;
189
+ --secondary: 217.2 32.6% 17.5%;
190
+ --secondary-foreground: 210 40% 98%;
191
+ --muted: 217.2 32.6% 17.5%;
192
+ --muted-foreground: 215 20.2% 65.1%;
193
+ --accent: 217.2 32.6% 17.5%;
194
+ --accent-foreground: 210 40% 98%;
195
+ --destructive: 0 62.8% 30.6%;
196
+ --destructive-foreground: 210 40% 98%;
197
+ --border: 217.2 32.6% 17.5%;
198
+ --input: 217.2 32.6% 17.5%;
199
+ --ring: 212.7 26.8% 83.9%;
200
+ }
201
+ }
202
+
203
+ @layer base {
204
+ * {
205
+ @apply border-border;
206
+ }
207
+ body {
208
+ @apply bg-background text-foreground;
209
+ }
210
+ }
211
+ `);
212
+
213
+ // Create utility lib (cn function from shadcn/ui)
214
+ vfs.writeFileSync('/lib/utils.ts', `// Utility functions
215
+ // Note: In production, use clsx and tailwind-merge packages
216
+
217
+ export function cn(...inputs: (string | undefined | null | false)[]) {
218
+ return inputs.filter(Boolean).join(' ');
219
+ }
220
+ `);
221
+
222
+ // Create Convex config (required by CLI bundler)
223
+ // IMPORTANT: CLI needs BOTH .ts and .js versions!
224
+ vfs.writeFileSync('/convex/convex.config.ts', `import { defineApp } from "convex/server";
225
+
226
+ const app = defineApp();
227
+ export default app;
228
+ `);
229
+ vfs.writeFileSync('/convex/convex.config.js', `import { defineApp } from "convex/server";
230
+
231
+ const app = defineApp();
232
+ export default app;
233
+ `);
234
+
235
+ // Create Convex schema
236
+ vfs.writeFileSync('/convex/schema.ts', `import { defineSchema, defineTable } from "convex/server";
237
+ import { v } from "convex/values";
238
+
239
+ export default defineSchema({
240
+ todos: defineTable({
241
+ title: v.string(),
242
+ completed: v.boolean(),
243
+ priority: v.union(v.literal("low"), v.literal("medium"), v.literal("high")),
244
+ }),
245
+ });
246
+ `);
247
+
248
+ // Create Convex functions for todos
249
+ vfs.writeFileSync('/convex/todos.ts', `import { query, mutation } from "./_generated/server";
250
+ import { v } from "convex/values";
251
+
252
+ export const list = query({
253
+ args: {},
254
+ handler: async (ctx) => {
255
+ return await ctx.db.query("todos").order("desc").collect();
256
+ },
257
+ });
258
+
259
+ export const create = mutation({
260
+ args: {
261
+ title: v.string(),
262
+ priority: v.union(v.literal("low"), v.literal("medium"), v.literal("high")),
263
+ },
264
+ handler: async (ctx, args) => {
265
+ return await ctx.db.insert("todos", {
266
+ title: args.title,
267
+ completed: false,
268
+ priority: args.priority,
269
+ });
270
+ },
271
+ });
272
+
273
+ export const toggle = mutation({
274
+ args: { id: v.id("todos") },
275
+ handler: async (ctx, args) => {
276
+ const todo = await ctx.db.get(args.id);
277
+ if (!todo) throw new Error("Todo not found");
278
+ await ctx.db.patch(args.id, { completed: !todo.completed });
279
+ },
280
+ });
281
+
282
+ export const remove = mutation({
283
+ args: { id: v.id("todos") },
284
+ handler: async (ctx, args) => {
285
+ await ctx.db.delete(args.id);
286
+ },
287
+ });
288
+ `);
289
+
290
+ // Create Convex API (normally auto-generated, but we create manually for the demo)
291
+ // This creates function references that Convex's useQuery/useMutation understand
292
+ vfs.writeFileSync('/convex/_generated/api.ts', `// Convex API - manually created for browser demo
293
+ // In a real project, this is auto-generated by 'npx convex dev'
294
+
295
+ // Function references for the Convex client
296
+ // These are string identifiers that map to server functions
297
+ export const api = {
298
+ todos: {
299
+ list: "todos:list",
300
+ create: "todos:create",
301
+ toggle: "todos:toggle",
302
+ remove: "todos:remove",
303
+ },
304
+ } as const;
305
+ `);
306
+
307
+ // Create server stubs (needed for schema/function imports to work)
308
+ vfs.writeFileSync('/convex/_generated/server.ts', `// Server stubs for browser demo
309
+ // In a real project, this is auto-generated by Convex
310
+
311
+ export function query<Args, Output>(config: {
312
+ args: Args;
313
+ handler: (ctx: any, args: any) => Promise<Output>;
314
+ }) {
315
+ return config;
316
+ }
317
+
318
+ export function mutation<Args, Output>(config: {
319
+ args: Args;
320
+ handler: (ctx: any, args: any) => Promise<Output>;
321
+ }) {
322
+ return config;
323
+ }
324
+ `);
325
+
326
+ // Create Convex provider using real Convex client from CDN
327
+ vfs.writeFileSync('/lib/convex.tsx', `"use client";
328
+
329
+ import React, { useState, useEffect } from 'react';
330
+ import { ConvexProvider as BaseConvexProvider, ConvexReactClient, useQuery as useConvexQuery, useMutation as useConvexMutation } from 'convex/react';
331
+
332
+ // Re-export the API
333
+ export { api } from '../convex/_generated/api.ts';
334
+
335
+ // Get Convex URL using standard Next.js env var pattern
336
+ // Falls back to window.__CONVEX_URL__ for backwards compatibility
337
+ const getConvexUrl = () => {
338
+ // Standard Next.js pattern: process.env.NEXT_PUBLIC_*
339
+ if (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_CONVEX_URL) {
340
+ return process.env.NEXT_PUBLIC_CONVEX_URL;
341
+ }
342
+ // Fallback for backwards compatibility
343
+ if (typeof window !== 'undefined' && (window as any).__CONVEX_URL__) {
344
+ return (window as any).__CONVEX_URL__;
345
+ }
346
+ return null;
347
+ };
348
+
349
+ // Create client lazily
350
+ let client: ConvexReactClient | null = null;
351
+
352
+ function getClient() {
353
+ const url = getConvexUrl();
354
+ if (!url) return null;
355
+ if (!client || (client as any)._address !== url) {
356
+ client = new ConvexReactClient(url);
357
+ }
358
+ return client;
359
+ }
360
+
361
+ // Wrapper hooks that handle the case when Convex is not connected
362
+ export function useQuery(query: any, ...args: any[]) {
363
+ const url = getConvexUrl();
364
+ // When not connected, return undefined
365
+ if (!url) return undefined;
366
+ return useConvexQuery(query, ...args);
367
+ }
368
+
369
+ export function useMutation(mutation: any) {
370
+ const url = getConvexUrl();
371
+ const convexMutation = url ? useConvexMutation(mutation) : null;
372
+
373
+ return async (args: any) => {
374
+ if (!convexMutation) {
375
+ console.warn('Convex not connected - mutation ignored');
376
+ return;
377
+ }
378
+ return convexMutation(args);
379
+ };
380
+ }
381
+
382
+ export function ConvexProvider({ children }: { children: React.ReactNode }) {
383
+ const [convexUrl, setConvexUrl] = useState(getConvexUrl());
384
+
385
+ // Check for URL changes (after deploy)
386
+ useEffect(() => {
387
+ const checkUrl = () => {
388
+ const url = getConvexUrl();
389
+ if (url !== convexUrl) {
390
+ setConvexUrl(url);
391
+ }
392
+ };
393
+
394
+ // Check periodically for URL changes
395
+ const interval = setInterval(checkUrl, 1000);
396
+ return () => clearInterval(interval);
397
+ }, [convexUrl]);
398
+
399
+ const convexClient = getClient();
400
+
401
+ if (!convexClient) {
402
+ // Show a message when Convex is not configured
403
+ return (
404
+ <div className="min-h-screen bg-background font-sans antialiased">
405
+ <div className="flex flex-col items-center justify-center min-h-screen p-8 text-center">
406
+ <div className="max-w-md space-y-4">
407
+ <h2 className="text-2xl font-bold">Connect to Convex</h2>
408
+ <p className="text-muted-foreground">
409
+ Enter your Convex deploy key in the console panel and click "Deploy Schema" to connect.
410
+ </p>
411
+ <div className="p-4 bg-muted rounded-lg text-left text-sm">
412
+ <p className="font-medium mb-2">Files ready in /convex/:</p>
413
+ <ul className="space-y-1 text-muted-foreground">
414
+ <li>schema.ts - Database schema (todos table)</li>
415
+ <li>todos.ts - Query and mutation functions</li>
416
+ </ul>
417
+ </div>
418
+ <p className="text-xs text-muted-foreground">
419
+ Get a deploy key from your Convex dashboard at convex.dev
420
+ </p>
421
+ </div>
422
+ </div>
423
+ </div>
424
+ );
425
+ }
426
+
427
+ return (
428
+ <BaseConvexProvider client={convexClient}>
429
+ {children}
430
+ </BaseConvexProvider>
431
+ );
432
+ }
433
+ `);
434
+
435
+ // Create Button component (shadcn/ui style)
436
+ vfs.writeFileSync('/components/ui/button.tsx', `import React from 'react';
437
+ import { cn } from '../../lib/utils.ts';
438
+
439
+ const buttonVariants = {
440
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
441
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
442
+ outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
443
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
444
+ ghost: "hover:bg-accent hover:text-accent-foreground",
445
+ link: "text-primary underline-offset-4 hover:underline",
446
+ };
447
+
448
+ const buttonSizes = {
449
+ default: "h-10 px-4 py-2",
450
+ sm: "h-9 rounded-md px-3",
451
+ lg: "h-11 rounded-md px-8",
452
+ icon: "h-10 w-10",
453
+ };
454
+
455
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
456
+ variant?: keyof typeof buttonVariants;
457
+ size?: keyof typeof buttonSizes;
458
+ }
459
+
460
+ export function Button({
461
+ className,
462
+ variant = "default",
463
+ size = "default",
464
+ ...props
465
+ }: ButtonProps) {
466
+ return (
467
+ <button
468
+ className={cn(
469
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
470
+ buttonVariants[variant],
471
+ buttonSizes[size],
472
+ className
473
+ )}
474
+ {...props}
475
+ />
476
+ );
477
+ }
478
+ `);
479
+
480
+ // Create Card component
481
+ vfs.writeFileSync('/components/ui/card.tsx', `import React from 'react';
482
+ import { cn } from '../../lib/utils.ts';
483
+
484
+ export function Card({
485
+ className,
486
+ ...props
487
+ }: React.HTMLAttributes<HTMLDivElement>) {
488
+ return (
489
+ <div
490
+ className={cn(
491
+ "rounded-lg border bg-card text-card-foreground shadow-sm",
492
+ className
493
+ )}
494
+ {...props}
495
+ />
496
+ );
497
+ }
498
+
499
+ export function CardHeader({
500
+ className,
501
+ ...props
502
+ }: React.HTMLAttributes<HTMLDivElement>) {
503
+ return (
504
+ <div
505
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
506
+ {...props}
507
+ />
508
+ );
509
+ }
510
+
511
+ export function CardTitle({
512
+ className,
513
+ ...props
514
+ }: React.HTMLAttributes<HTMLHeadingElement>) {
515
+ return (
516
+ <h3
517
+ className={cn("text-2xl font-semibold leading-none tracking-tight", className)}
518
+ {...props}
519
+ />
520
+ );
521
+ }
522
+
523
+ export function CardDescription({
524
+ className,
525
+ ...props
526
+ }: React.HTMLAttributes<HTMLParagraphElement>) {
527
+ return (
528
+ <p
529
+ className={cn("text-sm text-muted-foreground", className)}
530
+ {...props}
531
+ />
532
+ );
533
+ }
534
+
535
+ export function CardContent({
536
+ className,
537
+ ...props
538
+ }: React.HTMLAttributes<HTMLDivElement>) {
539
+ return <div className={cn("p-6 pt-0", className)} {...props} />;
540
+ }
541
+
542
+ export function CardFooter({
543
+ className,
544
+ ...props
545
+ }: React.HTMLAttributes<HTMLDivElement>) {
546
+ return (
547
+ <div
548
+ className={cn("flex items-center p-6 pt-0", className)}
549
+ {...props}
550
+ />
551
+ );
552
+ }
553
+ `);
554
+
555
+ // Create Input component
556
+ vfs.writeFileSync('/components/ui/input.tsx', `import React from 'react';
557
+ import { cn } from '../../lib/utils.ts';
558
+
559
+ export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
560
+
561
+ export function Input({ className, type, ...props }: InputProps) {
562
+ return (
563
+ <input
564
+ type={type}
565
+ className={cn(
566
+ "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
567
+ className
568
+ )}
569
+ {...props}
570
+ />
571
+ );
572
+ }
573
+ `);
574
+
575
+ // Create Badge component
576
+ vfs.writeFileSync('/components/ui/badge.tsx', `import React from 'react';
577
+ import { cn } from '../../lib/utils.ts';
578
+
579
+ const badgeVariants = {
580
+ default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
581
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
582
+ destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
583
+ outline: "text-foreground",
584
+ success: "border-transparent bg-green-500 text-white",
585
+ warning: "border-transparent bg-yellow-500 text-white",
586
+ };
587
+
588
+ export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
589
+ variant?: keyof typeof badgeVariants;
590
+ }
591
+
592
+ export function Badge({ className, variant = "default", ...props }: BadgeProps) {
593
+ return (
594
+ <div
595
+ className={cn(
596
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
597
+ badgeVariants[variant],
598
+ className
599
+ )}
600
+ {...props}
601
+ />
602
+ );
603
+ }
604
+ `);
605
+
606
+ // Create Checkbox component
607
+ vfs.writeFileSync('/components/ui/checkbox.tsx', `import React from 'react';
608
+ import { cn } from '../../lib/utils.ts';
609
+
610
+ export interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
611
+ onCheckedChange?: (checked: boolean) => void;
612
+ }
613
+
614
+ export function Checkbox({ className, checked, onCheckedChange, ...props }: CheckboxProps) {
615
+ return (
616
+ <input
617
+ type="checkbox"
618
+ checked={checked}
619
+ onChange={(e) => onCheckedChange?.(e.target.checked)}
620
+ className={cn(
621
+ "h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
622
+ className
623
+ )}
624
+ {...props}
625
+ />
626
+ );
627
+ }
628
+ `);
629
+
630
+ // Create Select component (simplified)
631
+ vfs.writeFileSync('/components/ui/select.tsx', `import React from 'react';
632
+ import { cn } from '../../lib/utils.ts';
633
+
634
+ export interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {}
635
+
636
+ export function Select({ className, children, ...props }: SelectProps) {
637
+ return (
638
+ <select
639
+ className={cn(
640
+ "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
641
+ className
642
+ )}
643
+ {...props}
644
+ >
645
+ {children}
646
+ </select>
647
+ );
648
+ }
649
+ `);
650
+
651
+ // Create TaskList component (uses real Convex API)
652
+ vfs.writeFileSync('/components/task-list.tsx', `"use client";
653
+
654
+ import React from 'react';
655
+ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from './ui/card.tsx';
656
+ import { Button } from './ui/button.tsx';
657
+ import { Input } from './ui/input.tsx';
658
+ import { Badge } from './ui/badge.tsx';
659
+ import { Checkbox } from './ui/checkbox.tsx';
660
+ import { Select } from './ui/select.tsx';
661
+ import { useQuery, useMutation, api } from '../lib/convex.tsx';
662
+ import { cn } from '../lib/utils.ts';
663
+
664
+ type Todo = {
665
+ _id: string;
666
+ _creationTime: number;
667
+ title: string;
668
+ completed: boolean;
669
+ priority: "low" | "medium" | "high";
670
+ };
671
+
672
+ const priorityColors = {
673
+ low: "success" as const,
674
+ medium: "warning" as const,
675
+ high: "destructive" as const,
676
+ };
677
+
678
+ function TaskItem({
679
+ task,
680
+ onToggle,
681
+ onDelete
682
+ }: {
683
+ task: Todo;
684
+ onToggle: () => void;
685
+ onDelete: () => void;
686
+ }) {
687
+ return (
688
+ <div className={cn(
689
+ "flex items-center gap-4 p-4 border rounded-lg transition-all",
690
+ task.completed && "opacity-50 bg-muted"
691
+ )}>
692
+ <Checkbox
693
+ checked={task.completed}
694
+ onCheckedChange={onToggle}
695
+ />
696
+ <div className="flex-1 min-w-0">
697
+ <p className={cn(
698
+ "font-medium truncate",
699
+ task.completed && "line-through text-muted-foreground"
700
+ )}>
701
+ {task.title}
702
+ </p>
703
+ <p className="text-xs text-muted-foreground">
704
+ Created {new Date(task._creationTime).toLocaleDateString()}
705
+ </p>
706
+ </div>
707
+ <Badge variant={priorityColors[task.priority]}>
708
+ {task.priority}
709
+ </Badge>
710
+ <Button
711
+ variant="ghost"
712
+ size="sm"
713
+ onClick={onDelete}
714
+ className="text-destructive hover:text-destructive"
715
+ >
716
+ Delete
717
+ </Button>
718
+ </div>
719
+ );
720
+ }
721
+
722
+ export function TaskList() {
723
+ const todos = useQuery(api.todos.list) as Todo[] | undefined;
724
+ const createTodo = useMutation(api.todos.create);
725
+ const toggleTodo = useMutation(api.todos.toggle);
726
+ const removeTodo = useMutation(api.todos.remove);
727
+
728
+ const [newTitle, setNewTitle] = React.useState("");
729
+ const [priority, setPriority] = React.useState<Todo["priority"]>("medium");
730
+
731
+ const handleSubmit = async (e: React.FormEvent) => {
732
+ e.preventDefault();
733
+ if (!newTitle.trim()) return;
734
+
735
+ await createTodo({ title: newTitle.trim(), priority });
736
+ setNewTitle("");
737
+ };
738
+
739
+ const completedCount = todos?.filter(t => t.completed).length ?? 0;
740
+ const totalCount = todos?.length ?? 0;
741
+
742
+ return (
743
+ <Card className="w-full max-w-2xl mx-auto">
744
+ <CardHeader>
745
+ <CardTitle className="flex items-center gap-2">
746
+ Task Manager
747
+ <Badge variant="secondary">
748
+ {completedCount}/{totalCount} done
749
+ </Badge>
750
+ </CardTitle>
751
+ <CardDescription>
752
+ Real-time sync powered by Convex - running from the browser!
753
+ </CardDescription>
754
+ </CardHeader>
755
+ <CardContent className="space-y-4">
756
+ <form onSubmit={handleSubmit} className="flex gap-2">
757
+ <Input
758
+ placeholder="Add a new task..."
759
+ value={newTitle}
760
+ onChange={(e) => setNewTitle(e.target.value)}
761
+ className="flex-1"
762
+ />
763
+ <Select
764
+ value={priority}
765
+ onChange={(e) => setPriority(e.target.value as Todo["priority"])}
766
+ className="w-32"
767
+ >
768
+ <option value="low">Low</option>
769
+ <option value="medium">Medium</option>
770
+ <option value="high">High</option>
771
+ </Select>
772
+ <Button type="submit">Add Task</Button>
773
+ </form>
774
+
775
+ <div className="space-y-2">
776
+ {todos === undefined ? (
777
+ <div className="text-center py-8 text-muted-foreground">
778
+ Loading tasks...
779
+ </div>
780
+ ) : todos.length === 0 ? (
781
+ <div className="text-center py-8 text-muted-foreground">
782
+ No tasks yet. Add one above!
783
+ </div>
784
+ ) : (
785
+ todos.map((task) => (
786
+ <TaskItem
787
+ key={task._id}
788
+ task={task}
789
+ onToggle={() => toggleTodo({ id: task._id })}
790
+ onDelete={() => removeTodo({ id: task._id })}
791
+ />
792
+ ))
793
+ )}
794
+ </div>
795
+ </CardContent>
796
+ </Card>
797
+ );
798
+ }
799
+ `);
800
+
801
+ // Create root layout (App Router)
802
+ // Note: In browser environment, we don't use <html>/<head>/<body> tags
803
+ // since we're rendering inside an existing HTML document's #__next div
804
+ vfs.writeFileSync('/app/layout.tsx', `import React from 'react';
805
+ import './globals.css';
806
+ import { ConvexProvider } from '../lib/convex.tsx';
807
+
808
+ export const metadata = {
809
+ title: 'Convex App Demo',
810
+ description: 'A realistic Next.js + Convex app running in the browser',
811
+ };
812
+
813
+ export default function RootLayout({
814
+ children,
815
+ }: {
816
+ children: React.ReactNode;
817
+ }) {
818
+ return (
819
+ <ConvexProvider>
820
+ <div className="min-h-screen bg-background font-sans antialiased">
821
+ <div className="relative flex min-h-screen flex-col">
822
+ <header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
823
+ <div className="container flex h-14 items-center">
824
+ <div className="mr-4 flex">
825
+ <a href="/" className="mr-6 flex items-center space-x-2">
826
+ <span className="font-bold text-xl">TaskApp</span>
827
+ </a>
828
+ <nav className="flex items-center space-x-6 text-sm font-medium">
829
+ <a href="/" className="transition-colors hover:text-foreground/80 text-foreground">
830
+ Home
831
+ </a>
832
+ <a href="/tasks" className="transition-colors hover:text-foreground/80 text-muted-foreground">
833
+ Tasks
834
+ </a>
835
+ <a href="/about" className="transition-colors hover:text-foreground/80 text-muted-foreground">
836
+ About
837
+ </a>
838
+ </nav>
839
+ </div>
840
+ </div>
841
+ </header>
842
+ <main className="flex-1">
843
+ {children}
844
+ </main>
845
+ <footer className="border-t py-6 md:py-0">
846
+ <div className="container flex flex-col items-center justify-between gap-4 md:h-14 md:flex-row">
847
+ <p className="text-center text-sm leading-loose text-muted-foreground">
848
+ Running in browser with virtual Node.js
849
+ </p>
850
+ </div>
851
+ </footer>
852
+ </div>
853
+ </div>
854
+ </ConvexProvider>
855
+ );
856
+ }
857
+ `);
858
+
859
+ // Create home page (App Router) - Shows TaskList directly
860
+ vfs.writeFileSync('/app/page.tsx', `"use client";
861
+
862
+ import React from 'react';
863
+ import { TaskList } from '../components/task-list.tsx';
864
+
865
+ export default function HomePage() {
866
+ return (
867
+ <div className="container py-10">
868
+ <div className="mb-8 text-center">
869
+ <h1 className="text-3xl font-bold tracking-tight">Task Manager</h1>
870
+ <p className="text-muted-foreground mt-2">
871
+ Real-time sync powered by Convex - running in the browser!
872
+ </p>
873
+ </div>
874
+ <TaskList />
875
+ </div>
876
+ );
877
+ }
878
+ `);
879
+
880
+ // Create original home page content as a separate page (for reference)
881
+ vfs.writeFileSync('/app/features/page.tsx', `import React from 'react';
882
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../components/ui/card.tsx';
883
+ import { Button } from '../../components/ui/button.tsx';
884
+ import { Badge } from '../../components/ui/badge.tsx';
885
+
886
+ export default function FeaturesPage() {
887
+ return (
888
+ <div className="container py-10">
889
+ {/* Feature Cards */}
890
+ <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
891
+ <Card>
892
+ <CardHeader>
893
+ <CardTitle className="flex items-center gap-2">
894
+ ⚡ React 18
895
+ </CardTitle>
896
+ <CardDescription>
897
+ Latest React with Concurrent features
898
+ </CardDescription>
899
+ </CardHeader>
900
+ <CardContent>
901
+ <p className="text-sm text-muted-foreground">
902
+ Using React 18 with automatic batching, Suspense,
903
+ and concurrent rendering for optimal performance.
904
+ </p>
905
+ </CardContent>
906
+ </Card>
907
+
908
+ <Card>
909
+ <CardHeader>
910
+ <CardTitle className="flex items-center gap-2">
911
+ 🎨 shadcn/ui
912
+ </CardTitle>
913
+ <CardDescription>
914
+ Beautiful, accessible components
915
+ </CardDescription>
916
+ </CardHeader>
917
+ <CardContent>
918
+ <p className="text-sm text-muted-foreground">
919
+ Beautifully designed components built with Radix UI
920
+ primitives and Tailwind CSS.
921
+ </p>
922
+ </CardContent>
923
+ </Card>
924
+
925
+ <Card>
926
+ <CardHeader>
927
+ <CardTitle className="flex items-center gap-2">
928
+ 🔄 Convex (Mock)
929
+ </CardTitle>
930
+ <CardDescription>
931
+ Real-time data sync simulation
932
+ </CardDescription>
933
+ </CardHeader>
934
+ <CardContent>
935
+ <p className="text-sm text-muted-foreground">
936
+ Demonstrates the Convex pattern with useQuery and
937
+ useMutation hooks using mock data.
938
+ </p>
939
+ </CardContent>
940
+ </Card>
941
+
942
+ <Card>
943
+ <CardHeader>
944
+ <CardTitle className="flex items-center gap-2">
945
+ 🎯 TypeScript
946
+ </CardTitle>
947
+ <CardDescription>
948
+ Full type safety
949
+ </CardDescription>
950
+ </CardHeader>
951
+ <CardContent>
952
+ <p className="text-sm text-muted-foreground">
953
+ Written in TypeScript with strict mode enabled
954
+ for maximum type safety and developer experience.
955
+ </p>
956
+ </CardContent>
957
+ </Card>
958
+
959
+ <Card>
960
+ <CardHeader>
961
+ <CardTitle className="flex items-center gap-2">
962
+ 📱 Responsive
963
+ </CardTitle>
964
+ <CardDescription>
965
+ Mobile-first design
966
+ </CardDescription>
967
+ </CardHeader>
968
+ <CardContent>
969
+ <p className="text-sm text-muted-foreground">
970
+ Fully responsive design that works great on any device,
971
+ from mobile phones to desktop monitors.
972
+ </p>
973
+ </CardContent>
974
+ </Card>
975
+
976
+ <Card>
977
+ <CardHeader>
978
+ <CardTitle className="flex items-center gap-2">
979
+ 🌐 Browser Runtime
980
+ </CardTitle>
981
+ <CardDescription>
982
+ No server required
983
+ </CardDescription>
984
+ </CardHeader>
985
+ <CardContent>
986
+ <p className="text-sm text-muted-foreground">
987
+ Running entirely in the browser using virtual Node.js
988
+ shims and Service Workers.
989
+ </p>
990
+ </CardContent>
991
+ </Card>
992
+ </div>
993
+ </div>
994
+ );
995
+ }
996
+ `);
997
+
998
+ // Create features directory
999
+ vfs.mkdirSync('/app/features', { recursive: true });
1000
+
1001
+ // Create tasks page (App Router)
1002
+ vfs.writeFileSync('/app/tasks/page.tsx', `"use client";
1003
+
1004
+ import React from 'react';
1005
+ import { TaskList } from '../../components/task-list.tsx';
1006
+
1007
+ export default function TasksPage() {
1008
+ return (
1009
+ <div className="container py-10">
1010
+ <div className="mb-8 text-center">
1011
+ <h1 className="text-3xl font-bold tracking-tight">Task Manager</h1>
1012
+ <p className="text-muted-foreground mt-2">
1013
+ Add, complete, and manage your tasks
1014
+ </p>
1015
+ </div>
1016
+ <TaskList />
1017
+ </div>
1018
+ );
1019
+ }
1020
+ `);
1021
+
1022
+ // Create about page (App Router)
1023
+ vfs.writeFileSync('/app/about/page.tsx', `import React from 'react';
1024
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../components/ui/card.tsx';
1025
+ import { Badge } from '../../components/ui/badge.tsx';
1026
+
1027
+ export default function AboutPage() {
1028
+ return (
1029
+ <div className="container py-10 max-w-3xl">
1030
+ <div className="mb-8">
1031
+ <Badge variant="outline" className="mb-4">About</Badge>
1032
+ <h1 className="text-3xl font-bold tracking-tight">How It Works</h1>
1033
+ <p className="text-muted-foreground mt-2">
1034
+ This demo showcases running a complex Next.js application entirely in the browser.
1035
+ </p>
1036
+ </div>
1037
+
1038
+ <div className="space-y-6">
1039
+ <Card>
1040
+ <CardHeader>
1041
+ <CardTitle>Virtual File System</CardTitle>
1042
+ <CardDescription>In-memory file system simulation</CardDescription>
1043
+ </CardHeader>
1044
+ <CardContent className="prose prose-sm">
1045
+ <p>
1046
+ All project files exist in a virtual file system (VFS) in memory.
1047
+ This includes React components, configuration files, and even
1048
+ npm package contents.
1049
+ </p>
1050
+ </CardContent>
1051
+ </Card>
1052
+
1053
+ <Card>
1054
+ <CardHeader>
1055
+ <CardTitle>Node.js Shims</CardTitle>
1056
+ <CardDescription>Browser-compatible Node.js APIs</CardDescription>
1057
+ </CardHeader>
1058
+ <CardContent className="prose prose-sm">
1059
+ <p>
1060
+ Core Node.js modules like <code>fs</code>, <code>path</code>, <code>crypto</code>,
1061
+ <code>stream</code>, and <code>http</code> are shimmed to work in the browser
1062
+ using Web APIs.
1063
+ </p>
1064
+ </CardContent>
1065
+ </Card>
1066
+
1067
+ <Card>
1068
+ <CardHeader>
1069
+ <CardTitle>esbuild-wasm</CardTitle>
1070
+ <CardDescription>Fast JSX/TypeScript compilation</CardDescription>
1071
+ </CardHeader>
1072
+ <CardContent className="prose prose-sm">
1073
+ <p>
1074
+ JSX and TypeScript files are transformed to JavaScript in real-time
1075
+ using esbuild-wasm, which runs WebAssembly in the browser.
1076
+ </p>
1077
+ </CardContent>
1078
+ </Card>
1079
+
1080
+ <Card>
1081
+ <CardHeader>
1082
+ <CardTitle>Service Worker</CardTitle>
1083
+ <CardDescription>Request interception and routing</CardDescription>
1084
+ </CardHeader>
1085
+ <CardContent className="prose prose-sm">
1086
+ <p>
1087
+ A Service Worker intercepts HTTP requests and routes them to the
1088
+ virtual dev server, enabling file-based routing without a real backend.
1089
+ </p>
1090
+ </CardContent>
1091
+ </Card>
1092
+
1093
+ <Card>
1094
+ <CardHeader>
1095
+ <CardTitle>Convex Mock</CardTitle>
1096
+ <CardDescription>Simulated real-time database</CardDescription>
1097
+ </CardHeader>
1098
+ <CardContent className="prose prose-sm">
1099
+ <p>
1100
+ The Convex client is mocked to demonstrate the pattern of using
1101
+ <code>useQuery</code> and <code>useMutation</code> hooks. In production,
1102
+ this would connect to a real Convex backend.
1103
+ </p>
1104
+ </CardContent>
1105
+ </Card>
1106
+ </div>
1107
+ </div>
1108
+ );
1109
+ }
1110
+ `);
1111
+
1112
+ // Create API route
1113
+ vfs.writeFileSync('/pages/api/health.js', `export default function handler(req, res) {
1114
+ res.status(200).json({
1115
+ status: 'ok',
1116
+ timestamp: new Date().toISOString(),
1117
+ runtime: 'browser-node-shim'
1118
+ });
1119
+ }
1120
+ `);
1121
+
1122
+ // Create public files
1123
+ vfs.writeFileSync('/public/favicon.ico', 'favicon placeholder');
1124
+ vfs.writeFileSync('/public/robots.txt', 'User-agent: *\nAllow: /');
1125
+ }
1126
+
1127
+ /**
1128
+ * Initialize the Convex App demo
1129
+ */
1130
+ export async function initConvexAppDemo(
1131
+ outputElement: HTMLElement,
1132
+ options: {
1133
+ installPackages?: boolean;
1134
+ } = {}
1135
+ ): Promise<{ vfs: VirtualFS; runtime: Runtime }> {
1136
+ const log = (message: string) => {
1137
+ const line = document.createElement('div');
1138
+ line.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
1139
+ outputElement.appendChild(line);
1140
+ outputElement.scrollTop = outputElement.scrollHeight;
1141
+ };
1142
+
1143
+ log('Creating virtual file system...');
1144
+ const vfs = new VirtualFS();
1145
+
1146
+ log('Creating Convex App project structure...');
1147
+ createConvexAppProject(vfs);
1148
+
1149
+ // Optionally install npm packages
1150
+ if (options.installPackages) {
1151
+ log('Installing npm packages (this may take a while)...');
1152
+ const npm = new PackageManager(vfs);
1153
+
1154
+ for (const pkg of DEMO_PACKAGES) {
1155
+ try {
1156
+ log(`Installing ${pkg}...`);
1157
+ await npm.install(pkg, {
1158
+ onProgress: (msg) => log(` ${msg}`),
1159
+ });
1160
+ } catch (error) {
1161
+ log(`Warning: Failed to install ${pkg}: ${error}`);
1162
+ }
1163
+ }
1164
+ }
1165
+
1166
+ log('Initializing runtime...');
1167
+ const runtime = new Runtime(vfs, {
1168
+ cwd: '/',
1169
+ env: {
1170
+ NODE_ENV: 'development',
1171
+ },
1172
+ onConsole: (method, args) => {
1173
+ const prefix = method === 'error' ? '[ERROR]' : method === 'warn' ? '[WARN]' : '';
1174
+ log(`${prefix} ${args.map((a) => String(a)).join(' ')}`);
1175
+ },
1176
+ });
1177
+
1178
+ log('Setting up file watcher...');
1179
+ vfs.watch('/app', { recursive: true }, (eventType, filename) => {
1180
+ log(`File ${eventType}: ${filename}`);
1181
+ });
1182
+
1183
+ log('Convex App demo initialized!');
1184
+ log('');
1185
+ log('Project structure:');
1186
+ listFiles(vfs, '/', log, ' ');
1187
+
1188
+ return { vfs, runtime };
1189
+ }
1190
+
1191
+ /**
1192
+ * Start the dev server for Convex App demo
1193
+ */
1194
+ export async function startConvexAppDevServer(
1195
+ vfs: VirtualFS,
1196
+ options: {
1197
+ port?: number;
1198
+ log?: (message: string) => void;
1199
+ } = {}
1200
+ ): Promise<{
1201
+ server: NextDevServer;
1202
+ url: string;
1203
+ stop: () => void;
1204
+ }> {
1205
+ const port = options.port || 3002;
1206
+ const log = options.log || console.log;
1207
+
1208
+ log('Starting Convex App dev server...');
1209
+
1210
+ // Create NextDevServer with App Router preference
1211
+ const server = new NextDevServer(vfs, {
1212
+ port,
1213
+ root: '/',
1214
+ preferAppRouter: true,
1215
+ });
1216
+
1217
+ // Get the server bridge
1218
+ const bridge = getServerBridge();
1219
+
1220
+ // Initialize Service Worker
1221
+ try {
1222
+ log('Initializing Service Worker...');
1223
+ await bridge.initServiceWorker();
1224
+ log('Service Worker ready');
1225
+ } catch (error) {
1226
+ log(`Warning: Service Worker failed to initialize: ${error}`);
1227
+ log('Falling back to direct request handling...');
1228
+ }
1229
+
1230
+ // Register event handlers
1231
+ bridge.on('server-ready', (p: unknown, u: unknown) => {
1232
+ log(`Server ready at ${u}`);
1233
+ });
1234
+
1235
+ // Wire up the NextDevServer to handle requests through the bridge
1236
+ const httpServer = createHttpServerWrapper(server);
1237
+ bridge.registerServer(httpServer, port);
1238
+
1239
+ // Start watching for file changes
1240
+ server.start();
1241
+ log('File watcher started');
1242
+
1243
+ // Set up HMR event forwarding
1244
+ server.on('hmr-update', (update: unknown) => {
1245
+ log(`HMR update: ${JSON.stringify(update)}`);
1246
+ });
1247
+
1248
+ const url = bridge.getServerUrl(port);
1249
+ log(`Convex App dev server running at: ${url}/`);
1250
+
1251
+ return {
1252
+ server,
1253
+ url: url + '/',
1254
+ stop: () => {
1255
+ server.stop();
1256
+ bridge.unregisterServer(port);
1257
+ },
1258
+ };
1259
+ }
1260
+
1261
+ /**
1262
+ * Create an http.Server-compatible wrapper
1263
+ */
1264
+ function createHttpServerWrapper(devServer: NextDevServer) {
1265
+ return {
1266
+ listening: true,
1267
+ address: () => ({ port: devServer.getPort(), address: '0.0.0.0', family: 'IPv4' }),
1268
+ async handleRequest(
1269
+ method: string,
1270
+ url: string,
1271
+ headers: Record<string, string>,
1272
+ body?: string | Buffer
1273
+ ) {
1274
+ const bodyBuffer = body
1275
+ ? typeof body === 'string'
1276
+ ? Buffer.from(body)
1277
+ : body
1278
+ : undefined;
1279
+ return devServer.handleRequest(method, url, headers, bodyBuffer);
1280
+ },
1281
+ };
1282
+ }
1283
+
1284
+ function listFiles(
1285
+ vfs: VirtualFS,
1286
+ path: string,
1287
+ log: (msg: string) => void,
1288
+ indent: string
1289
+ ): void {
1290
+ try {
1291
+ const entries = vfs.readdirSync(path);
1292
+ for (const entry of entries) {
1293
+ if (entry === 'node_modules') {
1294
+ log(`${indent}${entry}/ (skipped)`);
1295
+ continue;
1296
+ }
1297
+ const fullPath = path === '/' ? `/${entry}` : `${path}/${entry}`;
1298
+ try {
1299
+ const stat = vfs.statSync(fullPath);
1300
+ if (stat.isDirectory()) {
1301
+ log(`${indent}${entry}/`);
1302
+ listFiles(vfs, fullPath, log, indent + ' ');
1303
+ } else {
1304
+ log(`${indent}${entry}`);
1305
+ }
1306
+ } catch {
1307
+ log(`${indent}${entry}`);
1308
+ }
1309
+ }
1310
+ } catch {
1311
+ // Directory doesn't exist or can't be read
1312
+ }
1313
+ }
1314
+
1315
+ // Export for use in HTML demos
1316
+ export { PACKAGE_JSON, DEMO_PACKAGES };