@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,803 @@
1
+ import { useAuth } from '@korajs/auth/react'
2
+ import {
3
+ AlertTriangle,
4
+ CheckCircle2,
5
+ Circle,
6
+ Loader2,
7
+ Monitor,
8
+ Plus,
9
+ Settings,
10
+ Trash2,
11
+ Wifi,
12
+ WifiOff,
13
+ } from 'lucide-react'
14
+ import { useState } from 'react'
15
+ import { useTodos } from './modules/todos/useTodos'
16
+ import { testConnection } from './sync-config'
17
+
18
+ type Filter = 'all' | 'active' | 'completed'
19
+
20
+ interface AppProps {
21
+ syncUrl: string | null
22
+ onChangeServer: (newUrl: string | null) => void
23
+ onFactoryReset: () => void
24
+ }
25
+
26
+ export function App({ syncUrl, onChangeServer, onFactoryReset }: AppProps) {
27
+ const { allTodos, activeTodos, completedTodos, addTodo, toggleTodo, deleteTodo } = useTodos()
28
+ const { user, isAuthenticated, signOut, error } = useAuth()
29
+ const isAdding = addTodo.isLoading
30
+
31
+ const [filter, setFilter] = useState<Filter>('all')
32
+ const [input, setInput] = useState('')
33
+ const [showSettings, setShowSettings] = useState(false)
34
+
35
+ const filteredTodos =
36
+ filter === 'active' ? activeTodos : filter === 'completed' ? completedTodos : allTodos
37
+
38
+ const handleSubmit = (e: React.FormEvent) => {
39
+ e.preventDefault()
40
+ const title = input.trim()
41
+ if (title) {
42
+ addTodo.mutate({ title })
43
+ setInput('')
44
+ }
45
+ }
46
+
47
+ const clearCompleted = () => {
48
+ for (const todo of completedTodos) {
49
+ deleteTodo.mutate(todo.id)
50
+ }
51
+ }
52
+
53
+ return (
54
+ <div style={{ minHeight: '100vh', background: '#0a0a0a', color: '#f3f4f6' }}>
55
+ <div style={{ maxWidth: '640px', margin: '0 auto', padding: '48px 16px' }}>
56
+ {/* Header */}
57
+ <div
58
+ style={{
59
+ display: 'flex',
60
+ alignItems: 'center',
61
+ justifyContent: 'space-between',
62
+ marginBottom: '32px',
63
+ }}
64
+ >
65
+ <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
66
+ <Monitor style={{ width: '32px', height: '32px', color: '#818cf8' }} />
67
+ <h1 style={{ fontSize: '24px', fontWeight: 'bold' }}>Desktop Tasks</h1>
68
+ </div>
69
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
70
+ <span
71
+ style={{
72
+ display: 'flex',
73
+ alignItems: 'center',
74
+ gap: '6px',
75
+ fontSize: '12px',
76
+ color: syncUrl ? '#34d399' : '#6b7280',
77
+ background: '#1f2937',
78
+ padding: '4px 12px',
79
+ borderRadius: '9999px',
80
+ }}
81
+ >
82
+ {syncUrl ? (
83
+ <Wifi style={{ width: '12px', height: '12px' }} />
84
+ ) : (
85
+ <WifiOff style={{ width: '12px', height: '12px' }} />
86
+ )}
87
+ {syncUrl ? 'Syncing' : 'Local only'}
88
+ </span>
89
+ <AuthButton
90
+ isAuthenticated={isAuthenticated}
91
+ label={user?.email || 'Sign out'}
92
+ onSignOut={signOut}
93
+ />
94
+ <button
95
+ type="button"
96
+ onClick={() => setShowSettings(!showSettings)}
97
+ style={{
98
+ background: showSettings ? '#374151' : '#1f2937',
99
+ border: 'none',
100
+ borderRadius: '9999px',
101
+ padding: '6px',
102
+ cursor: 'pointer',
103
+ color: showSettings ? '#f3f4f6' : '#6b7280',
104
+ display: 'flex',
105
+ }}
106
+ >
107
+ <Settings style={{ width: '14px', height: '14px' }} />
108
+ </button>
109
+ </div>
110
+ </div>
111
+
112
+ {/* Settings panel */}
113
+ {showSettings && (
114
+ <SettingsPanel
115
+ syncUrl={syncUrl}
116
+ onChangeServer={onChangeServer}
117
+ onFactoryReset={onFactoryReset}
118
+ onClose={() => setShowSettings(false)}
119
+ />
120
+ )}
121
+
122
+ {error && (
123
+ <div
124
+ style={{
125
+ border: '1px solid rgba(248, 113, 113, 0.35)',
126
+ borderRadius: '8px',
127
+ color: '#fca5a5',
128
+ fontSize: '13px',
129
+ marginBottom: '16px',
130
+ padding: '10px 12px',
131
+ }}
132
+ >
133
+ {error}
134
+ </div>
135
+ )}
136
+
137
+ {/* Stats */}
138
+ <div
139
+ style={{
140
+ display: 'grid',
141
+ gridTemplateColumns: '1fr 1fr 1fr',
142
+ gap: '16px',
143
+ marginBottom: '32px',
144
+ }}
145
+ >
146
+ <StatCard label="Total" value={allTodos.length} color="#d1d5db" />
147
+ <StatCard label="Remaining" value={activeTodos.length} color="#fbbf24" />
148
+ <StatCard label="Done" value={completedTodos.length} color="#34d399" />
149
+ </div>
150
+
151
+ {/* Add form */}
152
+ <form
153
+ onSubmit={handleSubmit}
154
+ style={{ display: 'flex', gap: '12px', marginBottom: '32px' }}
155
+ >
156
+ <input
157
+ type="text"
158
+ value={input}
159
+ onChange={(e) => setInput(e.target.value)}
160
+ placeholder="What needs to be done?"
161
+ style={{
162
+ flex: 1,
163
+ borderRadius: '8px',
164
+ border: '1px solid #374151',
165
+ background: '#111827',
166
+ padding: '12px 16px',
167
+ color: '#f3f4f6',
168
+ outline: 'none',
169
+ fontSize: '14px',
170
+ }}
171
+ />
172
+ <button
173
+ type="submit"
174
+ disabled={isAdding || !input.trim()}
175
+ style={{
176
+ display: 'flex',
177
+ alignItems: 'center',
178
+ gap: '8px',
179
+ borderRadius: '8px',
180
+ background: '#4f46e5',
181
+ padding: '12px 20px',
182
+ fontWeight: '500',
183
+ color: 'white',
184
+ border: 'none',
185
+ cursor: 'pointer',
186
+ opacity: isAdding || !input.trim() ? 0.5 : 1,
187
+ fontSize: '14px',
188
+ }}
189
+ >
190
+ {isAdding ? (
191
+ <Loader2
192
+ style={{ width: '16px', height: '16px', animation: 'spin 1s linear infinite' }}
193
+ />
194
+ ) : (
195
+ <Plus style={{ width: '16px', height: '16px' }} />
196
+ )}
197
+ Add
198
+ </button>
199
+ </form>
200
+
201
+ {/* Filter tabs */}
202
+ <div style={{ display: 'flex', gap: '8px', marginBottom: '24px' }}>
203
+ {(['all', 'active', 'completed'] as const).map((f) => {
204
+ const count =
205
+ f === 'all'
206
+ ? allTodos.length
207
+ : f === 'active'
208
+ ? activeTodos.length
209
+ : completedTodos.length
210
+ const isActive = filter === f
211
+ return (
212
+ <button
213
+ key={f}
214
+ onClick={() => setFilter(f)}
215
+ style={{
216
+ display: 'flex',
217
+ alignItems: 'center',
218
+ gap: '8px',
219
+ borderRadius: '8px',
220
+ padding: '8px 16px',
221
+ fontSize: '14px',
222
+ fontWeight: '500',
223
+ border: 'none',
224
+ cursor: 'pointer',
225
+ background: isActive ? '#4f46e5' : '#1f2937',
226
+ color: isActive ? 'white' : '#9ca3af',
227
+ }}
228
+ >
229
+ {f.charAt(0).toUpperCase() + f.slice(1)}
230
+ <span
231
+ style={{
232
+ borderRadius: '9999px',
233
+ padding: '2px 8px',
234
+ fontSize: '12px',
235
+ background: isActive ? '#4338ca' : '#374151',
236
+ color: isActive ? 'white' : '#9ca3af',
237
+ }}
238
+ >
239
+ {count}
240
+ </span>
241
+ </button>
242
+ )
243
+ })}
244
+ </div>
245
+
246
+ {/* Todo list */}
247
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
248
+ {filteredTodos.length === 0 ? (
249
+ <div
250
+ style={{
251
+ borderRadius: '8px',
252
+ border: '1px dashed #1f2937',
253
+ padding: '48px 0',
254
+ textAlign: 'center',
255
+ color: '#4b5563',
256
+ }}
257
+ >
258
+ {filter === 'all'
259
+ ? 'No tasks yet. Add one above!'
260
+ : filter === 'active'
261
+ ? 'All caught up!'
262
+ : 'No completed tasks yet.'}
263
+ </div>
264
+ ) : (
265
+ filteredTodos.map((todo) => (
266
+ <div
267
+ key={todo.id}
268
+ style={{
269
+ display: 'flex',
270
+ alignItems: 'center',
271
+ gap: '12px',
272
+ borderRadius: '8px',
273
+ border: '1px solid #1f2937',
274
+ background: '#111827',
275
+ padding: '12px 16px',
276
+ }}
277
+ >
278
+ <button
279
+ onClick={() => toggleTodo.mutate(todo.id, { completed: !todo.completed })}
280
+ style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0 }}
281
+ >
282
+ {todo.completed ? (
283
+ <CheckCircle2 style={{ width: '20px', height: '20px', color: '#34d399' }} />
284
+ ) : (
285
+ <Circle style={{ width: '20px', height: '20px', color: '#6b7280' }} />
286
+ )}
287
+ </button>
288
+ <span
289
+ style={{
290
+ flex: 1,
291
+ color: todo.completed ? '#6b7280' : '#f3f4f6',
292
+ textDecoration: todo.completed ? 'line-through' : 'none',
293
+ }}
294
+ >
295
+ {String(todo.title)}
296
+ </span>
297
+ {todo.createdAt && (
298
+ <span style={{ fontSize: '12px', color: '#374151' }}>
299
+ {formatTime(Number(todo.createdAt))}
300
+ </span>
301
+ )}
302
+ <button
303
+ onClick={() => deleteTodo.mutate(todo.id)}
304
+ style={{
305
+ background: 'none',
306
+ border: 'none',
307
+ cursor: 'pointer',
308
+ padding: 0,
309
+ color: '#4b5563',
310
+ }}
311
+ >
312
+ <Trash2 style={{ width: '16px', height: '16px' }} />
313
+ </button>
314
+ </div>
315
+ ))
316
+ )}
317
+ </div>
318
+
319
+ {/* Footer */}
320
+ {allTodos.length > 0 && (
321
+ <div
322
+ style={{
323
+ marginTop: '24px',
324
+ display: 'flex',
325
+ justifyContent: 'space-between',
326
+ fontSize: '14px',
327
+ color: '#6b7280',
328
+ }}
329
+ >
330
+ <span>
331
+ {activeTodos.length} item{activeTodos.length !== 1 ? 's' : ''} left
332
+ </span>
333
+ {completedTodos.length > 0 && (
334
+ <button
335
+ onClick={clearCompleted}
336
+ style={{
337
+ background: 'none',
338
+ border: 'none',
339
+ cursor: 'pointer',
340
+ color: '#6b7280',
341
+ fontSize: '14px',
342
+ }}
343
+ >
344
+ Clear completed
345
+ </button>
346
+ )}
347
+ </div>
348
+ )}
349
+
350
+ <p style={{ marginTop: '48px', textAlign: 'center', fontSize: '12px', color: '#374151' }}>
351
+ Powered by Kora &mdash; native SQLite, offline-first
352
+ </p>
353
+ </div>
354
+ </div>
355
+ )
356
+ }
357
+
358
+ function AuthButton({
359
+ isAuthenticated,
360
+ label,
361
+ onSignOut,
362
+ }: {
363
+ isAuthenticated: boolean
364
+ label: string
365
+ onSignOut: () => Promise<void>
366
+ }) {
367
+ const { getOAuthAuthorizationUrl } = useAuth()
368
+
369
+ const handleSignIn = async () => {
370
+ const { url } = await getOAuthAuthorizationUrl('google')
371
+ window.open(url, '_blank', 'noopener,noreferrer')
372
+ }
373
+
374
+ return (
375
+ <button
376
+ type="button"
377
+ onClick={() => (isAuthenticated ? onSignOut() : handleSignIn())}
378
+ style={{
379
+ background: '#1f2937',
380
+ border: '1px solid #374151',
381
+ borderRadius: '9999px',
382
+ color: '#d1d5db',
383
+ cursor: 'pointer',
384
+ fontSize: '12px',
385
+ maxWidth: '160px',
386
+ overflow: 'hidden',
387
+ padding: '4px 12px',
388
+ textOverflow: 'ellipsis',
389
+ whiteSpace: 'nowrap',
390
+ }}
391
+ >
392
+ {isAuthenticated ? label : 'Sign in'}
393
+ </button>
394
+ )
395
+ }
396
+
397
+ // ---------------------------------------------------------------------------
398
+ // Settings Panel
399
+ // ---------------------------------------------------------------------------
400
+
401
+ interface SettingsPanelProps {
402
+ syncUrl: string | null
403
+ onChangeServer: (newUrl: string | null) => void
404
+ onFactoryReset: () => void
405
+ onClose: () => void
406
+ }
407
+
408
+ function SettingsPanel({ syncUrl, onChangeServer, onFactoryReset, onClose }: SettingsPanelProps) {
409
+ const [editing, setEditing] = useState(false)
410
+ const [newUrl, setNewUrl] = useState(syncUrl ?? '')
411
+ const [testing, setTesting] = useState(false)
412
+ const [testResult, setTestResult] = useState<'success' | 'failure' | null>(null)
413
+ const [showResetConfirm, setShowResetConfirm] = useState(false)
414
+ const [showChangeConfirm, setShowChangeConfirm] = useState(false)
415
+
416
+ const handleTestConnection = async () => {
417
+ const url = editing ? newUrl.trim() : syncUrl
418
+ if (!url) return
419
+ setTesting(true)
420
+ setTestResult(null)
421
+ const ok = await testConnection(url)
422
+ setTestResult(ok ? 'success' : 'failure')
423
+ setTesting(false)
424
+ }
425
+
426
+ const handleSaveUrl = () => {
427
+ const trimmed = newUrl.trim()
428
+ if (trimmed === syncUrl) {
429
+ setEditing(false)
430
+ return
431
+ }
432
+ // If there's existing data and the URL is changing, warn about data isolation
433
+ setShowChangeConfirm(true)
434
+ }
435
+
436
+ const confirmChangeServer = (keepData: boolean) => {
437
+ setShowChangeConfirm(false)
438
+ if (keepData) {
439
+ onChangeServer(newUrl.trim() || null)
440
+ } else {
441
+ // Factory reset + change server
442
+ // Store the new URL so it's picked up after reload
443
+ if (newUrl.trim()) {
444
+ localStorage.setItem('kora-sync-url', newUrl.trim())
445
+ localStorage.setItem('kora-sync-configured', 'true')
446
+ }
447
+ onFactoryReset()
448
+ }
449
+ }
450
+
451
+ const handleDisconnect = () => {
452
+ onChangeServer(null)
453
+ }
454
+
455
+ const handleConnect = () => {
456
+ setEditing(true)
457
+ setNewUrl('')
458
+ }
459
+
460
+ return (
461
+ <div
462
+ style={{
463
+ marginBottom: '24px',
464
+ borderRadius: '8px',
465
+ border: '1px solid #1f2937',
466
+ background: '#111827',
467
+ overflow: 'hidden',
468
+ }}
469
+ >
470
+ {/* Server URL section */}
471
+ <div style={{ padding: '16px' }}>
472
+ <div
473
+ style={{
474
+ display: 'flex',
475
+ justifyContent: 'space-between',
476
+ alignItems: 'flex-start',
477
+ marginBottom: '12px',
478
+ }}
479
+ >
480
+ <p style={{ fontSize: '14px', fontWeight: '500', color: '#d1d5db' }}>Sync Server</p>
481
+ <button
482
+ onClick={onClose}
483
+ style={{
484
+ fontSize: '12px',
485
+ color: '#6b7280',
486
+ background: 'none',
487
+ border: 'none',
488
+ cursor: 'pointer',
489
+ }}
490
+ >
491
+ Close
492
+ </button>
493
+ </div>
494
+
495
+ {editing ? (
496
+ <div>
497
+ <input
498
+ type="text"
499
+ value={newUrl}
500
+ onChange={(e) => {
501
+ setNewUrl(e.target.value)
502
+ setTestResult(null)
503
+ }}
504
+ placeholder="wss://your-server.example.com/kora-sync"
505
+ style={{
506
+ width: '100%',
507
+ borderRadius: '6px',
508
+ border: '1px solid #374151',
509
+ background: '#0a0a0a',
510
+ padding: '8px 12px',
511
+ color: '#f3f4f6',
512
+ outline: 'none',
513
+ fontSize: '13px',
514
+ boxSizing: 'border-box',
515
+ marginBottom: '8px',
516
+ }}
517
+ />
518
+ <div style={{ display: 'flex', gap: '8px' }}>
519
+ <button
520
+ onClick={handleSaveUrl}
521
+ disabled={!newUrl.trim()}
522
+ style={btnStyle('#4f46e5', !newUrl.trim())}
523
+ >
524
+ Save
525
+ </button>
526
+ <button
527
+ onClick={handleTestConnection}
528
+ disabled={testing || !newUrl.trim()}
529
+ style={btnStyle('#374151', testing || !newUrl.trim())}
530
+ >
531
+ {testing ? 'Testing...' : 'Test'}
532
+ </button>
533
+ <button
534
+ onClick={() => {
535
+ setEditing(false)
536
+ setTestResult(null)
537
+ }}
538
+ style={btnStyle('#374151', false)}
539
+ >
540
+ Cancel
541
+ </button>
542
+ </div>
543
+ </div>
544
+ ) : (
545
+ <div>
546
+ <p
547
+ style={{
548
+ fontSize: '13px',
549
+ color: '#9ca3af',
550
+ marginBottom: '8px',
551
+ wordBreak: 'break-all',
552
+ }}
553
+ >
554
+ {syncUrl ?? 'Not connected — running in local-only mode'}
555
+ </p>
556
+ <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
557
+ {syncUrl ? (
558
+ <>
559
+ <button
560
+ onClick={() => {
561
+ setEditing(true)
562
+ setNewUrl(syncUrl)
563
+ }}
564
+ style={btnStyle('#374151', false)}
565
+ >
566
+ Change URL
567
+ </button>
568
+ <button
569
+ onClick={handleTestConnection}
570
+ disabled={testing}
571
+ style={btnStyle('#374151', testing)}
572
+ >
573
+ {testing ? 'Testing...' : 'Test Connection'}
574
+ </button>
575
+ <button onClick={handleDisconnect} style={btnStyle('#7f1d1d', false, '#ef4444')}>
576
+ Disconnect
577
+ </button>
578
+ </>
579
+ ) : (
580
+ <button onClick={handleConnect} style={btnStyle('#4f46e5', false)}>
581
+ Connect to Server
582
+ </button>
583
+ )}
584
+ </div>
585
+ </div>
586
+ )}
587
+
588
+ {/* Test result */}
589
+ {testResult && (
590
+ <p
591
+ style={{
592
+ fontSize: '12px',
593
+ marginTop: '8px',
594
+ color: testResult === 'success' ? '#34d399' : '#fbbf24',
595
+ }}
596
+ >
597
+ {testResult === 'success'
598
+ ? 'Connection successful'
599
+ : 'Server unreachable — it may be down temporarily'}
600
+ </p>
601
+ )}
602
+ </div>
603
+
604
+ {/* Danger zone */}
605
+ <div style={{ padding: '12px 16px', borderTop: '1px solid #1f2937', background: '#0a0a0a' }}>
606
+ {showResetConfirm ? (
607
+ <div>
608
+ <div
609
+ style={{ display: 'flex', gap: '8px', alignItems: 'flex-start', marginBottom: '8px' }}
610
+ >
611
+ <AlertTriangle
612
+ style={{
613
+ width: '16px',
614
+ height: '16px',
615
+ color: '#ef4444',
616
+ flexShrink: 0,
617
+ marginTop: '1px',
618
+ }}
619
+ />
620
+ <p style={{ fontSize: '13px', color: '#fca5a5' }}>
621
+ This will delete all local data and reset the app to its initial state. This cannot
622
+ be undone.
623
+ </p>
624
+ </div>
625
+ <div style={{ display: 'flex', gap: '8px' }}>
626
+ <button onClick={onFactoryReset} style={btnStyle('#7f1d1d', false, '#ef4444')}>
627
+ Confirm Reset
628
+ </button>
629
+ <button onClick={() => setShowResetConfirm(false)} style={btnStyle('#374151', false)}>
630
+ Cancel
631
+ </button>
632
+ </div>
633
+ </div>
634
+ ) : (
635
+ <button
636
+ onClick={() => setShowResetConfirm(true)}
637
+ style={{
638
+ fontSize: '13px',
639
+ color: '#6b7280',
640
+ background: 'none',
641
+ border: 'none',
642
+ cursor: 'pointer',
643
+ }}
644
+ >
645
+ Reset app (clear all local data)
646
+ </button>
647
+ )}
648
+ </div>
649
+
650
+ {/* Server change confirmation dialog */}
651
+ {showChangeConfirm && (
652
+ <div
653
+ style={{
654
+ position: 'fixed',
655
+ inset: 0,
656
+ background: 'rgba(0,0,0,0.7)',
657
+ display: 'flex',
658
+ alignItems: 'center',
659
+ justifyContent: 'center',
660
+ zIndex: 100,
661
+ }}
662
+ >
663
+ <div
664
+ style={{
665
+ maxWidth: '420px',
666
+ width: '100%',
667
+ margin: '0 16px',
668
+ borderRadius: '12px',
669
+ border: '1px solid #374151',
670
+ background: '#111827',
671
+ padding: '24px',
672
+ }}
673
+ >
674
+ <div
675
+ style={{
676
+ display: 'flex',
677
+ gap: '12px',
678
+ alignItems: 'flex-start',
679
+ marginBottom: '16px',
680
+ }}
681
+ >
682
+ <AlertTriangle
683
+ style={{
684
+ width: '20px',
685
+ height: '20px',
686
+ color: '#fbbf24',
687
+ flexShrink: 0,
688
+ marginTop: '2px',
689
+ }}
690
+ />
691
+ <div>
692
+ <p
693
+ style={{
694
+ fontSize: '15px',
695
+ fontWeight: '500',
696
+ color: '#f3f4f6',
697
+ marginBottom: '8px',
698
+ }}
699
+ >
700
+ Changing sync servers
701
+ </p>
702
+ <p style={{ fontSize: '13px', color: '#9ca3af', lineHeight: '1.6' }}>
703
+ Existing local data was created for the current server. Keeping it and connecting
704
+ to a different server may sync this data to the wrong place.
705
+ </p>
706
+ </div>
707
+ </div>
708
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
709
+ <button
710
+ onClick={() => confirmChangeServer(false)}
711
+ style={{
712
+ width: '100%',
713
+ padding: '10px',
714
+ borderRadius: '8px',
715
+ border: '1px solid #374151',
716
+ background: '#4f46e5',
717
+ color: 'white',
718
+ fontSize: '14px',
719
+ cursor: 'pointer',
720
+ fontWeight: '500',
721
+ }}
722
+ >
723
+ Clear data and switch (recommended)
724
+ </button>
725
+ <button
726
+ onClick={() => confirmChangeServer(true)}
727
+ style={{
728
+ width: '100%',
729
+ padding: '10px',
730
+ borderRadius: '8px',
731
+ border: '1px solid #374151',
732
+ background: '#1f2937',
733
+ color: '#9ca3af',
734
+ fontSize: '14px',
735
+ cursor: 'pointer',
736
+ }}
737
+ >
738
+ Keep data and switch
739
+ </button>
740
+ <button
741
+ onClick={() => setShowChangeConfirm(false)}
742
+ style={{
743
+ width: '100%',
744
+ padding: '10px',
745
+ borderRadius: '8px',
746
+ border: 'none',
747
+ background: 'transparent',
748
+ color: '#6b7280',
749
+ fontSize: '14px',
750
+ cursor: 'pointer',
751
+ }}
752
+ >
753
+ Cancel
754
+ </button>
755
+ </div>
756
+ </div>
757
+ </div>
758
+ )}
759
+ </div>
760
+ )
761
+ }
762
+
763
+ function btnStyle(bg: string, disabled: boolean, color = '#d1d5db'): React.CSSProperties {
764
+ return {
765
+ fontSize: '13px',
766
+ color,
767
+ background: bg,
768
+ border: 'none',
769
+ borderRadius: '6px',
770
+ padding: '6px 12px',
771
+ cursor: disabled ? 'default' : 'pointer',
772
+ opacity: disabled ? 0.5 : 1,
773
+ }
774
+ }
775
+
776
+ // ---------------------------------------------------------------------------
777
+ // Shared components
778
+ // ---------------------------------------------------------------------------
779
+
780
+ function StatCard({ label, value, color }: { label: string; value: number; color: string }) {
781
+ return (
782
+ <div
783
+ style={{
784
+ borderRadius: '8px',
785
+ border: '1px solid #1f2937',
786
+ background: '#111827',
787
+ padding: '16px',
788
+ }}
789
+ >
790
+ <p style={{ fontSize: '14px', color: '#6b7280' }}>{label}</p>
791
+ <p style={{ fontSize: '24px', fontWeight: 'bold', color }}>{value}</p>
792
+ </div>
793
+ )
794
+ }
795
+
796
+ function formatTime(timestamp: number): string {
797
+ const date = new Date(timestamp)
798
+ const now = new Date()
799
+ if (date.toDateString() === now.toDateString()) {
800
+ return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
801
+ }
802
+ return date.toLocaleDateString([], { month: 'short', day: 'numeric' })
803
+ }