groove-dev 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. package/docs/FILE-EDITOR-PLAN.md +253 -0
  2. package/node_modules/@codemirror/autocomplete/.github/workflows/dispatch.yml +16 -0
  3. package/node_modules/@codemirror/autocomplete/CHANGELOG.md +639 -0
  4. package/node_modules/@codemirror/autocomplete/LICENSE +21 -0
  5. package/node_modules/@codemirror/autocomplete/README.md +43 -0
  6. package/node_modules/@codemirror/autocomplete/dist/index.cjs +2151 -0
  7. package/node_modules/@codemirror/autocomplete/dist/index.d.cts +648 -0
  8. package/node_modules/@codemirror/autocomplete/dist/index.d.ts +648 -0
  9. package/node_modules/@codemirror/autocomplete/dist/index.js +2120 -0
  10. package/node_modules/@codemirror/autocomplete/package.json +41 -0
  11. package/node_modules/@codemirror/commands/.github/workflows/dispatch.yml +16 -0
  12. package/node_modules/@codemirror/commands/CHANGELOG.md +386 -0
  13. package/node_modules/@codemirror/commands/LICENSE +21 -0
  14. package/node_modules/@codemirror/commands/README.md +35 -0
  15. package/node_modules/@codemirror/commands/dist/index.cjs +1909 -0
  16. package/node_modules/@codemirror/commands/dist/index.d.cts +650 -0
  17. package/node_modules/@codemirror/commands/dist/index.d.ts +650 -0
  18. package/node_modules/@codemirror/commands/dist/index.js +1795 -0
  19. package/node_modules/@codemirror/commands/package.json +42 -0
  20. package/node_modules/@codemirror/lang-css/.github/workflows/dispatch.yml +16 -0
  21. package/node_modules/@codemirror/lang-css/CHANGELOG.md +106 -0
  22. package/node_modules/@codemirror/lang-css/LICENSE +21 -0
  23. package/node_modules/@codemirror/lang-css/README.md +50 -0
  24. package/node_modules/@codemirror/lang-css/dist/index.cjs +269 -0
  25. package/node_modules/@codemirror/lang-css/dist/index.d.cts +28 -0
  26. package/node_modules/@codemirror/lang-css/dist/index.d.ts +28 -0
  27. package/node_modules/@codemirror/lang-css/dist/index.js +264 -0
  28. package/node_modules/@codemirror/lang-css/package.json +42 -0
  29. package/node_modules/@codemirror/lang-html/.github/workflows/dispatch.yml +16 -0
  30. package/node_modules/@codemirror/lang-html/CHANGELOG.md +210 -0
  31. package/node_modules/@codemirror/lang-html/LICENSE +21 -0
  32. package/node_modules/@codemirror/lang-html/README.md +147 -0
  33. package/node_modules/@codemirror/lang-html/dist/index.cjs +667 -0
  34. package/node_modules/@codemirror/lang-html/dist/index.d.cts +115 -0
  35. package/node_modules/@codemirror/lang-html/dist/index.d.ts +115 -0
  36. package/node_modules/@codemirror/lang-html/dist/index.js +661 -0
  37. package/node_modules/@codemirror/lang-html/package.json +46 -0
  38. package/node_modules/@codemirror/lang-javascript/.github/workflows/dispatch.yml +16 -0
  39. package/node_modules/@codemirror/lang-javascript/CHANGELOG.md +206 -0
  40. package/node_modules/@codemirror/lang-javascript/LICENSE +21 -0
  41. package/node_modules/@codemirror/lang-javascript/README.md +125 -0
  42. package/node_modules/@codemirror/lang-javascript/dist/index.cjs +526 -0
  43. package/node_modules/@codemirror/lang-javascript/dist/index.d.cts +93 -0
  44. package/node_modules/@codemirror/lang-javascript/dist/index.d.ts +93 -0
  45. package/node_modules/@codemirror/lang-javascript/dist/index.js +513 -0
  46. package/node_modules/@codemirror/lang-javascript/package.json +45 -0
  47. package/node_modules/@codemirror/lang-json/.github/workflows/dispatch.yml +16 -0
  48. package/node_modules/@codemirror/lang-json/CHANGELOG.md +59 -0
  49. package/node_modules/@codemirror/lang-json/LICENSE +21 -0
  50. package/node_modules/@codemirror/lang-json/README.md +56 -0
  51. package/node_modules/@codemirror/lang-json/dist/index.cjs +68 -0
  52. package/node_modules/@codemirror/lang-json/dist/index.d.cts +22 -0
  53. package/node_modules/@codemirror/lang-json/dist/index.d.ts +22 -0
  54. package/node_modules/@codemirror/lang-json/dist/index.js +64 -0
  55. package/node_modules/@codemirror/lang-json/package.json +39 -0
  56. package/node_modules/@codemirror/lang-markdown/.github/workflows/dispatch.yml +16 -0
  57. package/node_modules/@codemirror/lang-markdown/CHANGELOG.md +246 -0
  58. package/node_modules/@codemirror/lang-markdown/LICENSE +21 -0
  59. package/node_modules/@codemirror/lang-markdown/README.md +162 -0
  60. package/node_modules/@codemirror/lang-markdown/dist/index.cjs +501 -0
  61. package/node_modules/@codemirror/lang-markdown/dist/index.d.cts +124 -0
  62. package/node_modules/@codemirror/lang-markdown/dist/index.d.ts +124 -0
  63. package/node_modules/@codemirror/lang-markdown/dist/index.js +492 -0
  64. package/node_modules/@codemirror/lang-markdown/package.json +44 -0
  65. package/node_modules/@codemirror/lang-python/.github/workflows/dispatch.yml +16 -0
  66. package/node_modules/@codemirror/lang-python/CHANGELOG.md +174 -0
  67. package/node_modules/@codemirror/lang-python/LICENSE +21 -0
  68. package/node_modules/@codemirror/lang-python/README.md +61 -0
  69. package/node_modules/@codemirror/lang-python/dist/index.cjs +313 -0
  70. package/node_modules/@codemirror/lang-python/dist/index.d.cts +26 -0
  71. package/node_modules/@codemirror/lang-python/dist/index.d.ts +26 -0
  72. package/node_modules/@codemirror/lang-python/dist/index.js +308 -0
  73. package/node_modules/@codemirror/lang-python/package.json +42 -0
  74. package/node_modules/@codemirror/language/.github/workflows/dispatch.yml +16 -0
  75. package/node_modules/@codemirror/language/CHANGELOG.md +412 -0
  76. package/node_modules/@codemirror/language/LICENSE +21 -0
  77. package/node_modules/@codemirror/language/README.md +66 -0
  78. package/node_modules/@codemirror/language/dist/index.cjs +2742 -0
  79. package/node_modules/@codemirror/language/dist/index.d.cts +1220 -0
  80. package/node_modules/@codemirror/language/dist/index.d.ts +1220 -0
  81. package/node_modules/@codemirror/language/dist/index.js +2687 -0
  82. package/node_modules/@codemirror/language/package.json +44 -0
  83. package/node_modules/@codemirror/lint/.github/workflows/dispatch.yml +16 -0
  84. package/node_modules/@codemirror/lint/CHANGELOG.md +318 -0
  85. package/node_modules/@codemirror/lint/LICENSE +21 -0
  86. package/node_modules/@codemirror/lint/README.md +18 -0
  87. package/node_modules/@codemirror/lint/dist/index.cjs +958 -0
  88. package/node_modules/@codemirror/lint/dist/index.d.cts +195 -0
  89. package/node_modules/@codemirror/lint/dist/index.d.ts +195 -0
  90. package/node_modules/@codemirror/lint/dist/index.js +945 -0
  91. package/node_modules/@codemirror/lint/package.json +40 -0
  92. package/node_modules/@codemirror/search/.github/workflows/dispatch.yml +16 -0
  93. package/node_modules/@codemirror/search/CHANGELOG.md +324 -0
  94. package/node_modules/@codemirror/search/LICENSE +21 -0
  95. package/node_modules/@codemirror/search/README.md +18 -0
  96. package/node_modules/@codemirror/search/dist/index.cjs +1237 -0
  97. package/node_modules/@codemirror/search/dist/index.d.cts +385 -0
  98. package/node_modules/@codemirror/search/dist/index.d.ts +385 -0
  99. package/node_modules/@codemirror/search/dist/index.js +1217 -0
  100. package/node_modules/@codemirror/search/package.json +40 -0
  101. package/node_modules/@codemirror/state/.github/workflows/dispatch.yml +16 -0
  102. package/node_modules/@codemirror/state/CHANGELOG.md +308 -0
  103. package/node_modules/@codemirror/state/LICENSE +21 -0
  104. package/node_modules/@codemirror/state/README.md +18 -0
  105. package/node_modules/@codemirror/state/dist/index.cjs +3922 -0
  106. package/node_modules/@codemirror/state/dist/index.d.cts +1713 -0
  107. package/node_modules/@codemirror/state/dist/index.d.ts +1713 -0
  108. package/node_modules/@codemirror/state/dist/index.js +3892 -0
  109. package/node_modules/@codemirror/state/package.json +38 -0
  110. package/node_modules/@codemirror/theme-one-dark/.github/workflows/dispatch.yml +16 -0
  111. package/node_modules/@codemirror/theme-one-dark/CHANGELOG.md +97 -0
  112. package/node_modules/@codemirror/theme-one-dark/LICENSE +21 -0
  113. package/node_modules/@codemirror/theme-one-dark/README.md +46 -0
  114. package/node_modules/@codemirror/theme-one-dark/dist/index.cjs +139 -0
  115. package/node_modules/@codemirror/theme-one-dark/dist/index.d.cts +39 -0
  116. package/node_modules/@codemirror/theme-one-dark/dist/index.d.ts +39 -0
  117. package/node_modules/@codemirror/theme-one-dark/dist/index.js +134 -0
  118. package/node_modules/@codemirror/theme-one-dark/package.json +41 -0
  119. package/node_modules/@codemirror/view/.github/workflows/dispatch.yml +16 -0
  120. package/node_modules/@codemirror/view/CHANGELOG.md +2284 -0
  121. package/node_modules/@codemirror/view/LICENSE +21 -0
  122. package/node_modules/@codemirror/view/README.md +37 -0
  123. package/node_modules/@codemirror/view/dist/index.cjs +11796 -0
  124. package/node_modules/@codemirror/view/dist/index.d.cts +2389 -0
  125. package/node_modules/@codemirror/view/dist/index.d.ts +2389 -0
  126. package/node_modules/@codemirror/view/dist/index.js +11745 -0
  127. package/node_modules/@codemirror/view/package.json +41 -0
  128. package/node_modules/@groove-dev/daemon/src/api.js +157 -2
  129. package/node_modules/@groove-dev/daemon/src/filewatcher.js +59 -0
  130. package/node_modules/@groove-dev/daemon/src/index.js +28 -1
  131. package/node_modules/@groove-dev/daemon/src/skills.js +20 -0
  132. package/node_modules/@groove-dev/gui/dist/assets/index-Dxg9hdf3.js +103 -0
  133. package/node_modules/@groove-dev/gui/dist/index.html +1 -1
  134. package/node_modules/@groove-dev/gui/package.json +15 -2
  135. package/node_modules/@groove-dev/gui/src/App.jsx +3 -0
  136. package/node_modules/@groove-dev/gui/src/components/CodeEditor.jsx +143 -0
  137. package/node_modules/@groove-dev/gui/src/components/EditorTabs.jsx +83 -0
  138. package/node_modules/@groove-dev/gui/src/components/FileTree.jsx +193 -0
  139. package/node_modules/@groove-dev/gui/src/stores/groove.js +158 -1
  140. package/node_modules/@groove-dev/gui/src/views/FileEditor.jsx +142 -0
  141. package/node_modules/@groove-dev/gui/src/views/SkillsMarketplace.jsx +112 -1
  142. package/node_modules/@lezer/common/LICENSE +21 -0
  143. package/node_modules/@lezer/common/README.md +14 -0
  144. package/node_modules/@lezer/common/dist/index.cjs +2209 -0
  145. package/node_modules/@lezer/common/dist/index.d.cts +1174 -0
  146. package/node_modules/@lezer/common/dist/index.d.ts +1174 -0
  147. package/node_modules/@lezer/common/dist/index.js +2196 -0
  148. package/node_modules/@lezer/common/package.json +32 -0
  149. package/node_modules/@lezer/css/CHANGELOG.md +307 -0
  150. package/node_modules/@lezer/css/LICENSE +21 -0
  151. package/node_modules/@lezer/css/README.md +6 -0
  152. package/node_modules/@lezer/css/dist/index.cjs +151 -0
  153. package/node_modules/@lezer/css/dist/index.d.cts +3 -0
  154. package/node_modules/@lezer/css/dist/index.d.ts +3 -0
  155. package/node_modules/@lezer/css/dist/index.js +147 -0
  156. package/node_modules/@lezer/css/package.json +36 -0
  157. package/node_modules/@lezer/css/rollup.config.js +16 -0
  158. package/node_modules/@lezer/css/src/css.grammar +290 -0
  159. package/node_modules/@lezer/css/src/highlight.js +36 -0
  160. package/node_modules/@lezer/css/src/parser.js +31 -0
  161. package/node_modules/@lezer/css/src/parser.terms.js +63 -0
  162. package/node_modules/@lezer/css/src/tokens.js +75 -0
  163. package/node_modules/@lezer/css/test/declarations.txt +241 -0
  164. package/node_modules/@lezer/css/test/selector.txt +178 -0
  165. package/node_modules/@lezer/css/test/statements.txt +227 -0
  166. package/node_modules/@lezer/css/test/test-css.js +16 -0
  167. package/node_modules/@lezer/highlight/LICENSE +21 -0
  168. package/node_modules/@lezer/highlight/README.md +14 -0
  169. package/node_modules/@lezer/highlight/dist/index.cjs +936 -0
  170. package/node_modules/@lezer/highlight/dist/index.d.cts +623 -0
  171. package/node_modules/@lezer/highlight/dist/index.d.ts +623 -0
  172. package/node_modules/@lezer/highlight/dist/index.js +927 -0
  173. package/node_modules/@lezer/highlight/package.json +31 -0
  174. package/node_modules/@lezer/html/CHANGELOG.md +303 -0
  175. package/node_modules/@lezer/html/LICENSE +21 -0
  176. package/node_modules/@lezer/html/README.md +37 -0
  177. package/node_modules/@lezer/html/dist/index.cjs +354 -0
  178. package/node_modules/@lezer/html/dist/index.d.cts +14 -0
  179. package/node_modules/@lezer/html/dist/index.d.ts +14 -0
  180. package/node_modules/@lezer/html/dist/index.js +349 -0
  181. package/node_modules/@lezer/html/package.json +37 -0
  182. package/node_modules/@lezer/html/rollup.config.js +16 -0
  183. package/node_modules/@lezer/html/src/.tern-port +1 -0
  184. package/node_modules/@lezer/html/src/content.js +87 -0
  185. package/node_modules/@lezer/html/src/highlight.js +15 -0
  186. package/node_modules/@lezer/html/src/html.grammar +181 -0
  187. package/node_modules/@lezer/html/src/index.js +2 -0
  188. package/node_modules/@lezer/html/src/parser.js +27 -0
  189. package/node_modules/@lezer/html/src/parser.terms.js +53 -0
  190. package/node_modules/@lezer/html/src/tokens.js +199 -0
  191. package/node_modules/@lezer/html/test/mixed.txt +69 -0
  192. package/node_modules/@lezer/html/test/tags.txt +370 -0
  193. package/node_modules/@lezer/html/test/test-html.js +29 -0
  194. package/node_modules/@lezer/html/test/test-incremental.js +97 -0
  195. package/node_modules/@lezer/html/test/vue.txt +56 -0
  196. package/node_modules/@lezer/javascript/CHANGELOG.md +485 -0
  197. package/node_modules/@lezer/javascript/LICENSE +21 -0
  198. package/node_modules/@lezer/javascript/README.md +14 -0
  199. package/node_modules/@lezer/javascript/dist/index.cjs +196 -0
  200. package/node_modules/@lezer/javascript/dist/index.d.cts +3 -0
  201. package/node_modules/@lezer/javascript/dist/index.d.ts +3 -0
  202. package/node_modules/@lezer/javascript/dist/index.js +192 -0
  203. package/node_modules/@lezer/javascript/package.json +36 -0
  204. package/node_modules/@lezer/javascript/rollup.config.js +16 -0
  205. package/node_modules/@lezer/javascript/src/highlight.js +62 -0
  206. package/node_modules/@lezer/javascript/src/javascript.grammar +735 -0
  207. package/node_modules/@lezer/javascript/src/parser.js +33 -0
  208. package/node_modules/@lezer/javascript/src/parser.terms.js +177 -0
  209. package/node_modules/@lezer/javascript/src/tokens.js +87 -0
  210. package/node_modules/@lezer/javascript/test/decorator.txt +64 -0
  211. package/node_modules/@lezer/javascript/test/expression.txt +686 -0
  212. package/node_modules/@lezer/javascript/test/jsx.txt +79 -0
  213. package/node_modules/@lezer/javascript/test/semicolon.txt +77 -0
  214. package/node_modules/@lezer/javascript/test/statement.txt +404 -0
  215. package/node_modules/@lezer/javascript/test/test-javascript.js +17 -0
  216. package/node_modules/@lezer/javascript/test/typescript.txt +401 -0
  217. package/node_modules/@lezer/json/CHANGELOG.md +79 -0
  218. package/node_modules/@lezer/json/LICENSE +21 -0
  219. package/node_modules/@lezer/json/README.md +7 -0
  220. package/node_modules/@lezer/json/dist/index.cjs +41 -0
  221. package/node_modules/@lezer/json/dist/index.d.cts +3 -0
  222. package/node_modules/@lezer/json/dist/index.d.ts +3 -0
  223. package/node_modules/@lezer/json/dist/index.js +37 -0
  224. package/node_modules/@lezer/json/package.json +36 -0
  225. package/node_modules/@lezer/json/rollup.config.js +15 -0
  226. package/node_modules/@lezer/json/src/highlight.js +12 -0
  227. package/node_modules/@lezer/json/src/json.grammar +38 -0
  228. package/node_modules/@lezer/json/src/parser.js +23 -0
  229. package/node_modules/@lezer/json/src/parser.terms.js +12 -0
  230. package/node_modules/@lezer/json/test/arrays.txt +34 -0
  231. package/node_modules/@lezer/json/test/literals.txt +23 -0
  232. package/node_modules/@lezer/json/test/numbers.txt +87 -0
  233. package/node_modules/@lezer/json/test/objects.txt +34 -0
  234. package/node_modules/@lezer/json/test/strings.txt +50 -0
  235. package/node_modules/@lezer/json/test/test-json.js +17 -0
  236. package/node_modules/@lezer/lr/LICENSE +21 -0
  237. package/node_modules/@lezer/lr/README.md +25 -0
  238. package/node_modules/@lezer/lr/dist/constants.d.ts +45 -0
  239. package/node_modules/@lezer/lr/dist/constants.js +5 -0
  240. package/node_modules/@lezer/lr/dist/index.cjs +1891 -0
  241. package/node_modules/@lezer/lr/dist/index.d.cts +303 -0
  242. package/node_modules/@lezer/lr/dist/index.d.ts +303 -0
  243. package/node_modules/@lezer/lr/dist/index.js +1884 -0
  244. package/node_modules/@lezer/lr/package.json +32 -0
  245. package/node_modules/@lezer/markdown/CHANGELOG.md +279 -0
  246. package/node_modules/@lezer/markdown/LICENSE +21 -0
  247. package/node_modules/@lezer/markdown/README.md +725 -0
  248. package/node_modules/@lezer/markdown/bin/build-readme.cjs +39 -0
  249. package/node_modules/@lezer/markdown/build.js +16 -0
  250. package/node_modules/@lezer/markdown/dist/index.cjs +2352 -0
  251. package/node_modules/@lezer/markdown/dist/index.d.cts +600 -0
  252. package/node_modules/@lezer/markdown/dist/index.d.ts +600 -0
  253. package/node_modules/@lezer/markdown/dist/index.js +2335 -0
  254. package/node_modules/@lezer/markdown/package.json +37 -0
  255. package/node_modules/@lezer/markdown/src/README.md +83 -0
  256. package/node_modules/@lezer/markdown/src/extension.ts +301 -0
  257. package/node_modules/@lezer/markdown/src/index.ts +5 -0
  258. package/node_modules/@lezer/markdown/src/markdown.ts +1961 -0
  259. package/node_modules/@lezer/markdown/src/nest.ts +46 -0
  260. package/node_modules/@lezer/markdown/test/compare-tree.ts +14 -0
  261. package/node_modules/@lezer/markdown/test/spec.ts +79 -0
  262. package/node_modules/@lezer/markdown/test/test-extension.ts +269 -0
  263. package/node_modules/@lezer/markdown/test/test-incremental.ts +265 -0
  264. package/node_modules/@lezer/markdown/test/test-markdown.ts +3574 -0
  265. package/node_modules/@lezer/markdown/test/test-nesting.ts +86 -0
  266. package/node_modules/@lezer/markdown/test/tsconfig.json +12 -0
  267. package/node_modules/@lezer/markdown/tsconfig.json +14 -0
  268. package/node_modules/@lezer/python/CHANGELOG.md +243 -0
  269. package/node_modules/@lezer/python/LICENSE +21 -0
  270. package/node_modules/@lezer/python/README.md +6 -0
  271. package/node_modules/@lezer/python/dist/index.cjs +330 -0
  272. package/node_modules/@lezer/python/dist/index.d.cts +3 -0
  273. package/node_modules/@lezer/python/dist/index.d.ts +3 -0
  274. package/node_modules/@lezer/python/dist/index.js +326 -0
  275. package/node_modules/@lezer/python/package.json +36 -0
  276. package/node_modules/@lezer/python/rollup.config.js +16 -0
  277. package/node_modules/@lezer/python/src/highlight.js +35 -0
  278. package/node_modules/@lezer/python/src/python.grammar +381 -0
  279. package/node_modules/@lezer/python/src/tokens.js +224 -0
  280. package/node_modules/@lezer/python/test/expression.txt +231 -0
  281. package/node_modules/@lezer/python/test/statement.txt +442 -0
  282. package/node_modules/@lezer/python/test/test-incremental.js +33 -0
  283. package/node_modules/@lezer/python/test/test-python.js +17 -0
  284. package/node_modules/@marijn/find-cluster-break/LICENSE +21 -0
  285. package/node_modules/@marijn/find-cluster-break/README.md +28 -0
  286. package/node_modules/@marijn/find-cluster-break/dist/index.cjs +85 -0
  287. package/node_modules/@marijn/find-cluster-break/dist/index.d.cts +15 -0
  288. package/node_modules/@marijn/find-cluster-break/package.json +35 -0
  289. package/node_modules/@marijn/find-cluster-break/rollup.config.js +7 -0
  290. package/node_modules/@marijn/find-cluster-break/src/index.d.ts +15 -0
  291. package/node_modules/@marijn/find-cluster-break/src/index.js +87 -0
  292. package/node_modules/@marijn/find-cluster-break/test/test-cluster.js +30 -0
  293. package/node_modules/crelt/LICENSE +19 -0
  294. package/node_modules/crelt/README.md +23 -0
  295. package/node_modules/crelt/dist/index.cjs +31 -0
  296. package/node_modules/crelt/dist/index.d.cts +4 -0
  297. package/node_modules/crelt/index.d.ts +4 -0
  298. package/node_modules/crelt/index.js +28 -0
  299. package/node_modules/crelt/package.json +35 -0
  300. package/node_modules/crelt/rollup.config.js +13 -0
  301. package/node_modules/style-mod/LICENSE +19 -0
  302. package/node_modules/style-mod/README.md +98 -0
  303. package/node_modules/style-mod/dist/style-mod.cjs +165 -0
  304. package/node_modules/style-mod/dist/style-mod.d.cts +16 -0
  305. package/node_modules/style-mod/package.json +39 -0
  306. package/node_modules/style-mod/src/README.md +34 -0
  307. package/node_modules/style-mod/src/style-mod.d.ts +16 -0
  308. package/node_modules/style-mod/src/style-mod.js +172 -0
  309. package/node_modules/style-mod/test/test-style-mod.js +104 -0
  310. package/node_modules/w3c-keyname/.tern-port +1 -0
  311. package/node_modules/w3c-keyname/LICENSE +19 -0
  312. package/node_modules/w3c-keyname/README.md +18 -0
  313. package/node_modules/w3c-keyname/index.cjs +127 -0
  314. package/node_modules/w3c-keyname/index.d.cts +5 -0
  315. package/node_modules/w3c-keyname/index.d.ts +5 -0
  316. package/node_modules/w3c-keyname/index.js +119 -0
  317. package/node_modules/w3c-keyname/package.json +37 -0
  318. package/package.json +1 -1
  319. package/packages/daemon/src/api.js +157 -2
  320. package/packages/daemon/src/filewatcher.js +59 -0
  321. package/packages/daemon/src/index.js +28 -1
  322. package/packages/daemon/src/skills.js +20 -0
  323. package/packages/gui/dist/assets/index-Dxg9hdf3.js +103 -0
  324. package/packages/gui/dist/index.html +1 -1
  325. package/packages/gui/package.json +15 -2
  326. package/packages/gui/src/App.jsx +3 -0
  327. package/packages/gui/src/components/CodeEditor.jsx +143 -0
  328. package/packages/gui/src/components/EditorTabs.jsx +83 -0
  329. package/packages/gui/src/components/FileTree.jsx +193 -0
  330. package/packages/gui/src/stores/groove.js +158 -1
  331. package/packages/gui/src/views/FileEditor.jsx +142 -0
  332. package/packages/gui/src/views/SkillsMarketplace.jsx +112 -1
  333. package/node_modules/@groove-dev/gui/dist/assets/index-CNCSwHwH.js +0 -74
  334. package/packages/gui/dist/assets/index-CNCSwHwH.js +0 -74
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>GROOVE</title>
7
- <script type="module" crossorigin src="/assets/index-CNCSwHwH.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-Dxg9hdf3.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-Gfb8Zxy9.css">
9
9
  </head>
10
10
  <body>
@@ -10,10 +10,23 @@
10
10
  "preview": "vite preview"
11
11
  },
12
12
  "dependencies": {
13
+ "@codemirror/autocomplete": "^6.20.1",
14
+ "@codemirror/commands": "^6.10.3",
15
+ "@codemirror/lang-css": "^6.3.1",
16
+ "@codemirror/lang-html": "^6.4.11",
17
+ "@codemirror/lang-javascript": "^6.2.5",
18
+ "@codemirror/lang-json": "^6.0.2",
19
+ "@codemirror/lang-markdown": "^6.5.0",
20
+ "@codemirror/lang-python": "^6.2.1",
21
+ "@codemirror/language": "^6.12.3",
22
+ "@codemirror/search": "^6.6.0",
23
+ "@codemirror/state": "^6.6.0",
24
+ "@codemirror/theme-one-dark": "^6.1.3",
25
+ "@codemirror/view": "^6.41.0",
26
+ "@xyflow/react": "^12.0.0",
13
27
  "react": "^19.0.0",
14
28
  "react-dom": "^19.0.0",
15
- "zustand": "^5.0.0",
16
- "@xyflow/react": "^12.0.0"
29
+ "zustand": "^5.0.0"
17
30
  },
18
31
  "devDependencies": {
19
32
  "@vitejs/plugin-react": "^4.3.0",
@@ -12,9 +12,11 @@ import TeamSelector from './components/TeamSelector';
12
12
  import CommandCenter from './views/CommandCenter';
13
13
  import ApprovalQueue from './components/ApprovalQueue';
14
14
  import SkillsMarketplace from './views/SkillsMarketplace';
15
+ import FileEditor from './views/FileEditor';
15
16
 
16
17
  const TABS = [
17
18
  { id: 'agents', label: 'Agents' },
19
+ { id: 'editor', label: 'Editor' },
18
20
  { id: 'skills', label: 'Skills' },
19
21
  { id: 'stats', label: 'Stats' },
20
22
  { id: 'teams', label: 'Teams' },
@@ -140,6 +142,7 @@ export default function App() {
140
142
  {activeTab === 'agents' && (
141
143
  !hasAgents ? <EmptyState /> : <AgentTree />
142
144
  )}
145
+ {activeTab === 'editor' && <FileEditor />}
143
146
  {activeTab === 'skills' && <SkillsMarketplace />}
144
147
  {activeTab === 'stats' && <CommandCenter />}
145
148
  {activeTab === 'teams' && <TeamSelector />}
@@ -0,0 +1,143 @@
1
+ // GROOVE GUI — CodeMirror 6 Editor Wrapper
2
+ // FSL-1.1-Apache-2.0 — see LICENSE
3
+
4
+ import React, { useRef, useEffect, useCallback } from 'react';
5
+ import { EditorView, keymap, lineNumbers, highlightActiveLine, highlightActiveLineGutter, drawSelection } from '@codemirror/view';
6
+ import { EditorState, Compartment } from '@codemirror/state';
7
+ import { defaultKeymap, indentWithTab, history, historyKeymap } from '@codemirror/commands';
8
+ import { bracketMatching, indentOnInput, foldGutter, foldKeymap } from '@codemirror/language';
9
+ import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
10
+ import { autocompletion, completionKeymap } from '@codemirror/autocomplete';
11
+ import { oneDark } from '@codemirror/theme-one-dark';
12
+
13
+ import { javascript } from '@codemirror/lang-javascript';
14
+ import { css } from '@codemirror/lang-css';
15
+ import { html } from '@codemirror/lang-html';
16
+ import { json } from '@codemirror/lang-json';
17
+ import { markdown } from '@codemirror/lang-markdown';
18
+ import { python } from '@codemirror/lang-python';
19
+
20
+ const LANG_EXTENSIONS = {
21
+ javascript: () => javascript({ jsx: true, typescript: false }),
22
+ typescript: () => javascript({ jsx: true, typescript: true }),
23
+ css: () => css(),
24
+ html: () => html(),
25
+ json: () => json(),
26
+ markdown: () => markdown(),
27
+ python: () => python(),
28
+ };
29
+
30
+ // Override One Dark bg to match GROOVE's --bg-base
31
+ const grooveTheme = EditorView.theme({
32
+ '&': { background: '#24282f', fontSize: '12px', fontFamily: "'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" },
33
+ '.cm-content': { caretColor: '#33afbc' },
34
+ '.cm-cursor': { borderLeftColor: '#33afbc' },
35
+ '.cm-gutters': { background: '#24282f', borderRight: '1px solid #4b5263', color: '#5c6370' },
36
+ '.cm-activeLineGutter': { background: '#2c313a' },
37
+ '&.cm-focused .cm-selectionBackground, .cm-selectionBackground': { background: 'rgba(51, 175, 188, 0.2) !important' },
38
+ '.cm-activeLine': { background: 'rgba(44, 49, 58, 0.5)' },
39
+ }, { dark: true });
40
+
41
+ export default function CodeEditor({ content, language, onContentChange, onSave }) {
42
+ const containerRef = useRef(null);
43
+ const viewRef = useRef(null);
44
+ const langCompartment = useRef(new Compartment());
45
+ const onChangeRef = useRef(onContentChange);
46
+ const onSaveRef = useRef(onSave);
47
+
48
+ // Keep callback refs current
49
+ onChangeRef.current = onContentChange;
50
+ onSaveRef.current = onSave;
51
+
52
+ // Build language extension
53
+ const getLangExtension = useCallback((lang) => {
54
+ const factory = LANG_EXTENSIONS[lang];
55
+ return factory ? factory() : [];
56
+ }, []);
57
+
58
+ // Create editor on mount
59
+ useEffect(() => {
60
+ if (!containerRef.current) return;
61
+
62
+ const saveKeymap = keymap.of([{
63
+ key: 'Mod-s',
64
+ run: () => { onSaveRef.current?.(); return true; },
65
+ }]);
66
+
67
+ const updateListener = EditorView.updateListener.of((update) => {
68
+ if (update.docChanged) {
69
+ onChangeRef.current?.(update.state.doc.toString());
70
+ }
71
+ });
72
+
73
+ const state = EditorState.create({
74
+ doc: content || '',
75
+ extensions: [
76
+ lineNumbers(),
77
+ highlightActiveLine(),
78
+ highlightActiveLineGutter(),
79
+ drawSelection(),
80
+ bracketMatching(),
81
+ indentOnInput(),
82
+ foldGutter(),
83
+ history(),
84
+ autocompletion(),
85
+ highlightSelectionMatches(),
86
+ langCompartment.current.of(getLangExtension(language)),
87
+ keymap.of([
88
+ ...defaultKeymap,
89
+ ...historyKeymap,
90
+ ...searchKeymap,
91
+ ...foldKeymap,
92
+ ...completionKeymap,
93
+ indentWithTab,
94
+ ]),
95
+ saveKeymap,
96
+ oneDark,
97
+ grooveTheme,
98
+ updateListener,
99
+ EditorView.lineWrapping,
100
+ ],
101
+ });
102
+
103
+ const view = new EditorView({ state, parent: containerRef.current });
104
+ viewRef.current = view;
105
+
106
+ return () => {
107
+ view.destroy();
108
+ viewRef.current = null;
109
+ };
110
+ }, []); // Mount once — content updates handled via dispatch
111
+
112
+ // Update content when file changes (file switch or external reload)
113
+ useEffect(() => {
114
+ const view = viewRef.current;
115
+ if (!view) return;
116
+ const currentDoc = view.state.doc.toString();
117
+ if (content !== currentDoc) {
118
+ view.dispatch({
119
+ changes: { from: 0, to: view.state.doc.length, insert: content || '' },
120
+ });
121
+ }
122
+ }, [content]);
123
+
124
+ // Switch language
125
+ useEffect(() => {
126
+ const view = viewRef.current;
127
+ if (!view) return;
128
+ view.dispatch({
129
+ effects: langCompartment.current.reconfigure(getLangExtension(language)),
130
+ });
131
+ }, [language, getLangExtension]);
132
+
133
+ return (
134
+ <div ref={containerRef} style={styles.container} />
135
+ );
136
+ }
137
+
138
+ const styles = {
139
+ container: {
140
+ flex: 1, overflow: 'hidden',
141
+ display: 'flex', flexDirection: 'column',
142
+ },
143
+ };
@@ -0,0 +1,83 @@
1
+ // GROOVE GUI — Editor Tabs
2
+ // FSL-1.1-Apache-2.0 — see LICENSE
3
+
4
+ import React from 'react';
5
+ import { useGrooveStore } from '../stores/groove';
6
+
7
+ export default function EditorTabs() {
8
+ const tabs = useGrooveStore((s) => s.editorOpenTabs);
9
+ const activeFile = useGrooveStore((s) => s.editorActiveFile);
10
+ const files = useGrooveStore((s) => s.editorFiles);
11
+ const setActiveFile = useGrooveStore((s) => s.setActiveFile);
12
+ const closeFile = useGrooveStore((s) => s.closeFile);
13
+
14
+ if (tabs.length === 0) return null;
15
+
16
+ return (
17
+ <div style={styles.container}>
18
+ {tabs.map((path) => {
19
+ const file = files[path];
20
+ const isActive = path === activeFile;
21
+ const isDirty = file && file.content !== file.originalContent;
22
+ const filename = path.split('/').pop();
23
+
24
+ return (
25
+ <div
26
+ key={path}
27
+ onClick={() => setActiveFile(path)}
28
+ title={path}
29
+ style={{
30
+ ...styles.tab,
31
+ color: isActive ? 'var(--text-bright)' : 'var(--text-primary)',
32
+ borderBottom: isActive ? '2px solid var(--accent)' : '2px solid transparent',
33
+ background: isActive ? 'var(--bg-active)' : 'transparent',
34
+ }}
35
+ >
36
+ {isDirty && <span style={styles.dirtyDot} />}
37
+ <span style={styles.tabName}>{filename}</span>
38
+ <button
39
+ onClick={(e) => { e.stopPropagation(); closeFile(path); }}
40
+ style={styles.closeBtn}
41
+ >
42
+ x
43
+ </button>
44
+ </div>
45
+ );
46
+ })}
47
+ </div>
48
+ );
49
+ }
50
+
51
+ const styles = {
52
+ container: {
53
+ display: 'flex', alignItems: 'center',
54
+ height: 32, flexShrink: 0,
55
+ background: 'var(--bg-chrome)',
56
+ borderBottom: '1px solid var(--border)',
57
+ overflowX: 'auto', overflowY: 'hidden',
58
+ },
59
+ tab: {
60
+ display: 'flex', alignItems: 'center', gap: 5,
61
+ padding: '0 12px', height: '100%',
62
+ cursor: 'pointer', whiteSpace: 'nowrap',
63
+ fontSize: 11, fontFamily: 'var(--font)',
64
+ transition: 'background 0.08s',
65
+ borderRight: '1px solid var(--border)',
66
+ flexShrink: 0,
67
+ },
68
+ tabName: {
69
+ overflow: 'hidden', textOverflow: 'ellipsis',
70
+ maxWidth: 140,
71
+ },
72
+ dirtyDot: {
73
+ width: 6, height: 6, borderRadius: '50%',
74
+ background: 'var(--amber)', flexShrink: 0,
75
+ },
76
+ closeBtn: {
77
+ background: 'none', border: 'none',
78
+ color: 'var(--text-muted)', fontSize: 10,
79
+ cursor: 'pointer', fontFamily: 'var(--font)',
80
+ padding: '0 2px', lineHeight: 1,
81
+ opacity: 0.6,
82
+ },
83
+ };
@@ -0,0 +1,193 @@
1
+ // GROOVE GUI — File Tree (expandable directory browser)
2
+ // FSL-1.1-Apache-2.0 — see LICENSE
3
+
4
+ import React, { useState, useEffect, useCallback } from 'react';
5
+ import { useGrooveStore } from '../stores/groove';
6
+
7
+ const FILE_COLORS = {
8
+ javascript: '#e5c07b',
9
+ typescript: '#61afef',
10
+ css: '#c678dd',
11
+ html: '#e06c75',
12
+ json: '#4ae168',
13
+ markdown: '#c678dd',
14
+ python: '#61afef',
15
+ rust: '#e06c75',
16
+ go: '#33afbc',
17
+ shell: '#4ae168',
18
+ yaml: '#e06c75',
19
+ text: '#abb2bf',
20
+ };
21
+
22
+ function TreeNode({ entry, depth, activeFile, expandedDirs, onToggleDir, onFileClick }) {
23
+ const isDir = entry.type === 'dir';
24
+ const isExpanded = expandedDirs.has(entry.path);
25
+ const isActive = !isDir && entry.path === activeFile;
26
+ const treeCache = useGrooveStore((s) => s.editorTreeCache);
27
+ const children = treeCache[entry.path] || [];
28
+
29
+ return (
30
+ <>
31
+ <div
32
+ onClick={() => isDir ? onToggleDir(entry.path, entry.hasChildren) : onFileClick(entry.path)}
33
+ style={{
34
+ ...styles.row,
35
+ paddingLeft: 12 + depth * 16,
36
+ background: isActive ? 'var(--bg-active)' : 'transparent',
37
+ borderLeft: isActive ? '2px solid var(--accent)' : '2px solid transparent',
38
+ }}
39
+ >
40
+ {isDir ? (
41
+ <span style={styles.arrow}>{isExpanded ? '\u25BE' : '\u25B8'}</span>
42
+ ) : (
43
+ <span style={{ ...styles.fileDot, background: FILE_COLORS[entry.language] || FILE_COLORS.text }} />
44
+ )}
45
+ <span style={{
46
+ ...styles.name,
47
+ color: isDir ? 'var(--text-primary)' : (isActive ? 'var(--text-bright)' : 'var(--text-primary)'),
48
+ fontWeight: isDir ? 600 : 400,
49
+ }}>
50
+ {entry.name}
51
+ </span>
52
+ {!isDir && entry.size > 0 && (
53
+ <span style={styles.size}>{formatSize(entry.size)}</span>
54
+ )}
55
+ </div>
56
+ {isDir && isExpanded && children.map((child) => (
57
+ <TreeNode
58
+ key={child.path}
59
+ entry={child}
60
+ depth={depth + 1}
61
+ activeFile={activeFile}
62
+ expandedDirs={expandedDirs}
63
+ onToggleDir={onToggleDir}
64
+ onFileClick={onFileClick}
65
+ />
66
+ ))}
67
+ </>
68
+ );
69
+ }
70
+
71
+ function formatSize(bytes) {
72
+ if (bytes < 1024) return `${bytes}B`;
73
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)}K`;
74
+ return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
75
+ }
76
+
77
+ export default function FileTree() {
78
+ const treeCache = useGrooveStore((s) => s.editorTreeCache);
79
+ const activeFile = useGrooveStore((s) => s.editorActiveFile);
80
+ const fetchTreeDir = useGrooveStore((s) => s.fetchTreeDir);
81
+ const openFile = useGrooveStore((s) => s.openFile);
82
+
83
+ const [expandedDirs, setExpandedDirs] = useState(new Set(['']));
84
+ const [filter, setFilter] = useState('');
85
+
86
+ // Fetch root on mount
87
+ useEffect(() => {
88
+ fetchTreeDir('');
89
+ }, [fetchTreeDir]);
90
+
91
+ const onToggleDir = useCallback((path, hasChildren) => {
92
+ setExpandedDirs((prev) => {
93
+ const next = new Set(prev);
94
+ if (next.has(path)) {
95
+ next.delete(path);
96
+ } else {
97
+ next.add(path);
98
+ if (hasChildren || !treeCache[path]) {
99
+ fetchTreeDir(path);
100
+ }
101
+ }
102
+ return next;
103
+ });
104
+ }, [fetchTreeDir, treeCache]);
105
+
106
+ const rootEntries = treeCache[''] || [];
107
+
108
+ // Client-side filter
109
+ const filtered = filter
110
+ ? rootEntries.filter((e) => e.name.toLowerCase().includes(filter.toLowerCase()))
111
+ : rootEntries;
112
+
113
+ return (
114
+ <div style={styles.container}>
115
+ {/* Search */}
116
+ <div style={styles.searchWrap}>
117
+ <input
118
+ type="text"
119
+ placeholder="Filter..."
120
+ value={filter}
121
+ onChange={(e) => setFilter(e.target.value)}
122
+ style={styles.searchInput}
123
+ />
124
+ </div>
125
+
126
+ {/* Tree */}
127
+ <div style={styles.tree}>
128
+ {filtered.length === 0 && (
129
+ <div style={styles.empty}>
130
+ {rootEntries.length === 0 ? 'Loading...' : 'No matches'}
131
+ </div>
132
+ )}
133
+ {filtered.map((entry) => (
134
+ <TreeNode
135
+ key={entry.path}
136
+ entry={entry}
137
+ depth={0}
138
+ activeFile={activeFile}
139
+ expandedDirs={expandedDirs}
140
+ onToggleDir={onToggleDir}
141
+ onFileClick={openFile}
142
+ />
143
+ ))}
144
+ </div>
145
+ </div>
146
+ );
147
+ }
148
+
149
+ const styles = {
150
+ container: {
151
+ display: 'flex', flexDirection: 'column',
152
+ height: '100%', background: 'var(--bg-chrome)',
153
+ },
154
+ searchWrap: {
155
+ padding: '8px 8px 6px',
156
+ borderBottom: '1px solid var(--border)',
157
+ },
158
+ searchInput: {
159
+ width: '100%', padding: '5px 8px',
160
+ background: 'var(--bg-base)', border: '1px solid var(--border)',
161
+ borderRadius: 3, color: 'var(--text-primary)',
162
+ fontSize: 11, fontFamily: 'var(--font)',
163
+ outline: 'none',
164
+ },
165
+ tree: {
166
+ flex: 1, overflowY: 'auto', overflowX: 'hidden',
167
+ },
168
+ row: {
169
+ display: 'flex', alignItems: 'center', gap: 6,
170
+ padding: '4px 12px', cursor: 'pointer',
171
+ transition: 'background 0.08s',
172
+ whiteSpace: 'nowrap',
173
+ },
174
+ arrow: {
175
+ fontSize: 10, color: 'var(--text-muted)',
176
+ width: 10, flexShrink: 0, textAlign: 'center',
177
+ },
178
+ fileDot: {
179
+ width: 6, height: 6, borderRadius: '50%',
180
+ flexShrink: 0,
181
+ },
182
+ name: {
183
+ fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis',
184
+ flex: 1, minWidth: 0,
185
+ },
186
+ size: {
187
+ fontSize: 9, color: 'var(--text-muted)', flexShrink: 0,
188
+ },
189
+ empty: {
190
+ padding: 16, textAlign: 'center',
191
+ fontSize: 11, color: 'var(--text-dim)',
192
+ },
193
+ };
@@ -15,7 +15,7 @@ export const useGrooveStore = create((set, get) => ({
15
15
  tunneled: false, // true when accessed via SSH tunnel (port mismatch)
16
16
 
17
17
  // UI state — unified panel model
18
- activeTab: 'agents', // 'agents' | 'stats' | 'teams' | 'approvals'
18
+ activeTab: 'agents', // 'agents' | 'stats' | 'teams' | 'approvals' | 'editor'
19
19
  detailPanel: null, // null | { type: 'agent', agentId } | { type: 'spawn' } | { type: 'journalist' }
20
20
  activityLog: {},
21
21
  statusMessage: null, // inline status text (replaces toast notifications)
@@ -24,6 +24,13 @@ export const useGrooveStore = create((set, get) => ({
24
24
  tokenTimeline: {}, // { [agentId]: [{ t: timestamp, v: tokensUsed }] }
25
25
  dashTelemetry: {}, // { [agentId]: [{ t, v, name }] } — persists across tab switches
26
26
 
27
+ // Editor state
28
+ editorFiles: {}, // { [path]: { content, originalContent, language, loadedAt } }
29
+ editorActiveFile: null, // currently visible file path
30
+ editorOpenTabs: [], // ordered array of open file paths
31
+ editorTreeCache: {}, // { [dirPath]: entries[] }
32
+ editorChangedFiles: {}, // { [path]: timestamp } — externally modified files
33
+
27
34
  // Connection
28
35
  connect() {
29
36
  if (get().ws) return;
@@ -124,6 +131,12 @@ export const useGrooveStore = create((set, get) => ({
124
131
  get().showStatus(`rotation failed: ${msg.error}`);
125
132
  break;
126
133
 
134
+ case 'file:changed':
135
+ set((s) => ({
136
+ editorChangedFiles: { ...s.editorChangedFiles, [msg.path]: msg.timestamp },
137
+ }));
138
+ break;
139
+
127
140
  case 'journalist:cycle':
128
141
  break; // Journalist feed polls separately
129
142
  }
@@ -256,6 +269,150 @@ export const useGrooveStore = create((set, get) => ({
256
269
  return data;
257
270
  },
258
271
 
272
+ // Editor actions
273
+ async openFile(path) {
274
+ // Already loaded — just switch tab
275
+ if (get().editorFiles[path]) {
276
+ set((s) => ({
277
+ editorActiveFile: path,
278
+ editorOpenTabs: s.editorOpenTabs.includes(path) ? s.editorOpenTabs : [...s.editorOpenTabs, path],
279
+ }));
280
+ return;
281
+ }
282
+ try {
283
+ const res = await fetch(`${API_BASE}/api/files/read?path=${encodeURIComponent(path)}`);
284
+ if (!res.ok) {
285
+ const err = await res.json().catch(() => ({}));
286
+ get().showStatus(err.error || 'Failed to read file');
287
+ return;
288
+ }
289
+ const data = await res.json();
290
+ if (data.binary) {
291
+ get().showStatus('Binary file — cannot open');
292
+ return;
293
+ }
294
+ set((s) => ({
295
+ editorFiles: {
296
+ ...s.editorFiles,
297
+ [path]: { content: data.content, originalContent: data.content, language: data.language, loadedAt: Date.now() },
298
+ },
299
+ editorActiveFile: path,
300
+ editorOpenTabs: s.editorOpenTabs.includes(path) ? s.editorOpenTabs : [...s.editorOpenTabs, path],
301
+ }));
302
+ // Tell daemon to watch this file
303
+ const ws = get().ws;
304
+ if (ws?.readyState === 1) {
305
+ ws.send(JSON.stringify({ type: 'editor:watch', path }));
306
+ }
307
+ } catch {
308
+ get().showStatus('Failed to open file');
309
+ }
310
+ },
311
+
312
+ closeFile(path) {
313
+ set((s) => {
314
+ const tabs = s.editorOpenTabs.filter((t) => t !== path);
315
+ const files = { ...s.editorFiles };
316
+ delete files[path];
317
+ const changed = { ...s.editorChangedFiles };
318
+ delete changed[path];
319
+ let active = s.editorActiveFile;
320
+ if (active === path) {
321
+ const idx = s.editorOpenTabs.indexOf(path);
322
+ active = tabs[Math.min(idx, tabs.length - 1)] || null;
323
+ }
324
+ return { editorOpenTabs: tabs, editorFiles: files, editorChangedFiles: changed, editorActiveFile: active };
325
+ });
326
+ // Stop watching
327
+ const ws = get().ws;
328
+ if (ws?.readyState === 1) {
329
+ ws.send(JSON.stringify({ type: 'editor:unwatch', path }));
330
+ }
331
+ },
332
+
333
+ setActiveFile(path) {
334
+ set({ editorActiveFile: path });
335
+ },
336
+
337
+ updateFileContent(path, content) {
338
+ set((s) => ({
339
+ editorFiles: {
340
+ ...s.editorFiles,
341
+ [path]: { ...s.editorFiles[path], content },
342
+ },
343
+ }));
344
+ },
345
+
346
+ async saveFile(path) {
347
+ const file = get().editorFiles[path];
348
+ if (!file) return;
349
+ try {
350
+ const res = await fetch(`${API_BASE}/api/files/write`, {
351
+ method: 'POST',
352
+ headers: { 'Content-Type': 'application/json' },
353
+ body: JSON.stringify({ path, content: file.content }),
354
+ });
355
+ if (!res.ok) {
356
+ const err = await res.json().catch(() => ({}));
357
+ get().showStatus(err.error || 'Save failed');
358
+ return;
359
+ }
360
+ set((s) => ({
361
+ editorFiles: {
362
+ ...s.editorFiles,
363
+ [path]: { ...s.editorFiles[path], originalContent: file.content },
364
+ },
365
+ editorChangedFiles: (() => {
366
+ const c = { ...s.editorChangedFiles };
367
+ delete c[path];
368
+ return c;
369
+ })(),
370
+ }));
371
+ get().showStatus('Saved');
372
+ } catch {
373
+ get().showStatus('Save failed');
374
+ }
375
+ },
376
+
377
+ async reloadFile(path) {
378
+ try {
379
+ const res = await fetch(`${API_BASE}/api/files/read?path=${encodeURIComponent(path)}`);
380
+ if (!res.ok) return;
381
+ const data = await res.json();
382
+ if (data.binary) return;
383
+ set((s) => ({
384
+ editorFiles: {
385
+ ...s.editorFiles,
386
+ [path]: { content: data.content, originalContent: data.content, language: data.language, loadedAt: Date.now() },
387
+ },
388
+ editorChangedFiles: (() => {
389
+ const c = { ...s.editorChangedFiles };
390
+ delete c[path];
391
+ return c;
392
+ })(),
393
+ }));
394
+ } catch { /* ignore */ }
395
+ },
396
+
397
+ dismissFileChange(path) {
398
+ set((s) => {
399
+ const c = { ...s.editorChangedFiles };
400
+ delete c[path];
401
+ return { editorChangedFiles: c };
402
+ });
403
+ },
404
+
405
+ async fetchTreeDir(dirPath) {
406
+ try {
407
+ const res = await fetch(`${API_BASE}/api/files/tree?path=${encodeURIComponent(dirPath)}`);
408
+ if (!res.ok) return;
409
+ const data = await res.json();
410
+ set((s) => ({
411
+ editorTreeCache: { ...s.editorTreeCache, [dirPath]: data.entries },
412
+ }));
413
+ } catch { /* ignore */ }
414
+ },
415
+
259
416
  addCommand(text) {
260
417
  set((s) => ({
261
418
  commandHistory: [...s.commandHistory.slice(-49), text],