@agimon-ai/doompi-web 0.0.1-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +196 -0
  3. package/dist/_virtual/_rolldown/runtime.cjs +1 -0
  4. package/dist/bin/serve.cjs +3 -0
  5. package/dist/bin/serve.cjs.map +1 -0
  6. package/dist/bin/serve.d.cts +1 -0
  7. package/dist/bin/serve.d.mts +1 -0
  8. package/dist/bin/serve.mjs +3 -0
  9. package/dist/bin/serve.mjs.map +1 -0
  10. package/dist/bundler.cjs +1 -0
  11. package/dist/bundler.d.cts +2 -0
  12. package/dist/bundler.d.mts +2 -0
  13. package/dist/bundler.mjs +1 -0
  14. package/dist/index.cjs +1 -0
  15. package/dist/index.d.cts +7 -0
  16. package/dist/index.d.mts +7 -0
  17. package/dist/index.mjs +1 -0
  18. package/dist/src/adapters/authRoutes.cjs +2 -0
  19. package/dist/src/adapters/authRoutes.cjs.map +1 -0
  20. package/dist/src/adapters/authRoutes.mjs +2 -0
  21. package/dist/src/adapters/authRoutes.mjs.map +1 -0
  22. package/dist/src/adapters/bundledServer.cjs +2 -0
  23. package/dist/src/adapters/bundledServer.cjs.map +1 -0
  24. package/dist/src/adapters/bundledServer.mjs +2 -0
  25. package/dist/src/adapters/bundledServer.mjs.map +1 -0
  26. package/dist/src/adapters/directoryListing.cjs +2 -0
  27. package/dist/src/adapters/directoryListing.cjs.map +1 -0
  28. package/dist/src/adapters/directoryListing.mjs +2 -0
  29. package/dist/src/adapters/directoryListing.mjs.map +1 -0
  30. package/dist/src/adapters/directorySearch.cjs +3 -0
  31. package/dist/src/adapters/directorySearch.cjs.map +1 -0
  32. package/dist/src/adapters/directorySearch.mjs +3 -0
  33. package/dist/src/adapters/directorySearch.mjs.map +1 -0
  34. package/dist/src/adapters/gitStatus.cjs +2 -0
  35. package/dist/src/adapters/gitStatus.cjs.map +1 -0
  36. package/dist/src/adapters/gitStatus.mjs +2 -0
  37. package/dist/src/adapters/gitStatus.mjs.map +1 -0
  38. package/dist/src/adapters/httpServer.cjs +2 -0
  39. package/dist/src/adapters/httpServer.cjs.map +1 -0
  40. package/dist/src/adapters/httpServer.d.cts +14 -0
  41. package/dist/src/adapters/httpServer.d.cts.map +1 -0
  42. package/dist/src/adapters/httpServer.d.mts +14 -0
  43. package/dist/src/adapters/httpServer.d.mts.map +1 -0
  44. package/dist/src/adapters/httpServer.mjs +2 -0
  45. package/dist/src/adapters/httpServer.mjs.map +1 -0
  46. package/dist/src/adapters/hubProbe.cjs +2 -0
  47. package/dist/src/adapters/hubProbe.cjs.map +1 -0
  48. package/dist/src/adapters/hubProbe.mjs +2 -0
  49. package/dist/src/adapters/hubProbe.mjs.map +1 -0
  50. package/dist/src/adapters/packageApiProxy.cjs +2 -0
  51. package/dist/src/adapters/packageApiProxy.cjs.map +1 -0
  52. package/dist/src/adapters/packageApiProxy.mjs +2 -0
  53. package/dist/src/adapters/packageApiProxy.mjs.map +1 -0
  54. package/dist/src/adapters/piHubService.cjs +2 -0
  55. package/dist/src/adapters/piHubService.cjs.map +1 -0
  56. package/dist/src/adapters/piHubService.mjs +2 -0
  57. package/dist/src/adapters/piHubService.mjs.map +1 -0
  58. package/dist/src/adapters/piWebSocketListener.cjs +2 -0
  59. package/dist/src/adapters/piWebSocketListener.cjs.map +1 -0
  60. package/dist/src/adapters/piWebSocketListener.mjs +2 -0
  61. package/dist/src/adapters/piWebSocketListener.mjs.map +1 -0
  62. package/dist/src/adapters/providerAuth.cjs +2 -0
  63. package/dist/src/adapters/providerAuth.cjs.map +1 -0
  64. package/dist/src/adapters/providerAuth.mjs +2 -0
  65. package/dist/src/adapters/providerAuth.mjs.map +1 -0
  66. package/dist/src/adapters/registryWatcher.cjs +2 -0
  67. package/dist/src/adapters/registryWatcher.cjs.map +1 -0
  68. package/dist/src/adapters/registryWatcher.mjs +2 -0
  69. package/dist/src/adapters/registryWatcher.mjs.map +1 -0
  70. package/dist/src/adapters/serverSpawner.cjs +2 -0
  71. package/dist/src/adapters/serverSpawner.cjs.map +1 -0
  72. package/dist/src/adapters/serverSpawner.mjs +2 -0
  73. package/dist/src/adapters/serverSpawner.mjs.map +1 -0
  74. package/dist/src/adapters/sessionFiles.cjs +3 -0
  75. package/dist/src/adapters/sessionFiles.cjs.map +1 -0
  76. package/dist/src/adapters/sessionFiles.mjs +3 -0
  77. package/dist/src/adapters/sessionFiles.mjs.map +1 -0
  78. package/dist/src/adapters/sessionHub.cjs +2 -0
  79. package/dist/src/adapters/sessionHub.cjs.map +1 -0
  80. package/dist/src/adapters/sessionHub.mjs +2 -0
  81. package/dist/src/adapters/sessionHub.mjs.map +1 -0
  82. package/dist/src/adapters/sessionSocketClient.cjs +2 -0
  83. package/dist/src/adapters/sessionSocketClient.cjs.map +1 -0
  84. package/dist/src/adapters/sessionSocketClient.mjs +2 -0
  85. package/dist/src/adapters/sessionSocketClient.mjs.map +1 -0
  86. package/dist/src/adapters/syncGuard.cjs +2 -0
  87. package/dist/src/adapters/syncGuard.cjs.map +1 -0
  88. package/dist/src/adapters/syncGuard.mjs +2 -0
  89. package/dist/src/adapters/syncGuard.mjs.map +1 -0
  90. package/dist/src/adapters/threadJournals.cjs +2 -0
  91. package/dist/src/adapters/threadJournals.cjs.map +1 -0
  92. package/dist/src/adapters/threadJournals.mjs +2 -0
  93. package/dist/src/adapters/threadJournals.mjs.map +1 -0
  94. package/dist/src/adapters/webBundler.cjs +2 -0
  95. package/dist/src/adapters/webBundler.cjs.map +1 -0
  96. package/dist/src/adapters/webBundler.d.cts +28 -0
  97. package/dist/src/adapters/webBundler.d.cts.map +1 -0
  98. package/dist/src/adapters/webBundler.d.mts +28 -0
  99. package/dist/src/adapters/webBundler.d.mts.map +1 -0
  100. package/dist/src/adapters/webBundler.mjs +2 -0
  101. package/dist/src/adapters/webBundler.mjs.map +1 -0
  102. package/dist/src/adapters/webHubPluginLoader.cjs +2 -0
  103. package/dist/src/adapters/webHubPluginLoader.cjs.map +1 -0
  104. package/dist/src/adapters/webHubPluginLoader.mjs +2 -0
  105. package/dist/src/adapters/webHubPluginLoader.mjs.map +1 -0
  106. package/dist/src/adapters/webHubPlugins.generated.cjs +2 -0
  107. package/dist/src/adapters/webHubPlugins.generated.cjs.map +1 -0
  108. package/dist/src/adapters/webHubPlugins.generated.mjs +2 -0
  109. package/dist/src/adapters/webHubPlugins.generated.mjs.map +1 -0
  110. package/dist/src/adapters/webPluginGenerate.cjs +4 -0
  111. package/dist/src/adapters/webPluginGenerate.cjs.map +1 -0
  112. package/dist/src/adapters/webPluginGenerate.mjs +4 -0
  113. package/dist/src/adapters/webPluginGenerate.mjs.map +1 -0
  114. package/dist/src/adapters/webPluginScan.cjs +2 -0
  115. package/dist/src/adapters/webPluginScan.cjs.map +1 -0
  116. package/dist/src/adapters/webPluginScan.mjs +2 -0
  117. package/dist/src/adapters/webPluginScan.mjs.map +1 -0
  118. package/dist/src/adapters/webPluginVite.cjs +2 -0
  119. package/dist/src/adapters/webPluginVite.cjs.map +1 -0
  120. package/dist/src/adapters/webPluginVite.mjs +2 -0
  121. package/dist/src/adapters/webPluginVite.mjs.map +1 -0
  122. package/dist/src/services/directoryMatch.cjs +2 -0
  123. package/dist/src/services/directoryMatch.cjs.map +1 -0
  124. package/dist/src/services/directoryMatch.mjs +2 -0
  125. package/dist/src/services/directoryMatch.mjs.map +1 -0
  126. package/dist/src/services/fileMatch.cjs +2 -0
  127. package/dist/src/services/fileMatch.cjs.map +1 -0
  128. package/dist/src/services/fileMatch.mjs +2 -0
  129. package/dist/src/services/fileMatch.mjs.map +1 -0
  130. package/dist/src/services/fileMedia.cjs +2 -0
  131. package/dist/src/services/fileMedia.cjs.map +1 -0
  132. package/dist/src/services/fileMedia.mjs +2 -0
  133. package/dist/src/services/fileMedia.mjs.map +1 -0
  134. package/dist/src/services/frameRing.cjs +2 -0
  135. package/dist/src/services/frameRing.cjs.map +1 -0
  136. package/dist/src/services/frameRing.mjs +2 -0
  137. package/dist/src/services/frameRing.mjs.map +1 -0
  138. package/dist/src/services/journalTail.cjs +3 -0
  139. package/dist/src/services/journalTail.cjs.map +1 -0
  140. package/dist/src/services/journalTail.mjs +3 -0
  141. package/dist/src/services/journalTail.mjs.map +1 -0
  142. package/dist/src/services/loginFlow.cjs +2 -0
  143. package/dist/src/services/loginFlow.cjs.map +1 -0
  144. package/dist/src/services/loginFlow.mjs +2 -0
  145. package/dist/src/services/loginFlow.mjs.map +1 -0
  146. package/dist/src/services/registryStore.cjs +2 -0
  147. package/dist/src/services/registryStore.cjs.map +1 -0
  148. package/dist/src/services/registryStore.mjs +2 -0
  149. package/dist/src/services/registryStore.mjs.map +1 -0
  150. package/dist/src/services/retryPolicy.cjs +2 -0
  151. package/dist/src/services/retryPolicy.cjs.map +1 -0
  152. package/dist/src/services/retryPolicy.mjs +2 -0
  153. package/dist/src/services/retryPolicy.mjs.map +1 -0
  154. package/dist/src/services/serveOptions.cjs +2 -0
  155. package/dist/src/services/serveOptions.cjs.map +1 -0
  156. package/dist/src/services/serveOptions.d.cts +21 -0
  157. package/dist/src/services/serveOptions.d.cts.map +1 -0
  158. package/dist/src/services/serveOptions.d.mts +21 -0
  159. package/dist/src/services/serveOptions.d.mts.map +1 -0
  160. package/dist/src/services/serveOptions.mjs +2 -0
  161. package/dist/src/services/serveOptions.mjs.map +1 -0
  162. package/dist/src/services/sessionFraming.cjs +4 -0
  163. package/dist/src/services/sessionFraming.cjs.map +1 -0
  164. package/dist/src/services/sessionFraming.mjs +4 -0
  165. package/dist/src/services/sessionFraming.mjs.map +1 -0
  166. package/dist/src/services/sessionPresence.cjs +2 -0
  167. package/dist/src/services/sessionPresence.cjs.map +1 -0
  168. package/dist/src/services/sessionPresence.mjs +2 -0
  169. package/dist/src/services/sessionPresence.mjs.map +1 -0
  170. package/dist/src/services/staticAssets.cjs +2 -0
  171. package/dist/src/services/staticAssets.cjs.map +1 -0
  172. package/dist/src/services/staticAssets.mjs +2 -0
  173. package/dist/src/services/staticAssets.mjs.map +1 -0
  174. package/dist/src/services/webDevRoots.cjs +2 -0
  175. package/dist/src/services/webDevRoots.cjs.map +1 -0
  176. package/dist/src/services/webDevRoots.mjs +2 -0
  177. package/dist/src/services/webDevRoots.mjs.map +1 -0
  178. package/dist/src/services/webPluginManifest.cjs +2 -0
  179. package/dist/src/services/webPluginManifest.cjs.map +1 -0
  180. package/dist/src/services/webPluginManifest.mjs +2 -0
  181. package/dist/src/services/webPluginManifest.mjs.map +1 -0
  182. package/dist/src/types/auth.cjs +2 -0
  183. package/dist/src/types/auth.cjs.map +1 -0
  184. package/dist/src/types/auth.d.cts +94 -0
  185. package/dist/src/types/auth.d.cts.map +1 -0
  186. package/dist/src/types/auth.d.mts +94 -0
  187. package/dist/src/types/auth.d.mts.map +1 -0
  188. package/dist/src/types/auth.mjs +2 -0
  189. package/dist/src/types/auth.mjs.map +1 -0
  190. package/dist/src/types/bridge.d.cts +31 -0
  191. package/dist/src/types/bridge.d.cts.map +1 -0
  192. package/dist/src/types/bridge.d.mts +31 -0
  193. package/dist/src/types/bridge.d.mts.map +1 -0
  194. package/dist/src/types/hub.cjs +2 -0
  195. package/dist/src/types/hub.cjs.map +1 -0
  196. package/dist/src/types/hub.d.cts +58 -0
  197. package/dist/src/types/hub.d.cts.map +1 -0
  198. package/dist/src/types/hub.d.mts +58 -0
  199. package/dist/src/types/hub.d.mts.map +1 -0
  200. package/dist/src/types/hub.mjs +2 -0
  201. package/dist/src/types/hub.mjs.map +1 -0
  202. package/dist/src/types/media.cjs +2 -0
  203. package/dist/src/types/media.cjs.map +1 -0
  204. package/dist/src/types/media.mjs +2 -0
  205. package/dist/src/types/media.mjs.map +1 -0
  206. package/dist/src/types/registry.cjs +1 -0
  207. package/dist/src/types/registry.d.cts +2 -0
  208. package/dist/src/types/registry.d.mts +2 -0
  209. package/dist/src/types/registry.mjs +1 -0
  210. package/dist/src/types/session.cjs +2 -0
  211. package/dist/src/types/session.cjs.map +1 -0
  212. package/dist/src/types/session.d.cts +24 -0
  213. package/dist/src/types/session.d.cts.map +1 -0
  214. package/dist/src/types/session.d.mts +24 -0
  215. package/dist/src/types/session.d.mts.map +1 -0
  216. package/dist/src/types/session.mjs +2 -0
  217. package/dist/src/types/session.mjs.map +1 -0
  218. package/dist/web/apple-touch-icon.png +0 -0
  219. package/dist/web/assets/index-Cg52ngKv.css +2 -0
  220. package/dist/web/assets/index-DQO7NTIP.js +90 -0
  221. package/dist/web/assets/index-DQO7NTIP.js.map +1 -0
  222. package/dist/web/favicon-16.png +0 -0
  223. package/dist/web/favicon-32.png +0 -0
  224. package/dist/web/favicon.ico +0 -0
  225. package/dist/web/favicon.svg +16 -0
  226. package/dist/web/icon-512.png +0 -0
  227. package/dist/web/index.html +17 -0
  228. package/llms.txt +8 -0
  229. package/package.json +106 -0
  230. package/src/adapters/authRoutes.ts +95 -0
  231. package/src/adapters/bundledServer.ts +64 -0
  232. package/src/adapters/directoryListing.ts +127 -0
  233. package/src/adapters/directorySearch.ts +124 -0
  234. package/src/adapters/gitStatus.ts +27 -0
  235. package/src/adapters/httpServer.ts +556 -0
  236. package/src/adapters/hubProbe.ts +27 -0
  237. package/src/adapters/packageApiProxy.ts +60 -0
  238. package/src/adapters/piHubService.ts +129 -0
  239. package/src/adapters/piWebSocketListener.ts +98 -0
  240. package/src/adapters/providerAuth.ts +181 -0
  241. package/src/adapters/registryWatcher.ts +112 -0
  242. package/src/adapters/serverSpawner.ts +181 -0
  243. package/src/adapters/sessionFiles.ts +99 -0
  244. package/src/adapters/sessionHub.ts +567 -0
  245. package/src/adapters/sessionSocketClient.ts +105 -0
  246. package/src/adapters/syncGuard.ts +119 -0
  247. package/src/adapters/threadJournals.ts +184 -0
  248. package/src/adapters/webBundler.ts +83 -0
  249. package/src/adapters/webHubPluginLoader.ts +67 -0
  250. package/src/adapters/webHubPlugins.generated.ts +4 -0
  251. package/src/adapters/webPluginGenerate.ts +172 -0
  252. package/src/adapters/webPluginScan.ts +62 -0
  253. package/src/adapters/webPluginVite.ts +51 -0
  254. package/src/bin/serve.ts +64 -0
  255. package/src/exports/bundler.ts +1 -0
  256. package/src/exports/index.ts +9 -0
  257. package/src/services/directoryMatch.ts +71 -0
  258. package/src/services/fileMatch.ts +37 -0
  259. package/src/services/fileMedia.ts +27 -0
  260. package/src/services/frameRing.ts +33 -0
  261. package/src/services/journalTail.ts +47 -0
  262. package/src/services/loginFlow.ts +144 -0
  263. package/src/services/registryStore.ts +83 -0
  264. package/src/services/retryPolicy.ts +15 -0
  265. package/src/services/serveOptions.ts +63 -0
  266. package/src/services/sessionFraming.ts +32 -0
  267. package/src/services/sessionPresence.ts +131 -0
  268. package/src/services/staticAssets.ts +37 -0
  269. package/src/services/webDevRoots.ts +36 -0
  270. package/src/services/webPluginManifest.ts +149 -0
  271. package/src/types/auth.ts +118 -0
  272. package/src/types/bridge.ts +41 -0
  273. package/src/types/hub.ts +290 -0
  274. package/src/types/media.ts +38 -0
  275. package/src/types/registry.ts +2 -0
  276. package/src/types/session.ts +36 -0
  277. package/src/web/app/Providers.tsx +46 -0
  278. package/src/web/app/protocolRuntime.ts +106 -0
  279. package/src/web/app/sessionRuntime.ts +189 -0
  280. package/src/web/app/webPlugins.generated.ts +4 -0
  281. package/src/web/components/MascotMark.tsx +46 -0
  282. package/src/web/components/PluginSurface.tsx +10 -0
  283. package/src/web/components/ToolRendererBoundary.tsx +32 -0
  284. package/src/web/features/activity/ActivityDock.tsx +125 -0
  285. package/src/web/features/connection/RefusedCard.tsx +63 -0
  286. package/src/web/features/dialogs/DialogOverlay.tsx +176 -0
  287. package/src/web/features/leader/CommandPalette.tsx +386 -0
  288. package/src/web/features/selection/SelectionBar.tsx +529 -0
  289. package/src/web/features/session/Composer.tsx +368 -0
  290. package/src/web/features/session/MentionPreviews.tsx +58 -0
  291. package/src/web/features/session/ThreadView.tsx +35 -0
  292. package/src/web/features/session/Timeline.tsx +382 -0
  293. package/src/web/features/session/ToolCard.tsx +79 -0
  294. package/src/web/features/sessions/NewSessionDialog.tsx +222 -0
  295. package/src/web/features/sessions/SessionRail.tsx +411 -0
  296. package/src/web/features/settings/AppearanceSettings.tsx +88 -0
  297. package/src/web/features/settings/LoginFlowDialog.tsx +221 -0
  298. package/src/web/features/settings/PluginSettings.tsx +92 -0
  299. package/src/web/features/settings/ProviderSettings.tsx +238 -0
  300. package/src/web/features/settings/SettingsMenu.tsx +32 -0
  301. package/src/web/features/status/TopBar.tsx +208 -0
  302. package/src/web/index.html +16 -0
  303. package/src/web/lib/authApi.ts +90 -0
  304. package/src/web/lib/commands.ts +61 -0
  305. package/src/web/lib/composition.ts +225 -0
  306. package/src/web/lib/fileMentions.ts +32 -0
  307. package/src/web/lib/hubApi.ts +86 -0
  308. package/src/web/lib/leaderTree.ts +159 -0
  309. package/src/web/lib/piTransport.ts +61 -0
  310. package/src/web/lib/pluginRegistry.ts +488 -0
  311. package/src/web/lib/pluginSlotProps.ts +50 -0
  312. package/src/web/lib/promptFocus.ts +25 -0
  313. package/src/web/lib/protocolTimeline.ts +73 -0
  314. package/src/web/lib/sessionModel.ts +673 -0
  315. package/src/web/lib/sessionSummary.ts +58 -0
  316. package/src/web/lib/settingsSections.ts +21 -0
  317. package/src/web/lib/statusLine.ts +90 -0
  318. package/src/web/lib/threadRenderer.ts +25 -0
  319. package/src/web/lib/toolMessageProps.ts +21 -0
  320. package/src/web/lib/transport.ts +30 -0
  321. package/src/web/lib/wsClient.ts +73 -0
  322. package/src/web/main.tsx +17 -0
  323. package/src/web/public/apple-touch-icon.png +0 -0
  324. package/src/web/public/favicon-16.png +0 -0
  325. package/src/web/public/favicon-32.png +0 -0
  326. package/src/web/public/favicon.ico +0 -0
  327. package/src/web/public/favicon.svg +16 -0
  328. package/src/web/public/icon-512.png +0 -0
  329. package/src/web/routes/CockpitPage.tsx +98 -0
  330. package/src/web/routes/SettingsPage.tsx +60 -0
  331. package/src/web/routes/routeTree.tsx +49 -0
  332. package/src/web/stores/menuStore.ts +78 -0
  333. package/src/web/stores/paletteStore.ts +25 -0
  334. package/src/web/stores/sessionStore.ts +303 -0
  335. package/src/web/stores/sessionsStore.ts +155 -0
  336. package/src/web/stores/themeStore.ts +52 -0
  337. package/src/web/stores/threadStore.ts +68 -0
  338. package/src/web/stores/transientTabsStore.ts +63 -0
  339. package/src/web/stores/uiStore.ts +28 -0
  340. package/src/web/stores/useOpenTab.ts +19 -0
  341. package/src/web/stores/usePluginSlotProps.ts +22 -0
  342. package/src/web/styles/app.css +15 -0
  343. package/src/web/styles/webPluginSources.generated.css +1 -0
  344. package/src/web/vite-env.d.ts +1 -0
@@ -0,0 +1,411 @@
1
+ import {
2
+ BranchIcon,
3
+ Button,
4
+ buttonVariants,
5
+ cn,
6
+ Dialog,
7
+ DialogBody,
8
+ DialogContent,
9
+ DialogDescription,
10
+ DialogFooter,
11
+ DialogHeader,
12
+ DialogTitle,
13
+ DropdownMenu,
14
+ DropdownMenuContent,
15
+ DropdownMenuItem,
16
+ DropdownMenuTrigger,
17
+ GearIcon,
18
+ Input,
19
+ KebabIcon,
20
+ PlusIcon,
21
+ SectionLabel,
22
+ Tooltip,
23
+ TooltipContent,
24
+ TooltipTrigger,
25
+ } from '@agimon-ai/doompi-web-components';
26
+ import { Link, useNavigate } from '@tanstack/react-router';
27
+ import { useStore } from '@tanstack/react-store';
28
+ import { useEffect, useRef, useState } from 'react';
29
+ import { MascotMark } from '../../components/MascotMark.tsx';
30
+ import { PluginSurface } from '../../components/PluginSurface.tsx';
31
+ import { HOST_SLOTS } from '../../lib/pluginRegistry.ts';
32
+ import { stopSession } from '../../lib/hubApi.ts';
33
+ import { abbreviateCwd, runningCount, sessionStatusLine } from '../../lib/sessionSummary.ts';
34
+ import { DEFAULT_SETTINGS_SECTION } from '../../lib/settingsSections.ts';
35
+ import { paletteStore } from '../../stores/paletteStore.ts';
36
+ import { renameSession, sessionStoreFor } from '../../stores/sessionStore.ts';
37
+ import { sessionsStore, type SessionMeta } from '../../stores/sessionsStore.ts';
38
+ import { NewSessionDialog } from './NewSessionDialog.tsx';
39
+
40
+ const STATUS_REFRESH_MS = 30_000;
41
+
42
+ /**
43
+ * What the card is doing besides showing its session: nothing, showing its
44
+ * action menu, taking a new name, or asking before a stop.
45
+ */
46
+ type CardMode = 'view' | 'menu' | 'rename' | 'confirm';
47
+
48
+ /** The confirmation that stands between the "remove" menu item and the stop. */
49
+ function RemoveSessionDialog({
50
+ sessionId,
51
+ name,
52
+ open,
53
+ onConfirm,
54
+ onCancel,
55
+ }: {
56
+ sessionId: string;
57
+ name: string;
58
+ open: boolean;
59
+ onConfirm: () => void;
60
+ onCancel: () => void;
61
+ }) {
62
+ return (
63
+ <Dialog
64
+ open={open}
65
+ onOpenChange={(next) => {
66
+ if (!next) onCancel();
67
+ }}
68
+ >
69
+ <DialogContent width="sm" data-testid={`session-stop-dialog-${sessionId}`} aria-describedby={undefined}>
70
+ <DialogHeader>
71
+ <DialogTitle>remove session</DialogTitle>
72
+ </DialogHeader>
73
+ <DialogBody>
74
+ <DialogDescription>
75
+ this stops <span className="font-bold text-doom-hi">{name || 'untitled'}</span> and removes it from the
76
+ rail. anything it is running ends now.
77
+ </DialogDescription>
78
+ <DialogFooter>
79
+ <Button
80
+ variant="outline"
81
+ size="md"
82
+ data-testid={`session-stop-cancel-${sessionId}`}
83
+ autoFocus
84
+ onClick={onCancel}
85
+ >
86
+ cancel
87
+ </Button>
88
+ <Button variant="danger" size="md" data-testid={`session-stop-confirm-${sessionId}`} onClick={onConfirm}>
89
+ remove
90
+ </Button>
91
+ </DialogFooter>
92
+ </DialogBody>
93
+ </DialogContent>
94
+ </Dialog>
95
+ );
96
+ }
97
+
98
+ function SessionCard({
99
+ meta,
100
+ ordinal,
101
+ active,
102
+ now,
103
+ }: {
104
+ meta: SessionMeta;
105
+ ordinal: number;
106
+ active: boolean;
107
+ now: number;
108
+ }) {
109
+ const navigate = useNavigate();
110
+ const summary = meta.summary;
111
+ const [mode, setMode] = useState<CardMode>('view');
112
+ const [draft, setDraft] = useState('');
113
+ const [error, setError] = useState('');
114
+ // Read when the menu closes: a choice that moved the card into another
115
+ // mode keeps the focus it took, rather than handing it back to the kebab.
116
+ const modeRef = useRef(mode);
117
+ modeRef.current = mode;
118
+ const status = sessionStatusLine(
119
+ {
120
+ attach: meta.attach,
121
+ phase: summary.phase,
122
+ phaseSince: summary.phaseSince,
123
+ awaitingInput: summary.awaitingInput,
124
+ everPrompted: summary.everPrompted,
125
+ lastSettledAt: summary.lastSettledAt,
126
+ },
127
+ now,
128
+ );
129
+
130
+ const beginRename = (): void => {
131
+ setDraft(summary.name);
132
+ setError('');
133
+ setMode('rename');
134
+ };
135
+ const commitRename = (): void => {
136
+ const name = draft.trim();
137
+ if (name && name !== summary.name) renameSession(name, summary.id);
138
+ setMode('view');
139
+ };
140
+ const stop = async (): Promise<void> => {
141
+ setMode('view');
142
+ const result = await stopSession(summary.id);
143
+ if ('error' in result) setError(result.error);
144
+ };
145
+
146
+ const cardClass = active ? 'bg-doom-selected hover:bg-doom-selected' : 'hover:bg-doom-panel';
147
+ const menuOpen = mode === 'menu';
148
+ const details = (
149
+ <>
150
+ <span
151
+ data-testid="session-status"
152
+ className={`text-[11px] leading-snug ${active ? 'line-clamp-2 text-doom-on-selected/85' : 'truncate text-doom-dim'}`}
153
+ >
154
+ {status}
155
+ </span>
156
+ {summary.git ? (
157
+ <span
158
+ data-testid="session-branch"
159
+ className={`flex items-center gap-[7px] pt-0.5 text-[10px] ${active ? 'text-doom-on-selected/85' : 'text-doom-faint'}`}
160
+ >
161
+ <BranchIcon
162
+ className={`h-[10px] w-[10px] shrink-0 ${active ? 'text-doom-on-selected/70' : 'text-doom-faint'}`}
163
+ />
164
+ {summary.git.branch}
165
+ {summary.git.dirty ? '*' : ''}
166
+ </span>
167
+ ) : null}
168
+ <span className={`truncate text-[10px] ${active ? 'text-doom-on-selected/70' : 'text-doom-faint'}`}>
169
+ {abbreviateCwd(summary.cwd)}
170
+ </span>
171
+ {error ? (
172
+ <span data-testid="session-error" className="line-clamp-3 text-[10px] break-words text-doom-red">
173
+ {error}
174
+ </span>
175
+ ) : null}
176
+ </>
177
+ );
178
+
179
+ return (
180
+ <div className="group relative" data-testid={`session-card-${summary.id}`} data-active={active}>
181
+ {mode === 'rename' ? (
182
+ // The name field cannot live inside the card button, so the card
183
+ // briefly stops being one while it takes a name.
184
+ <div className={cn(buttonVariants({ variant: 'ghost', size: 'card' }), cardClass, 'cursor-default')}>
185
+ <Input
186
+ data-testid={`session-name-input-${summary.id}`}
187
+ value={draft}
188
+ autoFocus
189
+ onChange={(event) => setDraft(event.target.value)}
190
+ onKeyDown={(event) => {
191
+ if (event.key === 'Enter') commitRename();
192
+ if (event.key === 'Escape') setMode('view');
193
+ }}
194
+ onBlur={() => setMode('view')}
195
+ className="border-doom-blue/60 px-1.5 py-0.5 text-[13px] font-bold"
196
+ />
197
+ {details}
198
+ </div>
199
+ ) : (
200
+ <Button
201
+ variant="ghost"
202
+ size="card"
203
+ data-testid={`session-open-${summary.id}`}
204
+ onClick={() => void navigate({ to: '/session/$sessionId', params: { sessionId: summary.id } })}
205
+ className={cardClass}
206
+ >
207
+ <div className="flex items-center gap-2">
208
+ <span
209
+ className={`min-w-0 flex-1 truncate text-[13px] font-bold ${active ? 'text-doom-on-selected' : 'text-doom-hi'}`}
210
+ >
211
+ {summary.name || 'untitled'}
212
+ </span>
213
+ {ordinal <= 9 ? (
214
+ <span
215
+ title={`press ${String(ordinal)} to focus`}
216
+ className={`flex h-[17px] w-[17px] shrink-0 items-center justify-center rounded-full text-[9px] font-bold transition-opacity group-focus-within:opacity-0 group-hover:opacity-0 ${
217
+ active ? 'bg-doom-on-selected/20 text-doom-on-selected' : 'bg-doom-tint-magenta text-doom-magenta'
218
+ }`}
219
+ >
220
+ {ordinal}
221
+ </span>
222
+ ) : null}
223
+ </div>
224
+ {details}
225
+ </Button>
226
+ )}
227
+
228
+ {mode === 'view' || menuOpen ? (
229
+ <div
230
+ className={`absolute top-2 right-2 transition-opacity ${
231
+ menuOpen ? 'opacity-100' : 'opacity-0 group-focus-within:opacity-100 group-hover:opacity-100'
232
+ }`}
233
+ >
234
+ <DropdownMenu
235
+ open={menuOpen}
236
+ onOpenChange={(next) => setMode((current) => (next ? 'menu' : current === 'menu' ? 'view' : current))}
237
+ >
238
+ <DropdownMenuTrigger asChild>
239
+ <Button
240
+ variant="ghost"
241
+ size="icon"
242
+ data-testid={`session-menu-${summary.id}`}
243
+ title="session actions"
244
+ className={
245
+ active
246
+ ? 'text-doom-on-selected/80 hover:bg-doom-on-selected/20 hover:text-doom-on-selected data-[state=open]:bg-doom-on-selected/20 data-[state=open]:text-doom-on-selected'
247
+ : 'text-doom-faint hover:bg-doom-deep hover:text-doom-hi data-[state=open]:bg-doom-deep data-[state=open]:text-doom-hi'
248
+ }
249
+ >
250
+ <KebabIcon className="h-3 w-3" />
251
+ </Button>
252
+ </DropdownMenuTrigger>
253
+ <DropdownMenuContent
254
+ data-testid={`session-menu-list-${summary.id}`}
255
+ onCloseAutoFocus={(event) => {
256
+ if (modeRef.current !== 'view') event.preventDefault();
257
+ }}
258
+ >
259
+ <DropdownMenuItem data-testid={`session-rename-${summary.id}`} onSelect={beginRename}>
260
+ edit
261
+ </DropdownMenuItem>
262
+ <DropdownMenuItem
263
+ variant="destructive"
264
+ data-testid={`session-stop-${summary.id}`}
265
+ onSelect={() => setMode('confirm')}
266
+ >
267
+ remove
268
+ </DropdownMenuItem>
269
+ </DropdownMenuContent>
270
+ </DropdownMenu>
271
+ </div>
272
+ ) : null}
273
+ <RemoveSessionDialog
274
+ sessionId={summary.id}
275
+ name={summary.name}
276
+ open={mode === 'confirm'}
277
+ onConfirm={() => void stop()}
278
+ onCancel={() => setMode('view')}
279
+ />
280
+ </div>
281
+ );
282
+ }
283
+
284
+ function isEditable(target: EventTarget | null): boolean {
285
+ if (!(target instanceof HTMLElement)) return false;
286
+ return target.isContentEditable || target.tagName === 'INPUT' || target.tagName === 'TEXTAREA';
287
+ }
288
+
289
+ /** A key pressed inside an overlay belongs to that overlay, never to the rail. */
290
+ function insideOverlay(target: EventTarget | null): boolean {
291
+ return target instanceof Element && target.closest('[role="dialog"], [role="menu"], [role="alertdialog"]') !== null;
292
+ }
293
+
294
+ /** The mockup's rail: brand, live session cards, the new-session flow, and nothing else. */
295
+ export function SessionRail() {
296
+ const navigate = useNavigate();
297
+ const order = useStore(sessionsStore, (state) => state.order);
298
+ const byId = useStore(sessionsStore, (state) => state.byId);
299
+ const activeId = useStore(sessionsStore, (state) => state.activeId);
300
+ const hasDialog = useStore(sessionStoreFor(activeId), (state) => state.dialog !== null);
301
+ const [creating, setCreating] = useState(false);
302
+ const [now, setNow] = useState(() => Date.now());
303
+
304
+ // Keeps "running · 12m" honest without any frame arriving.
305
+ useEffect(() => {
306
+ const timer = setInterval(() => setNow(Date.now()), STATUS_REFRESH_MS);
307
+ return () => clearInterval(timer);
308
+ }, []);
309
+
310
+ useEffect(() => {
311
+ const onKeyDown = (event: KeyboardEvent): void => {
312
+ if (event.ctrlKey && !event.metaKey && !event.altKey && event.key === 't') {
313
+ event.preventDefault();
314
+ setCreating(true);
315
+ return;
316
+ }
317
+ if (event.ctrlKey || event.metaKey || event.altKey || event.defaultPrevented) return;
318
+ if (isEditable(event.target) || insideOverlay(event.target) || paletteStore.state.open || hasDialog) return;
319
+ const ordinal = Number.parseInt(event.key, 10);
320
+ if (!Number.isInteger(ordinal) || ordinal < 1 || ordinal > 9) return;
321
+ const target = order[ordinal - 1];
322
+ if (target === undefined) return;
323
+ event.preventDefault();
324
+ void navigate({ to: '/session/$sessionId', params: { sessionId: target } });
325
+ };
326
+ window.addEventListener('keydown', onKeyDown);
327
+ return () => window.removeEventListener('keydown', onKeyDown);
328
+ }, [order, hasDialog, navigate]);
329
+
330
+ const running = runningCount(order.map((id) => byId[id].summary.phase));
331
+
332
+ return (
333
+ <div className="flex h-full flex-col">
334
+ <div className="flex items-center gap-[9px] border-b border-doom-border px-4 pt-4 pb-3.5">
335
+ <span className="flex items-center gap-[3px]" aria-label="DoomPi">
336
+ <span aria-hidden="true" className="text-[15px] font-bold tracking-[0.16em] text-doom-hi">
337
+ DOOM
338
+ </span>
339
+ <MascotMark size={22} />
340
+ </span>
341
+ </div>
342
+
343
+ <div className="flex items-center justify-between px-4 pt-3.5 pb-2">
344
+ <SectionLabel>sessions</SectionLabel>
345
+ <span className="flex items-center gap-2.5">
346
+ <span data-testid="sessions-running-rail" className="text-[9px] text-doom-faint">
347
+ {running} running
348
+ </span>
349
+ {/* The rail's own action lives in its heading, where a heading's
350
+ action belongs, instead of a full-width button competing with the
351
+ cards for the eye. */}
352
+ <Button
353
+ variant="ghost"
354
+ size="icon"
355
+ data-testid="new-session-open"
356
+ title="new session"
357
+ aria-label="new session"
358
+ onClick={() => setCreating(true)}
359
+ className="text-doom-faint hover:text-doom-hi"
360
+ >
361
+ <PlusIcon className="h-3 w-3" />
362
+ </Button>
363
+ </span>
364
+ </div>
365
+ <div className="flex flex-col gap-1 px-2.5">
366
+ {order.map((id, index) => (
367
+ <SessionCard key={id} meta={byId[id]} ordinal={index + 1} active={id === activeId} now={now} />
368
+ ))}
369
+ </div>
370
+ {/* With no sessions the rail has nothing to show and the plus alone is a
371
+ thin invitation, so the empty state spells the action out. */}
372
+ {order.length === 0 ? (
373
+ <div className="px-2.5 pt-2">
374
+ <Button
375
+ variant="outline"
376
+ size="lg"
377
+ data-testid="new-session-empty"
378
+ onClick={() => setCreating(true)}
379
+ className="w-full justify-start px-[11px] text-[11px]"
380
+ >
381
+ <PlusIcon className="h-3 w-3" />
382
+ new session
383
+ </Button>
384
+ </div>
385
+ ) : null}
386
+
387
+ <PluginSurface slot={HOST_SLOTS.rail} sessionId={activeId} />
388
+ <div className="flex-1" />
389
+ <div className="flex items-center gap-2.5 border-t border-doom-border px-4 py-3">
390
+ <Tooltip>
391
+ <TooltipTrigger asChild>
392
+ <Button asChild variant="ghost" size="icon" className="text-doom-faint">
393
+ <Link
394
+ to="/settings/$section"
395
+ params={{ section: DEFAULT_SETTINGS_SECTION }}
396
+ data-testid="settings-open"
397
+ aria-label="settings"
398
+ >
399
+ <GearIcon className="h-3 w-3" />
400
+ </Link>
401
+ </Button>
402
+ </TooltipTrigger>
403
+ <TooltipContent side="top">settings</TooltipContent>
404
+ </Tooltip>
405
+ <span className="text-[9px] text-doom-faint">ctrl+k commands · ctrl+t new session</span>
406
+ </div>
407
+
408
+ {creating ? <NewSessionDialog onClose={() => setCreating(false)} /> : null}
409
+ </div>
410
+ );
411
+ }
@@ -0,0 +1,88 @@
1
+ import { CheckIcon, RadioGroup, RadioGroupCard } from '@agimon-ai/doompi-web-components';
2
+ import type { ThemeConfig } from '@agimon-ai/doompi-web-components/theme';
3
+ import { useStore } from '@tanstack/react-store';
4
+ import { availableThemes, selectTheme, themeStore } from '../../stores/themeStore.ts';
5
+
6
+ /** The accents a swatch previews, in the order the bar shows them. */
7
+ const SWATCH_ACCENTS = ['blue', 'green', 'yellow', 'red', 'magenta', 'cyan'] as const;
8
+
9
+ /** A miniature of the cockpit in the theme's own colours, drawn from its tokens rather than the page's. */
10
+ function ThemeSwatch({ theme }: { theme: ThemeConfig }) {
11
+ const { tokens } = theme;
12
+ return (
13
+ <div
14
+ aria-hidden
15
+ className="flex h-[74px] w-full overflow-hidden rounded border"
16
+ style={{ backgroundColor: tokens.bg, borderColor: tokens.border }}
17
+ >
18
+ <div className="flex w-[34%] flex-col gap-1 p-1.5" style={{ backgroundColor: tokens.rail }}>
19
+ <span className="h-[7px] w-full rounded-sm" style={{ backgroundColor: tokens.selected }} />
20
+ <span className="h-[7px] w-3/4 rounded-sm" style={{ backgroundColor: tokens.panel }} />
21
+ <span className="h-[7px] w-3/4 rounded-sm" style={{ backgroundColor: tokens.panel }} />
22
+ </div>
23
+ <div className="flex flex-1 flex-col justify-between p-1.5">
24
+ <div className="flex flex-col gap-1">
25
+ <span className="h-[5px] w-2/3 rounded-sm" style={{ backgroundColor: tokens.text }} />
26
+ <span className="h-[5px] w-1/2 rounded-sm" style={{ backgroundColor: tokens.dim }} />
27
+ </div>
28
+ <div className="flex gap-1">
29
+ {SWATCH_ACCENTS.map((accent) => (
30
+ <span key={accent} className="h-2 w-2 rounded-full" style={{ backgroundColor: tokens[accent] }} />
31
+ ))}
32
+ </div>
33
+ </div>
34
+ </div>
35
+ );
36
+ }
37
+
38
+ /**
39
+ * The appearance page: the shipped themes as a picker. A choice applies at
40
+ * once and is remembered per browser; the token contract the themes fill
41
+ * is documented in the component library.
42
+ */
43
+ export function AppearanceSettings() {
44
+ const current = useStore(themeStore, (state) => state.name);
45
+ return (
46
+ <div data-testid="appearance-settings" className="flex max-w-[780px] flex-col gap-4">
47
+ <div className="flex flex-col gap-1">
48
+ <h2 className="text-[13px] font-bold text-doom-hi">appearance</h2>
49
+ <p className="text-[11px] leading-relaxed text-doom-dim">
50
+ pick the theme the cockpit renders with. the choice is remembered in this browser; every colour on the page,
51
+ including the plugins&apos; tabs, follows it.
52
+ </p>
53
+ </div>
54
+ <RadioGroup
55
+ aria-label="theme"
56
+ data-testid="theme-list"
57
+ className="grid grid-cols-3 gap-3"
58
+ value={current}
59
+ onValueChange={selectTheme}
60
+ >
61
+ {availableThemes().map((theme) => {
62
+ const selected = theme.name === current;
63
+ return (
64
+ <RadioGroupCard
65
+ key={theme.name}
66
+ value={theme.name}
67
+ data-testid={`theme-${theme.name}`}
68
+ data-selected={selected}
69
+ className="flex flex-col gap-2 p-2.5"
70
+ >
71
+ <ThemeSwatch theme={theme} />
72
+ <span className="flex items-center gap-2">
73
+ <span className={`flex-1 text-[12px] font-bold ${selected ? 'text-doom-blue' : 'text-doom-hi'}`}>
74
+ {theme.label}
75
+ </span>
76
+ <span className="text-[9px] text-doom-faint">{theme.scheme}</span>
77
+ {selected ? <CheckIcon className="h-3 w-3 text-doom-blue" /> : null}
78
+ </span>
79
+ </RadioGroupCard>
80
+ );
81
+ })}
82
+ </RadioGroup>
83
+ <p className="text-[10px] leading-relaxed text-doom-faint">
84
+ a theme is a JSON config of twenty palette tokens; see @agimon-ai/doompi-web-components for the contract.
85
+ </p>
86
+ </div>
87
+ );
88
+ }