@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,293 @@
1
+ import { useAuth } from '@korajs/auth/react'
2
+ import {
3
+ AlertCircle,
4
+ CheckCircle2,
5
+ Circle,
6
+ ClipboardList,
7
+ Loader2,
8
+ Plus,
9
+ Trash2,
10
+ Wifi,
11
+ WifiOff,
12
+ } from 'lucide-react'
13
+ import { useState } from 'react'
14
+ import { useSyncStatus } from './kora'
15
+ import { useTodos } from './modules/todos/useTodos'
16
+
17
+ type Filter = 'all' | 'active' | 'completed'
18
+
19
+ export function App() {
20
+ const { allTodos, activeTodos, completedTodos, addTodo, toggleTodo, deleteTodo } = useTodos()
21
+ const isAdding = addTodo.isLoading
22
+ const status = useSyncStatus()
23
+ const { user, isAuthenticated, signInWithOAuth, signOut, error } = useAuth()
24
+
25
+ const [filter, setFilter] = useState<Filter>('all')
26
+ const [input, setInput] = useState('')
27
+
28
+ const filteredTodos =
29
+ filter === 'active' ? activeTodos : filter === 'completed' ? completedTodos : allTodos
30
+
31
+ const handleSubmit = (e: React.FormEvent) => {
32
+ e.preventDefault()
33
+ const title = input.trim()
34
+ if (title) {
35
+ addTodo.mutate({ title })
36
+ setInput('')
37
+ }
38
+ }
39
+
40
+ const clearCompleted = () => {
41
+ for (const todo of completedTodos) {
42
+ deleteTodo.mutate(todo.id)
43
+ }
44
+ }
45
+
46
+ return (
47
+ <div className="min-h-screen bg-gray-950 text-gray-100">
48
+ <div className="mx-auto max-w-2xl px-4 py-12">
49
+ {/* Header */}
50
+ <div className="flex items-center justify-between mb-8">
51
+ <div className="flex items-center gap-3">
52
+ <ClipboardList className="h-8 w-8 text-indigo-400" />
53
+ <h1 className="text-2xl font-bold">My Tasks</h1>
54
+ </div>
55
+ <div className="flex items-center gap-2">
56
+ <SyncBadge status={status} />
57
+ {isAuthenticated ? (
58
+ <button
59
+ type="button"
60
+ onClick={() => signOut()}
61
+ className="max-w-44 truncate rounded-full border border-gray-700 bg-gray-900 px-3 py-1.5 text-sm text-gray-300 transition hover:border-gray-600"
62
+ >
63
+ {user?.email || 'Sign out'}
64
+ </button>
65
+ ) : (
66
+ <button
67
+ type="button"
68
+ onClick={() => signInWithOAuth('google')}
69
+ className="rounded-full border border-gray-700 bg-gray-900 px-3 py-1.5 text-sm text-gray-300 transition hover:border-gray-600"
70
+ >
71
+ Sign in
72
+ </button>
73
+ )}
74
+ </div>
75
+ </div>
76
+
77
+ {error && (
78
+ <div className="mb-4 rounded-lg border border-red-400/30 px-3 py-2 text-sm text-red-300">
79
+ {error}
80
+ </div>
81
+ )}
82
+
83
+ <ClockErrorBanner status={status} />
84
+
85
+ {/* Stats */}
86
+ <div className="grid grid-cols-3 gap-4 mb-8">
87
+ <StatCard label="Total" value={allTodos.length} color="text-gray-300" />
88
+ <StatCard label="Remaining" value={activeTodos.length} color="text-amber-400" />
89
+ <StatCard label="Done" value={completedTodos.length} color="text-emerald-400" />
90
+ </div>
91
+
92
+ {/* Add form */}
93
+ <form onSubmit={handleSubmit} className="flex gap-3 mb-8">
94
+ <input
95
+ type="text"
96
+ value={input}
97
+ onChange={(e) => setInput(e.target.value)}
98
+ placeholder="What needs to be done?"
99
+ className="flex-1 rounded-lg border border-gray-700 bg-gray-900 px-4 py-3 text-gray-100 placeholder-gray-500 outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500"
100
+ />
101
+ <button
102
+ type="submit"
103
+ disabled={isAdding || !input.trim()}
104
+ className="flex items-center gap-2 rounded-lg bg-indigo-600 px-5 py-3 font-medium text-white transition hover:bg-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
105
+ >
106
+ {isAdding ? <Loader2 className="h-4 w-4 animate-spin" /> : <Plus className="h-4 w-4" />}
107
+ Add
108
+ </button>
109
+ </form>
110
+
111
+ {/* Filter tabs */}
112
+ <div className="flex gap-2 mb-6">
113
+ {(['all', 'active', 'completed'] as const).map((f) => {
114
+ const count =
115
+ f === 'all'
116
+ ? allTodos.length
117
+ : f === 'active'
118
+ ? activeTodos.length
119
+ : completedTodos.length
120
+ return (
121
+ <button
122
+ key={f}
123
+ onClick={() => setFilter(f)}
124
+ className={`flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition ${
125
+ filter === f
126
+ ? 'bg-indigo-600 text-white'
127
+ : 'bg-gray-800 text-gray-400 hover:bg-gray-700 hover:text-gray-200'
128
+ }`}
129
+ >
130
+ {f.charAt(0).toUpperCase() + f.slice(1)}
131
+ <span
132
+ className={`rounded-full px-2 py-0.5 text-xs ${
133
+ filter === f ? 'bg-indigo-500 text-white' : 'bg-gray-700 text-gray-400'
134
+ }`}
135
+ >
136
+ {count}
137
+ </span>
138
+ </button>
139
+ )
140
+ })}
141
+ </div>
142
+
143
+ {/* Todo list */}
144
+ <div className="space-y-2">
145
+ {filteredTodos.length === 0 ? (
146
+ <EmptyState filter={filter} />
147
+ ) : (
148
+ filteredTodos.map((todo) => (
149
+ <div
150
+ key={todo.id}
151
+ className="group flex items-center gap-3 rounded-lg border border-gray-800 bg-gray-900 px-4 py-3 transition hover:border-gray-700"
152
+ >
153
+ <button
154
+ onClick={() => toggleTodo.mutate(todo.id, { completed: !todo.completed })}
155
+ className="shrink-0 text-gray-500 hover:text-indigo-400 transition"
156
+ >
157
+ {todo.completed ? (
158
+ <CheckCircle2 className="h-5 w-5 text-emerald-400" />
159
+ ) : (
160
+ <Circle className="h-5 w-5" />
161
+ )}
162
+ </button>
163
+ <span
164
+ className={`flex-1 ${
165
+ todo.completed ? 'text-gray-500 line-through' : 'text-gray-100'
166
+ }`}
167
+ >
168
+ {String(todo.title)}
169
+ </span>
170
+ {todo.createdAt && (
171
+ <span className="text-xs text-gray-600">
172
+ {formatTime(Number(todo.createdAt))}
173
+ </span>
174
+ )}
175
+ <button
176
+ onClick={() => deleteTodo.mutate(todo.id)}
177
+ className="shrink-0 text-gray-600 opacity-0 transition hover:text-red-400 group-hover:opacity-100"
178
+ >
179
+ <Trash2 className="h-4 w-4" />
180
+ </button>
181
+ </div>
182
+ ))
183
+ )}
184
+ </div>
185
+
186
+ {/* Footer */}
187
+ {allTodos.length > 0 && (
188
+ <div className="mt-6 flex items-center justify-between text-sm text-gray-500">
189
+ <span>
190
+ {activeTodos.length} item{activeTodos.length !== 1 ? 's' : ''} left
191
+ </span>
192
+ {completedTodos.length > 0 && (
193
+ <button
194
+ onClick={clearCompleted}
195
+ className="text-gray-500 transition hover:text-gray-300"
196
+ >
197
+ Clear completed
198
+ </button>
199
+ )}
200
+ </div>
201
+ )}
202
+
203
+ <p className="mt-12 text-center text-xs text-gray-700">
204
+ Powered by Kora &mdash; offline-first, real-time sync
205
+ </p>
206
+ </div>
207
+ </div>
208
+ )
209
+ }
210
+
211
+ function SyncBadge({ status }: { status: { status: string; pendingOperations?: number } }) {
212
+ const s = status.status
213
+ const pending = status.pendingOperations ?? 0
214
+
215
+ const config: Record<string, { icon: typeof Wifi; color: string; label: string }> = {
216
+ connected: { icon: Wifi, color: 'text-emerald-400', label: 'Connected' },
217
+ syncing: { icon: Wifi, color: 'text-amber-400', label: 'Syncing' },
218
+ synced: { icon: Wifi, color: 'text-emerald-400', label: 'Synced' },
219
+ offline: { icon: WifiOff, color: 'text-gray-500', label: 'Offline' },
220
+ error: { icon: AlertCircle, color: 'text-red-400', label: 'Error' },
221
+ 'clock-error': { icon: AlertCircle, color: 'text-red-400', label: 'Clock error' },
222
+ }
223
+
224
+ const { icon: Icon, color, label } = config[s] ?? config.offline!
225
+
226
+ return (
227
+ <div className="flex items-center gap-2 rounded-full bg-gray-800 px-3 py-1.5 text-sm">
228
+ <Icon className={`h-4 w-4 ${color}`} />
229
+ <span className={color}>{label}</span>
230
+ {pending > 0 && (
231
+ <span className="rounded-full bg-gray-700 px-2 py-0.5 text-xs text-gray-400">
232
+ {pending} pending
233
+ </span>
234
+ )}
235
+ </div>
236
+ )
237
+ }
238
+
239
+ /**
240
+ * Shown when sync is paused because this device's clock is set too far ahead.
241
+ * Local changes keep saving; they sync automatically once the clock is fixed.
242
+ */
243
+ function ClockErrorBanner({
244
+ status,
245
+ }: {
246
+ status: { status: string; clockSkewMs?: number | null }
247
+ }) {
248
+ if (status.status !== 'clock-error') return null
249
+ const skew = status.clockSkewMs
250
+ const minutes =
251
+ typeof skew === 'number' && Number.isFinite(skew) ? Math.round(Math.abs(skew) / 60000) : null
252
+ return (
253
+ <div className="mb-4 rounded-lg border border-amber-400/40 bg-amber-400/10 px-4 py-3 text-sm text-amber-200">
254
+ <strong>
255
+ This device's clock looks wrong{minutes !== null ? ` (about ${minutes} min ahead)` : ''}.
256
+ </strong>{' '}
257
+ Your changes are safe on this device, but they can't be shared with your other devices until
258
+ the clock is corrected. Open your device's Settings, find Date & Time, and turn on "Set
259
+ automatically", then return here.
260
+ </div>
261
+ )
262
+ }
263
+
264
+ function StatCard({ label, value, color }: { label: string; value: number; color: string }) {
265
+ return (
266
+ <div className="rounded-lg border border-gray-800 bg-gray-900 p-4">
267
+ <p className="text-sm text-gray-500">{label}</p>
268
+ <p className={`text-2xl font-bold ${color}`}>{value}</p>
269
+ </div>
270
+ )
271
+ }
272
+
273
+ function EmptyState({ filter }: { filter: Filter }) {
274
+ const messages: Record<Filter, string> = {
275
+ all: 'No tasks yet. Add one above!',
276
+ active: 'All caught up! No active tasks.',
277
+ completed: 'No completed tasks yet.',
278
+ }
279
+ return (
280
+ <div className="rounded-lg border border-dashed border-gray-800 py-12 text-center text-gray-600">
281
+ {messages[filter]}
282
+ </div>
283
+ )
284
+ }
285
+
286
+ function formatTime(timestamp: number): string {
287
+ const date = new Date(timestamp)
288
+ const now = new Date()
289
+ if (date.toDateString() === now.toDateString()) {
290
+ return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
291
+ }
292
+ return date.toLocaleDateString([], { month: 'short', day: 'numeric' })
293
+ }
@@ -0,0 +1,25 @@
1
+ import { createKoraAuth } from '@korajs/auth'
2
+
3
+ export const authServerUrl =
4
+ import.meta.env.VITE_AUTH_URL || `${window.location.protocol}//${window.location.host}`
5
+
6
+ export const authClient = createKoraAuth({
7
+ serverUrl: authServerUrl,
8
+ })
9
+
10
+ export async function completeOAuthCallbackFromLocation(): Promise<void> {
11
+ const url = new URL(window.location.href)
12
+ const code = url.searchParams.get('code')
13
+ const state = url.searchParams.get('state')
14
+ const provider = url.searchParams.get('provider') || 'google'
15
+
16
+ if (!code || !state) {
17
+ return
18
+ }
19
+
20
+ await authClient.completeOAuthSignIn(provider, { code, state })
21
+ url.searchParams.delete('code')
22
+ url.searchParams.delete('state')
23
+ url.searchParams.delete('provider')
24
+ window.history.replaceState({}, document.title, `${url.pathname}${url.search}${url.hash}`)
25
+ }
@@ -0,0 +1,7 @@
1
+ @import "tailwindcss";
2
+
3
+ @layer base {
4
+ body {
5
+ @apply bg-gray-950 text-gray-100 antialiased;
6
+ }
7
+ }
@@ -0,0 +1,9 @@
1
+ // Web Worker entry point for SQLite WASM.
2
+ // Loaded automatically by the Kora store to run SQLite in a background thread.
3
+
4
+ // Import the WASM binary URL so Vite resolves it with the correct content hash.
5
+ // Without this, production builds fail because sqlite3 looks for the unhashed filename.
6
+ import sqliteWasmUrl from '@sqlite.org/sqlite-wasm/sqlite3.wasm?url'
7
+ ;(globalThis as Record<string, unknown>).__KORA_SQLITE_WASM_URL = sqliteWasmUrl
8
+
9
+ import '@korajs/store/sqlite-wasm/worker'
@@ -0,0 +1,32 @@
1
+ import { createKoraAuthSync } from '@korajs/auth'
2
+ import { createApp } from 'korajs'
3
+ import { createKoraHooks } from 'korajs/react'
4
+ import { authClient } from './auth'
5
+ import koraWorkerUrl from './kora-worker.ts?worker&url'
6
+ import schema from './schema'
7
+
8
+ // Build sync URL: use env var if set, otherwise derive from current page host.
9
+ // This allows the Vite proxy (/kora-sync → ws://localhost:3001) to work in dev,
10
+ // and also works through any tunnel (ngrok, cloudflared) without extra configuration.
11
+ const syncUrl =
12
+ import.meta.env.VITE_SYNC_URL ||
13
+ `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/kora-sync`
14
+
15
+ export const app = createApp({
16
+ schema,
17
+ sync: {
18
+ url: syncUrl,
19
+ authClient: createKoraAuthSync({ authClient, schema }),
20
+ },
21
+ store: {
22
+ workerUrl: koraWorkerUrl,
23
+ },
24
+ devtools: import.meta.env.DEV,
25
+ })
26
+
27
+ /** The typed `todos` collection: inserts, updates and queries are checked against the schema. */
28
+ export type Todos = typeof app.todos
29
+
30
+ // Hooks bound to this app's schema: `useCollection('todoz')` is a type error, and the
31
+ // rows `useQuery` returns are typed from the query.
32
+ export const { useCollection, useQuery, useMutation, useSyncStatus } = createKoraHooks<typeof app>()
@@ -0,0 +1,44 @@
1
+ import { AuthProvider } from '@korajs/auth/react'
2
+ import { KoraProvider } from '@korajs/react'
3
+ import { StrictMode } from 'react'
4
+ import { createRoot } from 'react-dom/client'
5
+ import { App } from './App'
6
+ import { authClient, completeOAuthCallbackFromLocation } from './auth'
7
+ import { app } from './kora'
8
+ import './index.css'
9
+
10
+ // Connect to sync server once the app is ready
11
+ app.ready.then(() => app.sync?.connect())
12
+ void completeOAuthCallbackFromLocation().catch((error) => {
13
+ console.error('[Kora Auth] OAuth callback failed:', error)
14
+ })
15
+
16
+ const rootElement = document.getElementById('root')
17
+
18
+ if (!rootElement) {
19
+ throw new Error('Root element not found')
20
+ }
21
+
22
+ createRoot(rootElement).render(
23
+ <StrictMode>
24
+ <AuthProvider
25
+ client={authClient}
26
+ fallback={
27
+ <div className="flex h-screen items-center justify-center bg-gray-950 text-gray-400">
28
+ Restoring session...
29
+ </div>
30
+ }
31
+ >
32
+ <KoraProvider
33
+ app={app}
34
+ fallback={
35
+ <div className="flex h-screen items-center justify-center bg-gray-950 text-gray-400">
36
+ Loading...
37
+ </div>
38
+ }
39
+ >
40
+ <App />
41
+ </KoraProvider>
42
+ </AuthProvider>
43
+ </StrictMode>,
44
+ )
@@ -0,0 +1,21 @@
1
+ import type { Todos } from '../../kora'
2
+
3
+ export interface CreateTodoInput {
4
+ title: string
5
+ }
6
+
7
+ export interface UpdateTodoStatusInput {
8
+ completed: boolean
9
+ }
10
+
11
+ export function createTodo(todos: Todos, data: CreateTodoInput) {
12
+ return todos.insert({ title: data.title })
13
+ }
14
+
15
+ export function updateTodoStatus(todos: Todos, id: string, data: UpdateTodoStatusInput) {
16
+ return todos.update(id, { completed: data.completed })
17
+ }
18
+
19
+ export function deleteTodo(todos: Todos, id: string) {
20
+ return todos.delete(id)
21
+ }
@@ -0,0 +1,5 @@
1
+ import type { Todos } from '../../kora'
2
+
3
+ export function orderedTodos(todos: Todos) {
4
+ return todos.where({}).orderBy('createdAt', 'desc')
5
+ }
@@ -0,0 +1,10 @@
1
+ import { t } from 'korajs'
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 '../../kora'
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'
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,19 @@
1
+ interface ImportMetaEnv {
2
+ readonly DEV: boolean
3
+ readonly VITE_SYNC_URL?: string
4
+ readonly VITE_AUTH_URL?: string
5
+ }
6
+
7
+ interface ImportMeta {
8
+ readonly env: ImportMetaEnv
9
+ }
10
+
11
+ declare module '*?worker&url' {
12
+ const url: string
13
+ export default url
14
+ }
15
+
16
+ declare module '*?url' {
17
+ const url: string
18
+ export default url
19
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "jsx": "react-jsx",
7
+ "strict": true,
8
+ "noUncheckedIndexedAccess": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "outDir": "dist"
12
+ },
13
+ "include": ["src"]
14
+ }
@@ -0,0 +1,112 @@
1
+ import { copyFileSync, existsSync, readdirSync } from 'node:fs'
2
+ import { join, resolve } from 'node:path'
3
+ import { koraServiceWorker } from '@korajs/cli/vite'
4
+ import tailwindcss from '@tailwindcss/vite'
5
+ import react from '@vitejs/plugin-react'
6
+ import type { Plugin } from 'vite'
7
+ import { defineConfig } from 'vite'
8
+
9
+ /**
10
+ * Adds Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers
11
+ * enabling SharedArrayBuffer where browsers support it while keeping common
12
+ * third-party embeds such as YouTube and Vimeo usable. Kora falls back to
13
+ * durable IndexedDB storage when SharedArrayBuffer is unavailable.
14
+ */
15
+ function crossOriginIsolation(): Plugin {
16
+ const embedderPolicy = process.env.KORA_COEP_POLICY || 'credentialless'
17
+
18
+ return {
19
+ name: 'cross-origin-isolation',
20
+ configureServer(server) {
21
+ server.middlewares.use((_req, res, next) => {
22
+ res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
23
+ res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
24
+ next()
25
+ })
26
+ },
27
+ configurePreviewServer(server) {
28
+ server.middlewares.use((_req, res, next) => {
29
+ res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
30
+ res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
31
+ next()
32
+ })
33
+ },
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Fixes sqlite3 WASM assets in production builds:
39
+ * 1. Copies the hashed sqlite3.wasm to an unhashed name so Emscripten's locateFile works
40
+ * 2. Copies sqlite3-opfs-async-proxy.js from node_modules (not bundled by Vite since
41
+ * sqlite3 loads it dynamically) so the full OPFS VFS can initialize without errors
42
+ */
43
+ function sqliteWasmHotfix(): Plugin {
44
+ return {
45
+ name: 'sqlite-wasm-hotfix',
46
+ apply: 'build',
47
+ closeBundle() {
48
+ const assetsDir = resolve('dist', 'assets')
49
+ if (!existsSync(assetsDir)) return
50
+
51
+ // Copy hashed sqlite3.wasm to unhashed name
52
+ for (const file of readdirSync(assetsDir)) {
53
+ if (/^sqlite3-.+\.wasm$/.test(file)) {
54
+ copyFileSync(join(assetsDir, file), join(assetsDir, 'sqlite3.wasm'))
55
+ break
56
+ }
57
+ }
58
+
59
+ // Copy OPFS async proxy worker (dynamically loaded by sqlite3, not detected by Vite)
60
+ const proxyFile = resolve(
61
+ 'node_modules',
62
+ '@sqlite.org',
63
+ 'sqlite-wasm',
64
+ 'sqlite-wasm',
65
+ 'jswasm',
66
+ 'sqlite3-opfs-async-proxy.js',
67
+ )
68
+ if (existsSync(proxyFile)) {
69
+ copyFileSync(proxyFile, join(assetsDir, 'sqlite3-opfs-async-proxy.js'))
70
+ }
71
+ },
72
+ }
73
+ }
74
+
75
+ // koraServiceWorker() (build only, keep it last): precaches the built app shell so the
76
+ // app opens offline, with a "new version available" reload prompt. See @korajs/cli/vite.
77
+ export default defineConfig({
78
+ plugins: [
79
+ react(),
80
+ tailwindcss(),
81
+ crossOriginIsolation(),
82
+ sqliteWasmHotfix(),
83
+ koraServiceWorker(),
84
+ ],
85
+ worker: {
86
+ format: 'es',
87
+ },
88
+ optimizeDeps: {
89
+ exclude: ['@sqlite.org/sqlite-wasm', '@korajs/store'],
90
+ include: ['yjs'],
91
+ },
92
+ resolve: {
93
+ dedupe: ['yjs'],
94
+ },
95
+ server: {
96
+ allowedHosts: true,
97
+ watch: {
98
+ ignored: ['**/.kora/**', '**/kora/generated/**', '**/*.db', '**/*.db-shm', '**/*.db-wal'],
99
+ },
100
+ proxy: {
101
+ '/kora-sync': {
102
+ target: 'ws://localhost:3001',
103
+ ws: true,
104
+ rewriteWsOrigin: true,
105
+ },
106
+ '/auth': {
107
+ target: 'http://localhost:3001',
108
+ changeOrigin: true,
109
+ },
110
+ },
111
+ },
112
+ })
@@ -0,0 +1,3 @@
1
+ # No environment variables required for local-only mode.
2
+ # To enable sync, see the README for instructions.
3
+ # VITE_SYNC_URL=ws://localhost:3001