@korajs/cli 0.0.0-canary-20261008174129

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 (310) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -0
  3. package/dist/bin.cjs +9465 -0
  4. package/dist/bin.cjs.map +1 -0
  5. package/dist/bin.js +3150 -0
  6. package/dist/bin.js.map +1 -0
  7. package/dist/chunk-5NI2FEQL.js +92 -0
  8. package/dist/chunk-5NI2FEQL.js.map +1 -0
  9. package/dist/chunk-6C4BHSRA.js +82 -0
  10. package/dist/chunk-6C4BHSRA.js.map +1 -0
  11. package/dist/chunk-7Y4J2GXP.js +796 -0
  12. package/dist/chunk-7Y4J2GXP.js.map +1 -0
  13. package/dist/chunk-BURFQVMV.js +719 -0
  14. package/dist/chunk-BURFQVMV.js.map +1 -0
  15. package/dist/chunk-TVIRMDER.js +2569 -0
  16. package/dist/chunk-TVIRMDER.js.map +1 -0
  17. package/dist/create.cjs +1368 -0
  18. package/dist/create.cjs.map +1 -0
  19. package/dist/create.js +11 -0
  20. package/dist/create.js.map +1 -0
  21. package/dist/index.cjs +3489 -0
  22. package/dist/index.cjs.map +1 -0
  23. package/dist/index.d.cts +772 -0
  24. package/dist/index.d.ts +772 -0
  25. package/dist/index.js +129 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/lab-manager-KUDII6BT.js +280 -0
  28. package/dist/lab-manager-KUDII6BT.js.map +1 -0
  29. package/dist/schema-loader-GGHECMTM.js +9 -0
  30. package/dist/schema-loader-GGHECMTM.js.map +1 -0
  31. package/dist/spectator-manager-FTH2NFSE.js +145 -0
  32. package/dist/spectator-manager-FTH2NFSE.js.map +1 -0
  33. package/dist/studio-server-XLGZNGG5.js +1747 -0
  34. package/dist/studio-server-XLGZNGG5.js.map +1 -0
  35. package/dist/vite.cjs +351 -0
  36. package/dist/vite.cjs.map +1 -0
  37. package/dist/vite.d.cts +161 -0
  38. package/dist/vite.d.ts +161 -0
  39. package/dist/vite.js +321 -0
  40. package/dist/vite.js.map +1 -0
  41. package/package.json +79 -0
  42. package/templates/react-basic/.env.example +3 -0
  43. package/templates/react-basic/AGENTS.md +87 -0
  44. package/templates/react-basic/README.md.hbs +62 -0
  45. package/templates/react-basic/index.html.hbs +12 -0
  46. package/templates/react-basic/kora.config.ts +13 -0
  47. package/templates/react-basic/package.json.hbs +35 -0
  48. package/templates/react-basic/src/App.tsx +132 -0
  49. package/templates/react-basic/src/index.css +287 -0
  50. package/templates/react-basic/src/kora-worker.ts +9 -0
  51. package/templates/react-basic/src/main.tsx +24 -0
  52. package/templates/react-basic/src/modules/todos/todo.mutations.ts +25 -0
  53. package/templates/react-basic/src/modules/todos/todo.queries.ts +5 -0
  54. package/templates/react-basic/src/modules/todos/todo.schema.ts +10 -0
  55. package/templates/react-basic/src/modules/todos/useTodos.ts +31 -0
  56. package/templates/react-basic/src/schema.ts +9 -0
  57. package/templates/react-basic/src/vite-env.d.ts +18 -0
  58. package/templates/react-basic/tsconfig.json.hbs +14 -0
  59. package/templates/react-basic/vite.config.ts +88 -0
  60. package/templates/react-sync/.env.example +25 -0
  61. package/templates/react-sync/AGENTS.md +87 -0
  62. package/templates/react-sync/README.md.hbs +106 -0
  63. package/templates/react-sync/index.html.hbs +12 -0
  64. package/templates/react-sync/kora.config.ts +17 -0
  65. package/templates/react-sync/package.json.hbs +44 -0
  66. package/templates/react-sync/server.ts +141 -0
  67. package/templates/react-sync/src/App.tsx +160 -0
  68. package/templates/react-sync/src/auth.ts +25 -0
  69. package/templates/react-sync/src/index.css +350 -0
  70. package/templates/react-sync/src/kora-worker.ts +9 -0
  71. package/templates/react-sync/src/main.tsx +52 -0
  72. package/templates/react-sync/src/modules/todos/todo.mutations.ts +25 -0
  73. package/templates/react-sync/src/modules/todos/todo.queries.ts +5 -0
  74. package/templates/react-sync/src/modules/todos/todo.schema.ts +10 -0
  75. package/templates/react-sync/src/modules/todos/useTodos.ts +31 -0
  76. package/templates/react-sync/src/schema.ts +9 -0
  77. package/templates/react-sync/src/vite-env.d.ts +19 -0
  78. package/templates/react-sync/tsconfig.json.hbs +14 -0
  79. package/templates/react-sync/vite.config.ts +105 -0
  80. package/templates/react-tailwind/.env.example +3 -0
  81. package/templates/react-tailwind/AGENTS.md +87 -0
  82. package/templates/react-tailwind/README.md.hbs +62 -0
  83. package/templates/react-tailwind/index.html.hbs +12 -0
  84. package/templates/react-tailwind/kora.config.ts +13 -0
  85. package/templates/react-tailwind/package.json.hbs +38 -0
  86. package/templates/react-tailwind/src/App.tsx +201 -0
  87. package/templates/react-tailwind/src/index.css +7 -0
  88. package/templates/react-tailwind/src/kora-worker.ts +9 -0
  89. package/templates/react-tailwind/src/main.tsx +31 -0
  90. package/templates/react-tailwind/src/modules/todos/todo.mutations.ts +25 -0
  91. package/templates/react-tailwind/src/modules/todos/todo.queries.ts +5 -0
  92. package/templates/react-tailwind/src/modules/todos/todo.schema.ts +10 -0
  93. package/templates/react-tailwind/src/modules/todos/useTodos.ts +31 -0
  94. package/templates/react-tailwind/src/schema.ts +9 -0
  95. package/templates/react-tailwind/src/vite-env.d.ts +18 -0
  96. package/templates/react-tailwind/tsconfig.json.hbs +14 -0
  97. package/templates/react-tailwind/vite.config.ts +95 -0
  98. package/templates/react-tailwind-sync/.env.example +25 -0
  99. package/templates/react-tailwind-sync/AGENTS.md +89 -0
  100. package/templates/react-tailwind-sync/README.md.hbs +106 -0
  101. package/templates/react-tailwind-sync/index.html.hbs +12 -0
  102. package/templates/react-tailwind-sync/kora.config.ts +17 -0
  103. package/templates/react-tailwind-sync/package.json.hbs +47 -0
  104. package/templates/react-tailwind-sync/server.ts +141 -0
  105. package/templates/react-tailwind-sync/src/App.tsx +293 -0
  106. package/templates/react-tailwind-sync/src/auth.ts +25 -0
  107. package/templates/react-tailwind-sync/src/index.css +7 -0
  108. package/templates/react-tailwind-sync/src/kora-worker.ts +9 -0
  109. package/templates/react-tailwind-sync/src/kora.ts +32 -0
  110. package/templates/react-tailwind-sync/src/main.tsx +44 -0
  111. package/templates/react-tailwind-sync/src/modules/todos/todo.mutations.ts +21 -0
  112. package/templates/react-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
  113. package/templates/react-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
  114. package/templates/react-tailwind-sync/src/modules/todos/useTodos.ts +31 -0
  115. package/templates/react-tailwind-sync/src/schema.ts +9 -0
  116. package/templates/react-tailwind-sync/src/vite-env.d.ts +19 -0
  117. package/templates/react-tailwind-sync/tsconfig.json.hbs +14 -0
  118. package/templates/react-tailwind-sync/vite.config.ts +112 -0
  119. package/templates/svelte-basic/.env.example +3 -0
  120. package/templates/svelte-basic/AGENTS.md +76 -0
  121. package/templates/svelte-basic/README.md.hbs +62 -0
  122. package/templates/svelte-basic/index.html.hbs +12 -0
  123. package/templates/svelte-basic/kora.config.ts +12 -0
  124. package/templates/svelte-basic/package.json.hbs +33 -0
  125. package/templates/svelte-basic/src/App.svelte +128 -0
  126. package/templates/svelte-basic/src/Root.svelte +14 -0
  127. package/templates/svelte-basic/src/index.css +287 -0
  128. package/templates/svelte-basic/src/kora-worker.ts +9 -0
  129. package/templates/svelte-basic/src/main.ts +16 -0
  130. package/templates/svelte-basic/src/modules/todos/todo.mutations.ts +25 -0
  131. package/templates/svelte-basic/src/modules/todos/todo.queries.ts +5 -0
  132. package/templates/svelte-basic/src/modules/todos/todo.schema.ts +10 -0
  133. package/templates/svelte-basic/src/modules/todos/useTodos.ts +25 -0
  134. package/templates/svelte-basic/src/schema.ts +9 -0
  135. package/templates/svelte-basic/src/vite-env.d.ts +12 -0
  136. package/templates/svelte-basic/svelte.config.js +5 -0
  137. package/templates/svelte-basic/tsconfig.json.hbs +14 -0
  138. package/templates/svelte-basic/vite.config.ts +57 -0
  139. package/templates/svelte-sync/.env.example +25 -0
  140. package/templates/svelte-sync/AGENTS.md +76 -0
  141. package/templates/svelte-sync/README.md.hbs +106 -0
  142. package/templates/svelte-sync/index.html.hbs +12 -0
  143. package/templates/svelte-sync/kora.config.ts +17 -0
  144. package/templates/svelte-sync/package.json.hbs +43 -0
  145. package/templates/svelte-sync/server.ts +141 -0
  146. package/templates/svelte-sync/src/App.svelte +151 -0
  147. package/templates/svelte-sync/src/Root.svelte +22 -0
  148. package/templates/svelte-sync/src/auth.ts +25 -0
  149. package/templates/svelte-sync/src/index.css +339 -0
  150. package/templates/svelte-sync/src/kora-worker.ts +9 -0
  151. package/templates/svelte-sync/src/main.ts +32 -0
  152. package/templates/svelte-sync/src/modules/todos/todo.mutations.ts +25 -0
  153. package/templates/svelte-sync/src/modules/todos/todo.queries.ts +5 -0
  154. package/templates/svelte-sync/src/modules/todos/todo.schema.ts +10 -0
  155. package/templates/svelte-sync/src/modules/todos/useTodos.ts +25 -0
  156. package/templates/svelte-sync/src/schema.ts +9 -0
  157. package/templates/svelte-sync/src/vite-env.d.ts +22 -0
  158. package/templates/svelte-sync/svelte.config.js +5 -0
  159. package/templates/svelte-sync/tsconfig.json.hbs +14 -0
  160. package/templates/svelte-sync/vite.config.ts +91 -0
  161. package/templates/svelte-tailwind/AGENTS.md +76 -0
  162. package/templates/svelte-tailwind/README.md.hbs +62 -0
  163. package/templates/svelte-tailwind/index.html.hbs +12 -0
  164. package/templates/svelte-tailwind/kora.config.ts +12 -0
  165. package/templates/svelte-tailwind/package.json.hbs +36 -0
  166. package/templates/svelte-tailwind/src/App.svelte +76 -0
  167. package/templates/svelte-tailwind/src/Root.svelte +14 -0
  168. package/templates/svelte-tailwind/src/index.css +7 -0
  169. package/templates/svelte-tailwind/src/kora-worker.ts +9 -0
  170. package/templates/svelte-tailwind/src/main.ts +16 -0
  171. package/templates/svelte-tailwind/src/modules/todos/todo.mutations.ts +25 -0
  172. package/templates/svelte-tailwind/src/modules/todos/todo.queries.ts +5 -0
  173. package/templates/svelte-tailwind/src/modules/todos/todo.schema.ts +10 -0
  174. package/templates/svelte-tailwind/src/modules/todos/useTodos.ts +25 -0
  175. package/templates/svelte-tailwind/src/schema.ts +9 -0
  176. package/templates/svelte-tailwind/src/vite-env.d.ts +12 -0
  177. package/templates/svelte-tailwind/svelte.config.js +5 -0
  178. package/templates/svelte-tailwind/tsconfig.json.hbs +14 -0
  179. package/templates/svelte-tailwind/vite.config.ts +64 -0
  180. package/templates/svelte-tailwind-sync/.env.example +25 -0
  181. package/templates/svelte-tailwind-sync/AGENTS.md +76 -0
  182. package/templates/svelte-tailwind-sync/README.md.hbs +106 -0
  183. package/templates/svelte-tailwind-sync/index.html.hbs +12 -0
  184. package/templates/svelte-tailwind-sync/kora.config.ts +17 -0
  185. package/templates/svelte-tailwind-sync/package.json.hbs +46 -0
  186. package/templates/svelte-tailwind-sync/server.ts +141 -0
  187. package/templates/svelte-tailwind-sync/src/App.svelte +193 -0
  188. package/templates/svelte-tailwind-sync/src/Root.svelte +26 -0
  189. package/templates/svelte-tailwind-sync/src/auth.ts +25 -0
  190. package/templates/svelte-tailwind-sync/src/index.css +7 -0
  191. package/templates/svelte-tailwind-sync/src/kora-worker.ts +9 -0
  192. package/templates/svelte-tailwind-sync/src/main.ts +32 -0
  193. package/templates/svelte-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
  194. package/templates/svelte-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
  195. package/templates/svelte-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
  196. package/templates/svelte-tailwind-sync/src/modules/todos/useTodos.ts +25 -0
  197. package/templates/svelte-tailwind-sync/src/schema.ts +9 -0
  198. package/templates/svelte-tailwind-sync/src/vite-env.d.ts +22 -0
  199. package/templates/svelte-tailwind-sync/svelte.config.js +5 -0
  200. package/templates/svelte-tailwind-sync/tsconfig.json.hbs +14 -0
  201. package/templates/svelte-tailwind-sync/vite.config.ts +98 -0
  202. package/templates/tauri-react/.env.example +27 -0
  203. package/templates/tauri-react/.github/workflows/release-desktop.yml +107 -0
  204. package/templates/tauri-react/AGENTS.md +91 -0
  205. package/templates/tauri-react/README.md.hbs +183 -0
  206. package/templates/tauri-react/dev.ts +37 -0
  207. package/templates/tauri-react/index.html.hbs +16 -0
  208. package/templates/tauri-react/kora.config.ts +17 -0
  209. package/templates/tauri-react/package.json.hbs +46 -0
  210. package/templates/tauri-react/server.ts +148 -0
  211. package/templates/tauri-react/src/App.tsx +803 -0
  212. package/templates/tauri-react/src/AppShell.tsx +158 -0
  213. package/templates/tauri-react/src/SetupScreen.tsx +288 -0
  214. package/templates/tauri-react/src/auth.ts +45 -0
  215. package/templates/tauri-react/src/main.tsx +9 -0
  216. package/templates/tauri-react/src/modules/todos/todo.mutations.ts +25 -0
  217. package/templates/tauri-react/src/modules/todos/todo.queries.ts +5 -0
  218. package/templates/tauri-react/src/modules/todos/todo.schema.ts +10 -0
  219. package/templates/tauri-react/src/modules/todos/useTodos.ts +31 -0
  220. package/templates/tauri-react/src/schema.ts +9 -0
  221. package/templates/tauri-react/src/sync-config.ts +103 -0
  222. package/templates/tauri-react/src/updater.ts +38 -0
  223. package/templates/tauri-react/src/vite-env.d.ts +19 -0
  224. package/templates/tauri-react/src-tauri/Cargo.toml +19 -0
  225. package/templates/tauri-react/src-tauri/build.rs +3 -0
  226. package/templates/tauri-react/src-tauri/capabilities/default.json +12 -0
  227. package/templates/tauri-react/src-tauri/icons/128x128.png +0 -0
  228. package/templates/tauri-react/src-tauri/icons/128x128@2x.png +0 -0
  229. package/templates/tauri-react/src-tauri/icons/32x32.png +0 -0
  230. package/templates/tauri-react/src-tauri/icons/icon.icns +0 -0
  231. package/templates/tauri-react/src-tauri/icons/icon.ico +0 -0
  232. package/templates/tauri-react/src-tauri/src/lib.rs +8 -0
  233. package/templates/tauri-react/src-tauri/src/main.rs +6 -0
  234. package/templates/tauri-react/src-tauri/tauri.conf.json +44 -0
  235. package/templates/tauri-react/tsconfig.json.hbs +14 -0
  236. package/templates/tauri-react/vite.config.ts +15 -0
  237. package/templates/vue-basic/AGENTS.md +84 -0
  238. package/templates/vue-basic/README.md.hbs +62 -0
  239. package/templates/vue-basic/index.html.hbs +12 -0
  240. package/templates/vue-basic/kora.config.ts +12 -0
  241. package/templates/vue-basic/package.json.hbs +32 -0
  242. package/templates/vue-basic/src/App.vue +117 -0
  243. package/templates/vue-basic/src/index.css +287 -0
  244. package/templates/vue-basic/src/kora-worker.ts +9 -0
  245. package/templates/vue-basic/src/main.ts +20 -0
  246. package/templates/vue-basic/src/modules/todos/todo.mutations.ts +25 -0
  247. package/templates/vue-basic/src/modules/todos/todo.queries.ts +5 -0
  248. package/templates/vue-basic/src/modules/todos/todo.schema.ts +10 -0
  249. package/templates/vue-basic/src/modules/todos/useTodos.ts +32 -0
  250. package/templates/vue-basic/src/schema.ts +9 -0
  251. package/templates/vue-basic/src/vite-env.d.ts +17 -0
  252. package/templates/vue-basic/tsconfig.json.hbs +15 -0
  253. package/templates/vue-basic/vite.config.ts +74 -0
  254. package/templates/vue-sync/.env.example +25 -0
  255. package/templates/vue-sync/AGENTS.md +84 -0
  256. package/templates/vue-sync/README.md.hbs +106 -0
  257. package/templates/vue-sync/index.html.hbs +12 -0
  258. package/templates/vue-sync/kora.config.ts +17 -0
  259. package/templates/vue-sync/package.json.hbs +42 -0
  260. package/templates/vue-sync/server.ts +141 -0
  261. package/templates/vue-sync/src/App.vue +135 -0
  262. package/templates/vue-sync/src/auth.ts +25 -0
  263. package/templates/vue-sync/src/index.css +339 -0
  264. package/templates/vue-sync/src/kora-worker.ts +9 -0
  265. package/templates/vue-sync/src/main.ts +47 -0
  266. package/templates/vue-sync/src/modules/todos/todo.mutations.ts +25 -0
  267. package/templates/vue-sync/src/modules/todos/todo.queries.ts +5 -0
  268. package/templates/vue-sync/src/modules/todos/todo.schema.ts +10 -0
  269. package/templates/vue-sync/src/modules/todos/useTodos.ts +32 -0
  270. package/templates/vue-sync/src/schema.ts +9 -0
  271. package/templates/vue-sync/src/vite-env.d.ts +27 -0
  272. package/templates/vue-sync/tsconfig.json.hbs +15 -0
  273. package/templates/vue-sync/vite.config.ts +91 -0
  274. package/templates/vue-tailwind/AGENTS.md +84 -0
  275. package/templates/vue-tailwind/README.md.hbs +62 -0
  276. package/templates/vue-tailwind/index.html.hbs +12 -0
  277. package/templates/vue-tailwind/kora.config.ts +12 -0
  278. package/templates/vue-tailwind/package.json.hbs +35 -0
  279. package/templates/vue-tailwind/src/App.vue +135 -0
  280. package/templates/vue-tailwind/src/index.css +7 -0
  281. package/templates/vue-tailwind/src/kora-worker.ts +9 -0
  282. package/templates/vue-tailwind/src/main.ts +31 -0
  283. package/templates/vue-tailwind/src/modules/todos/todo.mutations.ts +25 -0
  284. package/templates/vue-tailwind/src/modules/todos/todo.queries.ts +5 -0
  285. package/templates/vue-tailwind/src/modules/todos/todo.schema.ts +10 -0
  286. package/templates/vue-tailwind/src/modules/todos/useTodos.ts +32 -0
  287. package/templates/vue-tailwind/src/schema.ts +9 -0
  288. package/templates/vue-tailwind/src/vite-env.d.ts +17 -0
  289. package/templates/vue-tailwind/tsconfig.json.hbs +15 -0
  290. package/templates/vue-tailwind/vite.config.ts +75 -0
  291. package/templates/vue-tailwind-sync/.env.example +25 -0
  292. package/templates/vue-tailwind-sync/AGENTS.md +84 -0
  293. package/templates/vue-tailwind-sync/README.md.hbs +106 -0
  294. package/templates/vue-tailwind-sync/index.html.hbs +12 -0
  295. package/templates/vue-tailwind-sync/kora.config.ts +17 -0
  296. package/templates/vue-tailwind-sync/package.json.hbs +45 -0
  297. package/templates/vue-tailwind-sync/server.ts +141 -0
  298. package/templates/vue-tailwind-sync/src/App.vue +245 -0
  299. package/templates/vue-tailwind-sync/src/auth.ts +25 -0
  300. package/templates/vue-tailwind-sync/src/index.css +7 -0
  301. package/templates/vue-tailwind-sync/src/kora-worker.ts +9 -0
  302. package/templates/vue-tailwind-sync/src/main.ts +56 -0
  303. package/templates/vue-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
  304. package/templates/vue-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
  305. package/templates/vue-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
  306. package/templates/vue-tailwind-sync/src/modules/todos/useTodos.ts +32 -0
  307. package/templates/vue-tailwind-sync/src/schema.ts +9 -0
  308. package/templates/vue-tailwind-sync/src/vite-env.d.ts +27 -0
  309. package/templates/vue-tailwind-sync/tsconfig.json.hbs +15 -0
  310. package/templates/vue-tailwind-sync/vite.config.ts +92 -0
@@ -0,0 +1,158 @@
1
+ import { createKoraAuthSync } from '@korajs/auth'
2
+ import { AuthProvider } from '@korajs/auth/react'
3
+ import { KoraProvider } from '@korajs/react'
4
+ import { createApp } from 'korajs'
5
+ import { useCallback, useEffect, useState } from 'react'
6
+ import { App } from './App'
7
+ import { SetupScreen } from './SetupScreen'
8
+ import { completeOAuthCallbackFromLocation, createDesktopAuthClient } from './auth'
9
+ import schema from './schema'
10
+ import {
11
+ clearSyncUrl,
12
+ factoryReset,
13
+ getSyncUrl,
14
+ hasCompletedSetup,
15
+ markSetupComplete,
16
+ setSyncUrl,
17
+ } from './sync-config'
18
+ import { checkForUpdates } from './updater'
19
+
20
+ type AppState = { phase: 'setup' } | { phase: 'running'; syncUrl: string | null }
21
+
22
+ /**
23
+ * Root component that handles the first-launch setup flow.
24
+ *
25
+ * - If setup not completed, shows the setup screen.
26
+ * - If completed, initializes Kora and renders the app.
27
+ * - Handles server changes with proper data isolation warnings.
28
+ *
29
+ * Changing the sync server requires an app restart (to re-create the
30
+ * Kora app instance with the new URL). This is intentional — it prevents
31
+ * partial state from one server leaking to another.
32
+ */
33
+ export function AppShell() {
34
+ const [state, setState] = useState<AppState>(() => {
35
+ if (hasCompletedSetup()) {
36
+ return { phase: 'running', syncUrl: getSyncUrl() }
37
+ }
38
+ return { phase: 'setup' }
39
+ })
40
+
41
+ const handleConnect = useCallback((url: string) => {
42
+ setSyncUrl(url)
43
+ setState({ phase: 'running', syncUrl: url })
44
+ }, [])
45
+
46
+ const handleSkip = useCallback(() => {
47
+ markSetupComplete()
48
+ setState({ phase: 'running', syncUrl: null })
49
+ }, [])
50
+
51
+ // Change to a different server URL.
52
+ // This requires restarting the app to create a fresh Kora instance.
53
+ const handleChangeServer = useCallback((newUrl: string | null) => {
54
+ if (newUrl) {
55
+ setSyncUrl(newUrl)
56
+ } else {
57
+ clearSyncUrl()
58
+ markSetupComplete() // Still completed — just disconnected
59
+ }
60
+ // Reload to re-initialize Kora with the new URL.
61
+ // This is cleaner than trying to hot-swap the sync connection,
62
+ // and prevents data from one server context leaking to another.
63
+ window.location.reload()
64
+ }, [])
65
+
66
+ const handleFactoryReset = useCallback(() => {
67
+ factoryReset() // Clears all data and reloads
68
+ }, [])
69
+
70
+ if (state.phase === 'setup') {
71
+ return <SetupScreen onConnect={handleConnect} onSkip={handleSkip} />
72
+ }
73
+
74
+ return (
75
+ <ConnectedApp
76
+ syncUrl={state.syncUrl}
77
+ onChangeServer={handleChangeServer}
78
+ onFactoryReset={handleFactoryReset}
79
+ />
80
+ )
81
+ }
82
+
83
+ interface ConnectedAppProps {
84
+ syncUrl: string | null
85
+ onChangeServer: (newUrl: string | null) => void
86
+ onFactoryReset: () => void
87
+ }
88
+
89
+ function ConnectedApp({ syncUrl, onChangeServer, onFactoryReset }: ConnectedAppProps) {
90
+ const [authClient] = useState(() => createDesktopAuthClient(syncUrl))
91
+ // Create the Kora app instance with optional sync.
92
+ // This only runs once — the app instance is stable for the lifetime of this component.
93
+ const [app] = useState(() =>
94
+ createApp({
95
+ schema,
96
+ ...(syncUrl
97
+ ? {
98
+ sync: {
99
+ url: syncUrl,
100
+ authClient: createKoraAuthSync({ authClient, schema }),
101
+ },
102
+ }
103
+ : {}),
104
+ devtools: import.meta.env.DEV,
105
+ }),
106
+ )
107
+
108
+ // Connect to sync server once ready, and check for updates
109
+ useEffect(() => {
110
+ void completeOAuthCallbackFromLocation(authClient).catch((error) => {
111
+ console.error('[Kora Auth] OAuth callback failed:', error)
112
+ })
113
+ if (syncUrl) {
114
+ app.ready.then(() => app.sync?.connect())
115
+ }
116
+ checkForUpdates()
117
+ }, [app, authClient, syncUrl])
118
+
119
+ return (
120
+ <AuthProvider
121
+ client={authClient}
122
+ fallback={
123
+ <div
124
+ style={{
125
+ display: 'flex',
126
+ alignItems: 'center',
127
+ justifyContent: 'center',
128
+ height: '100vh',
129
+ background: '#0a0a0a',
130
+ color: '#6b7280',
131
+ }}
132
+ >
133
+ Restoring session...
134
+ </div>
135
+ }
136
+ >
137
+ <KoraProvider
138
+ app={app}
139
+ fallback={
140
+ <div
141
+ style={{
142
+ display: 'flex',
143
+ alignItems: 'center',
144
+ justifyContent: 'center',
145
+ height: '100vh',
146
+ background: '#0a0a0a',
147
+ color: '#6b7280',
148
+ }}
149
+ >
150
+ Loading...
151
+ </div>
152
+ }
153
+ >
154
+ <App syncUrl={syncUrl} onChangeServer={onChangeServer} onFactoryReset={onFactoryReset} />
155
+ </KoraProvider>
156
+ </AuthProvider>
157
+ )
158
+ }
@@ -0,0 +1,288 @@
1
+ import { AlertCircle, ArrowRight, Loader2, Monitor, Wifi } from 'lucide-react'
2
+ import { useState } from 'react'
3
+ import { testConnection } from './sync-config'
4
+
5
+ interface SetupScreenProps {
6
+ onConnect: (syncUrl: string) => void
7
+ onSkip: () => void
8
+ }
9
+
10
+ /**
11
+ * First-launch setup screen.
12
+ *
13
+ * Shown when no sync server URL is configured. The user enters their
14
+ * organization's sync server URL, or skips to use the app in local-only mode.
15
+ *
16
+ * The connection test is advisory — if the server is unreachable (e.g.,
17
+ * during maintenance), the user can still save the URL and the app will
18
+ * connect when the server comes back.
19
+ */
20
+ export function SetupScreen({ onConnect, onSkip }: SetupScreenProps) {
21
+ const [url, setUrl] = useState('')
22
+ const [testing, setTesting] = useState(false)
23
+ const [error, setError] = useState<string | null>(null)
24
+ const [unreachable, setUnreachable] = useState(false)
25
+
26
+ const validateUrl = (value: string): string | null => {
27
+ if (!value.startsWith('ws://') && !value.startsWith('wss://')) {
28
+ return 'URL must start with ws:// or wss://'
29
+ }
30
+ try {
31
+ new URL(value.replace(/^ws/, 'http'))
32
+ } catch {
33
+ return 'Invalid URL format'
34
+ }
35
+ return null
36
+ }
37
+
38
+ const handleSubmit = async (e: React.FormEvent) => {
39
+ e.preventDefault()
40
+ const syncUrl = url.trim()
41
+ if (!syncUrl) return
42
+
43
+ const validationError = validateUrl(syncUrl)
44
+ if (validationError) {
45
+ setError(validationError)
46
+ return
47
+ }
48
+
49
+ setTesting(true)
50
+ setError(null)
51
+ setUnreachable(false)
52
+
53
+ const reachable = await testConnection(syncUrl)
54
+ setTesting(false)
55
+
56
+ if (reachable) {
57
+ onConnect(syncUrl)
58
+ } else {
59
+ // Server unreachable — let the user save anyway
60
+ setUnreachable(true)
61
+ }
62
+ }
63
+
64
+ const handleSaveAnyway = () => {
65
+ onConnect(url.trim())
66
+ }
67
+
68
+ return (
69
+ <div
70
+ style={{
71
+ minHeight: '100vh',
72
+ background: '#0a0a0a',
73
+ color: '#f3f4f6',
74
+ display: 'flex',
75
+ alignItems: 'center',
76
+ justifyContent: 'center',
77
+ }}
78
+ >
79
+ <div style={{ maxWidth: '480px', width: '100%', padding: '0 24px' }}>
80
+ {/* Logo */}
81
+ <div style={{ textAlign: 'center', marginBottom: '48px' }}>
82
+ <Monitor
83
+ style={{ width: '48px', height: '48px', color: '#818cf8', margin: '0 auto 16px' }}
84
+ />
85
+ <h1 style={{ fontSize: '28px', fontWeight: 'bold', marginBottom: '8px' }}>Welcome</h1>
86
+ <p style={{ color: '#6b7280', fontSize: '15px' }}>
87
+ Connect to your organization's sync server to sync data across devices.
88
+ </p>
89
+ </div>
90
+
91
+ {/* Connection form */}
92
+ <form onSubmit={handleSubmit}>
93
+ <div style={{ marginBottom: '16px' }}>
94
+ <label
95
+ htmlFor="sync-server-url"
96
+ style={{
97
+ display: 'block',
98
+ fontSize: '14px',
99
+ fontWeight: '500',
100
+ marginBottom: '8px',
101
+ color: '#d1d5db',
102
+ }}
103
+ >
104
+ Sync server URL
105
+ </label>
106
+ <div style={{ position: 'relative' }}>
107
+ <Wifi
108
+ style={{
109
+ position: 'absolute',
110
+ left: '12px',
111
+ top: '50%',
112
+ transform: 'translateY(-50%)',
113
+ width: '18px',
114
+ height: '18px',
115
+ color: '#6b7280',
116
+ }}
117
+ />
118
+ <input
119
+ id="sync-server-url"
120
+ type="text"
121
+ value={url}
122
+ onChange={(e) => {
123
+ setUrl(e.target.value)
124
+ setError(null)
125
+ setUnreachable(false)
126
+ }}
127
+ placeholder="wss://your-server.example.com/kora-sync"
128
+ style={{
129
+ width: '100%',
130
+ borderRadius: '8px',
131
+ border: `1px solid ${error ? '#ef4444' : '#374151'}`,
132
+ background: '#111827',
133
+ padding: '12px 16px 12px 40px',
134
+ color: '#f3f4f6',
135
+ outline: 'none',
136
+ fontSize: '14px',
137
+ boxSizing: 'border-box',
138
+ }}
139
+ />
140
+ </div>
141
+ {error && (
142
+ <p style={{ color: '#ef4444', fontSize: '13px', marginTop: '8px' }}>{error}</p>
143
+ )}
144
+ </div>
145
+
146
+ {/* Server unreachable — offer to save anyway */}
147
+ {unreachable && (
148
+ <div
149
+ style={{
150
+ marginBottom: '16px',
151
+ padding: '12px 16px',
152
+ borderRadius: '8px',
153
+ border: '1px solid #92400e',
154
+ background: '#451a03',
155
+ }}
156
+ >
157
+ <div style={{ display: 'flex', gap: '8px', alignItems: 'flex-start' }}>
158
+ <AlertCircle
159
+ style={{
160
+ width: '16px',
161
+ height: '16px',
162
+ color: '#fbbf24',
163
+ flexShrink: 0,
164
+ marginTop: '2px',
165
+ }}
166
+ />
167
+ <div>
168
+ <p style={{ fontSize: '13px', color: '#fde68a', marginBottom: '8px' }}>
169
+ Server is not reachable right now. This could be temporary (maintenance, network
170
+ issues).
171
+ </p>
172
+ <div style={{ display: 'flex', gap: '8px' }}>
173
+ <button
174
+ type="button"
175
+ onClick={handleSaveAnyway}
176
+ style={{
177
+ fontSize: '13px',
178
+ color: '#fbbf24',
179
+ background: 'none',
180
+ border: '1px solid #92400e',
181
+ borderRadius: '6px',
182
+ padding: '4px 12px',
183
+ cursor: 'pointer',
184
+ }}
185
+ >
186
+ Save anyway — connect later
187
+ </button>
188
+ <button
189
+ type="submit"
190
+ style={{
191
+ fontSize: '13px',
192
+ color: '#9ca3af',
193
+ background: 'none',
194
+ border: '1px solid #374151',
195
+ borderRadius: '6px',
196
+ padding: '4px 12px',
197
+ cursor: 'pointer',
198
+ }}
199
+ >
200
+ Retry
201
+ </button>
202
+ </div>
203
+ </div>
204
+ </div>
205
+ </div>
206
+ )}
207
+
208
+ {!unreachable && (
209
+ <button
210
+ type="submit"
211
+ disabled={testing || !url.trim()}
212
+ style={{
213
+ width: '100%',
214
+ display: 'flex',
215
+ alignItems: 'center',
216
+ justifyContent: 'center',
217
+ gap: '8px',
218
+ borderRadius: '8px',
219
+ background: '#4f46e5',
220
+ padding: '12px 20px',
221
+ fontWeight: '500',
222
+ color: 'white',
223
+ border: 'none',
224
+ cursor: testing || !url.trim() ? 'default' : 'pointer',
225
+ opacity: testing || !url.trim() ? 0.5 : 1,
226
+ fontSize: '14px',
227
+ marginBottom: '12px',
228
+ }}
229
+ >
230
+ {testing ? (
231
+ <>
232
+ <Loader2
233
+ style={{ width: '16px', height: '16px', animation: 'spin 1s linear infinite' }}
234
+ />
235
+ Testing connection...
236
+ </>
237
+ ) : (
238
+ <>
239
+ Connect
240
+ <ArrowRight style={{ width: '16px', height: '16px' }} />
241
+ </>
242
+ )}
243
+ </button>
244
+ )}
245
+ </form>
246
+
247
+ {/* Skip option */}
248
+ <div style={{ textAlign: 'center', marginTop: '24px' }}>
249
+ <button
250
+ onClick={onSkip}
251
+ style={{
252
+ background: 'none',
253
+ border: 'none',
254
+ color: '#6b7280',
255
+ cursor: 'pointer',
256
+ fontSize: '14px',
257
+ textDecoration: 'underline',
258
+ textUnderlineOffset: '3px',
259
+ }}
260
+ >
261
+ Skip — use offline only
262
+ </button>
263
+ <p style={{ color: '#374151', fontSize: '12px', marginTop: '8px' }}>
264
+ You can connect to a sync server later from settings.
265
+ </p>
266
+ </div>
267
+
268
+ {/* Help text */}
269
+ <div
270
+ style={{
271
+ marginTop: '48px',
272
+ padding: '16px',
273
+ borderRadius: '8px',
274
+ border: '1px solid #1f2937',
275
+ background: '#111827',
276
+ }}
277
+ >
278
+ <p style={{ fontSize: '13px', color: '#9ca3af', lineHeight: '1.6' }}>
279
+ <strong style={{ color: '#d1d5db' }}>Don't have a server URL?</strong>
280
+ <br />
281
+ Ask your administrator for the sync server address. For local development, use{' '}
282
+ <code style={{ color: '#818cf8' }}>ws://localhost:3001/kora-sync</code>.
283
+ </p>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ )
288
+ }
@@ -0,0 +1,45 @@
1
+ import { type AuthClient, createKoraAuth } from '@korajs/auth'
2
+
3
+ export function createDesktopAuthClient(syncUrl: string | null): AuthClient {
4
+ return createKoraAuth({
5
+ serverUrl: getAuthServerUrl(syncUrl),
6
+ })
7
+ }
8
+
9
+ export function getAuthServerUrl(syncUrl: string | null): string {
10
+ if (import.meta.env.VITE_AUTH_URL) {
11
+ return import.meta.env.VITE_AUTH_URL
12
+ }
13
+ if (!syncUrl) {
14
+ return 'http://localhost:3001'
15
+ }
16
+
17
+ const url = new URL(syncUrl)
18
+ url.protocol = url.protocol === 'wss:' ? 'https:' : 'http:'
19
+ url.pathname = url.pathname.replace(/\/kora-sync\/?$/, '') || '/'
20
+ url.search = ''
21
+ url.hash = ''
22
+ return url.toString().replace(/\/+$/, '')
23
+ }
24
+
25
+ export async function openOAuthSignIn(authClient: AuthClient, provider = 'google'): Promise<void> {
26
+ const { url } = await authClient.getOAuthAuthorizationUrl(provider)
27
+ window.open(url, '_blank', 'noopener,noreferrer')
28
+ }
29
+
30
+ export async function completeOAuthCallbackFromLocation(authClient: AuthClient): Promise<void> {
31
+ const url = new URL(window.location.href)
32
+ const code = url.searchParams.get('code')
33
+ const state = url.searchParams.get('state')
34
+ const provider = url.searchParams.get('provider') || 'google'
35
+
36
+ if (!code || !state) {
37
+ return
38
+ }
39
+
40
+ await authClient.completeOAuthSignIn(provider, { code, state })
41
+ url.searchParams.delete('code')
42
+ url.searchParams.delete('state')
43
+ url.searchParams.delete('provider')
44
+ window.history.replaceState({}, document.title, `${url.pathname}${url.search}${url.hash}`)
45
+ }
@@ -0,0 +1,9 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import { AppShell } from './AppShell'
4
+
5
+ createRoot(document.getElementById('root')!).render(
6
+ <StrictMode>
7
+ <AppShell />
8
+ </StrictMode>,
9
+ )
@@ -0,0 +1,25 @@
1
+ import type { CollectionAccessor } from 'korajs'
2
+
3
+ export interface CreateTodoInput {
4
+ title: string
5
+ }
6
+
7
+ export interface UpdateTodoStatusInput {
8
+ completed: boolean
9
+ }
10
+
11
+ export function createTodo(todos: CollectionAccessor, data: CreateTodoInput) {
12
+ return todos.insert({ title: data.title })
13
+ }
14
+
15
+ export function updateTodoStatus(
16
+ todos: CollectionAccessor,
17
+ id: string,
18
+ data: UpdateTodoStatusInput,
19
+ ) {
20
+ return todos.update(id, { completed: data.completed })
21
+ }
22
+
23
+ export function deleteTodo(todos: CollectionAccessor, id: string) {
24
+ return todos.delete(id)
25
+ }
@@ -0,0 +1,5 @@
1
+ import type { CollectionAccessor } from 'korajs'
2
+
3
+ export function orderedTodos(todos: CollectionAccessor) {
4
+ return todos.where({}).orderBy('createdAt', 'desc')
5
+ }
@@ -0,0 +1,10 @@
1
+ import { t } from '@korajs/core'
2
+
3
+ export const todos = {
4
+ fields: {
5
+ title: t.string(),
6
+ completed: t.boolean().default(false),
7
+ createdAt: t.timestamp().auto(),
8
+ },
9
+ indexes: ['completed', 'createdAt'],
10
+ }
@@ -0,0 +1,31 @@
1
+ import { useCollection, useMutation, useQuery } from '@korajs/react'
2
+ import {
3
+ type CreateTodoInput,
4
+ type UpdateTodoStatusInput,
5
+ createTodo,
6
+ deleteTodo,
7
+ updateTodoStatus,
8
+ } from './todo.mutations'
9
+ import { orderedTodos } from './todo.queries'
10
+
11
+ export function useTodos() {
12
+ const todos = useCollection('todos')
13
+ const allTodos = useQuery(orderedTodos(todos))
14
+ const addTodo = useMutation((data: CreateTodoInput) => createTodo(todos, data))
15
+ const toggleTodo = useMutation((id: string, data: UpdateTodoStatusInput) =>
16
+ updateTodoStatus(todos, id, data),
17
+ )
18
+ const removeTodo = useMutation((id: string) => deleteTodo(todos, id))
19
+
20
+ const activeTodos = allTodos.filter((todo) => !todo.completed)
21
+ const completedTodos = allTodos.filter((todo) => !!todo.completed)
22
+
23
+ return {
24
+ allTodos,
25
+ activeTodos,
26
+ completedTodos,
27
+ addTodo,
28
+ toggleTodo,
29
+ deleteTodo: removeTodo,
30
+ }
31
+ }
@@ -0,0 +1,9 @@
1
+ import { defineSchema } from '@korajs/core'
2
+ import { todos } from './modules/todos/todo.schema'
3
+
4
+ export default defineSchema({
5
+ version: 1,
6
+ collections: {
7
+ todos,
8
+ },
9
+ })
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Runtime sync server configuration.
3
+ *
4
+ * On first launch, the user enters their sync server URL (or organization URL).
5
+ * This is stored in localStorage and used for all subsequent launches.
6
+ *
7
+ * This allows a single binary to be distributed to multiple organizations,
8
+ * each pointing to their own sync server.
9
+ */
10
+
11
+ const SYNC_URL_KEY = 'kora-sync-url'
12
+ const SYNC_CONFIGURED_KEY = 'kora-sync-configured'
13
+
14
+ /**
15
+ * Get the configured sync server URL.
16
+ * Priority: localStorage → VITE_SYNC_URL env var → null (needs setup).
17
+ */
18
+ export function getSyncUrl(): string | null {
19
+ const stored = localStorage.getItem(SYNC_URL_KEY)
20
+ if (stored) return stored
21
+
22
+ // Compile-time default (set via VITE_SYNC_URL when building)
23
+ const envUrl = import.meta.env.VITE_SYNC_URL
24
+ if (envUrl) return envUrl
25
+
26
+ return null
27
+ }
28
+
29
+ /** Save a sync server URL to persistent local config. */
30
+ export function setSyncUrl(url: string): void {
31
+ localStorage.setItem(SYNC_URL_KEY, url)
32
+ localStorage.setItem(SYNC_CONFIGURED_KEY, 'true')
33
+ }
34
+
35
+ /** Clear the stored sync server URL (returns to setup screen). */
36
+ export function clearSyncUrl(): void {
37
+ localStorage.removeItem(SYNC_URL_KEY)
38
+ localStorage.removeItem(SYNC_CONFIGURED_KEY)
39
+ }
40
+
41
+ /**
42
+ * Whether the user has completed initial setup (connected or explicitly skipped).
43
+ * This is separate from getSyncUrl() — a user who skipped still completed setup.
44
+ */
45
+ export function hasCompletedSetup(): boolean {
46
+ // If there's a compile-time URL, setup is not needed
47
+ if (import.meta.env.VITE_SYNC_URL) return true
48
+ return localStorage.getItem(SYNC_CONFIGURED_KEY) === 'true'
49
+ }
50
+
51
+ /** Mark setup as completed (used when user skips sync). */
52
+ export function markSetupComplete(): void {
53
+ localStorage.setItem(SYNC_CONFIGURED_KEY, 'true')
54
+ }
55
+
56
+ /**
57
+ * Full factory reset: clears sync config and all local data.
58
+ * The app reloads with a fresh state, showing the setup screen.
59
+ */
60
+ export function factoryReset(): void {
61
+ // Clear sync config
62
+ clearSyncUrl()
63
+ localStorage.removeItem(SYNC_CONFIGURED_KEY)
64
+
65
+ // Clear all Kora local data by removing IndexedDB databases
66
+ // and any other localStorage keys the app may have set.
67
+ const keysToRemove: string[] = []
68
+ for (let i = 0; i < localStorage.length; i++) {
69
+ const key = localStorage.key(i)
70
+ if (key?.startsWith('kora-')) {
71
+ keysToRemove.push(key)
72
+ }
73
+ }
74
+ for (const key of keysToRemove) {
75
+ localStorage.removeItem(key)
76
+ }
77
+
78
+ // Reload to reinitialize everything from scratch
79
+ window.location.reload()
80
+ }
81
+
82
+ /** Test if a WebSocket URL is reachable by attempting a brief connection. */
83
+ export function testConnection(url: string, timeoutMs = 5000): Promise<boolean> {
84
+ return new Promise((resolve) => {
85
+ const timeout = setTimeout(() => {
86
+ ws.close()
87
+ resolve(false)
88
+ }, timeoutMs)
89
+
90
+ const ws = new WebSocket(url)
91
+
92
+ ws.onopen = () => {
93
+ clearTimeout(timeout)
94
+ ws.close()
95
+ resolve(true)
96
+ }
97
+
98
+ ws.onerror = () => {
99
+ clearTimeout(timeout)
100
+ resolve(false)
101
+ }
102
+ })
103
+ }