neural-loom 0.1.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 (368) hide show
  1. package/.next/BUILD_ID +1 -0
  2. package/.next/app-path-routes-manifest.json +18 -0
  3. package/.next/build-manifest.json +20 -0
  4. package/.next/cache/.previewinfo +1 -0
  5. package/.next/cache/.rscinfo +1 -0
  6. package/.next/cache/.tsbuildinfo +1 -0
  7. package/.next/diagnostics/build-diagnostics.json +6 -0
  8. package/.next/diagnostics/framework.json +1 -0
  9. package/.next/diagnostics/route-bundle-stats.json +25 -0
  10. package/.next/export-marker.json +6 -0
  11. package/.next/fallback-build-manifest.json +13 -0
  12. package/.next/images-manifest.json +68 -0
  13. package/.next/next-minimal-server.js.nft.json +1 -0
  14. package/.next/next-server.js.nft.json +1 -0
  15. package/.next/package.json +1 -0
  16. package/.next/prerender-manifest.json +114 -0
  17. package/.next/required-server-files.js +332 -0
  18. package/.next/required-server-files.json +332 -0
  19. package/.next/routes-manifest.json +141 -0
  20. package/.next/server/app/_global-error/page/app-paths-manifest.json +3 -0
  21. package/.next/server/app/_global-error/page/build-manifest.json +16 -0
  22. package/.next/server/app/_global-error/page/next-font-manifest.json +6 -0
  23. package/.next/server/app/_global-error/page/react-loadable-manifest.json +1 -0
  24. package/.next/server/app/_global-error/page/server-reference-manifest.json +4 -0
  25. package/.next/server/app/_global-error/page.js +10 -0
  26. package/.next/server/app/_global-error/page.js.map +5 -0
  27. package/.next/server/app/_global-error/page.js.nft.json +1 -0
  28. package/.next/server/app/_global-error/page_client-reference-manifest.js +3 -0
  29. package/.next/server/app/_global-error.html +1 -0
  30. package/.next/server/app/_global-error.meta +15 -0
  31. package/.next/server/app/_global-error.rsc +15 -0
  32. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +5 -0
  33. package/.next/server/app/_global-error.segments/_full.segment.rsc +15 -0
  34. package/.next/server/app/_global-error.segments/_head.segment.rsc +6 -0
  35. package/.next/server/app/_global-error.segments/_index.segment.rsc +5 -0
  36. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -0
  37. package/.next/server/app/_not-found/page/app-paths-manifest.json +3 -0
  38. package/.next/server/app/_not-found/page/build-manifest.json +16 -0
  39. package/.next/server/app/_not-found/page/next-font-manifest.json +11 -0
  40. package/.next/server/app/_not-found/page/react-loadable-manifest.json +1 -0
  41. package/.next/server/app/_not-found/page/server-reference-manifest.json +4 -0
  42. package/.next/server/app/_not-found/page.js +13 -0
  43. package/.next/server/app/_not-found/page.js.map +5 -0
  44. package/.next/server/app/_not-found/page.js.nft.json +1 -0
  45. package/.next/server/app/_not-found/page_client-reference-manifest.js +3 -0
  46. package/.next/server/app/_not-found.html +1 -0
  47. package/.next/server/app/_not-found.meta +16 -0
  48. package/.next/server/app/_not-found.rsc +16 -0
  49. package/.next/server/app/_not-found.segments/_full.segment.rsc +16 -0
  50. package/.next/server/app/_not-found.segments/_head.segment.rsc +6 -0
  51. package/.next/server/app/_not-found.segments/_index.segment.rsc +5 -0
  52. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -0
  53. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +5 -0
  54. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -0
  55. package/.next/server/app/api/context/aider/route/app-paths-manifest.json +3 -0
  56. package/.next/server/app/api/context/aider/route/build-manifest.json +9 -0
  57. package/.next/server/app/api/context/aider/route/server-reference-manifest.json +4 -0
  58. package/.next/server/app/api/context/aider/route.js +6 -0
  59. package/.next/server/app/api/context/aider/route.js.map +5 -0
  60. package/.next/server/app/api/context/aider/route.js.nft.json +1 -0
  61. package/.next/server/app/api/context/aider/route_client-reference-manifest.js +3 -0
  62. package/.next/server/app/api/context/claude/route/app-paths-manifest.json +3 -0
  63. package/.next/server/app/api/context/claude/route/build-manifest.json +9 -0
  64. package/.next/server/app/api/context/claude/route/server-reference-manifest.json +4 -0
  65. package/.next/server/app/api/context/claude/route.js +6 -0
  66. package/.next/server/app/api/context/claude/route.js.map +5 -0
  67. package/.next/server/app/api/context/claude/route.js.nft.json +1 -0
  68. package/.next/server/app/api/context/claude/route_client-reference-manifest.js +3 -0
  69. package/.next/server/app/api/context/route/app-paths-manifest.json +3 -0
  70. package/.next/server/app/api/context/route/build-manifest.json +9 -0
  71. package/.next/server/app/api/context/route/server-reference-manifest.json +4 -0
  72. package/.next/server/app/api/context/route.js +6 -0
  73. package/.next/server/app/api/context/route.js.map +5 -0
  74. package/.next/server/app/api/context/route.js.nft.json +1 -0
  75. package/.next/server/app/api/context/route_client-reference-manifest.js +3 -0
  76. package/.next/server/app/api/context/ssh/route/app-paths-manifest.json +3 -0
  77. package/.next/server/app/api/context/ssh/route/build-manifest.json +9 -0
  78. package/.next/server/app/api/context/ssh/route/server-reference-manifest.json +4 -0
  79. package/.next/server/app/api/context/ssh/route.js +6 -0
  80. package/.next/server/app/api/context/ssh/route.js.map +5 -0
  81. package/.next/server/app/api/context/ssh/route.js.nft.json +1 -0
  82. package/.next/server/app/api/context/ssh/route_client-reference-manifest.js +3 -0
  83. package/.next/server/app/api/files/route/app-paths-manifest.json +3 -0
  84. package/.next/server/app/api/files/route/build-manifest.json +9 -0
  85. package/.next/server/app/api/files/route/server-reference-manifest.json +4 -0
  86. package/.next/server/app/api/files/route.js +7 -0
  87. package/.next/server/app/api/files/route.js.map +5 -0
  88. package/.next/server/app/api/files/route.js.nft.json +1 -0
  89. package/.next/server/app/api/files/route_client-reference-manifest.js +3 -0
  90. package/.next/server/app/api/git/route/app-paths-manifest.json +3 -0
  91. package/.next/server/app/api/git/route/build-manifest.json +9 -0
  92. package/.next/server/app/api/git/route/server-reference-manifest.json +4 -0
  93. package/.next/server/app/api/git/route.js +6 -0
  94. package/.next/server/app/api/git/route.js.map +5 -0
  95. package/.next/server/app/api/git/route.js.nft.json +1 -0
  96. package/.next/server/app/api/git/route_client-reference-manifest.js +3 -0
  97. package/.next/server/app/api/sessions/inject/route/app-paths-manifest.json +3 -0
  98. package/.next/server/app/api/sessions/inject/route/build-manifest.json +9 -0
  99. package/.next/server/app/api/sessions/inject/route/server-reference-manifest.json +4 -0
  100. package/.next/server/app/api/sessions/inject/route.js +7 -0
  101. package/.next/server/app/api/sessions/inject/route.js.map +5 -0
  102. package/.next/server/app/api/sessions/inject/route.js.nft.json +1 -0
  103. package/.next/server/app/api/sessions/inject/route_client-reference-manifest.js +3 -0
  104. package/.next/server/app/api/sessions/input/route/app-paths-manifest.json +3 -0
  105. package/.next/server/app/api/sessions/input/route/build-manifest.json +9 -0
  106. package/.next/server/app/api/sessions/input/route/server-reference-manifest.json +4 -0
  107. package/.next/server/app/api/sessions/input/route.js +7 -0
  108. package/.next/server/app/api/sessions/input/route.js.map +5 -0
  109. package/.next/server/app/api/sessions/input/route.js.nft.json +1 -0
  110. package/.next/server/app/api/sessions/input/route_client-reference-manifest.js +3 -0
  111. package/.next/server/app/api/sessions/launch/route/app-paths-manifest.json +3 -0
  112. package/.next/server/app/api/sessions/launch/route/build-manifest.json +9 -0
  113. package/.next/server/app/api/sessions/launch/route/server-reference-manifest.json +4 -0
  114. package/.next/server/app/api/sessions/launch/route.js +7 -0
  115. package/.next/server/app/api/sessions/launch/route.js.map +5 -0
  116. package/.next/server/app/api/sessions/launch/route.js.nft.json +1 -0
  117. package/.next/server/app/api/sessions/launch/route_client-reference-manifest.js +3 -0
  118. package/.next/server/app/api/sessions/route/app-paths-manifest.json +3 -0
  119. package/.next/server/app/api/sessions/route/build-manifest.json +9 -0
  120. package/.next/server/app/api/sessions/route/server-reference-manifest.json +4 -0
  121. package/.next/server/app/api/sessions/route.js +7 -0
  122. package/.next/server/app/api/sessions/route.js.map +5 -0
  123. package/.next/server/app/api/sessions/route.js.nft.json +1 -0
  124. package/.next/server/app/api/sessions/route_client-reference-manifest.js +3 -0
  125. package/.next/server/app/api/sessions/stats/route/app-paths-manifest.json +3 -0
  126. package/.next/server/app/api/sessions/stats/route/build-manifest.json +9 -0
  127. package/.next/server/app/api/sessions/stats/route/server-reference-manifest.json +4 -0
  128. package/.next/server/app/api/sessions/stats/route.js +7 -0
  129. package/.next/server/app/api/sessions/stats/route.js.map +5 -0
  130. package/.next/server/app/api/sessions/stats/route.js.nft.json +1 -0
  131. package/.next/server/app/api/sessions/stats/route_client-reference-manifest.js +3 -0
  132. package/.next/server/app/api/sessions/stop/route/app-paths-manifest.json +3 -0
  133. package/.next/server/app/api/sessions/stop/route/build-manifest.json +9 -0
  134. package/.next/server/app/api/sessions/stop/route/server-reference-manifest.json +4 -0
  135. package/.next/server/app/api/sessions/stop/route.js +7 -0
  136. package/.next/server/app/api/sessions/stop/route.js.map +5 -0
  137. package/.next/server/app/api/sessions/stop/route.js.nft.json +1 -0
  138. package/.next/server/app/api/sessions/stop/route_client-reference-manifest.js +3 -0
  139. package/.next/server/app/favicon.ico/route/app-paths-manifest.json +3 -0
  140. package/.next/server/app/favicon.ico/route/build-manifest.json +9 -0
  141. package/.next/server/app/favicon.ico/route.js +7 -0
  142. package/.next/server/app/favicon.ico/route.js.map +5 -0
  143. package/.next/server/app/favicon.ico/route.js.nft.json +1 -0
  144. package/.next/server/app/favicon.ico.body +0 -0
  145. package/.next/server/app/favicon.ico.meta +1 -0
  146. package/.next/server/app/index.html +1 -0
  147. package/.next/server/app/index.meta +14 -0
  148. package/.next/server/app/index.rsc +21 -0
  149. package/.next/server/app/index.segments/__PAGE__.segment.rsc +10 -0
  150. package/.next/server/app/index.segments/_full.segment.rsc +21 -0
  151. package/.next/server/app/index.segments/_head.segment.rsc +6 -0
  152. package/.next/server/app/index.segments/_index.segment.rsc +5 -0
  153. package/.next/server/app/index.segments/_tree.segment.rsc +5 -0
  154. package/.next/server/app/page/app-paths-manifest.json +3 -0
  155. package/.next/server/app/page/build-manifest.json +16 -0
  156. package/.next/server/app/page/next-font-manifest.json +11 -0
  157. package/.next/server/app/page/react-loadable-manifest.json +42 -0
  158. package/.next/server/app/page/server-reference-manifest.json +4 -0
  159. package/.next/server/app/page.js +14 -0
  160. package/.next/server/app/page.js.map +5 -0
  161. package/.next/server/app/page.js.nft.json +1 -0
  162. package/.next/server/app/page_client-reference-manifest.js +3 -0
  163. package/.next/server/app-paths-manifest.json +18 -0
  164. package/.next/server/chunks/[externals]__0shxiss._.js +3 -0
  165. package/.next/server/chunks/[externals]__0shxiss._.js.map +1 -0
  166. package/.next/server/chunks/[externals]_next_dist_0arv.vj._.js +3 -0
  167. package/.next/server/chunks/[externals]_next_dist_0arv.vj._.js.map +1 -0
  168. package/.next/server/chunks/[externals]_node-pty_12c-8pf._.js +3 -0
  169. package/.next/server/chunks/[externals]_node-pty_12c-8pf._.js.map +1 -0
  170. package/.next/server/chunks/[root-of-the-server]__0-k9zyi._.js +15 -0
  171. package/.next/server/chunks/[root-of-the-server]__0-k9zyi._.js.map +1 -0
  172. package/.next/server/chunks/[root-of-the-server]__04kdofw._.js +15 -0
  173. package/.next/server/chunks/[root-of-the-server]__04kdofw._.js.map +1 -0
  174. package/.next/server/chunks/[root-of-the-server]__0c.5qbe._.js +15 -0
  175. package/.next/server/chunks/[root-of-the-server]__0c.5qbe._.js.map +1 -0
  176. package/.next/server/chunks/[root-of-the-server]__0d0pykb._.js +28 -0
  177. package/.next/server/chunks/[root-of-the-server]__0d0pykb._.js.map +1 -0
  178. package/.next/server/chunks/[root-of-the-server]__0e5cwnx._.js +3 -0
  179. package/.next/server/chunks/[root-of-the-server]__0e5cwnx._.js.map +1 -0
  180. package/.next/server/chunks/[root-of-the-server]__0efi5cr._.js +3 -0
  181. package/.next/server/chunks/[root-of-the-server]__0efi5cr._.js.map +1 -0
  182. package/.next/server/chunks/[root-of-the-server]__0j8-xkl._.js +13 -0
  183. package/.next/server/chunks/[root-of-the-server]__0j8-xkl._.js.map +1 -0
  184. package/.next/server/chunks/[root-of-the-server]__0k6w___._.js +15 -0
  185. package/.next/server/chunks/[root-of-the-server]__0k6w___._.js.map +1 -0
  186. package/.next/server/chunks/[root-of-the-server]__0l7c-gn._.js +3 -0
  187. package/.next/server/chunks/[root-of-the-server]__0l7c-gn._.js.map +1 -0
  188. package/.next/server/chunks/[root-of-the-server]__0l_q72g._.js +15 -0
  189. package/.next/server/chunks/[root-of-the-server]__0l_q72g._.js.map +1 -0
  190. package/.next/server/chunks/[root-of-the-server]__0m7-xfc._.js +15 -0
  191. package/.next/server/chunks/[root-of-the-server]__0m7-xfc._.js.map +1 -0
  192. package/.next/server/chunks/[root-of-the-server]__0m_~dan._.js +15 -0
  193. package/.next/server/chunks/[root-of-the-server]__0m_~dan._.js.map +1 -0
  194. package/.next/server/chunks/[root-of-the-server]__0ntt3om._.js +3 -0
  195. package/.next/server/chunks/[root-of-the-server]__0ntt3om._.js.map +1 -0
  196. package/.next/server/chunks/[turbopack]_runtime.js +903 -0
  197. package/.next/server/chunks/[turbopack]_runtime.js.map +11 -0
  198. package/.next/server/chunks/_next-internal_server_app_api_context_aider_route_actions_0dszia..js +3 -0
  199. package/.next/server/chunks/_next-internal_server_app_api_context_aider_route_actions_0dszia..js.map +1 -0
  200. package/.next/server/chunks/_next-internal_server_app_api_context_claude_route_actions_12srkdn.js +3 -0
  201. package/.next/server/chunks/_next-internal_server_app_api_context_claude_route_actions_12srkdn.js.map +1 -0
  202. package/.next/server/chunks/_next-internal_server_app_api_context_route_actions_03ko8ta.js +3 -0
  203. package/.next/server/chunks/_next-internal_server_app_api_context_route_actions_03ko8ta.js.map +1 -0
  204. package/.next/server/chunks/_next-internal_server_app_api_context_ssh_route_actions_0~0~96d.js +3 -0
  205. package/.next/server/chunks/_next-internal_server_app_api_context_ssh_route_actions_0~0~96d.js.map +1 -0
  206. package/.next/server/chunks/_next-internal_server_app_api_files_route_actions_0x8kqqx.js +3 -0
  207. package/.next/server/chunks/_next-internal_server_app_api_files_route_actions_0x8kqqx.js.map +1 -0
  208. package/.next/server/chunks/_next-internal_server_app_api_git_route_actions_0xetuf~.js +3 -0
  209. package/.next/server/chunks/_next-internal_server_app_api_git_route_actions_0xetuf~.js.map +1 -0
  210. package/.next/server/chunks/_next-internal_server_app_api_sessions_inject_route_actions_0y2_m1_.js +3 -0
  211. package/.next/server/chunks/_next-internal_server_app_api_sessions_inject_route_actions_0y2_m1_.js.map +1 -0
  212. package/.next/server/chunks/_next-internal_server_app_api_sessions_input_route_actions_032sdjp.js +3 -0
  213. package/.next/server/chunks/_next-internal_server_app_api_sessions_input_route_actions_032sdjp.js.map +1 -0
  214. package/.next/server/chunks/_next-internal_server_app_api_sessions_launch_route_actions_0_qrmsm.js +3 -0
  215. package/.next/server/chunks/_next-internal_server_app_api_sessions_launch_route_actions_0_qrmsm.js.map +1 -0
  216. package/.next/server/chunks/_next-internal_server_app_api_sessions_route_actions_0y1t9w0.js +3 -0
  217. package/.next/server/chunks/_next-internal_server_app_api_sessions_route_actions_0y1t9w0.js.map +1 -0
  218. package/.next/server/chunks/_next-internal_server_app_api_sessions_stats_route_actions_0gc_vw-.js +3 -0
  219. package/.next/server/chunks/_next-internal_server_app_api_sessions_stats_route_actions_0gc_vw-.js.map +1 -0
  220. package/.next/server/chunks/_next-internal_server_app_api_sessions_stop_route_actions_0xg1t9k.js +3 -0
  221. package/.next/server/chunks/_next-internal_server_app_api_sessions_stop_route_actions_0xg1t9k.js.map +1 -0
  222. package/.next/server/chunks/_next-internal_server_app_favicon_ico_route_actions_095lj93.js +3 -0
  223. package/.next/server/chunks/_next-internal_server_app_favicon_ico_route_actions_095lj93.js.map +1 -0
  224. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0ev3h.z.js +3 -0
  225. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0ev3h.z.js.map +1 -0
  226. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0m.429v.js +3 -0
  227. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0m.429v.js.map +1 -0
  228. package/.next/server/chunks/ssr/[root-of-the-server]__0.kd7dh._.js +33 -0
  229. package/.next/server/chunks/ssr/[root-of-the-server]__0.kd7dh._.js.map +1 -0
  230. package/.next/server/chunks/ssr/[root-of-the-server]__089dc4i._.js +33 -0
  231. package/.next/server/chunks/ssr/[root-of-the-server]__089dc4i._.js.map +1 -0
  232. package/.next/server/chunks/ssr/[root-of-the-server]__0_7i5h0._.js +3 -0
  233. package/.next/server/chunks/ssr/[root-of-the-server]__0_7i5h0._.js.map +1 -0
  234. package/.next/server/chunks/ssr/[root-of-the-server]__0hw~y-4._.js +3 -0
  235. package/.next/server/chunks/ssr/[root-of-the-server]__0hw~y-4._.js.map +1 -0
  236. package/.next/server/chunks/ssr/[root-of-the-server]__0uk0awy._.js +3 -0
  237. package/.next/server/chunks/ssr/[root-of-the-server]__0uk0awy._.js.map +1 -0
  238. package/.next/server/chunks/ssr/[root-of-the-server]__0v73tbn._.js +3 -0
  239. package/.next/server/chunks/ssr/[root-of-the-server]__0v73tbn._.js.map +1 -0
  240. package/.next/server/chunks/ssr/[root-of-the-server]__0~y8ue.._.js +3 -0
  241. package/.next/server/chunks/ssr/[root-of-the-server]__0~y8ue.._.js.map +1 -0
  242. package/.next/server/chunks/ssr/[turbopack]_runtime.js +903 -0
  243. package/.next/server/chunks/ssr/[turbopack]_runtime.js.map +11 -0
  244. package/.next/server/chunks/ssr/_0t7oqy6._.js +3 -0
  245. package/.next/server/chunks/ssr/_0t7oqy6._.js.map +1 -0
  246. package/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0k77kol.js +3 -0
  247. package/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0k77kol.js.map +1 -0
  248. package/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0eq97pa.js +3 -0
  249. package/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0eq97pa.js.map +1 -0
  250. package/.next/server/chunks/ssr/_next-internal_server_app_page_actions_09-gtaw.js +3 -0
  251. package/.next/server/chunks/ssr/_next-internal_server_app_page_actions_09-gtaw.js.map +1 -0
  252. package/.next/server/chunks/ssr/node_modules_05l396c._.js +3 -0
  253. package/.next/server/chunks/ssr/node_modules_05l396c._.js.map +1 -0
  254. package/.next/server/chunks/ssr/node_modules_09w7yel._.js +33 -0
  255. package/.next/server/chunks/ssr/node_modules_09w7yel._.js.map +1 -0
  256. package/.next/server/chunks/ssr/node_modules_@swc_helpers_cjs__interop_require_default_cjs_11~q6fv._.js +3 -0
  257. package/.next/server/chunks/ssr/node_modules_@swc_helpers_cjs__interop_require_default_cjs_11~q6fv._.js.map +1 -0
  258. package/.next/server/chunks/ssr/node_modules_next_dist_0ppctuh._.js +19 -0
  259. package/.next/server/chunks/ssr/node_modules_next_dist_0ppctuh._.js.map +1 -0
  260. package/.next/server/chunks/ssr/node_modules_next_dist_0qoh8ry._.js +6 -0
  261. package/.next/server/chunks/ssr/node_modules_next_dist_0qoh8ry._.js.map +1 -0
  262. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0inhx6q._.js +3 -0
  263. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0inhx6q._.js.map +1 -0
  264. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_forbidden_0ghu-f7.js +3 -0
  265. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_forbidden_0ghu-f7.js.map +1 -0
  266. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0lgvd_..js +3 -0
  267. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0lgvd_..js.map +1 -0
  268. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0cjv-23.js +3 -0
  269. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0cjv-23.js.map +1 -0
  270. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_03-z2qq.js +4 -0
  271. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_03-z2qq.js.map +1 -0
  272. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_07vh7rm.js +4 -0
  273. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_07vh7rm.js.map +1 -0
  274. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0qp4u6g.js +4 -0
  275. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0qp4u6g.js.map +1 -0
  276. package/.next/server/functions-config-manifest.json +4 -0
  277. package/.next/server/interception-route-rewrite-manifest.js +1 -0
  278. package/.next/server/middleware-build-manifest.js +20 -0
  279. package/.next/server/middleware-manifest.json +6 -0
  280. package/.next/server/next-font-manifest.js +1 -0
  281. package/.next/server/next-font-manifest.json +15 -0
  282. package/.next/server/pages/404.html +1 -0
  283. package/.next/server/pages/500.html +1 -0
  284. package/.next/server/pages-manifest.json +4 -0
  285. package/.next/server/prefetch-hints.json +1 -0
  286. package/.next/server/server-reference-manifest.js +1 -0
  287. package/.next/server/server-reference-manifest.json +5 -0
  288. package/.next/static/chunks/03g6xpslyid3~.js +1 -0
  289. package/.next/static/chunks/03~yq9q893hmn.js +1 -0
  290. package/.next/static/chunks/07b7-dl8gh17u.js +5 -0
  291. package/.next/static/chunks/07lhk_q6pmm3r.js +1 -0
  292. package/.next/static/chunks/0dbhjjzl8qfwv.js +1 -0
  293. package/.next/static/chunks/0ewdhgxsa_h.q.js +1 -0
  294. package/.next/static/chunks/0f4at4_v-tkgj.js +1 -0
  295. package/.next/static/chunks/0fpki3y6aj230.js +31 -0
  296. package/.next/static/chunks/0ga14ztvrhau2.css +1 -0
  297. package/.next/static/chunks/0lnobx4eh3~-_.js +1 -0
  298. package/.next/static/chunks/0mej.ad_ddvf1.js +1 -0
  299. package/.next/static/chunks/0q4suv6uexpud.css +1 -0
  300. package/.next/static/chunks/0xq6bhmghl02_.js +1 -0
  301. package/.next/static/chunks/139c54uo1w7.4.css +3 -0
  302. package/.next/static/chunks/140ovxvjat1ch.js +5 -0
  303. package/.next/static/chunks/16w4~t4h7gk13.js +1 -0
  304. package/.next/static/chunks/turbopack-08fpdsd.-ns2y.js +1 -0
  305. package/.next/static/media/4fa387ec64143e14-s.0wkzw~je483f-.woff2 +0 -0
  306. package/.next/static/media/53b9e256198e5412-s.0-wfv7uh4i7h9.woff2 +0 -0
  307. package/.next/static/media/5ce348bf30bf5439-s.0zgw-jeven.3w.woff2 +0 -0
  308. package/.next/static/media/6306c77e7c8268e4-s.0rhz0arwfsn~5.woff2 +0 -0
  309. package/.next/static/media/7178b3e590c64307-s.0nx0ww8fni_q3.woff2 +0 -0
  310. package/.next/static/media/797e433ab948586e-s.p.08e28id.o-okb.woff2 +0 -0
  311. package/.next/static/media/7d817b4c03b0c5f1-s.0l76wvqk9d84w.woff2 +0 -0
  312. package/.next/static/media/8a480f0b521d4e75-s.0jzbimsg8vl84.woff2 +0 -0
  313. package/.next/static/media/bbc41e54d2fcbd21-s.0k4k9394f2q-k.woff2 +0 -0
  314. package/.next/static/media/caa3a2e1cccd8315-s.p.09~u27dqhyhd6.woff2 +0 -0
  315. package/.next/static/media/favicon.0x3dzn~oxb6tn.ico +0 -0
  316. package/.next/static/media/fef07dbb0973bf53-s.12tyk43_3sh9u.woff2 +0 -0
  317. package/.next/static/r-T5V6BKLiZ32Ai9Boe1d/_buildManifest.js +11 -0
  318. package/.next/static/r-T5V6BKLiZ32Ai9Boe1d/_clientMiddlewareManifest.js +1 -0
  319. package/.next/static/r-T5V6BKLiZ32Ai9Boe1d/_ssgManifest.js +1 -0
  320. package/.next/trace +1 -0
  321. package/.next/trace-build +1 -0
  322. package/.next/turbopack +0 -0
  323. package/.next/types/cache-life.d.ts +145 -0
  324. package/.next/types/routes.d.ts +84 -0
  325. package/.next/types/validator.ts +178 -0
  326. package/README.md +125 -0
  327. package/bin/cli.js +36 -0
  328. package/next.config.ts +7 -0
  329. package/package.json +40 -0
  330. package/public/file.svg +1 -0
  331. package/public/globe.svg +1 -0
  332. package/public/next.svg +1 -0
  333. package/public/vercel.svg +1 -0
  334. package/public/window.svg +1 -0
  335. package/src/app/api/context/aider/route.ts +113 -0
  336. package/src/app/api/context/claude/route.ts +64 -0
  337. package/src/app/api/context/route.ts +37 -0
  338. package/src/app/api/context/ssh/route.ts +88 -0
  339. package/src/app/api/files/route.ts +276 -0
  340. package/src/app/api/git/route.ts +81 -0
  341. package/src/app/api/sessions/inject/route.ts +21 -0
  342. package/src/app/api/sessions/input/route.ts +30 -0
  343. package/src/app/api/sessions/launch/route.ts +25 -0
  344. package/src/app/api/sessions/route.ts +27 -0
  345. package/src/app/api/sessions/stats/route.ts +76 -0
  346. package/src/app/api/sessions/stop/route.ts +26 -0
  347. package/src/app/components/AiderWizard.tsx +412 -0
  348. package/src/app/components/ClaudeWizard.tsx +335 -0
  349. package/src/app/components/ContextEditor.tsx +169 -0
  350. package/src/app/components/IdeLayout.tsx +2095 -0
  351. package/src/app/components/SshWizard.tsx +379 -0
  352. package/src/app/components/TerminalConsole.tsx +228 -0
  353. package/src/app/favicon.ico +0 -0
  354. package/src/app/globals.css +81 -0
  355. package/src/app/layout.tsx +30 -0
  356. package/src/app/page.module.css +330 -0
  357. package/src/app/page.tsx +581 -0
  358. package/src/lib/agents/AgentRunner.ts +86 -0
  359. package/src/lib/agents/AiderRunner.ts +261 -0
  360. package/src/lib/agents/ClaudeRunner.ts +148 -0
  361. package/src/lib/agents/ContextManager.ts +142 -0
  362. package/src/lib/agents/DockerRunner.ts +182 -0
  363. package/src/lib/agents/MockRunner.ts +126 -0
  364. package/src/lib/agents/SSHRunner.ts +180 -0
  365. package/src/lib/agents/SessionManager.ts +147 -0
  366. package/src/lib/agents/SessionPersister.ts +85 -0
  367. package/src/lib/agents/WebSocketServer.ts +118 -0
  368. package/tsconfig.json +34 -0
@@ -0,0 +1,2095 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState, useCallback } from "react";
4
+ import TerminalConsole from "./TerminalConsole";
5
+
6
+ interface FileNode {
7
+ name: string;
8
+ path: string;
9
+ type: "file" | "directory";
10
+ children?: FileNode[];
11
+ }
12
+
13
+ interface WorkspaceData {
14
+ name: string;
15
+ path: string;
16
+ tree: FileNode[];
17
+ }
18
+
19
+ interface ScopedData {
20
+ name: string;
21
+ path: string;
22
+ tree: FileNode[];
23
+ }
24
+
25
+ interface GitFile {
26
+ status: string;
27
+ relativePath: string;
28
+ absolutePath: string;
29
+ name: string;
30
+ }
31
+
32
+ interface SessionStatus {
33
+ type?: string;
34
+ status?: string;
35
+ logs?: string[];
36
+ tokenUsage?: {
37
+ input: number;
38
+ output: number;
39
+ cost: number;
40
+ };
41
+ }
42
+
43
+ interface SessionStats {
44
+ cpu: string;
45
+ memory: string;
46
+ memoryPercent: string;
47
+ type?: string;
48
+ }
49
+
50
+ interface FlatFile {
51
+ name: string;
52
+ path: string;
53
+ relativePath: string;
54
+ }
55
+
56
+ interface RunnerStatus {
57
+ id: string;
58
+ name: string;
59
+ type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker';
60
+ status: 'idle' | 'running' | 'stopped';
61
+ createdAt: string;
62
+ logsCount: number;
63
+ workspaceRoot?: string;
64
+ scopedDirs?: string[];
65
+ }
66
+
67
+ interface IdeLayoutProps {
68
+ sessionId: string;
69
+ workspaceRoot: string;
70
+ scopedDirs: string[];
71
+ onClose: () => void;
72
+ sessions: RunnerStatus[];
73
+ onSwitchSession: (id: string) => void;
74
+ }
75
+
76
+ interface OpenFile {
77
+ name: string;
78
+ path: string;
79
+ content: string;
80
+ isModified: boolean;
81
+ }
82
+
83
+ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClose, sessions, onSwitchSession }: IdeLayoutProps) {
84
+ const scopedDirsStr = JSON.stringify(scopedDirs);
85
+ const [workspace, setWorkspace] = useState<WorkspaceData | null>(null);
86
+ const [scoped, setScoped] = useState<ScopedData[]>([]);
87
+
88
+ // Track open files and active files per session to preserve state on tab swap
89
+ const [sessionOpenFiles, setSessionOpenFiles] = useState<{ [sessionId: string]: OpenFile[] }>({});
90
+ const [sessionActiveFiles, setSessionActiveFiles] = useState<{ [sessionId: string]: string | null }>({});
91
+
92
+ const openFiles = sessionOpenFiles[sessionId] || [];
93
+ const activeFilePath = sessionActiveFiles[sessionId] || null;
94
+
95
+ const setOpenFiles = useCallback((newFiles: OpenFile[] | ((prev: OpenFile[]) => OpenFile[])) => {
96
+ setSessionOpenFiles(prev => {
97
+ const currentFiles = prev[sessionId] || [];
98
+ const updatedFiles = typeof newFiles === "function" ? newFiles(currentFiles) : newFiles;
99
+ return { ...prev, [sessionId]: updatedFiles };
100
+ });
101
+ }, [sessionId]);
102
+
103
+ const setActiveFilePath = useCallback((path: string | null) => {
104
+ setSessionActiveFiles(prev => ({ ...prev, [sessionId]: path }));
105
+ }, [sessionId]);
106
+
107
+ const [explorerVisible, setExplorerVisible] = useState(true);
108
+ const [activeTab, setActiveTab] = useState<"explorer" | "git" | "analytics" | "auditor">("explorer");
109
+ const [isRefreshing, setIsRefreshing] = useState(false);
110
+
111
+ // Git Diff Panel state
112
+ const [gitFiles, setGitFiles] = useState<GitFile[]>([]);
113
+ const [isGitRepo, setIsGitRepo] = useState(false);
114
+ const [gitLoading, setGitLoading] = useState(false);
115
+
116
+ // Status & Telemetry
117
+ const [sessionStatus, setSessionStatus] = useState<SessionStatus | null>(null);
118
+ const [stats, setStats] = useState<SessionStats | null>(null);
119
+
120
+ // Log Search & Auditor
121
+ const [logSearchQuery, setLogSearchQuery] = useState("");
122
+
123
+ // Custom Commands
124
+ const [customCommands, setCustomCommands] = useState<{ name: string; command: string }[]>([]);
125
+ const [newCmdName, setNewCmdName] = useState("");
126
+ const [newCmdStr, setNewCmdStr] = useState("");
127
+
128
+ // Console split view state
129
+ const [splitSessionId, setSplitSessionId] = useState<string | null>(null);
130
+
131
+ // Load Git status
132
+ const loadGitStatus = useCallback(async () => {
133
+ // Defer state updates to prevent synchronous execution inside useEffect
134
+ await new Promise(resolve => setTimeout(resolve, 0));
135
+ setGitLoading(true);
136
+ try {
137
+ const res = await fetch(`/api/git?action=status&root=${encodeURIComponent(workspaceRoot)}`);
138
+ if (res.ok) {
139
+ const data = await res.json();
140
+ if (data.success) {
141
+ setIsGitRepo(data.isGit);
142
+ setGitFiles(data.files || []);
143
+ }
144
+ }
145
+ } catch (e) {
146
+ console.error("Failed to load git status:", e);
147
+ } finally {
148
+ setGitLoading(false);
149
+ }
150
+ }, [workspaceRoot]);
151
+
152
+ useEffect(() => {
153
+ setTimeout(() => {
154
+ loadGitStatus();
155
+ }, 0);
156
+ const id = setInterval(loadGitStatus, 5000);
157
+ return () => clearInterval(id);
158
+ }, [loadGitStatus]);
159
+
160
+ // Poll Session Status (logs, tokens, type)
161
+ useEffect(() => {
162
+ const fetchStatus = async () => {
163
+ try {
164
+ const res = await fetch(`/api/sessions?id=${sessionId}`);
165
+ if (res.ok) {
166
+ const data = await res.json();
167
+ setSessionStatus(data);
168
+ }
169
+ } catch (e) {
170
+ console.error("Failed to fetch session status:", e);
171
+ }
172
+ };
173
+ fetchStatus();
174
+ const id = setInterval(fetchStatus, 3000);
175
+ return () => clearInterval(id);
176
+ }, [sessionId]);
177
+
178
+ // Poll CPU / Memory Stats
179
+ useEffect(() => {
180
+ const fetchStats = async () => {
181
+ try {
182
+ const res = await fetch(`/api/sessions/stats?id=${sessionId}`);
183
+ if (res.ok) {
184
+ const data = await res.json();
185
+ setStats(data);
186
+ }
187
+ } catch (e) {
188
+ console.error("Failed to fetch container/process stats:", e);
189
+ }
190
+ };
191
+ fetchStats();
192
+ const id = setInterval(fetchStats, 3000);
193
+ return () => clearInterval(id);
194
+ }, [sessionId]);
195
+
196
+ // Custom Commands Persistence
197
+ useEffect(() => {
198
+ const saved = localStorage.getItem("neural_loom_custom_commands");
199
+ if (saved) {
200
+ try {
201
+ const parsed = JSON.parse(saved);
202
+ setTimeout(() => {
203
+ setCustomCommands(parsed);
204
+ }, 0);
205
+ } catch {}
206
+ }
207
+ }, []);
208
+
209
+ const saveCommands = (cmds: { name: string; command: string }[]) => {
210
+ setCustomCommands(cmds);
211
+ localStorage.setItem("neural_loom_custom_commands", JSON.stringify(cmds));
212
+ };
213
+
214
+ const handleAddCommand = () => {
215
+ if (!newCmdName.trim() || !newCmdStr.trim()) return;
216
+ const updated = [...customCommands, { name: newCmdName.trim(), command: newCmdStr.trim() }];
217
+ saveCommands(updated);
218
+ setNewCmdName("");
219
+ setNewCmdStr("");
220
+ };
221
+
222
+ const handleDeleteCommand = (idx: number) => {
223
+ const updated = customCommands.filter((_, i) => i !== idx);
224
+ saveCommands(updated);
225
+ };
226
+
227
+ const triggerInject = async (command: string) => {
228
+ try {
229
+ await fetch("/api/sessions/inject", {
230
+ method: "POST",
231
+ headers: { "Content-Type": "application/json" },
232
+ body: JSON.stringify({ id: sessionId, command }),
233
+ });
234
+ } catch (e) {
235
+ console.error("Failed to inject command:", e);
236
+ }
237
+ };
238
+
239
+ const handleExportLog = () => {
240
+ if (!sessionStatus?.logs) return;
241
+ const fullLog = sessionStatus.logs.join("").replace(/\x1b\[[0-9;]*m/g, "");
242
+ const blob = new Blob([fullLog], { type: "text/plain;charset=utf-8" });
243
+ const url = URL.createObjectURL(blob);
244
+ const a = document.createElement("a");
245
+ a.href = url;
246
+ a.download = `session-${sessionId}-logs.txt`;
247
+ a.click();
248
+ URL.revokeObjectURL(url);
249
+ };
250
+
251
+ const isAider = sessionStatus?.type?.includes("aider");
252
+
253
+ // Dialog operations state
254
+ const [dialog, setDialog] = useState<{
255
+ type: "create-file" | "create-folder" | "rename" | "delete" | null;
256
+ targetPath: string | null;
257
+ inputValue: string;
258
+ error: string | null;
259
+ }>({ type: null, targetPath: null, inputValue: "", error: null });
260
+
261
+ // Search modal state
262
+ const [searchOpen, setSearchOpen] = useState(false);
263
+ const [searchQuery, setSearchQuery] = useState("");
264
+ const [flatFiles, setFlatFiles] = useState<FlatFile[]>([]);
265
+ const [searchSelectedIndex, setSearchSelectedIndex] = useState(0);
266
+
267
+ // Load directory tree
268
+ const loadFiles = useCallback(async () => {
269
+ // Defer state updates to prevent synchronous execution inside useEffect
270
+ await new Promise(resolve => setTimeout(resolve, 0));
271
+ setIsRefreshing(true);
272
+ try {
273
+ const scopedQuery = scopedDirs && scopedDirs.length > 0 ? `&scoped=${encodeURIComponent(scopedDirs.join(","))}` : "";
274
+ const res = await fetch(`/api/files?action=list&root=${encodeURIComponent(workspaceRoot)}${scopedQuery}`);
275
+ if (res.ok) {
276
+ const data = await res.json();
277
+ if (data.success) {
278
+ setWorkspace(data.workspace);
279
+ setScoped(data.scoped || []);
280
+ }
281
+ }
282
+ } catch (err) {
283
+ console.error("Failed to load file explorer:", err);
284
+ } finally {
285
+ setIsRefreshing(false);
286
+ }
287
+ // eslint-disable-next-line react-hooks/exhaustive-deps
288
+ }, [workspaceRoot, scopedDirsStr]);
289
+
290
+ useEffect(() => {
291
+ setTimeout(() => {
292
+ loadFiles();
293
+ }, 0);
294
+ }, [loadFiles]);
295
+
296
+ // Trigger creation dialog
297
+ const handleTriggerCreate = (parentPath: string, type: "file" | "directory") => {
298
+ setDialog({
299
+ type: type === "file" ? "create-file" : "create-folder",
300
+ targetPath: parentPath,
301
+ inputValue: "",
302
+ error: null
303
+ });
304
+ };
305
+
306
+ // Trigger rename dialog
307
+ const handleTriggerRename = (targetPath: string) => {
308
+ const name = targetPath.split(/[/\\]/).pop() || "";
309
+ setDialog({
310
+ type: "rename",
311
+ targetPath,
312
+ inputValue: name,
313
+ error: null
314
+ });
315
+ };
316
+
317
+ // Trigger delete dialog
318
+ const handleTriggerDelete = (targetPath: string) => {
319
+ setDialog({
320
+ type: "delete",
321
+ targetPath,
322
+ inputValue: "",
323
+ error: null
324
+ });
325
+ };
326
+
327
+ const handleDialogSubmit = async (e?: React.FormEvent) => {
328
+ if (e) e.preventDefault();
329
+ if (!dialog.type || !dialog.targetPath) return;
330
+
331
+ setDialog(prev => ({ ...prev, error: null }));
332
+
333
+ try {
334
+ if (dialog.type === "create-file" || dialog.type === "create-folder") {
335
+ if (!dialog.inputValue.trim()) {
336
+ setDialog(prev => ({ ...prev, error: "Name cannot be empty" }));
337
+ return;
338
+ }
339
+
340
+ const newPath = `${dialog.targetPath}/${dialog.inputValue.trim()}`;
341
+ const itemType = dialog.type === "create-file" ? "file" : "directory";
342
+
343
+ const res = await fetch("/api/files", {
344
+ method: "POST",
345
+ headers: { "Content-Type": "application/json" },
346
+ body: JSON.stringify({
347
+ action: "create",
348
+ path: newPath,
349
+ type: itemType,
350
+ }),
351
+ });
352
+
353
+ if (res.ok) {
354
+ const data = await res.json();
355
+ if (data.success) {
356
+ setDialog({ type: null, targetPath: null, inputValue: "", error: null });
357
+ await loadFiles();
358
+ if (itemType === "file") {
359
+ await handleOpenFile(newPath);
360
+ }
361
+ } else {
362
+ setDialog(prev => ({ ...prev, error: data.error || "Failed to create item" }));
363
+ }
364
+ } else {
365
+ const data = await res.json();
366
+ setDialog(prev => ({ ...prev, error: data.error || "Failed to create item" }));
367
+ }
368
+ }
369
+
370
+ if (dialog.type === "rename") {
371
+ if (!dialog.inputValue.trim()) {
372
+ setDialog(prev => ({ ...prev, error: "Name cannot be empty" }));
373
+ return;
374
+ }
375
+
376
+ const normPath = dialog.targetPath.replace(/\\/g, "/");
377
+ const parentDir = normPath.substring(0, normPath.lastIndexOf("/"));
378
+ const newPath = parentDir ? `${parentDir}/${dialog.inputValue.trim()}` : dialog.inputValue.trim();
379
+
380
+ const res = await fetch("/api/files", {
381
+ method: "POST",
382
+ headers: { "Content-Type": "application/json" },
383
+ body: JSON.stringify({
384
+ action: "rename",
385
+ oldPath: dialog.targetPath,
386
+ newPath: newPath,
387
+ }),
388
+ });
389
+
390
+ if (res.ok) {
391
+ const data = await res.json();
392
+ if (data.success) {
393
+ // Update openFiles state if renamed file was open
394
+ setOpenFiles(prev => prev.map(f => {
395
+ if (f.path === dialog.targetPath) {
396
+ return {
397
+ ...f,
398
+ path: newPath,
399
+ name: dialog.inputValue.trim()
400
+ };
401
+ }
402
+ return f;
403
+ }));
404
+ if (activeFilePath === dialog.targetPath) {
405
+ setActiveFilePath(newPath);
406
+ }
407
+ setDialog({ type: null, targetPath: null, inputValue: "", error: null });
408
+ await loadFiles();
409
+ } else {
410
+ setDialog(prev => ({ ...prev, error: data.error || "Failed to rename item" }));
411
+ }
412
+ } else {
413
+ const data = await res.json();
414
+ setDialog(prev => ({ ...prev, error: data.error || "Failed to rename item" }));
415
+ }
416
+ }
417
+
418
+ if (dialog.type === "delete") {
419
+ const res = await fetch("/api/files", {
420
+ method: "POST",
421
+ headers: { "Content-Type": "application/json" },
422
+ body: JSON.stringify({
423
+ action: "delete",
424
+ path: dialog.targetPath,
425
+ }),
426
+ });
427
+
428
+ if (res.ok) {
429
+ const data = await res.json();
430
+ if (data.success) {
431
+ handleCloseFile(dialog.targetPath);
432
+ setDialog({ type: null, targetPath: null, inputValue: "", error: null });
433
+ await loadFiles();
434
+ } else {
435
+ setDialog(prev => ({ ...prev, error: data.error || "Failed to delete item" }));
436
+ }
437
+ } else {
438
+ const data = await res.json();
439
+ setDialog(prev => ({ ...prev, error: data.error || "Failed to delete item" }));
440
+ }
441
+ }
442
+ } catch (err) {
443
+ console.error("File operation failed:", err);
444
+ setDialog(prev => ({ ...prev, error: "An unexpected error occurred" }));
445
+ }
446
+ };
447
+
448
+ const fetchFlatFiles = useCallback(async () => {
449
+ try {
450
+ const scopedQuery = scopedDirs && scopedDirs.length > 0 ? `&scoped=${encodeURIComponent(scopedDirs.join(","))}` : "";
451
+ const res = await fetch(`/api/files?action=flat_list&root=${encodeURIComponent(workspaceRoot)}${scopedQuery}`);
452
+ if (res.ok) {
453
+ const data = await res.json();
454
+ if (data.success && data.files) {
455
+ setFlatFiles(data.files);
456
+ }
457
+ }
458
+ } catch (err) {
459
+ console.error("Failed to load flat files list:", err);
460
+ }
461
+ // eslint-disable-next-line react-hooks/exhaustive-deps
462
+ }, [workspaceRoot, scopedDirsStr]);
463
+
464
+ useEffect(() => {
465
+ if (searchOpen) {
466
+ // Defer state updates to prevent synchronous execution inside useEffect
467
+ setTimeout(() => {
468
+ setSearchQuery("");
469
+ setSearchSelectedIndex(0);
470
+ fetchFlatFiles();
471
+ }, 0);
472
+ }
473
+ }, [searchOpen, fetchFlatFiles]);
474
+
475
+ // Handle file click in explorer
476
+ const handleOpenFile = async (filePath: string) => {
477
+ // Check if already open
478
+ const alreadyOpen = openFiles.find((f) => f.path === filePath);
479
+ if (alreadyOpen) {
480
+ setActiveFilePath(filePath);
481
+ return;
482
+ }
483
+
484
+ try {
485
+ const res = await fetch(`/api/files?action=read&path=${encodeURIComponent(filePath)}`);
486
+ if (res.ok) {
487
+ const data = await res.json();
488
+ if (data.success) {
489
+ const newFile: OpenFile = {
490
+ name: filePath.split(/[/\\]/).pop() || "Untitled",
491
+ path: filePath,
492
+ content: data.content,
493
+ isModified: false,
494
+ };
495
+ setOpenFiles([...openFiles, newFile]);
496
+ setActiveFilePath(filePath);
497
+ }
498
+ }
499
+ } catch (err) {
500
+ console.error(`Failed to read file ${filePath}:`, err);
501
+ }
502
+ };
503
+
504
+ const handleCloseFile = (filePath: string) => {
505
+ const updated = openFiles.filter((f) => f.path !== filePath);
506
+ setOpenFiles(updated);
507
+ if (activeFilePath === filePath) {
508
+ setActiveFilePath(updated.length > 0 ? updated[updated.length - 1].path : null);
509
+ }
510
+ };
511
+
512
+ const activeFile = openFiles.find((f) => f.path === activeFilePath);
513
+
514
+ const handleContentChange = (newVal: string) => {
515
+ if (!activeFilePath) return;
516
+ setOpenFiles(
517
+ openFiles.map((f) => (f.path === activeFilePath ? { ...f, content: newVal, isModified: true } : f))
518
+ );
519
+ };
520
+
521
+ const handleSaveFile = useCallback(async () => {
522
+ if (!activeFile) return;
523
+ try {
524
+ const res = await fetch("/api/files", {
525
+ method: "POST",
526
+ headers: { "Content-Type": "application/json" },
527
+ body: JSON.stringify({
528
+ action: "write",
529
+ path: activeFile.path,
530
+ content: activeFile.content,
531
+ }),
532
+ });
533
+
534
+ if (res.ok) {
535
+ setOpenFiles(prev =>
536
+ prev.map((f) => (f.path === activeFilePath ? { ...f, isModified: false } : f))
537
+ );
538
+ }
539
+ } catch (err) {
540
+ console.error(`Failed to save file ${activeFile.path}:`, err);
541
+ }
542
+ }, [activeFile, activeFilePath, setOpenFiles]);
543
+
544
+ // Listen for Ctrl+S and Ctrl+P
545
+ useEffect(() => {
546
+ const handleKeyDown = (e: KeyboardEvent) => {
547
+ if ((e.ctrlKey || e.metaKey) && e.key === "s") {
548
+ e.preventDefault();
549
+ handleSaveFile();
550
+ }
551
+ if ((e.ctrlKey || e.metaKey) && e.key === "p") {
552
+ e.preventDefault();
553
+ setSearchOpen(prev => !prev);
554
+ }
555
+ };
556
+ window.addEventListener("keydown", handleKeyDown);
557
+ return () => window.removeEventListener("keydown", handleKeyDown);
558
+ }, [handleSaveFile]);
559
+
560
+
561
+ const filteredFiles = flatFiles.filter(f =>
562
+ f.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
563
+ f.relativePath.toLowerCase().includes(searchQuery.toLowerCase())
564
+ ).slice(0, 10);
565
+
566
+ const handleSearchKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
567
+ if (e.key === "ArrowDown") {
568
+ e.preventDefault();
569
+ setSearchSelectedIndex(prev => (prev + 1) % Math.max(1, filteredFiles.length));
570
+ } else if (e.key === "ArrowUp") {
571
+ e.preventDefault();
572
+ setSearchSelectedIndex(prev => (prev - 1 + filteredFiles.length) % Math.max(1, filteredFiles.length));
573
+ } else if (e.key === "Enter") {
574
+ e.preventDefault();
575
+ if (filteredFiles[searchSelectedIndex]) {
576
+ handleOpenFile(filteredFiles[searchSelectedIndex].path);
577
+ setSearchOpen(false);
578
+ }
579
+ } else if (e.key === "Escape") {
580
+ e.preventDefault();
581
+ setSearchOpen(false);
582
+ }
583
+ };
584
+
585
+ return (
586
+ <div style={ideContainerStyle}>
587
+ {/* Activity Bar (Sidebar leftmost panel icons) */}
588
+ <div style={activityBarStyle}>
589
+ <div style={topIconsStyle}>
590
+ {/* Explorer Tab */}
591
+ <div
592
+ onClick={() => {
593
+ if (activeTab === "explorer") {
594
+ setExplorerVisible(!explorerVisible);
595
+ } else {
596
+ setActiveTab("explorer");
597
+ setExplorerVisible(true);
598
+ }
599
+ }}
600
+ style={{
601
+ ...sidebarIconStyle,
602
+ backgroundColor: activeTab === "explorer" && explorerVisible ? "rgba(255,255,255,0.08)" : "transparent",
603
+ borderLeft: activeTab === "explorer" && explorerVisible ? "2px solid var(--primary)" : "2px solid transparent",
604
+ }}
605
+ title="File Explorer (Ctrl+P to search files)"
606
+ >
607
+ 📁
608
+ </div>
609
+
610
+ {/* Git Source Control Tab */}
611
+ <div
612
+ onClick={() => {
613
+ if (activeTab === "git") {
614
+ setExplorerVisible(!explorerVisible);
615
+ } else {
616
+ setActiveTab("git");
617
+ setExplorerVisible(true);
618
+ }
619
+ }}
620
+ style={{
621
+ ...sidebarIconStyle,
622
+ backgroundColor: activeTab === "git" && explorerVisible ? "rgba(255,255,255,0.08)" : "transparent",
623
+ borderLeft: activeTab === "git" && explorerVisible ? "2px solid var(--primary)" : "2px solid transparent",
624
+ }}
625
+ title="Source Control (Git Changes)"
626
+ >
627
+ 🌿
628
+ </div>
629
+
630
+ {/* Token & Cost Analytics Tab */}
631
+ <div
632
+ onClick={() => {
633
+ if (activeTab === "analytics") {
634
+ setExplorerVisible(!explorerVisible);
635
+ } else {
636
+ setActiveTab("analytics");
637
+ setExplorerVisible(true);
638
+ }
639
+ }}
640
+ style={{
641
+ ...sidebarIconStyle,
642
+ backgroundColor: activeTab === "analytics" && explorerVisible ? "rgba(255,255,255,0.08)" : "transparent",
643
+ borderLeft: activeTab === "analytics" && explorerVisible ? "2px solid var(--primary)" : "2px solid transparent",
644
+ }}
645
+ title="Token Usage & Cost Analytics"
646
+ >
647
+ 📊
648
+ </div>
649
+
650
+ {/* Log Auditor Tab */}
651
+ <div
652
+ onClick={() => {
653
+ if (activeTab === "auditor") {
654
+ setExplorerVisible(!explorerVisible);
655
+ } else {
656
+ setActiveTab("auditor");
657
+ setExplorerVisible(true);
658
+ }
659
+ }}
660
+ style={{
661
+ ...sidebarIconStyle,
662
+ backgroundColor: activeTab === "auditor" && explorerVisible ? "rgba(255,255,255,0.08)" : "transparent",
663
+ borderLeft: activeTab === "auditor" && explorerVisible ? "2px solid var(--primary)" : "2px solid transparent",
664
+ }}
665
+ title="Session Log Auditor & Search"
666
+ >
667
+ 🔍
668
+ </div>
669
+ </div>
670
+
671
+ <div
672
+ onClick={onClose}
673
+ style={{ ...sidebarIconStyle, color: "var(--danger)", fontSize: "1rem" }}
674
+ title="Exit Session Layout"
675
+ >
676
+
677
+ </div>
678
+ </div>
679
+
680
+ {/* Primary Sidebar (File Tree Panel) */}
681
+ {explorerVisible && (
682
+ <div style={sidebarPanelStyle}>
683
+ <div style={sidebarHeaderStyle}>
684
+ <span style={{ fontWeight: 700, fontSize: "0.75rem", textTransform: "uppercase", letterSpacing: "0.05em" }}>
685
+ {activeTab === "explorer" && "Workspace Explorer"}
686
+ {activeTab === "git" && "Source Control"}
687
+ {activeTab === "analytics" && "Telemetry & Cost"}
688
+ {activeTab === "auditor" && "Session Auditor"}
689
+ </span>
690
+ {activeTab === "explorer" && (
691
+ <div style={{ display: "flex", gap: "0.25rem" }}>
692
+ {workspace && (
693
+ <>
694
+ <button
695
+ onClick={() => handleTriggerCreate(workspace.path, "file")}
696
+ style={iconButtonStyle}
697
+ title="New File at Root..."
698
+ >
699
+ 📄+
700
+ </button>
701
+ <button
702
+ onClick={() => handleTriggerCreate(workspace.path, "directory")}
703
+ style={iconButtonStyle}
704
+ title="New Folder at Root..."
705
+ >
706
+ 📁+
707
+ </button>
708
+ </>
709
+ )}
710
+ <button
711
+ onClick={loadFiles}
712
+ disabled={isRefreshing}
713
+ style={iconButtonStyle}
714
+ title="Refresh tree"
715
+ >
716
+ 🔄
717
+ </button>
718
+ </div>
719
+ )}
720
+ {activeTab === "git" && (
721
+ <button
722
+ onClick={loadGitStatus}
723
+ disabled={gitLoading}
724
+ style={iconButtonStyle}
725
+ title="Refresh git status"
726
+ >
727
+ 🔄
728
+ </button>
729
+ )}
730
+ </div>
731
+
732
+ <div style={sidebarContentStyle}>
733
+ {activeTab === "explorer" && (
734
+ <>
735
+ {/* Workspace Root Section */}
736
+ {workspace ? (
737
+ <div style={{ marginBottom: "1.5rem" }}>
738
+ <div style={treeSectionHeaderStyle}>
739
+ 💼 {workspace.name} <span style={pathBadgeStyle} title={workspace.path}>root</span>
740
+ </div>
741
+ <div style={{ padding: "0.25rem 0.5rem" }}>
742
+ {workspace.tree.map((node) => (
743
+ <FileItemNode
744
+ key={node.path}
745
+ node={node}
746
+ onSelect={handleOpenFile}
747
+ onCreate={handleTriggerCreate}
748
+ onRename={handleTriggerRename}
749
+ onDelete={handleTriggerDelete}
750
+ isReadOnly={false}
751
+ />
752
+ ))}
753
+ </div>
754
+ </div>
755
+ ) : (
756
+ <div style={{ padding: "1rem", color: "var(--muted)", fontSize: "0.8rem" }}>
757
+ Scanning Workspace directory...
758
+ </div>
759
+ )}
760
+
761
+ {/* Scoped Directories Section */}
762
+ {scoped.length > 0 && (
763
+ <div>
764
+ <div style={{ ...treeSectionHeaderStyle, borderTop: "1px solid var(--border)", paddingTop: "1rem" }}>
765
+ 🔍 Scoped Folders ({scoped.length})
766
+ </div>
767
+ {scoped.map((scope) => (
768
+ <div key={scope.path} style={{ marginTop: "0.75rem", padding: "0 0.5rem" }}>
769
+ <div style={{ ...itemHeaderStyle, display: "flex", gap: "0.25rem", color: "#a855f7", fontSize: "0.75rem", fontWeight: 700, padding: "0.25rem" }} title={scope.path}>
770
+ 🔗 {scope.name}/
771
+ </div>
772
+ <div style={{ paddingLeft: "0.25rem" }}>
773
+ {scope.tree.map((node) => (
774
+ <FileItemNode
775
+ key={node.path}
776
+ node={node}
777
+ onSelect={handleOpenFile}
778
+ onCreate={handleTriggerCreate}
779
+ onRename={handleTriggerRename}
780
+ onDelete={handleTriggerDelete}
781
+ isReadOnly={true}
782
+ />
783
+ ))}
784
+ </div>
785
+ </div>
786
+ ))}
787
+ </div>
788
+ )}
789
+ </>
790
+ )}
791
+
792
+ {activeTab === "git" && (
793
+ <div style={{ padding: "0.5rem 1rem" }}>
794
+ <div style={{ fontSize: "0.75rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", marginBottom: "0.5rem" }}>
795
+ Changes
796
+ </div>
797
+ {!isGitRepo ? (
798
+ <div style={{ color: "var(--muted)", fontSize: "0.75rem", fontStyle: "italic" }}>
799
+ Not a git repository.
800
+ </div>
801
+ ) : gitFiles.length === 0 ? (
802
+ <div style={{ color: "var(--success)", fontSize: "0.75rem", padding: "0.5rem 0" }}>
803
+ ✔ Workspace clean.
804
+ </div>
805
+ ) : (
806
+ <div style={{ display: "flex", flexDirection: "column", gap: "0.25rem" }}>
807
+ {gitFiles.map((file) => {
808
+ let badgeColor = "#eab308";
809
+ if (file.status === "??") badgeColor = "#10b981";
810
+ if (file.status === "A") badgeColor = "#10b981";
811
+ if (file.status === "D") badgeColor = "#ef4444";
812
+ return (
813
+ <div
814
+ key={file.relativePath}
815
+ onClick={async () => {
816
+ try {
817
+ const res = await fetch(`/api/git?action=diff&file=${encodeURIComponent(file.relativePath)}&root=${encodeURIComponent(workspaceRoot)}`);
818
+ if (res.ok) {
819
+ const data = await res.json();
820
+ if (data.success) {
821
+ const diffPath = `diff:${file.relativePath}`;
822
+ if (!openFiles.some(f => f.path === diffPath)) {
823
+ setOpenFiles(prev => [
824
+ ...prev,
825
+ {
826
+ name: `Diff: ${file.name}`,
827
+ path: diffPath,
828
+ content: data.diff,
829
+ isModified: false
830
+ }
831
+ ]);
832
+ }
833
+ setActiveFilePath(diffPath);
834
+ }
835
+ }
836
+ } catch (e) {
837
+ console.error(e);
838
+ }
839
+ }}
840
+ style={{
841
+ display: "flex",
842
+ alignItems: "center",
843
+ justifyContent: "space-between",
844
+ padding: "0.35rem 0.5rem",
845
+ borderRadius: "4px",
846
+ backgroundColor: "rgba(255,255,255,0.02)",
847
+ cursor: "pointer",
848
+ fontSize: "0.75rem",
849
+ fontFamily: "var(--font-mono)",
850
+ }}
851
+ onMouseEnter={(e) => e.currentTarget.style.backgroundColor = "rgba(255,255,255,0.06)"}
852
+ onMouseLeave={(e) => e.currentTarget.style.backgroundColor = "rgba(255,255,255,0.02)"}
853
+ >
854
+ <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: 1 }} title={file.relativePath}>
855
+ {file.name}
856
+ </span>
857
+ <span style={{ fontSize: "0.6rem", padding: "0.1rem 0.3rem", borderRadius: "3px", backgroundColor: badgeColor + "22", color: badgeColor, fontWeight: 700 }}>
858
+ {file.status}
859
+ </span>
860
+ </div>
861
+ );
862
+ })}
863
+ </div>
864
+ )}
865
+ </div>
866
+ )}
867
+
868
+ {activeTab === "analytics" && (
869
+ <div style={{ padding: "0.5rem 1rem", display: "flex", flexDirection: "column", gap: "1.25rem" }}>
870
+ <div>
871
+ <div style={{ fontSize: "0.75rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", marginBottom: "0.5rem" }}>
872
+ Token Usage
873
+ </div>
874
+ <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "0.5rem" }}>
875
+ <div style={analyticsCardStyle}>
876
+ <div style={analyticsCardLabelStyle}>Input</div>
877
+ <div style={analyticsCardValueStyle}>{(sessionStatus?.tokenUsage?.input ?? 0).toLocaleString()}</div>
878
+ </div>
879
+ <div style={analyticsCardStyle}>
880
+ <div style={analyticsCardLabelStyle}>Output</div>
881
+ <div style={analyticsCardValueStyle}>{(sessionStatus?.tokenUsage?.output ?? 0).toLocaleString()}</div>
882
+ </div>
883
+ <div style={{ ...analyticsCardStyle, gridColumn: "span 2" }}>
884
+ <div style={analyticsCardLabelStyle}>Cost</div>
885
+ <div style={{ ...analyticsCardValueStyle, color: "#10b981", fontSize: "1.25rem" }}>
886
+ ${(sessionStatus?.tokenUsage?.cost ?? 0).toFixed(4)}
887
+ </div>
888
+ </div>
889
+ </div>
890
+ </div>
891
+
892
+ <div>
893
+ <div style={{ fontSize: "0.75rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", marginBottom: "0.5rem" }}>
894
+ Resource Telemetry
895
+ </div>
896
+ <div style={{ display: "flex", flexDirection: "column", gap: "0.75rem" }}>
897
+ <div>
898
+ <div style={{ display: "flex", justifyContent: "space-between", fontSize: "0.7rem", marginBottom: "0.25rem" }}>
899
+ <span>CPU Usage</span>
900
+ <span style={{ fontWeight: 600, color: "var(--primary)" }}>{stats?.cpu ?? "0.0%"}</span>
901
+ </div>
902
+ <div style={gaugeOuterStyle}>
903
+ <div style={{ ...gaugeInnerStyle, width: stats?.cpu || "0%", backgroundColor: "var(--primary)" }}></div>
904
+ </div>
905
+ </div>
906
+ <div>
907
+ <div style={{ display: "flex", justifyContent: "space-between", fontSize: "0.7rem", marginBottom: "0.25rem" }}>
908
+ <span>Memory Usage</span>
909
+ <span style={{ fontWeight: 600, color: "#eab308" }}>{stats?.memory ?? "0.0MiB"}</span>
910
+ </div>
911
+ <div style={gaugeOuterStyle}>
912
+ <div style={{ ...gaugeInnerStyle, width: stats?.memoryPercent || "0%", backgroundColor: "#eab308" }}></div>
913
+ </div>
914
+ </div>
915
+ <div style={{ fontSize: "0.6rem", color: "var(--muted)", marginTop: "0.25rem", fontStyle: "italic" }}>
916
+ {stats?.type?.includes("docker") ? "🐳 Docker container statistics" : "💻 Local process statistics"}
917
+ </div>
918
+ </div>
919
+ </div>
920
+ </div>
921
+ )}
922
+
923
+ {activeTab === "auditor" && (
924
+ <div style={{ padding: "0.5rem 1rem", display: "flex", flexDirection: "column", gap: "1rem", height: "100%" }}>
925
+ <div style={{ display: "flex", gap: "0.25rem" }}>
926
+ <input
927
+ type="text"
928
+ placeholder="Search logs..."
929
+ value={logSearchQuery}
930
+ onChange={(e) => setLogSearchQuery(e.target.value)}
931
+ style={{
932
+ flex: 1,
933
+ backgroundColor: "#05070a",
934
+ border: "1px solid var(--border)",
935
+ borderRadius: "4px",
936
+ padding: "0.25rem 0.5rem",
937
+ color: "#ffffff",
938
+ fontSize: "0.75rem",
939
+ outline: "none"
940
+ }}
941
+ />
942
+ <button
943
+ onClick={handleExportLog}
944
+ style={{
945
+ backgroundColor: "rgba(59, 130, 246, 0.15)",
946
+ border: "1px solid #3b82f6",
947
+ color: "#3b82f6",
948
+ padding: "0.25rem 0.5rem",
949
+ borderRadius: "4px",
950
+ fontSize: "0.7rem",
951
+ fontWeight: 600,
952
+ cursor: "pointer"
953
+ }}
954
+ >
955
+ Export
956
+ </button>
957
+ </div>
958
+
959
+ <div style={{
960
+ height: "220px",
961
+ overflowY: "auto",
962
+ backgroundColor: "#05070a",
963
+ border: "1px solid var(--border)",
964
+ borderRadius: "6px",
965
+ padding: "0.5rem",
966
+ fontFamily: "var(--font-mono)",
967
+ fontSize: "0.7rem",
968
+ lineHeight: "1.4",
969
+ whiteSpace: "pre-wrap",
970
+ color: "#cbd5e1"
971
+ }}>
972
+ {(() => {
973
+ if (!sessionStatus?.logs || sessionStatus.logs.length === 0) {
974
+ return <div style={{ color: "var(--muted)", fontStyle: "italic", textAlign: "center" }}>No logs captured.</div>;
975
+ }
976
+ const fullLogString = sessionStatus.logs.join("");
977
+ const cleanLogs = fullLogString.replace(/\x1b\[[0-9;]*m/g, "");
978
+ const lines = cleanLogs.split(/\r?\n/);
979
+ const filteredLines = logSearchQuery.trim()
980
+ ? lines.filter((line: string) => line.toLowerCase().includes(logSearchQuery.toLowerCase()))
981
+ : lines;
982
+ if (filteredLines.length === 0) {
983
+ return <div style={{ color: "var(--muted)", fontStyle: "italic", textAlign: "center" }}>No matches.</div>;
984
+ }
985
+ return filteredLines.map((line: string, i: number) => {
986
+ if (!logSearchQuery.trim()) return <div key={i}>{line}</div>;
987
+ const parts = line.split(new RegExp(`(${logSearchQuery})`, "gi"));
988
+ return (
989
+ <div key={i}>
990
+ {parts.map((part: string, j: number) =>
991
+ part.toLowerCase() === logSearchQuery.toLowerCase()
992
+ ? <span key={j} style={{ backgroundColor: "#eab308", color: "#000000", fontWeight: 600 }}>{part}</span>
993
+ : part
994
+ )}
995
+ </div>
996
+ );
997
+ });
998
+ })()}
999
+ </div>
1000
+
1001
+ <div style={{ borderTop: "1px solid var(--border)", paddingTop: "0.75rem" }}>
1002
+ <div style={{ fontSize: "0.75rem", fontWeight: 700, color: "var(--muted)", marginBottom: "0.5rem" }}>
1003
+ Custom Buttons
1004
+ </div>
1005
+ <div style={{ display: "flex", flexDirection: "column", gap: "0.35rem" }}>
1006
+ <input
1007
+ type="text"
1008
+ placeholder="Button Name (e.g. /undo)"
1009
+ value={newCmdName}
1010
+ onChange={(e) => setNewCmdName(e.target.value)}
1011
+ style={{
1012
+ backgroundColor: "#05070a",
1013
+ border: "1px solid var(--border)",
1014
+ borderRadius: "4px",
1015
+ padding: "0.25rem 0.5rem",
1016
+ color: "#ffffff",
1017
+ fontSize: "0.7rem",
1018
+ outline: "none"
1019
+ }}
1020
+ />
1021
+ <input
1022
+ type="text"
1023
+ placeholder="Injection Command"
1024
+ value={newCmdStr}
1025
+ onChange={(e) => setNewCmdStr(e.target.value)}
1026
+ style={{
1027
+ backgroundColor: "#05070a",
1028
+ border: "1px solid var(--border)",
1029
+ borderRadius: "4px",
1030
+ padding: "0.25rem 0.5rem",
1031
+ color: "#ffffff",
1032
+ fontSize: "0.7rem",
1033
+ outline: "none"
1034
+ }}
1035
+ />
1036
+ <button
1037
+ onClick={handleAddCommand}
1038
+ style={{
1039
+ backgroundColor: "rgba(168, 85, 247, 0.15)",
1040
+ border: "1px solid #a855f7",
1041
+ color: "#c084fc",
1042
+ padding: "0.3rem",
1043
+ borderRadius: "4px",
1044
+ fontSize: "0.7rem",
1045
+ fontWeight: 600,
1046
+ cursor: "pointer"
1047
+ }}
1048
+ >
1049
+ + Add Button
1050
+ </button>
1051
+ {customCommands.length > 0 && (
1052
+ <div style={{ marginTop: "0.25rem", maxHeight: "60px", overflowY: "auto" }}>
1053
+ {customCommands.map((cmd, idx) => (
1054
+ <div key={idx} style={{ display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: "0.65rem", backgroundColor: "rgba(255,255,255,0.02)", padding: "0.1rem 0.25rem", borderRadius: "3px", marginTop: "0.15rem" }}>
1055
+ <span style={{ color: "#c084fc" }}>{cmd.name}</span>
1056
+ <span onClick={() => handleDeleteCommand(idx)} style={{ color: "var(--danger)", cursor: "pointer" }}>✖</span>
1057
+ </div>
1058
+ ))}
1059
+ </div>
1060
+ )}
1061
+ </div>
1062
+ </div>
1063
+ </div>
1064
+ )}
1065
+ </div>
1066
+ </div>
1067
+ )}
1068
+
1069
+ {/* Editor & Console Split View */}
1070
+ <div style={mainContentAreaStyle}>
1071
+ {/* Session Tab Bar Switcher */}
1072
+ <div style={sessionTabBarStyle}>
1073
+ <div style={{ display: "flex", gap: "0.25rem", overflowX: "auto" }}>
1074
+ {sessions.map((s) => {
1075
+ const isActive = s.id === sessionId;
1076
+ const statusColor = s.status === "running" ? "#10b981" : "rgba(255,255,255,0.2)";
1077
+ return (
1078
+ <div
1079
+ key={s.id}
1080
+ onClick={() => {
1081
+ // Close split view if switching to that session
1082
+ if (splitSessionId === s.id) {
1083
+ setSplitSessionId(null);
1084
+ }
1085
+ onSwitchSession(s.id);
1086
+ }}
1087
+ style={{
1088
+ ...sessionTabItemStyle,
1089
+ backgroundColor: isActive ? "#0b101b" : "rgba(8,12,20,0.6)",
1090
+ borderColor: isActive ? "var(--border-focus)" : "var(--border)",
1091
+ color: isActive ? "var(--foreground)" : "var(--muted)",
1092
+ }}
1093
+ >
1094
+ <span
1095
+ style={{
1096
+ width: "6px",
1097
+ height: "6px",
1098
+ borderRadius: "50%",
1099
+ backgroundColor: statusColor,
1100
+ display: "inline-block",
1101
+ }}
1102
+ />
1103
+ <span style={{ fontSize: "0.75rem", fontWeight: isActive ? 600 : 500 }}>
1104
+ {s.name}
1105
+ </span>
1106
+ <span style={{ fontSize: "0.6rem", color: "var(--muted)", backgroundColor: "rgba(255,255,255,0.05)", padding: "1px 4px", borderRadius: "3px" }}>
1107
+ {s.type.replace("claude-", "").replace("aider-", "")}
1108
+ </span>
1109
+ </div>
1110
+ );
1111
+ })}
1112
+ </div>
1113
+ </div>
1114
+
1115
+ {/* Editor Panel (top half) */}
1116
+ <div style={editorPanelStyle}>
1117
+ {/* Tabs header */}
1118
+ <div style={tabHeaderStyle}>
1119
+ <div style={tabsWrapperStyle}>
1120
+ {openFiles.map((file) => (
1121
+ <div
1122
+ key={file.path}
1123
+ style={{
1124
+ ...tabItemStyle,
1125
+ backgroundColor: activeFilePath === file.path ? "#0b101b" : "rgba(8,12,20,0.4)",
1126
+ borderColor: activeFilePath === file.path ? "var(--border-focus)" : "transparent",
1127
+ color: activeFilePath === file.path ? "var(--foreground)" : "var(--muted)",
1128
+ }}
1129
+ onClick={() => setActiveFilePath(file.path)}
1130
+ >
1131
+ <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
1132
+ {file.name} {file.isModified && <span style={modifiedDotStyle}>●</span>}
1133
+ </span>
1134
+ <span
1135
+ onClick={(e) => {
1136
+ e.stopPropagation();
1137
+ handleCloseFile(file.path);
1138
+ }}
1139
+ style={closeTabStyle}
1140
+ >
1141
+
1142
+ </span>
1143
+ </div>
1144
+ ))}
1145
+ </div>
1146
+
1147
+ {activeFile && !activeFile.path.startsWith("diff:") && (
1148
+ <button
1149
+ onClick={handleSaveFile}
1150
+ disabled={!activeFile.isModified}
1151
+ style={{
1152
+ ...saveButtonStyle,
1153
+ opacity: activeFile.isModified ? 1 : 0.5,
1154
+ cursor: activeFile.isModified ? "pointer" : "not-allowed",
1155
+ }}
1156
+ >
1157
+ 💾 Save (Ctrl+S)
1158
+ </button>
1159
+ )}
1160
+ </div>
1161
+
1162
+ {/* Editor contents */}
1163
+ <div style={editorWorkspaceStyle}>
1164
+ {activeFile ? (
1165
+ activeFile.path.startsWith("diff:") ? (
1166
+ <div style={{ fontFamily: "var(--font-mono)", fontSize: "0.8rem", padding: "1rem", overflow: "auto", height: "100%", backgroundColor: "#0b101b", lineHeight: 1.5 }}>
1167
+ {activeFile.content.split("\n").map((line, i) => {
1168
+ let color = "#e2e8f0";
1169
+ let bg = "transparent";
1170
+ if (line.startsWith("+")) {
1171
+ color = "#4ade80";
1172
+ bg = "rgba(74, 222, 128, 0.08)";
1173
+ } else if (line.startsWith("-")) {
1174
+ color = "#f87171";
1175
+ bg = "rgba(248, 113, 113, 0.08)";
1176
+ } else if (line.startsWith("@@")) {
1177
+ color = "#60a5fa";
1178
+ bg = "rgba(96, 165, 250, 0.05)";
1179
+ }
1180
+ return (
1181
+ <div key={i} style={{ color, backgroundColor: bg, whiteSpace: "pre-wrap", padding: "0.1rem 0.5rem" }}>
1182
+ {line}
1183
+ </div>
1184
+ );
1185
+ })}
1186
+ </div>
1187
+ ) : (
1188
+ <textarea
1189
+ value={activeFile.content}
1190
+ onChange={(e) => handleContentChange(e.target.value)}
1191
+ style={textareaStyle}
1192
+ spellCheck={false}
1193
+ />
1194
+ )
1195
+ ) : (
1196
+ <div style={emptyEditorStateStyle}>
1197
+ <div style={{ fontSize: "2rem", marginBottom: "1rem" }}>💻</div>
1198
+ <h4 style={{ margin: 0, fontWeight: 600 }}>NeuralLoom Workspace Editor</h4>
1199
+ <p style={{ margin: "0.5rem 0 0 0", color: "var(--muted)", fontSize: "0.8rem" }}>
1200
+ Select files from the left explorer panel to view and edit. Use Ctrl+S to save changes.
1201
+ </p>
1202
+ <p style={{ margin: "0.5rem 0 0 0", color: "var(--muted)", fontSize: "0.75rem", fontStyle: "italic" }}>
1203
+ Press <kbd style={{ background: "rgba(255,255,255,0.1)", padding: "2px 4px", borderRadius: "3px" }}>Ctrl+P</kbd> to search files.
1204
+ </p>
1205
+ </div>
1206
+ )}
1207
+ </div>
1208
+ </div>
1209
+
1210
+ {/* Console / PTY Bridge Panel (bottom half) */}
1211
+ <div style={terminalPanelStyle}>
1212
+ {/* Dashboard control headers injected above xterm */}
1213
+ <div style={terminalHeaderStyle}>
1214
+ <span style={{ display: "flex", alignItems: "center", gap: "0.5rem", fontWeight: 600, fontSize: "0.8rem" }}>
1215
+ 🖥️ Session Console Bridge {splitSessionId && `(Split View Active)`}
1216
+ </span>
1217
+
1218
+ <div style={{ display: "flex", gap: "0.5rem", alignItems: "center" }}>
1219
+ {/* Split dropdown controller */}
1220
+ <select
1221
+ onChange={(e) => {
1222
+ const val = e.target.value;
1223
+ setSplitSessionId(val ? val : null);
1224
+ }}
1225
+ value={splitSessionId || ""}
1226
+ style={{
1227
+ backgroundColor: "#080c14",
1228
+ border: "1px solid var(--border)",
1229
+ borderRadius: "4px",
1230
+ padding: "0.2rem 0.5rem",
1231
+ color: "var(--foreground)",
1232
+ fontSize: "0.7rem",
1233
+ outline: "none",
1234
+ cursor: "pointer",
1235
+ }}
1236
+ >
1237
+ <option value="">🖥️ Single Terminal</option>
1238
+ {sessions
1239
+ .filter(s => s.id !== sessionId && s.status === 'running')
1240
+ .map(s => (
1241
+ <option key={s.id} value={s.id}>🖥️|🖥️ Split: {s.name}</option>
1242
+ ))
1243
+ }
1244
+ </select>
1245
+
1246
+ <button
1247
+ onClick={async () => {
1248
+ await fetch("/api/sessions/inject", {
1249
+ method: "POST",
1250
+ headers: { "Content-Type": "application/json" },
1251
+ body: JSON.stringify({ id: sessionId, command: "/ccc" }),
1252
+ });
1253
+ }}
1254
+ style={contextButtonStyle}
1255
+ >
1256
+ 🚀 Mount Context (/ccc)
1257
+ </button>
1258
+ <button
1259
+ onClick={async () => {
1260
+ await fetch("/api/sessions/inject", {
1261
+ method: "POST",
1262
+ headers: { "Content-Type": "application/json" },
1263
+ body: JSON.stringify({ id: sessionId, command: "/ccp" }),
1264
+ });
1265
+ }}
1266
+ style={promptButtonStyle}
1267
+ >
1268
+ ⚡ Trigger Prompt (/ccp)
1269
+ </button>
1270
+ </div>
1271
+ </div>
1272
+
1273
+ {/* Quick command buttons toolbar */}
1274
+ <div style={quickCommandToolbarStyle}>
1275
+ <span style={{ fontSize: "0.7rem", color: "var(--muted)", textTransform: "uppercase", letterSpacing: "0.05em", fontWeight: 700 }}>
1276
+ Quick Commands:
1277
+ </span>
1278
+ <div style={{ display: "flex", gap: "0.35rem", flexWrap: "wrap", alignItems: "center" }}>
1279
+ {isAider ? (
1280
+ <>
1281
+ <button onClick={() => triggerInject("/undo")} style={quickCmdBtnStyle} title="Aider: undo last commit">/undo</button>
1282
+ <button onClick={() => triggerInject("/diff")} style={quickCmdBtnStyle} title="Aider: show git diff">/diff</button>
1283
+ <button onClick={() => triggerInject("/commit")} style={quickCmdBtnStyle} title="Aider: commit edits">/commit</button>
1284
+ <button onClick={() => triggerInject("/test")} style={quickCmdBtnStyle} title="Aider: run tests">/test</button>
1285
+ <button onClick={() => triggerInject("/clear")} style={quickCmdBtnStyle} title="Clear terminal screen">/clear</button>
1286
+ </>
1287
+ ) : (
1288
+ <>
1289
+ <button onClick={() => triggerInject("/compact")} style={quickCmdBtnStyle} title="Claude Code: compact history">/compact</button>
1290
+ <button onClick={() => triggerInject("/history")} style={quickCmdBtnStyle} title="Claude Code: view history">/history</button>
1291
+ <button onClick={() => triggerInject("/reset")} style={quickCmdBtnStyle} title="Claude Code: reset conversation">/reset</button>
1292
+ <button onClick={() => triggerInject("/help")} style={quickCmdBtnStyle} title="Claude Code: show help">/help</button>
1293
+ </>
1294
+ )}
1295
+
1296
+ {/* Custom buttons */}
1297
+ {customCommands.map((cmd, i) => (
1298
+ <button
1299
+ key={i}
1300
+ onClick={() => triggerInject(cmd.command)}
1301
+ style={{ ...quickCmdBtnStyle, borderColor: "rgba(168, 85, 247, 0.4)", color: "#c084fc" }}
1302
+ title={`Inject command: ${cmd.command}`}
1303
+ >
1304
+ {cmd.name}
1305
+ </button>
1306
+ ))}
1307
+ </div>
1308
+ </div>
1309
+
1310
+ <div style={{ ...terminalConsoleWrapperStyle, display: "flex", width: "100%", height: "100%" }}>
1311
+ <div style={{ flex: 1, height: "100%", borderRight: splitSessionId ? "1px solid var(--border)" : "none", overflow: "hidden" }}>
1312
+ <TerminalConsole sessionId={sessionId} height="100%" />
1313
+ </div>
1314
+ {splitSessionId && (
1315
+ <div style={{ flex: 1, height: "100%", overflow: "hidden", display: "flex", flexDirection: "column" }}>
1316
+ {/* Header for split console */}
1317
+ <div style={{ padding: "0.25rem 0.5rem", backgroundColor: "#030406", fontSize: "0.7rem", borderBottom: "1px solid var(--border)", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
1318
+ <span style={{ color: "#a855f7", fontWeight: 600 }}>🔗 Focused PTY Connection: {sessions.find(s => s.id === splitSessionId)?.name}</span>
1319
+ <button
1320
+ onClick={() => setSplitSessionId(null)}
1321
+ style={{ background: "none", border: "none", color: "var(--danger)", cursor: "pointer", fontSize: "0.7rem" }}
1322
+ >
1323
+ Close Split ✖
1324
+ </button>
1325
+ </div>
1326
+ <div style={{ flex: 1, overflow: "hidden" }}>
1327
+ <TerminalConsole sessionId={splitSessionId} height="100%" />
1328
+ </div>
1329
+ </div>
1330
+ )}
1331
+ </div>
1332
+ </div>
1333
+ </div>
1334
+
1335
+ {/* Overlay Modal for File Actions */}
1336
+ {dialog.type && (
1337
+ <div style={modalOverlayStyle}>
1338
+ <div style={modalContentStyle}>
1339
+ <h3 style={{ margin: "0 0 1rem 0", fontSize: "1rem", fontWeight: 600 }}>
1340
+ {dialog.type === "create-file" && "Create New File"}
1341
+ {dialog.type === "create-folder" && "Create New Folder"}
1342
+ {dialog.type === "rename" && "Rename Item"}
1343
+ {dialog.type === "delete" && "Confirm Deletion"}
1344
+ </h3>
1345
+
1346
+ {dialog.error && (
1347
+ <div style={{ color: "var(--danger)", fontSize: "0.8rem", marginBottom: "1rem" }}>
1348
+ ⚠️ {dialog.error}
1349
+ </div>
1350
+ )}
1351
+
1352
+ {dialog.type !== "delete" ? (
1353
+ <form onSubmit={handleDialogSubmit}>
1354
+ <div style={{ marginBottom: "0.5rem", fontSize: "0.75rem", color: "var(--muted)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={dialog.targetPath || ""}>
1355
+ Target: {dialog.targetPath}
1356
+ </div>
1357
+ <input
1358
+ type="text"
1359
+ value={dialog.inputValue}
1360
+ onChange={(e) => setDialog({ ...dialog, inputValue: e.target.value })}
1361
+ style={modalInputStyle}
1362
+ autoFocus
1363
+ placeholder={
1364
+ dialog.type === "rename" ? "Enter new name" : "Enter name"
1365
+ }
1366
+ />
1367
+ <div style={modalActionsStyle}>
1368
+ <button
1369
+ type="button"
1370
+ onClick={() => setDialog({ type: null, targetPath: null, inputValue: "", error: null })}
1371
+ style={modalCancelBtnStyle}
1372
+ >
1373
+ Cancel
1374
+ </button>
1375
+ <button type="submit" style={modalConfirmBtnStyle}>
1376
+ Submit
1377
+ </button>
1378
+ </div>
1379
+ </form>
1380
+ ) : (
1381
+ <div>
1382
+ <div style={{ fontSize: "0.85rem", marginBottom: "1.5rem", color: "var(--muted)", overflowWrap: "break-word" }}>
1383
+ Are you sure you want to delete this item? This action is permanent.
1384
+ <div style={{ marginTop: "0.5rem", color: "#e2e8f0", fontFamily: "var(--font-mono)", fontSize: "0.75rem", background: "rgba(255,255,255,0.05)", padding: "0.5rem", borderRadius: "4px" }}>
1385
+ {dialog.targetPath}
1386
+ </div>
1387
+ </div>
1388
+ <div style={modalActionsStyle}>
1389
+ <button
1390
+ type="button"
1391
+ onClick={() => setDialog({ type: null, targetPath: null, inputValue: "", error: null })}
1392
+ style={modalCancelBtnStyle}
1393
+ >
1394
+ Cancel
1395
+ </button>
1396
+ <button
1397
+ onClick={() => handleDialogSubmit()}
1398
+ style={{ ...modalConfirmBtnStyle, backgroundColor: "var(--danger)" }}
1399
+ >
1400
+ Delete Permanently
1401
+ </button>
1402
+ </div>
1403
+ </div>
1404
+ )}
1405
+ </div>
1406
+ </div>
1407
+ )}
1408
+
1409
+ {/* Fuzzy File Finder Modal */}
1410
+ {searchOpen && (
1411
+ <div style={searchOverlayStyle} onClick={() => setSearchOpen(false)}>
1412
+ <div style={searchContentStyle} onClick={(e) => e.stopPropagation()}>
1413
+ <input
1414
+ type="text"
1415
+ value={searchQuery}
1416
+ onChange={(e) => {
1417
+ setSearchQuery(e.target.value);
1418
+ setSearchSelectedIndex(0);
1419
+ }}
1420
+ onKeyDown={handleSearchKeyDown}
1421
+ placeholder="Search files by name or path..."
1422
+ style={searchInputStyle}
1423
+ autoFocus
1424
+ />
1425
+ <div style={searchResultsListStyle}>
1426
+ {filteredFiles.length > 0 ? (
1427
+ filteredFiles.map((file, idx) => (
1428
+ <div
1429
+ key={file.path}
1430
+ onClick={() => {
1431
+ handleOpenFile(file.path);
1432
+ setSearchOpen(false);
1433
+ }}
1434
+ style={{
1435
+ ...searchResultItemStyle,
1436
+ backgroundColor: idx === searchSelectedIndex ? "rgba(168, 85, 247, 0.15)" : "transparent",
1437
+ borderLeft: idx === searchSelectedIndex ? "3px solid var(--primary)" : "3px solid transparent",
1438
+ }}
1439
+ >
1440
+ <span style={{ fontSize: "0.85rem", fontWeight: 600 }}>{file.name}</span>
1441
+ <span style={{ fontSize: "0.7rem", color: "var(--muted)", marginLeft: "0.5rem", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
1442
+ {file.relativePath}
1443
+ </span>
1444
+ </div>
1445
+ ))
1446
+ ) : (
1447
+ <div style={{ padding: "0.75rem 1rem", color: "var(--muted)", fontSize: "0.8rem", textAlign: "center" }}>
1448
+ No matching files found
1449
+ </div>
1450
+ )}
1451
+ </div>
1452
+ <div style={{ padding: "0.5rem 1rem", borderTop: "1px solid var(--border)", fontSize: "0.65rem", color: "var(--muted)", textAlign: "right" }}>
1453
+ Use ↑↓ to navigate, Enter to select, Esc to close
1454
+ </div>
1455
+ </div>
1456
+ </div>
1457
+ )}
1458
+ </div>
1459
+ );
1460
+ }
1461
+
1462
+ // Styling definitions to create premium dark-mode HSL style
1463
+ const ideContainerStyle: React.CSSProperties = {
1464
+ display: "flex",
1465
+ width: "100%",
1466
+ height: "calc(100vh - 120px)",
1467
+ backgroundColor: "#05070a",
1468
+ border: "1px solid var(--border)",
1469
+ borderRadius: "12px",
1470
+ overflow: "hidden",
1471
+ boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.6)",
1472
+ };
1473
+
1474
+ const activityBarStyle: React.CSSProperties = {
1475
+ width: "50px",
1476
+ backgroundColor: "#030406",
1477
+ borderRight: "1px solid var(--border)",
1478
+ display: "flex",
1479
+ flexDirection: "column",
1480
+ justifyContent: "space-between",
1481
+ alignItems: "center",
1482
+ padding: "0.75rem 0",
1483
+ };
1484
+
1485
+ const topIconsStyle: React.CSSProperties = {
1486
+ display: "flex",
1487
+ flexDirection: "column",
1488
+ width: "100%",
1489
+ gap: "0.5rem",
1490
+ };
1491
+
1492
+ const sidebarIconStyle: React.CSSProperties = {
1493
+ width: "100%",
1494
+ height: "45px",
1495
+ display: "flex",
1496
+ alignItems: "center",
1497
+ justifyContent: "center",
1498
+ cursor: "pointer",
1499
+ fontSize: "1.25rem",
1500
+ transition: "all 0.15s ease",
1501
+ };
1502
+
1503
+ const sidebarPanelStyle: React.CSSProperties = {
1504
+ width: "260px",
1505
+ backgroundColor: "#080c14",
1506
+ borderRight: "1px solid var(--border)",
1507
+ display: "flex",
1508
+ flexDirection: "column",
1509
+ overflow: "hidden",
1510
+ };
1511
+
1512
+ const sidebarHeaderStyle: React.CSSProperties = {
1513
+ padding: "0.75rem 1rem",
1514
+ borderBottom: "1px solid var(--border)",
1515
+ display: "flex",
1516
+ alignItems: "center",
1517
+ justifyContent: "space-between",
1518
+ background: "rgba(255,255,255,0.02)",
1519
+ };
1520
+
1521
+
1522
+ const sidebarContentStyle: React.CSSProperties = {
1523
+ flex: 1,
1524
+ overflowY: "auto",
1525
+ padding: "0.75rem 0",
1526
+ };
1527
+
1528
+ const treeSectionHeaderStyle: React.CSSProperties = {
1529
+ padding: "0.25rem 1rem",
1530
+ fontSize: "0.75rem",
1531
+ fontWeight: 700,
1532
+ color: "var(--muted)",
1533
+ display: "flex",
1534
+ alignItems: "center",
1535
+ justifyContent: "space-between",
1536
+ textTransform: "uppercase",
1537
+ letterSpacing: "0.05em",
1538
+ };
1539
+
1540
+ const pathBadgeStyle: React.CSSProperties = {
1541
+ fontSize: "0.6rem",
1542
+ backgroundColor: "rgba(59, 130, 246, 0.1)",
1543
+ color: "#3b82f6",
1544
+ padding: "0.1rem 0.3rem",
1545
+ borderRadius: "3px",
1546
+ textTransform: "none",
1547
+ letterSpacing: "normal",
1548
+ };
1549
+
1550
+ const itemHeaderStyle: React.CSSProperties = {
1551
+ fontSize: "0.75rem",
1552
+ fontWeight: 600,
1553
+ color: "var(--foreground)",
1554
+ };
1555
+
1556
+ const mainContentAreaStyle: React.CSSProperties = {
1557
+ flex: 1,
1558
+ display: "flex",
1559
+ flexDirection: "column",
1560
+ overflow: "hidden",
1561
+ backgroundColor: "#0b101b",
1562
+ };
1563
+
1564
+ const editorPanelStyle: React.CSSProperties = {
1565
+ flex: 1,
1566
+ display: "flex",
1567
+ flexDirection: "column",
1568
+ borderBottom: "1px solid var(--border)",
1569
+ overflow: "hidden",
1570
+ };
1571
+
1572
+ const tabHeaderStyle: React.CSSProperties = {
1573
+ height: "35px",
1574
+ backgroundColor: "#05070a",
1575
+ borderBottom: "1px solid var(--border)",
1576
+ display: "flex",
1577
+ alignItems: "center",
1578
+ justifyContent: "space-between",
1579
+ paddingRight: "0.75rem",
1580
+ };
1581
+
1582
+ const tabsWrapperStyle: React.CSSProperties = {
1583
+ display: "flex",
1584
+ height: "100%",
1585
+ overflowX: "auto",
1586
+ };
1587
+
1588
+ const tabItemStyle: React.CSSProperties = {
1589
+ padding: "0 1rem",
1590
+ height: "100%",
1591
+ display: "flex",
1592
+ alignItems: "center",
1593
+ gap: "0.75rem",
1594
+ fontSize: "0.75rem",
1595
+ borderRight: "1px solid var(--border)",
1596
+ borderBottom: "2px solid transparent",
1597
+ cursor: "pointer",
1598
+ transition: "all 0.15s ease",
1599
+ maxWidth: "150px",
1600
+ };
1601
+
1602
+ const closeTabStyle: React.CSSProperties = {
1603
+ fontSize: "0.65rem",
1604
+ color: "var(--muted)",
1605
+ cursor: "pointer",
1606
+ padding: "2px",
1607
+ };
1608
+
1609
+ const modifiedDotStyle: React.CSSProperties = {
1610
+ color: "var(--primary)",
1611
+ fontSize: "0.65rem",
1612
+ marginLeft: "2px",
1613
+ };
1614
+
1615
+ const saveButtonStyle: React.CSSProperties = {
1616
+ padding: "0.25rem 0.5rem",
1617
+ borderRadius: "4px",
1618
+ border: "1px solid var(--border-focus)",
1619
+ backgroundColor: "rgba(16, 185, 129, 0.15)",
1620
+ color: "var(--success)",
1621
+ fontSize: "0.7rem",
1622
+ fontWeight: 600,
1623
+ };
1624
+
1625
+ const editorWorkspaceStyle: React.CSSProperties = {
1626
+ flex: 1,
1627
+ position: "relative",
1628
+ overflow: "hidden",
1629
+ };
1630
+
1631
+ const textareaStyle: React.CSSProperties = {
1632
+ width: "100%",
1633
+ height: "100%",
1634
+ backgroundColor: "#0b101b",
1635
+ border: "none",
1636
+ outline: "none",
1637
+ color: "#e2e8f0",
1638
+ fontFamily: "var(--font-mono), monospace",
1639
+ fontSize: "0.85rem",
1640
+ padding: "1rem",
1641
+ resize: "none",
1642
+ lineHeight: 1.5,
1643
+ };
1644
+
1645
+ const emptyEditorStateStyle: React.CSSProperties = {
1646
+ display: "flex",
1647
+ flexDirection: "column",
1648
+ alignItems: "center",
1649
+ justifyContent: "center",
1650
+ height: "100%",
1651
+ color: "var(--muted)",
1652
+ textAlign: "center",
1653
+ padding: "2rem",
1654
+ };
1655
+
1656
+ const terminalPanelStyle: React.CSSProperties = {
1657
+ height: "360px",
1658
+ backgroundColor: "#05070a",
1659
+ display: "flex",
1660
+ flexDirection: "column",
1661
+ overflow: "hidden",
1662
+ };
1663
+
1664
+ const terminalHeaderStyle: React.CSSProperties = {
1665
+ padding: "0.5rem 1rem",
1666
+ borderBottom: "1px solid var(--border)",
1667
+ display: "flex",
1668
+ alignItems: "center",
1669
+ justifyContent: "space-between",
1670
+ backgroundColor: "rgba(255,255,255,0.01)",
1671
+ };
1672
+
1673
+ const contextButtonStyle: React.CSSProperties = {
1674
+ padding: "0.3rem 0.6rem",
1675
+ borderRadius: "4px",
1676
+ border: "1px solid var(--primary)",
1677
+ backgroundColor: "transparent",
1678
+ color: "var(--primary)",
1679
+ fontSize: "0.7rem",
1680
+ fontWeight: 600,
1681
+ cursor: "pointer",
1682
+ };
1683
+
1684
+ const promptButtonStyle: React.CSSProperties = {
1685
+ padding: "0.3rem 0.6rem",
1686
+ borderRadius: "4px",
1687
+ border: "1px solid var(--success)",
1688
+ backgroundColor: "transparent",
1689
+ color: "var(--success)",
1690
+ fontSize: "0.7rem",
1691
+ fontWeight: 600,
1692
+ cursor: "pointer",
1693
+ };
1694
+
1695
+ const terminalConsoleWrapperStyle: React.CSSProperties = {
1696
+ flex: 1,
1697
+ overflow: "hidden",
1698
+ };
1699
+
1700
+ interface FileItemNodeProps {
1701
+ node: FileNode;
1702
+ onSelect: (p: string) => void;
1703
+ onCreate: (parentPath: string, type: "file" | "directory") => void;
1704
+ onRename: (path: string) => void;
1705
+ onDelete: (path: string) => void;
1706
+ isReadOnly?: boolean;
1707
+ }
1708
+
1709
+ // Define outside IdeLayout to prevent component re-creation and auto-collapsing
1710
+ function FileItemNode({
1711
+ node,
1712
+ onSelect,
1713
+ onCreate,
1714
+ onRename,
1715
+ onDelete,
1716
+ isReadOnly = false
1717
+ }: FileItemNodeProps) {
1718
+ const [expanded, setExpanded] = useState(false);
1719
+ const [hovered, setHovered] = useState(false);
1720
+
1721
+ const isFolder = node.type === "directory";
1722
+
1723
+ const itemStyle = {
1724
+ padding: "0.25rem 0.5rem",
1725
+ display: "flex",
1726
+ alignItems: "center",
1727
+ justifyContent: "space-between",
1728
+ fontSize: "0.8rem",
1729
+ fontFamily: "var(--font-mono), monospace",
1730
+ cursor: "pointer",
1731
+ color: "var(--foreground)",
1732
+ borderRadius: "4px",
1733
+ transition: "background 0.1s ease",
1734
+ userSelect: "none" as const,
1735
+ backgroundColor: hovered ? "rgba(255, 255, 255, 0.05)" : "transparent",
1736
+ minWidth: 0,
1737
+ width: "100%",
1738
+ };
1739
+
1740
+ const textContainerStyle = {
1741
+ display: "flex",
1742
+ alignItems: "center",
1743
+ gap: "0.375rem",
1744
+ flex: 1,
1745
+ minWidth: 0,
1746
+ overflow: "hidden",
1747
+ textOverflow: "ellipsis",
1748
+ whiteSpace: "nowrap" as const,
1749
+ };
1750
+
1751
+ const actionsStyle = {
1752
+ display: hovered ? "flex" : "none",
1753
+ alignItems: "center",
1754
+ gap: "0.25rem",
1755
+ paddingLeft: "0.5rem",
1756
+ flexShrink: 0,
1757
+ };
1758
+
1759
+ const actionBtnStyle = {
1760
+ background: "none",
1761
+ border: "none",
1762
+ padding: "2px 4px",
1763
+ cursor: "pointer",
1764
+ fontSize: "0.75rem",
1765
+ color: "var(--muted)",
1766
+ borderRadius: "3px",
1767
+ transition: "all 0.1s ease",
1768
+ };
1769
+
1770
+ const handleAction = (e: React.MouseEvent, act: () => void) => {
1771
+ e.stopPropagation();
1772
+ act();
1773
+ };
1774
+
1775
+ if (!isFolder) {
1776
+ return (
1777
+ <div
1778
+ onClick={() => onSelect(node.path)}
1779
+ style={itemStyle}
1780
+ onMouseEnter={() => setHovered(true)}
1781
+ onMouseLeave={() => setHovered(false)}
1782
+ >
1783
+ <div style={textContainerStyle}>
1784
+ <span style={{ color: "#3b82f6" }}>📄</span>
1785
+ <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{node.name}</span>
1786
+ </div>
1787
+ {!isReadOnly && (
1788
+ <div style={actionsStyle}>
1789
+ <button
1790
+ onClick={(e) => handleAction(e, () => onRename(node.path))}
1791
+ style={actionBtnStyle}
1792
+ title="Rename..."
1793
+ onMouseEnter={(e) => e.currentTarget.style.color = "var(--primary)"}
1794
+ onMouseLeave={(e) => e.currentTarget.style.color = "var(--muted)"}
1795
+ >
1796
+ ✏️
1797
+ </button>
1798
+ <button
1799
+ onClick={(e) => handleAction(e, () => onDelete(node.path))}
1800
+ style={actionBtnStyle}
1801
+ title="Delete File"
1802
+ onMouseEnter={(e) => e.currentTarget.style.color = "var(--danger)"}
1803
+ onMouseLeave={(e) => e.currentTarget.style.color = "var(--muted)"}
1804
+ >
1805
+ 🗑️
1806
+ </button>
1807
+ </div>
1808
+ )}
1809
+ </div>
1810
+ );
1811
+ }
1812
+
1813
+ return (
1814
+ <div>
1815
+ <div
1816
+ onClick={() => setExpanded(!expanded)}
1817
+ style={itemStyle}
1818
+ onMouseEnter={() => setHovered(true)}
1819
+ onMouseLeave={() => setHovered(false)}
1820
+ >
1821
+ <div style={textContainerStyle}>
1822
+ <span style={{ color: "#eab308" }}>{expanded ? "📂" : "📁"}</span>
1823
+ <span style={{ fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{node.name}</span>
1824
+ </div>
1825
+ {!isReadOnly && (
1826
+ <div style={actionsStyle}>
1827
+ <button
1828
+ onClick={(e) => handleAction(e, () => onCreate(node.path, "file"))}
1829
+ style={actionBtnStyle}
1830
+ title="New File inside..."
1831
+ onMouseEnter={(e) => e.currentTarget.style.color = "var(--success)"}
1832
+ onMouseLeave={(e) => e.currentTarget.style.color = "var(--muted)"}
1833
+ >
1834
+ 📄+
1835
+ </button>
1836
+ <button
1837
+ onClick={(e) => handleAction(e, () => onCreate(node.path, "directory"))}
1838
+ style={actionBtnStyle}
1839
+ title="New Folder inside..."
1840
+ onMouseEnter={(e) => e.currentTarget.style.color = "var(--success)"}
1841
+ onMouseLeave={(e) => e.currentTarget.style.color = "var(--muted)"}
1842
+ >
1843
+ 📁+
1844
+ </button>
1845
+ <button
1846
+ onClick={(e) => handleAction(e, () => onRename(node.path))}
1847
+ style={actionBtnStyle}
1848
+ title="Rename..."
1849
+ onMouseEnter={(e) => e.currentTarget.style.color = "var(--primary)"}
1850
+ onMouseLeave={(e) => e.currentTarget.style.color = "var(--muted)"}
1851
+ >
1852
+ ✏️
1853
+ </button>
1854
+ <button
1855
+ onClick={(e) => handleAction(e, () => onDelete(node.path))}
1856
+ style={actionBtnStyle}
1857
+ title="Delete Folder"
1858
+ onMouseEnter={(e) => e.currentTarget.style.color = "var(--danger)"}
1859
+ onMouseLeave={(e) => e.currentTarget.style.color = "var(--muted)"}
1860
+ >
1861
+ 🗑️
1862
+ </button>
1863
+ </div>
1864
+ )}
1865
+ </div>
1866
+ {expanded && node.children && (
1867
+ <div style={{ paddingLeft: "10px", borderLeft: "1px solid rgba(255, 255, 255, 0.05)", marginLeft: "8px" }}>
1868
+ {node.children.map((child) => (
1869
+ <FileItemNode
1870
+ key={child.path}
1871
+ node={child}
1872
+ onSelect={onSelect}
1873
+ onCreate={onCreate}
1874
+ onRename={onRename}
1875
+ onDelete={onDelete}
1876
+ isReadOnly={isReadOnly}
1877
+ />
1878
+ ))}
1879
+ </div>
1880
+ )}
1881
+ </div>
1882
+ );
1883
+ }
1884
+
1885
+ // Dialog operation styles
1886
+ const modalOverlayStyle: React.CSSProperties = {
1887
+ position: "fixed",
1888
+ top: 0,
1889
+ left: 0,
1890
+ right: 0,
1891
+ bottom: 0,
1892
+ backgroundColor: "rgba(0,0,0,0.7)",
1893
+ display: "flex",
1894
+ alignItems: "center",
1895
+ justifyContent: "center",
1896
+ zIndex: 1000,
1897
+ backdropFilter: "blur(4px)",
1898
+ };
1899
+
1900
+ const modalContentStyle: React.CSSProperties = {
1901
+ width: "420px",
1902
+ backgroundColor: "#080c14",
1903
+ border: "1px solid var(--border)",
1904
+ borderRadius: "12px",
1905
+ padding: "1.5rem",
1906
+ boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.8)",
1907
+ };
1908
+
1909
+ const modalInputStyle: React.CSSProperties = {
1910
+ width: "100%",
1911
+ backgroundColor: "#05070a",
1912
+ border: "1px solid var(--border)",
1913
+ borderRadius: "6px",
1914
+ padding: "0.5rem 0.75rem",
1915
+ color: "#e2e8f0",
1916
+ fontSize: "0.85rem",
1917
+ outline: "none",
1918
+ marginBottom: "1.5rem",
1919
+ fontFamily: "var(--font-mono), monospace",
1920
+ };
1921
+
1922
+ const modalActionsStyle: React.CSSProperties = {
1923
+ display: "flex",
1924
+ justifyContent: "flex-end",
1925
+ gap: "0.75rem",
1926
+ };
1927
+
1928
+ const modalCancelBtnStyle: React.CSSProperties = {
1929
+ backgroundColor: "transparent",
1930
+ border: "1px solid var(--border)",
1931
+ color: "var(--foreground)",
1932
+ padding: "0.5rem 1rem",
1933
+ borderRadius: "6px",
1934
+ fontSize: "0.8rem",
1935
+ fontWeight: 600,
1936
+ cursor: "pointer",
1937
+ };
1938
+
1939
+ const modalConfirmBtnStyle: React.CSSProperties = {
1940
+ backgroundColor: "var(--primary)",
1941
+ border: "none",
1942
+ color: "#ffffff",
1943
+ padding: "0.5rem 1rem",
1944
+ borderRadius: "6px",
1945
+ fontSize: "0.8rem",
1946
+ fontWeight: 600,
1947
+ cursor: "pointer",
1948
+ };
1949
+
1950
+ const iconButtonStyle: React.CSSProperties = {
1951
+ background: "none",
1952
+ border: "none",
1953
+ color: "var(--muted)",
1954
+ cursor: "pointer",
1955
+ fontSize: "0.8rem",
1956
+ padding: "4px",
1957
+ borderRadius: "3px",
1958
+ transition: "all 0.1s ease",
1959
+ };
1960
+
1961
+ // Fuzzy search styles
1962
+ const searchOverlayStyle: React.CSSProperties = {
1963
+ position: "fixed",
1964
+ top: 0,
1965
+ left: 0,
1966
+ right: 0,
1967
+ bottom: 0,
1968
+ backgroundColor: "rgba(0,0,0,0.5)",
1969
+ display: "flex",
1970
+ alignItems: "flex-start",
1971
+ justifyContent: "center",
1972
+ zIndex: 1100,
1973
+ paddingTop: "5vh",
1974
+ backdropFilter: "blur(2px)",
1975
+ };
1976
+
1977
+ const searchContentStyle: React.CSSProperties = {
1978
+ width: "600px",
1979
+ backgroundColor: "#080c14",
1980
+ border: "1px solid var(--border)",
1981
+ borderRadius: "8px",
1982
+ boxShadow: "0 10px 25px -5px rgba(0, 0, 0, 0.8)",
1983
+ display: "flex",
1984
+ flexDirection: "column",
1985
+ overflow: "hidden",
1986
+ };
1987
+
1988
+ const searchInputStyle: React.CSSProperties = {
1989
+ width: "100%",
1990
+ backgroundColor: "#05070a",
1991
+ border: "none",
1992
+ borderBottom: "1px solid var(--border)",
1993
+ padding: "0.75rem 1rem",
1994
+ color: "#e2e8f0",
1995
+ fontSize: "0.9rem",
1996
+ outline: "none",
1997
+ fontFamily: "var(--font-mono), monospace",
1998
+ };
1999
+
2000
+ const searchResultsListStyle: React.CSSProperties = {
2001
+ maxHeight: "300px",
2002
+ overflowY: "auto",
2003
+ padding: "0.25rem 0",
2004
+ };
2005
+
2006
+ const searchResultItemStyle: React.CSSProperties = {
2007
+ display: "flex",
2008
+ alignItems: "center",
2009
+ padding: "0.5rem 1rem",
2010
+ cursor: "pointer",
2011
+ transition: "all 0.1s ease",
2012
+ justifyContent: "space-between",
2013
+ };
2014
+
2015
+ // Analytics Card & Resource Monitor styles
2016
+ const analyticsCardStyle: React.CSSProperties = {
2017
+ backgroundColor: "rgba(255,255,255,0.02)",
2018
+ border: "1px solid var(--border)",
2019
+ borderRadius: "6px",
2020
+ padding: "0.5rem 0.75rem",
2021
+ display: "flex",
2022
+ flexDirection: "column",
2023
+ gap: "0.25rem",
2024
+ };
2025
+
2026
+ const analyticsCardLabelStyle: React.CSSProperties = {
2027
+ fontSize: "0.6rem",
2028
+ color: "var(--muted)",
2029
+ textTransform: "uppercase",
2030
+ };
2031
+
2032
+ const analyticsCardValueStyle: React.CSSProperties = {
2033
+ fontSize: "1.1rem",
2034
+ fontWeight: 700,
2035
+ fontFamily: "var(--font-mono)",
2036
+ };
2037
+
2038
+ const gaugeOuterStyle: React.CSSProperties = {
2039
+ width: "100%",
2040
+ height: "6px",
2041
+ backgroundColor: "rgba(255,255,255,0.05)",
2042
+ borderRadius: "3px",
2043
+ overflow: "hidden",
2044
+ };
2045
+
2046
+ const gaugeInnerStyle: React.CSSProperties = {
2047
+ height: "100%",
2048
+ borderRadius: "3px",
2049
+ transition: "width 0.5s ease-out",
2050
+ };
2051
+
2052
+ const quickCommandToolbarStyle: React.CSSProperties = {
2053
+ padding: "0.35rem 1rem",
2054
+ borderBottom: "1px solid var(--border)",
2055
+ backgroundColor: "#030406",
2056
+ display: "flex",
2057
+ alignItems: "center",
2058
+ gap: "0.75rem",
2059
+ flexWrap: "nowrap",
2060
+ overflowX: "auto"
2061
+ };
2062
+
2063
+ const quickCmdBtnStyle: React.CSSProperties = {
2064
+ padding: "0.15rem 0.45rem",
2065
+ borderRadius: "4px",
2066
+ border: "1px solid var(--border)",
2067
+ backgroundColor: "rgba(255,255,255,0.02)",
2068
+ color: "var(--muted)",
2069
+ fontSize: "0.7rem",
2070
+ fontFamily: "var(--font-mono)",
2071
+ fontWeight: 600,
2072
+ cursor: "pointer",
2073
+ transition: "all 0.1s ease",
2074
+ };
2075
+
2076
+ const sessionTabBarStyle: React.CSSProperties = {
2077
+ backgroundColor: "#05070a",
2078
+ borderBottom: "1px solid var(--border)",
2079
+ padding: "0.25rem 0.75rem 0 0.75rem",
2080
+ display: "flex",
2081
+ alignItems: "center",
2082
+ justifyContent: "space-between",
2083
+ };
2084
+
2085
+ const sessionTabItemStyle: React.CSSProperties = {
2086
+ padding: "0.35rem 0.75rem",
2087
+ borderRadius: "6px 6px 0 0",
2088
+ border: "1px solid var(--border)",
2089
+ borderBottom: "none",
2090
+ display: "flex",
2091
+ alignItems: "center",
2092
+ gap: "0.5rem",
2093
+ cursor: "pointer",
2094
+ transition: "all 0.15s ease",
2095
+ };