@runcore-sh/runcore 0.4.0 → 0.5.1

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 (351) hide show
  1. package/dictionary.json +2 -2
  2. package/dist/activity/log.js +2 -2
  3. package/dist/activity/log.js.map +1 -1
  4. package/dist/agents/governed-spawn.d.ts.map +1 -1
  5. package/dist/cli.js +101 -11
  6. package/dist/cli.js.map +1 -1
  7. package/dist/extensions/cache.d.ts +57 -0
  8. package/dist/extensions/cache.d.ts.map +1 -0
  9. package/dist/extensions/cache.js +173 -0
  10. package/dist/extensions/cache.js.map +1 -0
  11. package/dist/extensions/client.d.ts +55 -0
  12. package/dist/extensions/client.d.ts.map +1 -0
  13. package/dist/extensions/client.js +120 -0
  14. package/dist/extensions/client.js.map +1 -0
  15. package/dist/extensions/index.d.ts +13 -0
  16. package/dist/extensions/index.d.ts.map +1 -0
  17. package/dist/extensions/index.js +12 -0
  18. package/dist/extensions/index.js.map +1 -0
  19. package/dist/extensions/loader.d.ts +50 -0
  20. package/dist/extensions/loader.d.ts.map +1 -0
  21. package/dist/extensions/loader.js +166 -0
  22. package/dist/extensions/loader.js.map +1 -0
  23. package/dist/extensions/manifest.d.ts +38 -0
  24. package/dist/extensions/manifest.d.ts.map +1 -0
  25. package/dist/extensions/manifest.js +17 -0
  26. package/dist/extensions/manifest.js.map +1 -0
  27. package/dist/extensions/stubs.d.ts +27 -0
  28. package/dist/extensions/stubs.d.ts.map +1 -0
  29. package/dist/extensions/stubs.js +45 -0
  30. package/dist/extensions/stubs.js.map +1 -0
  31. package/dist/lib/audit.js +2 -2
  32. package/dist/lib/audit.js.map +1 -1
  33. package/dist/lib/brain-migrate.d.ts +21 -0
  34. package/dist/lib/brain-migrate.d.ts.map +1 -0
  35. package/dist/lib/brain-migrate.js +137 -0
  36. package/dist/lib/brain-migrate.js.map +1 -0
  37. package/dist/lib/paths.d.ts +27 -0
  38. package/dist/lib/paths.d.ts.map +1 -1
  39. package/dist/lib/paths.js +65 -0
  40. package/dist/lib/paths.js.map +1 -1
  41. package/dist/llm/call-log.d.ts +40 -0
  42. package/dist/llm/call-log.d.ts.map +1 -0
  43. package/dist/llm/call-log.js +35 -0
  44. package/dist/llm/call-log.js.map +1 -0
  45. package/dist/llm/complete.d.ts +6 -0
  46. package/dist/llm/complete.d.ts.map +1 -1
  47. package/dist/llm/complete.js +27 -0
  48. package/dist/llm/complete.js.map +1 -1
  49. package/dist/mcp-server.js +118 -2
  50. package/dist/mcp-server.js.map +1 -1
  51. package/dist/memory/file-backed.d.ts +4 -0
  52. package/dist/memory/file-backed.d.ts.map +1 -1
  53. package/dist/memory/file-backed.js +4 -0
  54. package/dist/memory/file-backed.js.map +1 -1
  55. package/dist/memory/vector-index.d.ts +4 -12
  56. package/dist/memory/vector-index.d.ts.map +1 -1
  57. package/dist/memory/vector-index.js +11 -93
  58. package/dist/memory/vector-index.js.map +1 -1
  59. package/dist/search/brain-docs.d.ts +17 -7
  60. package/dist/search/brain-docs.d.ts.map +1 -1
  61. package/dist/search/brain-docs.js +170 -52
  62. package/dist/search/brain-docs.js.map +1 -1
  63. package/dist/search/brain-rag.d.ts +45 -0
  64. package/dist/search/brain-rag.d.ts.map +1 -0
  65. package/dist/search/brain-rag.js +275 -0
  66. package/dist/search/brain-rag.js.map +1 -0
  67. package/dist/search/chunker.d.ts +24 -0
  68. package/dist/search/chunker.d.ts.map +1 -0
  69. package/dist/search/chunker.js +95 -0
  70. package/dist/search/chunker.js.map +1 -0
  71. package/dist/search/embedder.d.ts +16 -0
  72. package/dist/search/embedder.d.ts.map +1 -0
  73. package/dist/search/embedder.js +108 -0
  74. package/dist/search/embedder.js.map +1 -0
  75. package/dist/search/file-watcher.d.ts +11 -0
  76. package/dist/search/file-watcher.d.ts.map +1 -0
  77. package/dist/search/file-watcher.js +86 -0
  78. package/dist/search/file-watcher.js.map +1 -0
  79. package/dist/server.d.ts.map +1 -1
  80. package/dist/server.js +814 -472
  81. package/dist/server.js.map +1 -1
  82. package/dist/sessions/store.d.ts +9 -0
  83. package/dist/sessions/store.d.ts.map +1 -1
  84. package/dist/sessions/store.js.map +1 -1
  85. package/dist/settings.d.ts +26 -0
  86. package/dist/settings.d.ts.map +1 -1
  87. package/dist/settings.js +78 -2
  88. package/dist/settings.js.map +1 -1
  89. package/dist/tracing/init.d.ts +1 -1
  90. package/dist/tracing/init.d.ts.map +1 -1
  91. package/dist/utils/logger.js +2 -2
  92. package/dist/utils/logger.js.map +1 -1
  93. package/module-tiers.json +164 -0
  94. package/package.json +9 -13
  95. package/public/avatar/cache/1184385ec5522b57.mp4 +0 -0
  96. package/public/avatar/cache/1f15f6a1ebd7e439.mp4 +0 -0
  97. package/public/avatar/cache/2c7e47ff0bdeb8d1.mp4 +0 -0
  98. package/public/avatar/cache/5f308566f7abb8f2.mp4 +0 -0
  99. package/public/avatar/cache/62f9cfba848d724e.mp4 +0 -0
  100. package/public/avatar/cache/6d64e657e6bf2aab.mp4 +0 -0
  101. package/public/avatar/cache/763ad0349e0b6f26.mp4 +0 -0
  102. package/public/avatar/cache/81a516cfd461b2b9.mp4 +0 -0
  103. package/public/avatar/cache/9366de15fd6910ca.mp4 +0 -0
  104. package/public/avatar/cache/ade41a846b283895.mp4 +0 -0
  105. package/public/avatar/cache/b6066e5c65383eec.mp4 +0 -0
  106. package/public/avatar/cache/edadb75d37891fc7.mp4 +0 -0
  107. package/public/avatar/cache/f0ae159640621dd9.mp4 +0 -0
  108. package/public/avatar/cache/fc2e5419adf29d96.mp4 +0 -0
  109. package/public/index.html +379 -59
  110. package/dist/agents/autonomous.js +0 -749
  111. package/dist/agents/autonomous.js.map +0 -1
  112. package/dist/agents/commit.js +0 -113
  113. package/dist/agents/commit.js.map +0 -1
  114. package/dist/agents/continue.js +0 -158
  115. package/dist/agents/continue.js.map +0 -1
  116. package/dist/agents/cooldown.js +0 -397
  117. package/dist/agents/cooldown.js.map +0 -1
  118. package/dist/agents/dedup-guard.js +0 -131
  119. package/dist/agents/dedup-guard.js.map +0 -1
  120. package/dist/agents/feed.js +0 -176
  121. package/dist/agents/feed.js.map +0 -1
  122. package/dist/agents/governance.js +0 -292
  123. package/dist/agents/governance.js.map +0 -1
  124. package/dist/agents/governed-spawn.js +0 -192
  125. package/dist/agents/governed-spawn.js.map +0 -1
  126. package/dist/agents/heartbeat.js +0 -324
  127. package/dist/agents/heartbeat.js.map +0 -1
  128. package/dist/agents/instance-manager.js +0 -850
  129. package/dist/agents/instance-manager.js.map +0 -1
  130. package/dist/agents/issue-reporter.js +0 -123
  131. package/dist/agents/issue-reporter.js.map +0 -1
  132. package/dist/agents/issues.js +0 -141
  133. package/dist/agents/issues.js.map +0 -1
  134. package/dist/agents/locks.js +0 -234
  135. package/dist/agents/locks.js.map +0 -1
  136. package/dist/agents/memory.js +0 -93
  137. package/dist/agents/memory.js.map +0 -1
  138. package/dist/agents/monitor.js +0 -235
  139. package/dist/agents/monitor.js.map +0 -1
  140. package/dist/agents/orchestration.js +0 -715
  141. package/dist/agents/orchestration.js.map +0 -1
  142. package/dist/agents/recover.js +0 -166
  143. package/dist/agents/recover.js.map +0 -1
  144. package/dist/agents/reflection.js +0 -199
  145. package/dist/agents/reflection.js.map +0 -1
  146. package/dist/agents/runtime/bus.js +0 -174
  147. package/dist/agents/runtime/bus.js.map +0 -1
  148. package/dist/agents/runtime/config.js +0 -101
  149. package/dist/agents/runtime/config.js.map +0 -1
  150. package/dist/agents/runtime/driver.js +0 -214
  151. package/dist/agents/runtime/driver.js.map +0 -1
  152. package/dist/agents/runtime/errors.js +0 -40
  153. package/dist/agents/runtime/errors.js.map +0 -1
  154. package/dist/agents/runtime/index.js +0 -54
  155. package/dist/agents/runtime/index.js.map +0 -1
  156. package/dist/agents/runtime/lifecycle.js +0 -116
  157. package/dist/agents/runtime/lifecycle.js.map +0 -1
  158. package/dist/agents/runtime/manager.js +0 -948
  159. package/dist/agents/runtime/manager.js.map +0 -1
  160. package/dist/agents/runtime/registry.js +0 -195
  161. package/dist/agents/runtime/registry.js.map +0 -1
  162. package/dist/agents/runtime/resources.js +0 -146
  163. package/dist/agents/runtime/resources.js.map +0 -1
  164. package/dist/agents/runtime/types.js +0 -24
  165. package/dist/agents/runtime/types.js.map +0 -1
  166. package/dist/agents/spawn-policy.js +0 -202
  167. package/dist/agents/spawn-policy.js.map +0 -1
  168. package/dist/agents/spawn.js +0 -970
  169. package/dist/agents/spawn.js.map +0 -1
  170. package/dist/agents/triage.js +0 -81
  171. package/dist/agents/triage.js.map +0 -1
  172. package/dist/agents/workflow.js +0 -543
  173. package/dist/agents/workflow.js.map +0 -1
  174. package/dist/avatar/client.js +0 -172
  175. package/dist/avatar/client.js.map +0 -1
  176. package/dist/avatar/sidecar.js +0 -125
  177. package/dist/avatar/sidecar.js.map +0 -1
  178. package/dist/browser/sessions.js +0 -122
  179. package/dist/browser/sessions.js.map +0 -1
  180. package/dist/capabilities/definitions/browser.js +0 -242
  181. package/dist/capabilities/definitions/browser.js.map +0 -1
  182. package/dist/channels/whatsapp.js +0 -200
  183. package/dist/channels/whatsapp.js.map +0 -1
  184. package/dist/credentials/store.js +0 -189
  185. package/dist/credentials/store.js.map +0 -1
  186. package/dist/files/deep-index.js +0 -337
  187. package/dist/files/deep-index.js.map +0 -1
  188. package/dist/files/extract.js +0 -33
  189. package/dist/files/extract.js.map +0 -1
  190. package/dist/files/gdrive.js +0 -246
  191. package/dist/files/gdrive.js.map +0 -1
  192. package/dist/github/client.js +0 -408
  193. package/dist/github/client.js.map +0 -1
  194. package/dist/github/commit-analysis.js +0 -276
  195. package/dist/github/commit-analysis.js.map +0 -1
  196. package/dist/github/contributor-stats.js +0 -119
  197. package/dist/github/contributor-stats.js.map +0 -1
  198. package/dist/github/issue-sla.js +0 -220
  199. package/dist/github/issue-sla.js.map +0 -1
  200. package/dist/github/issue-triage.js +0 -286
  201. package/dist/github/issue-triage.js.map +0 -1
  202. package/dist/github/pr-readiness.js +0 -197
  203. package/dist/github/pr-readiness.js.map +0 -1
  204. package/dist/github/pr-review.js +0 -410
  205. package/dist/github/pr-review.js.map +0 -1
  206. package/dist/github/release-notes.js +0 -227
  207. package/dist/github/release-notes.js.map +0 -1
  208. package/dist/github/repo-health.js +0 -303
  209. package/dist/github/repo-health.js.map +0 -1
  210. package/dist/github/retry.js +0 -117
  211. package/dist/github/retry.js.map +0 -1
  212. package/dist/github/types.js +0 -8
  213. package/dist/github/types.js.map +0 -1
  214. package/dist/github/webhooks.js +0 -153
  215. package/dist/github/webhooks.js.map +0 -1
  216. package/dist/google/auth.js +0 -325
  217. package/dist/google/auth.js.map +0 -1
  218. package/dist/google/calendar-timer.js +0 -91
  219. package/dist/google/calendar-timer.js.map +0 -1
  220. package/dist/google/calendar.js +0 -270
  221. package/dist/google/calendar.js.map +0 -1
  222. package/dist/google/docs.js +0 -309
  223. package/dist/google/docs.js.map +0 -1
  224. package/dist/google/gmail-send.js +0 -219
  225. package/dist/google/gmail-send.js.map +0 -1
  226. package/dist/google/gmail-timer.js +0 -223
  227. package/dist/google/gmail-timer.js.map +0 -1
  228. package/dist/google/gmail.js +0 -470
  229. package/dist/google/gmail.js.map +0 -1
  230. package/dist/google/plugin.js +0 -169
  231. package/dist/google/plugin.js.map +0 -1
  232. package/dist/google/tasks-timer.js +0 -107
  233. package/dist/google/tasks-timer.js.map +0 -1
  234. package/dist/google/tasks.js +0 -331
  235. package/dist/google/tasks.js.map +0 -1
  236. package/dist/google/temporal.js +0 -176
  237. package/dist/google/temporal.js.map +0 -1
  238. package/dist/integrations/gate.js +0 -100
  239. package/dist/integrations/gate.js.map +0 -1
  240. package/dist/integrations/github.js +0 -331
  241. package/dist/integrations/github.js.map +0 -1
  242. package/dist/integrations/google-tasks.js +0 -432
  243. package/dist/integrations/google-tasks.js.map +0 -1
  244. package/dist/mdns.js +0 -110
  245. package/dist/mdns.js.map +0 -1
  246. package/dist/notifications/channel.js +0 -83
  247. package/dist/notifications/channel.js.map +0 -1
  248. package/dist/notifications/channels/adapter.js +0 -55
  249. package/dist/notifications/channels/adapter.js.map +0 -1
  250. package/dist/notifications/channels/index.js +0 -6
  251. package/dist/notifications/channels/index.js.map +0 -1
  252. package/dist/notifications/channels/log.js +0 -29
  253. package/dist/notifications/channels/log.js.map +0 -1
  254. package/dist/notifications/email.js +0 -72
  255. package/dist/notifications/email.js.map +0 -1
  256. package/dist/notifications/engine.js +0 -198
  257. package/dist/notifications/engine.js.map +0 -1
  258. package/dist/notifications/index.js +0 -24
  259. package/dist/notifications/index.js.map +0 -1
  260. package/dist/notifications/phone.js +0 -48
  261. package/dist/notifications/phone.js.map +0 -1
  262. package/dist/notifications/sms.js +0 -65
  263. package/dist/notifications/sms.js.map +0 -1
  264. package/dist/notifications/types.js +0 -14
  265. package/dist/notifications/types.js.map +0 -1
  266. package/dist/notifications/webhook.js +0 -65
  267. package/dist/notifications/webhook.js.map +0 -1
  268. package/dist/resend/inbox.js +0 -199
  269. package/dist/resend/inbox.js.map +0 -1
  270. package/dist/resend/webhooks.js +0 -244
  271. package/dist/resend/webhooks.js.map +0 -1
  272. package/dist/search/browse.js +0 -225
  273. package/dist/search/browse.js.map +0 -1
  274. package/dist/search/perplexity.js +0 -41
  275. package/dist/search/perplexity.js.map +0 -1
  276. package/dist/slack/channels.js +0 -277
  277. package/dist/slack/channels.js.map +0 -1
  278. package/dist/slack/client.js +0 -468
  279. package/dist/slack/client.js.map +0 -1
  280. package/dist/slack/retry.js +0 -100
  281. package/dist/slack/retry.js.map +0 -1
  282. package/dist/slack/types.js +0 -52
  283. package/dist/slack/types.js.map +0 -1
  284. package/dist/slack/webhooks.js +0 -285
  285. package/dist/slack/webhooks.js.map +0 -1
  286. package/dist/stt/client.js +0 -66
  287. package/dist/stt/client.js.map +0 -1
  288. package/dist/stt/sidecar.js +0 -115
  289. package/dist/stt/sidecar.js.map +0 -1
  290. package/dist/tracing/bridge.js +0 -70
  291. package/dist/tracing/bridge.js.map +0 -1
  292. package/dist/tracing/correlation.js +0 -49
  293. package/dist/tracing/correlation.js.map +0 -1
  294. package/dist/tracing/index.js +0 -18
  295. package/dist/tracing/index.js.map +0 -1
  296. package/dist/tracing/init.js +0 -81
  297. package/dist/tracing/init.js.map +0 -1
  298. package/dist/tracing/instrument.js +0 -145
  299. package/dist/tracing/instrument.js.map +0 -1
  300. package/dist/tracing/middleware.js +0 -69
  301. package/dist/tracing/middleware.js.map +0 -1
  302. package/dist/tracing/tracer.js +0 -327
  303. package/dist/tracing/tracer.js.map +0 -1
  304. package/dist/tts/client.js +0 -48
  305. package/dist/tts/client.js.map +0 -1
  306. package/dist/tts/sidecar.js +0 -148
  307. package/dist/tts/sidecar.js.map +0 -1
  308. package/dist/twilio/call.js +0 -79
  309. package/dist/twilio/call.js.map +0 -1
  310. package/dist/vault/matcher.js +0 -197
  311. package/dist/vault/matcher.js.map +0 -1
  312. package/dist/vault/personal.js +0 -163
  313. package/dist/vault/personal.js.map +0 -1
  314. package/dist/vault/policy.js +0 -159
  315. package/dist/vault/policy.js.map +0 -1
  316. package/dist/vault/store.js +0 -122
  317. package/dist/vault/store.js.map +0 -1
  318. package/dist/vault/transfer.js +0 -188
  319. package/dist/vault/transfer.js.map +0 -1
  320. package/dist/volumes/index.js +0 -2
  321. package/dist/volumes/index.js.map +0 -1
  322. package/dist/volumes/manager.js +0 -462
  323. package/dist/volumes/manager.js.map +0 -1
  324. package/dist/volumes/types.js +0 -8
  325. package/dist/volumes/types.js.map +0 -1
  326. package/dist/webhooks/config.js +0 -214
  327. package/dist/webhooks/config.js.map +0 -1
  328. package/dist/webhooks/event-log.js +0 -132
  329. package/dist/webhooks/event-log.js.map +0 -1
  330. package/dist/webhooks/handler.js +0 -103
  331. package/dist/webhooks/handler.js.map +0 -1
  332. package/dist/webhooks/handlers.js +0 -231
  333. package/dist/webhooks/handlers.js.map +0 -1
  334. package/dist/webhooks/index.js +0 -33
  335. package/dist/webhooks/index.js.map +0 -1
  336. package/dist/webhooks/mount.js +0 -400
  337. package/dist/webhooks/mount.js.map +0 -1
  338. package/dist/webhooks/registry.js +0 -143
  339. package/dist/webhooks/registry.js.map +0 -1
  340. package/dist/webhooks/relay.js +0 -53
  341. package/dist/webhooks/relay.js.map +0 -1
  342. package/dist/webhooks/retry.js +0 -270
  343. package/dist/webhooks/retry.js.map +0 -1
  344. package/dist/webhooks/router.js +0 -290
  345. package/dist/webhooks/router.js.map +0 -1
  346. package/dist/webhooks/twilio.js +0 -129
  347. package/dist/webhooks/twilio.js.map +0 -1
  348. package/dist/webhooks/types.js +0 -8
  349. package/dist/webhooks/types.js.map +0 -1
  350. package/dist/webhooks/verify.js +0 -154
  351. package/dist/webhooks/verify.js.map +0 -1
package/public/index.html CHANGED
@@ -28,6 +28,36 @@
28
28
  --max-width: 720px;
29
29
  }
30
30
 
31
+ /* Light theme */
32
+ [data-theme="light"] {
33
+ --bg: #ffffff;
34
+ --surface: #f5f5f5;
35
+ --border: #e0e0e0;
36
+ --text: #1a1a1a;
37
+ --text-dim: #666666;
38
+ --accent: #6c5ce7;
39
+ --accent-dim: #4a3fb0;
40
+ --error: #dc2626;
41
+ --user-bg: #eef0ff;
42
+ --assistant-bg: #f5f5f5;
43
+ }
44
+ [data-theme="light"] body { background: var(--bg); color: var(--text); }
45
+ [data-theme="light"] .chat-header { background: var(--surface); border-color: var(--border); }
46
+ [data-theme="light"] .card { background: var(--surface); border-color: var(--border); }
47
+ [data-theme="light"] .card input { background: var(--bg); border-color: var(--border); color: var(--text); }
48
+ [data-theme="light"] .composer { border-color: var(--border); background: var(--surface); }
49
+ [data-theme="light"] .composer textarea { background: var(--bg); color: var(--text); border-color: var(--border); }
50
+ [data-theme="light"] .message.user { background: var(--user-bg); }
51
+ [data-theme="light"] .message.assistant { background: var(--assistant-bg); }
52
+ [data-theme="light"] .content-sidebar { background: var(--surface); border-color: var(--border); }
53
+ [data-theme="light"] .thread-item { border-color: var(--border); }
54
+ [data-theme="light"] .thread-item:hover { background: rgba(108,92,231,0.06); }
55
+ [data-theme="light"] .vault-panel { background: var(--surface); border-color: var(--border); }
56
+ [data-theme="light"] .vault-panel input, [data-theme="light"] .vault-panel select { background: var(--bg); color: var(--text); border-color: var(--border); }
57
+ [data-theme="light"] .lane-node { border-color: var(--bg); }
58
+ [data-theme="light"] code { background: #e8e8e8; color: #333; }
59
+ [data-theme="light"] pre { background: #f0f0f0; }
60
+
31
61
  body {
32
62
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
33
63
  background: var(--bg);
@@ -260,48 +290,66 @@
260
290
  }
261
291
 
262
292
  .message {
263
- padding: 12px 20px 12px 36px;
293
+ padding: 12px 20px 12px 76px;
264
294
  max-width: var(--max-width);
265
295
  margin: 0 auto;
266
296
  width: 100%;
267
297
  position: relative;
268
- overflow: hidden;
298
+ overflow: visible;
269
299
  }
270
300
 
271
- /* --- Topic branch indicator (git-style) --- */
272
- .drift-line {
301
+ /* --- Conversation graph (git-style branch visualization) --- */
302
+ .drift-graph {
273
303
  position: absolute;
274
- left: 12px;
275
- top: 4px;
276
- bottom: 4px;
277
- width: 3px;
278
- border-radius: 1.5px;
304
+ left: 0;
305
+ top: 0;
306
+ bottom: 0;
307
+ width: 68px;
308
+ pointer-events: none;
279
309
  z-index: 0;
280
310
  }
281
- .drift-line[data-drift="0"] { background: #2ea043; left: 12px; }
282
- .drift-line[data-drift="1"] { background: #3fb950; left: 14px; }
283
- .drift-line[data-drift="2"] { background: #d29922; left: 17px; }
284
- .drift-line[data-drift="3"] { background: #db6d28; left: 20px; }
285
- .drift-line[data-drift="4"] { background: #f85149; left: 24px; }
286
- .drift-node {
311
+ .lane-line {
312
+ position: absolute;
313
+ top: 0;
314
+ bottom: 0;
315
+ width: 2px;
316
+ opacity: 0.5;
317
+ border-radius: 1px;
318
+ }
319
+ .lane-line.active-lane { opacity: 0.8; }
320
+ .lane-node {
287
321
  position: absolute;
288
- left: -3px;
289
- top: 10px;
290
- width: 9px;
291
- height: 9px;
322
+ top: 14px;
323
+ width: 10px;
324
+ height: 10px;
292
325
  border-radius: 50%;
293
326
  border: 2px solid var(--bg);
294
- background: inherit;
327
+ z-index: 2;
328
+ transform: translateX(-4px);
295
329
  }
296
- .drift-line .drift-connector {
330
+ .lane-svg {
297
331
  position: absolute;
298
- top: 18px;
299
- height: 2px;
300
- background: inherit;
301
- border-radius: 1px;
332
+ top: 0;
333
+ left: 0;
334
+ width: 68px;
335
+ height: 40px;
336
+ overflow: visible;
337
+ z-index: 1;
338
+ }
339
+ .drift-label {
340
+ position: absolute;
341
+ left: 68px;
342
+ top: 2px;
343
+ font-size: 9px;
344
+ font-family: "SF Mono", "Cascadia Code", "Consolas", monospace;
345
+ color: var(--text-dim);
346
+ opacity: 0.6;
347
+ white-space: nowrap;
348
+ pointer-events: none;
349
+ max-width: 80px;
350
+ overflow: hidden;
351
+ text-overflow: ellipsis;
302
352
  }
303
- /* Connector from previous position to current */
304
- .drift-line[data-drift="0"] .drift-connector { display: none; }
305
353
 
306
354
  /* Continuity score badge */
307
355
  .continuity-badge {
@@ -2286,7 +2334,23 @@
2286
2334
  font-size: 11px;
2287
2335
  color: var(--text-dim);
2288
2336
  margin-top: 2px;
2337
+ display: flex;
2338
+ align-items: center;
2339
+ gap: 6px;
2340
+ }
2341
+ .thread-delete-btn {
2342
+ background: none;
2343
+ border: none;
2344
+ color: var(--text-dim);
2345
+ cursor: pointer;
2346
+ font-size: 14px;
2347
+ padding: 0 2px;
2348
+ margin-left: auto;
2349
+ opacity: 0;
2350
+ transition: opacity 0.15s, color 0.15s;
2289
2351
  }
2352
+ .thread-item:hover .thread-delete-btn { opacity: 0.7; }
2353
+ .thread-delete-btn:hover { color: var(--error); opacity: 1 !important; }
2290
2354
 
2291
2355
  .thread-toggle-btn {
2292
2356
  width: 32px;
@@ -2443,9 +2507,11 @@
2443
2507
  .content-sidebar.collapsed { width: 0; display: none; }
2444
2508
 
2445
2509
  /* Messages: tighter padding */
2446
- .message { padding: 10px 12px; }
2510
+ .message { padding: 10px 12px 10px 56px; }
2447
2511
  .message .content { font-size: 14px; }
2448
2512
  .message .role { font-size: 10px; }
2513
+ .drift-graph { width: 48px; }
2514
+ .drift-label { left: 48px; display: none; }
2449
2515
 
2450
2516
  /* Composer: stack on small screens */
2451
2517
  .composer { padding: 8px 10px; }
@@ -2610,11 +2676,12 @@
2610
2676
  <nav class="header-nav" id="header-nav">
2611
2677
  <a href="/" class="active">Chat</a>
2612
2678
  </nav>
2613
- <button class="thread-toggle-btn" id="thread-toggle-btn" title="Toggle threads">&#9776;</button>
2614
- <span class="thread-current-label" id="thread-current-label" title="Click to toggle threads"></span>
2679
+ <button class="thread-toggle-btn" id="thread-toggle-btn" title="Toggle chats">&#9776;</button>
2680
+ <span class="thread-current-label" id="thread-current-label" title="Click to toggle chats"></span>
2615
2681
  <span class="spacer"></span>
2616
2682
  <button class="gear-btn" id="mobile-pair-btn" onclick="openMobileModal()" title="Go Mobile"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg></button>
2617
2683
  <button class="gear-btn" id="share-btn" onclick="nativeShare()" title="Share"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg></button>
2684
+ <button class="gear-btn" id="theme-toggle-btn" title="Toggle theme"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg></button>
2618
2685
  <button class="gear-btn" id="vault-open-btn" title="Settings">&#9881;</button>
2619
2686
  <span class="tab-badge" id="activity-badge" style="display:none;"></span>
2620
2687
  </div>
@@ -2624,13 +2691,13 @@
2624
2691
  <input type="text" id="sidebar-search" placeholder="Search..." />
2625
2692
  </div>
2626
2693
  <div class="sidebar-tabs">
2627
- <button class="sidebar-tab active" data-tab="threads">Threads</button>
2694
+ <button class="sidebar-tab active" data-tab="threads">Chats</button>
2628
2695
  <button class="sidebar-tab" data-tab="files">Files</button>
2629
2696
  </div>
2630
- <!-- Threads tab -->
2697
+ <!-- Chats tab -->
2631
2698
  <div class="sidebar-tab-content active" id="sidebar-threads">
2632
2699
  <div class="thread-sidebar-header">
2633
- <button class="thread-new-btn" id="thread-new-btn">+ New</button>
2700
+ <button class="thread-new-btn" id="thread-new-btn">+ New chat</button>
2634
2701
  </div>
2635
2702
  <div class="thread-list" id="thread-list"></div>
2636
2703
  </div>
@@ -3210,7 +3277,7 @@
3210
3277
  // Add "Main chat" as the default option
3211
3278
  const mainItem = document.createElement("div");
3212
3279
  mainItem.className = "thread-item" + (currentThreadId === null ? " active" : "");
3213
- mainItem.innerHTML = '<div class="thread-item-title">Main chat</div><div class="thread-item-meta">Default conversation</div>';
3280
+ mainItem.innerHTML = '<div class="thread-item-title">Main</div><div class="thread-item-meta">Always here</div>';
3214
3281
  mainItem.addEventListener("click", () => switchThread(null));
3215
3282
  threadListEl.appendChild(mainItem);
3216
3283
 
@@ -3220,8 +3287,24 @@
3220
3287
  item.dataset.id = t.id;
3221
3288
  const ago = timeAgo(t.updatedAt);
3222
3289
  item.innerHTML = '<div class="thread-item-title">' + escapeHtml(t.title) + '</div>'
3223
- + '<div class="thread-item-meta">' + ago + '</div>';
3224
- item.addEventListener("click", () => switchThread(t.id));
3290
+ + '<div class="thread-item-meta">' + ago
3291
+ + '<button class="thread-delete-btn" title="Delete chat" data-thread-id="' + t.id + '">&times;</button></div>';
3292
+ item.addEventListener("click", (e) => {
3293
+ if (e.target.classList && e.target.classList.contains("thread-delete-btn")) return;
3294
+ switchThread(t.id);
3295
+ });
3296
+ item.querySelector(".thread-delete-btn").addEventListener("click", async (e) => {
3297
+ e.stopPropagation();
3298
+ const tid = e.target.dataset ? e.target.dataset.threadId : null;
3299
+ if (!tid) return;
3300
+ try {
3301
+ await api("/api/threads/" + encodeURIComponent(tid) + "?sessionId=" + encodeURIComponent(sessionId), {
3302
+ method: "DELETE",
3303
+ });
3304
+ if (currentThreadId === tid) await switchThread(null);
3305
+ loadThreads();
3306
+ } catch (err) { console.log("Delete thread failed:", err.message); }
3307
+ });
3225
3308
  threadListEl.appendChild(item);
3226
3309
  }
3227
3310
  }
@@ -3253,6 +3336,12 @@
3253
3336
  topicState.scores = [];
3254
3337
  topicState.messageTexts = [];
3255
3338
  topicState.topic = "";
3339
+ topicState.branches = 0;
3340
+ topicState.maxDrift = 0;
3341
+ topicState.merges = 0;
3342
+ graphState.lanes = [true, false, false, false, false];
3343
+ graphState.currentLane = 0;
3344
+ graphState.history = [];
3256
3345
  renderThreadList();
3257
3346
  updateThreadLabel();
3258
3347
 
@@ -3294,7 +3383,7 @@
3294
3383
  function updateThreadLabel() {
3295
3384
  if (currentThreadId) {
3296
3385
  const t = threads.find(t => t.id === currentThreadId);
3297
- threadCurrentLabel.textContent = t ? t.title : "Thread";
3386
+ threadCurrentLabel.textContent = t ? t.title : "Chat";
3298
3387
  } else {
3299
3388
  threadCurrentLabel.textContent = "";
3300
3389
  }
@@ -3317,6 +3406,57 @@
3317
3406
  }
3318
3407
  });
3319
3408
 
3409
+ // --- Auto-title chats after first exchange ---
3410
+ var chatAutoTitled = {}; // threadId → true if already titled
3411
+ function autoTitleChat(threadId) {
3412
+ if (!threadId || chatAutoTitled[threadId]) return;
3413
+ // Skip if already has a real title
3414
+ var existing = threads.find(function(x) { return x.id === threadId; });
3415
+ if (existing && existing.title && existing.title !== "New chat" && existing.title !== "New thread") {
3416
+ chatAutoTitled[threadId] = true;
3417
+ return;
3418
+ }
3419
+ // Get the first user message in the current view
3420
+ var msgs = messagesEl.querySelectorAll(".message.user .content");
3421
+ if (msgs.length === 0) return;
3422
+ var firstMsg = (msgs[0].textContent || "").trim();
3423
+ if (firstMsg.length < 3) return;
3424
+
3425
+ // Generate title: first sentence or first ~40 chars, cleaned up
3426
+ var title = firstMsg
3427
+ .replace(/```[\s\S]*?```/g, " ")
3428
+ .replace(/https?:\/\/\S+/g, "")
3429
+ .replace(/\s+/g, " ")
3430
+ .trim();
3431
+ // Use first sentence if short enough
3432
+ var sentenceEnd = title.search(/[.!?\n]/);
3433
+ if (sentenceEnd > 0 && sentenceEnd <= 50) {
3434
+ title = title.slice(0, sentenceEnd);
3435
+ } else {
3436
+ title = title.slice(0, 44);
3437
+ // Cut at last word boundary
3438
+ var lastSpace = title.lastIndexOf(" ");
3439
+ if (lastSpace > 20) title = title.slice(0, lastSpace);
3440
+ }
3441
+ title = title.trim();
3442
+ if (title.length < 3) return;
3443
+
3444
+ chatAutoTitled[threadId] = true;
3445
+
3446
+ // Update server
3447
+ fetch("/api/threads/" + encodeURIComponent(threadId), {
3448
+ method: "PATCH",
3449
+ headers: { "Content-Type": "application/json", ...authHeaders() },
3450
+ body: JSON.stringify({ sessionId: sessionId, title: title }),
3451
+ }).then(function() {
3452
+ // Update local state and sidebar
3453
+ var t = threads.find(function(x) { return x.id === threadId; });
3454
+ if (t) t.title = title;
3455
+ renderThreadList();
3456
+ updateThreadLabel();
3457
+ }).catch(function() {});
3458
+ }
3459
+
3320
3460
  // --- Attached files ---
3321
3461
  let pendingImages = []; // { data: base64, mimeType: string }
3322
3462
  let pendingFiles = []; // { name: string, text: string }
@@ -4069,6 +4209,20 @@
4069
4209
  // Pre-load threads list
4070
4210
  await loadThreads();
4071
4211
 
4212
+ // Load seal values from membrane so flagged terms blur on refresh
4213
+ try {
4214
+ const sealRes = await fetch("/api/sensitive/seals?" + new URLSearchParams({ sessionId: sessionId || "" }));
4215
+ if (sealRes.ok) {
4216
+ const sealData = await sealRes.json();
4217
+ if (sealData.values && sealData.values.length > 0) {
4218
+ window._serverSealValues = sealData.values;
4219
+ document.querySelectorAll("#messages .message .content").forEach(function(el) {
4220
+ applyServerSeals(el, window._serverSealValues);
4221
+ });
4222
+ }
4223
+ }
4224
+ } catch (e) { /* best effort */ }
4225
+
4072
4226
  // Restore thread + scroll after toast refresh
4073
4227
  try {
4074
4228
  const restore = sessionStorage.getItem("_dash_restore");
@@ -4399,13 +4553,27 @@
4399
4553
  }
4400
4554
  }
4401
4555
 
4402
- // --- Topic drift tracking (git-style branch indicator) ---
4556
+ // --- Conversation graph (git-style branch visualization) ---
4557
+ var LANE_X = [10, 22, 34, 46, 58]; // 5 lanes, 12px apart
4558
+ var LANE_COLORS = ["#2ea043", "#3fb950", "#d29922", "#db6d28", "#f85149"];
4559
+ // Compat aliases for old code paths
4560
+ var DRIFT_POSITIONS = LANE_X;
4561
+ var DRIFT_COLORS = LANE_COLORS;
4403
4562
  var topicState = {
4404
4563
  keywords: [], // main topic keywords extracted from first messages
4405
4564
  established: false, // topic established after N messages
4406
4565
  scores: [], // drift score per message (0-4)
4407
4566
  messageTexts: [], // raw text per message for scoring
4408
4567
  topic: "", // detected main topic label
4568
+ branches: 0, // number of active divergences
4569
+ maxDrift: 0, // peak drift in current branch
4570
+ merges: 0, // times conversation returned to topic
4571
+ };
4572
+ // Graph state: tracks which lanes are currently active
4573
+ var graphState = {
4574
+ lanes: [true, false, false, false, false],
4575
+ currentLane: 0,
4576
+ history: [], // per message: { lane, activeLanes, forkFrom?, mergeTo?, label? }
4409
4577
  };
4410
4578
 
4411
4579
  // Stop words to filter out
@@ -4496,6 +4664,120 @@
4496
4664
  + '<span class="continuity-topic">' + (topicState.topic || "detecting...") + '</span>';
4497
4665
  }
4498
4666
 
4667
+ // Compute graph entry for a new message at given drift level
4668
+ function computeGraphEntry(drift, prevDrift) {
4669
+ var entry = {
4670
+ lane: drift,
4671
+ activeLanes: graphState.lanes.slice(),
4672
+ forkFrom: undefined,
4673
+ mergeTo: undefined,
4674
+ label: undefined,
4675
+ };
4676
+
4677
+ if (drift > prevDrift && drift >= 2) {
4678
+ // Fork: activate new lane, keep old active
4679
+ graphState.lanes[drift] = true;
4680
+ entry.forkFrom = prevDrift;
4681
+ entry.activeLanes = graphState.lanes.slice();
4682
+ // Extract fork label from recent keywords
4683
+ var recent = topicState.messageTexts.slice(-2).join(" ");
4684
+ var rk = extractKeywords(recent);
4685
+ var topicSet = new Set(topicState.keywords);
4686
+ var newWords = rk.filter(function(w) { return !topicSet.has(w); }).slice(0, 2);
4687
+ if (newWords.length > 0) entry.label = newWords.join(" ");
4688
+ } else if (drift < prevDrift) {
4689
+ // Merge: deactivate old lane(s) between current and previous
4690
+ for (var i = drift + 1; i <= prevDrift; i++) {
4691
+ graphState.lanes[i] = false;
4692
+ }
4693
+ entry.mergeTo = prevDrift;
4694
+ entry.activeLanes = graphState.lanes.slice();
4695
+ }
4696
+
4697
+ graphState.currentLane = drift;
4698
+ graphState.history.push(entry);
4699
+ return entry;
4700
+ }
4701
+
4702
+ // Render the graph column for a message
4703
+ function renderGraphRow(messageDiv, entry) {
4704
+ var graph = document.createElement("div");
4705
+ graph.className = "drift-graph";
4706
+
4707
+ // Draw vertical line segments for each active lane
4708
+ for (var i = 0; i < 5; i++) {
4709
+ if (!entry.activeLanes[i]) continue;
4710
+ var line = document.createElement("div");
4711
+ line.className = "lane-line" + (i === entry.lane ? " active-lane" : "");
4712
+ line.style.left = LANE_X[i] + "px";
4713
+ line.style.background = LANE_COLORS[i];
4714
+ graph.appendChild(line);
4715
+ }
4716
+
4717
+ // Draw commit node on current lane
4718
+ var node = document.createElement("div");
4719
+ node.className = "lane-node";
4720
+ node.style.left = LANE_X[entry.lane] + "px";
4721
+ node.style.background = LANE_COLORS[entry.lane];
4722
+ graph.appendChild(node);
4723
+
4724
+ // Draw fork or merge curve via SVG
4725
+ if (entry.forkFrom !== undefined || entry.mergeTo !== undefined) {
4726
+ var svgNS = "http://www.w3.org/2000/svg";
4727
+ var svg = document.createElementNS(svgNS, "svg");
4728
+ svg.setAttribute("class", "lane-svg");
4729
+ svg.setAttribute("viewBox", "0 0 68 40");
4730
+ svg.setAttribute("fill", "none");
4731
+
4732
+ if (entry.forkFrom !== undefined) {
4733
+ // Curve from parent lane down to new lane
4734
+ var fx = LANE_X[entry.forkFrom] + 1;
4735
+ var tx = LANE_X[entry.lane] + 1;
4736
+ var path = document.createElementNS(svgNS, "path");
4737
+ path.setAttribute("d", "M" + fx + ",0 C" + fx + ",12 " + tx + ",7 " + tx + ",19");
4738
+ path.setAttribute("stroke", LANE_COLORS[entry.lane]);
4739
+ path.setAttribute("stroke-width", "2");
4740
+ path.setAttribute("stroke-linecap", "round");
4741
+ svg.appendChild(path);
4742
+ }
4743
+
4744
+ if (entry.mergeTo !== undefined) {
4745
+ // Curve from old lane merging back to current
4746
+ var mx = LANE_X[entry.mergeTo] + 1;
4747
+ var cx = LANE_X[entry.lane] + 1;
4748
+ var path2 = document.createElementNS(svgNS, "path");
4749
+ path2.setAttribute("d", "M" + mx + ",0 C" + mx + ",12 " + cx + ",7 " + cx + ",19");
4750
+ path2.setAttribute("stroke", LANE_COLORS[entry.mergeTo]);
4751
+ path2.setAttribute("stroke-width", "2");
4752
+ path2.setAttribute("stroke-linecap", "round");
4753
+ path2.setAttribute("opacity", "0.6");
4754
+ svg.appendChild(path2);
4755
+ }
4756
+
4757
+ graph.appendChild(svg);
4758
+ }
4759
+
4760
+ // Fork label
4761
+ if (entry.label) {
4762
+ var lbl = document.createElement("div");
4763
+ lbl.className = "drift-label";
4764
+ lbl.textContent = entry.label;
4765
+ lbl.style.color = LANE_COLORS[entry.lane];
4766
+ graph.appendChild(lbl);
4767
+ }
4768
+
4769
+ // Merge indicator
4770
+ if (entry.mergeTo !== undefined && entry.lane <= 1) {
4771
+ var ml = document.createElement("div");
4772
+ ml.className = "drift-label";
4773
+ ml.textContent = "↩ merged";
4774
+ ml.style.color = LANE_COLORS[0];
4775
+ graph.appendChild(ml);
4776
+ }
4777
+
4778
+ messageDiv.appendChild(graph);
4779
+ }
4780
+
4499
4781
  function addMessage(role, content, labelOverride) {
4500
4782
  const div = document.createElement("div");
4501
4783
  div.className = "message " + role;
@@ -4516,29 +4798,29 @@
4516
4798
  applyPrivacySeals(div.querySelector(".content"));
4517
4799
  if (window._serverSealValues) applyServerSeals(div.querySelector(".content"), window._serverSealValues);
4518
4800
 
4519
- // --- Drift tracking ---
4801
+ // --- Conversation graph ---
4520
4802
  if (role !== "system") {
4521
4803
  var text = typeof content === "string" ? content : "";
4522
4804
  topicState.messageTexts.push(text);
4523
4805
 
4524
- // Establish topic after a few messages
4525
4806
  if (!topicState.established && topicState.messageTexts.length >= 2) {
4526
4807
  updateTopicFromMessages();
4527
4808
  }
4528
4809
 
4529
4810
  var drift = scoreDrift(text);
4811
+ var prevDrift = topicState.scores.length > 0 ? topicState.scores[topicState.scores.length - 1] : 0;
4530
4812
  topicState.scores.push(drift);
4531
4813
 
4532
- // Add drift indicator line
4533
- var driftLine = document.createElement("div");
4534
- driftLine.className = "drift-line";
4535
- driftLine.setAttribute("data-drift", String(drift));
4536
- var node = document.createElement("div");
4537
- node.className = "drift-node";
4538
- driftLine.appendChild(node);
4539
- div.appendChild(driftLine);
4814
+ if (drift > prevDrift && drift >= 2) {
4815
+ topicState.branches++;
4816
+ topicState.maxDrift = Math.max(topicState.maxDrift, drift);
4817
+ }
4818
+ if (drift < prevDrift && drift <= 1 && prevDrift >= 2) {
4819
+ topicState.merges++;
4820
+ }
4540
4821
 
4541
- // Update continuity badge
4822
+ var entry = computeGraphEntry(drift, prevDrift);
4823
+ renderGraphRow(div, entry);
4542
4824
  renderContinuityBadge();
4543
4825
  }
4544
4826
 
@@ -4883,16 +5165,16 @@
4883
5165
  updateTopicFromMessages();
4884
5166
  }
4885
5167
  var drift = scoreDrift(finalText);
5168
+ var prevDrift = topicState.scores.length > 0 ? topicState.scores[topicState.scores.length - 1] : 0;
4886
5169
  topicState.scores.push(drift);
5170
+
5171
+ if (drift > prevDrift && drift >= 2) topicState.branches++;
5172
+ if (drift < prevDrift && drift <= 1 && prevDrift >= 2) topicState.merges++;
5173
+
4887
5174
  var msgDiv = contentEl.closest(".message");
4888
- if (msgDiv && !msgDiv.querySelector(".drift-line")) {
4889
- var driftLine = document.createElement("div");
4890
- driftLine.className = "drift-line";
4891
- driftLine.setAttribute("data-drift", String(drift));
4892
- var node = document.createElement("div");
4893
- node.className = "drift-node";
4894
- driftLine.appendChild(node);
4895
- msgDiv.appendChild(driftLine);
5175
+ if (msgDiv && !msgDiv.querySelector(".drift-graph")) {
5176
+ var entry = computeGraphEntry(drift, prevDrift);
5177
+ renderGraphRow(msgDiv, entry);
4896
5178
  }
4897
5179
  renderContinuityBadge();
4898
5180
  }
@@ -4921,8 +5203,11 @@
4921
5203
  abortController = null;
4922
5204
  setButtonSend();
4923
5205
  chatInput.focus();
4924
- // Refresh threads to pick up auto-generated titles
4925
- if (currentThreadId) loadThreads();
5206
+ // Auto-title chat after first assistant response
5207
+ if (currentThreadId) {
5208
+ autoTitleChat(currentThreadId);
5209
+ loadThreads();
5210
+ }
4926
5211
  }
4927
5212
  }
4928
5213
 
@@ -5732,6 +6017,41 @@
5732
6017
  document.getElementById("vault-open-btn").addEventListener("click", openSettings);
5733
6018
  document.getElementById("vault-close-btn").addEventListener("click", closeVault);
5734
6019
 
6020
+ // --- Theme toggle: dark → light → system ---
6021
+ var THEMES = ["dark", "light", "system"];
6022
+ var THEME_ICONS = {
6023
+ dark: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>',
6024
+ light: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>',
6025
+ system: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>',
6026
+ };
6027
+ function getStoredTheme() { return localStorage.getItem("dash_theme") || "dark"; }
6028
+ function applyTheme(theme) {
6029
+ var effective = theme;
6030
+ if (theme === "system") {
6031
+ effective = window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
6032
+ }
6033
+ document.documentElement.setAttribute("data-theme", effective);
6034
+ var themeBtn = document.getElementById("theme-toggle-btn");
6035
+ if (themeBtn) {
6036
+ themeBtn.innerHTML = THEME_ICONS[theme];
6037
+ themeBtn.title = "Theme: " + theme;
6038
+ }
6039
+ // Update meta theme-color
6040
+ var meta = document.querySelector('meta[name="theme-color"]');
6041
+ if (meta) meta.setAttribute("content", effective === "light" ? "#ffffff" : "#0a0a0a");
6042
+ }
6043
+ applyTheme(getStoredTheme());
6044
+ // Listen for system preference changes
6045
+ window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", function() {
6046
+ if (getStoredTheme() === "system") applyTheme("system");
6047
+ });
6048
+ document.getElementById("theme-toggle-btn").addEventListener("click", function() {
6049
+ var current = getStoredTheme();
6050
+ var next = THEMES[(THEMES.indexOf(current) + 1) % THEMES.length];
6051
+ localStorage.setItem("dash_theme", next);
6052
+ applyTheme(next);
6053
+ });
6054
+
5735
6055
  document.getElementById("save-your-name-btn").addEventListener("click", async () => {
5736
6056
  const nameInput = document.getElementById("settings-your-name");
5737
6057
  const newName = nameInput.value.trim();