@nocturnium/svelte-ide 1.0.0-rc.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 (330) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +251 -0
  3. package/dist/components/agents/AgentActivityPanel.svelte +565 -0
  4. package/dist/components/agents/AgentActivityPanel.svelte.d.ts +24 -0
  5. package/dist/components/agents/AgentAvatar.svelte +417 -0
  6. package/dist/components/agents/AgentAvatar.svelte.d.ts +23 -0
  7. package/dist/components/agents/AgentCursor.svelte +224 -0
  8. package/dist/components/agents/AgentCursor.svelte.d.ts +35 -0
  9. package/dist/components/agents/AgentPresenceBar.svelte +261 -0
  10. package/dist/components/agents/AgentPresenceBar.svelte.d.ts +20 -0
  11. package/dist/components/agents/index.d.ts +4 -0
  12. package/dist/components/agents/index.js +5 -0
  13. package/dist/components/ai/AIConversationList.svelte +524 -0
  14. package/dist/components/ai/AIConversationList.svelte.d.ts +17 -0
  15. package/dist/components/ai/AIEditPreview.svelte +132 -0
  16. package/dist/components/ai/AIEditPreview.svelte.d.ts +8 -0
  17. package/dist/components/ai/AIInlineEdit.svelte +155 -0
  18. package/dist/components/ai/AIInlineEdit.svelte.d.ts +10 -0
  19. package/dist/components/ai/AIMessage.svelte +239 -0
  20. package/dist/components/ai/AIMessage.svelte.d.ts +13 -0
  21. package/dist/components/ai/AIMessageActions.svelte +176 -0
  22. package/dist/components/ai/AIMessageActions.svelte.d.ts +12 -0
  23. package/dist/components/ai/AIMessageContent.svelte +355 -0
  24. package/dist/components/ai/AIMessageContent.svelte.d.ts +7 -0
  25. package/dist/components/ai/AIPanel.svelte +561 -0
  26. package/dist/components/ai/AIPanel.svelte.d.ts +7 -0
  27. package/dist/components/ai/AISuggestionWidget.svelte +132 -0
  28. package/dist/components/ai/AISuggestionWidget.svelte.d.ts +10 -0
  29. package/dist/components/ai/AIToolCallDisplay.svelte +317 -0
  30. package/dist/components/ai/AIToolCallDisplay.svelte.d.ts +12 -0
  31. package/dist/components/ai/index.d.ts +9 -0
  32. package/dist/components/ai/index.js +10 -0
  33. package/dist/components/core/Avatar.svelte +110 -0
  34. package/dist/components/core/Avatar.svelte.d.ts +12 -0
  35. package/dist/components/core/Badge.svelte +98 -0
  36. package/dist/components/core/Badge.svelte.d.ts +11 -0
  37. package/dist/components/core/Button.svelte +175 -0
  38. package/dist/components/core/Button.svelte.d.ts +18 -0
  39. package/dist/components/core/ConnectionStatus.svelte +294 -0
  40. package/dist/components/core/ConnectionStatus.svelte.d.ts +20 -0
  41. package/dist/components/core/ContextMenu.svelte +176 -0
  42. package/dist/components/core/ContextMenu.svelte.d.ts +19 -0
  43. package/dist/components/core/ErrorBoundary.svelte +277 -0
  44. package/dist/components/core/ErrorBoundary.svelte.d.ts +23 -0
  45. package/dist/components/core/Icon.svelte +107 -0
  46. package/dist/components/core/Icon.svelte.d.ts +8 -0
  47. package/dist/components/core/Input.svelte +138 -0
  48. package/dist/components/core/Input.svelte.d.ts +20 -0
  49. package/dist/components/core/Kbd.svelte +34 -0
  50. package/dist/components/core/Kbd.svelte.d.ts +7 -0
  51. package/dist/components/core/ResizeHandle.svelte +200 -0
  52. package/dist/components/core/ResizeHandle.svelte.d.ts +23 -0
  53. package/dist/components/core/Spinner.svelte +35 -0
  54. package/dist/components/core/Spinner.svelte.d.ts +7 -0
  55. package/dist/components/core/Textarea.svelte +112 -0
  56. package/dist/components/core/Textarea.svelte.d.ts +18 -0
  57. package/dist/components/core/Tooltip.svelte +103 -0
  58. package/dist/components/core/Tooltip.svelte.d.ts +11 -0
  59. package/dist/components/core/index.d.ts +13 -0
  60. package/dist/components/core/index.js +14 -0
  61. package/dist/components/editor/AIFocusLayer.svelte +430 -0
  62. package/dist/components/editor/AIFocusLayer.svelte.d.ts +32 -0
  63. package/dist/components/editor/Breadcrumbs.svelte +435 -0
  64. package/dist/components/editor/Breadcrumbs.svelte.d.ts +33 -0
  65. package/dist/components/editor/BreakpointLayer.svelte +642 -0
  66. package/dist/components/editor/BreakpointLayer.svelte.d.ts +20 -0
  67. package/dist/components/editor/CognitiveLoadMeter.svelte +324 -0
  68. package/dist/components/editor/CognitiveLoadMeter.svelte.d.ts +18 -0
  69. package/dist/components/editor/CollaborativeEditor.svelte +218 -0
  70. package/dist/components/editor/CollaborativeEditor.svelte.d.ts +32 -0
  71. package/dist/components/editor/CommandPalette.svelte +434 -0
  72. package/dist/components/editor/CommandPalette.svelte.d.ts +11 -0
  73. package/dist/components/editor/ComplexityLayer.svelte +293 -0
  74. package/dist/components/editor/ComplexityLayer.svelte.d.ts +23 -0
  75. package/dist/components/editor/ConflictZoneLayer.svelte +441 -0
  76. package/dist/components/editor/ConflictZoneLayer.svelte.d.ts +25 -0
  77. package/dist/components/editor/ContextLens.svelte +262 -0
  78. package/dist/components/editor/ContextLens.svelte.d.ts +27 -0
  79. package/dist/components/editor/CustomEditor.svelte +1242 -0
  80. package/dist/components/editor/CustomEditor.svelte.d.ts +37 -0
  81. package/dist/components/editor/DebugConsole.svelte +646 -0
  82. package/dist/components/editor/DebugConsole.svelte.d.ts +41 -0
  83. package/dist/components/editor/EchoCursorLayer.svelte +363 -0
  84. package/dist/components/editor/EchoCursorLayer.svelte.d.ts +24 -0
  85. package/dist/components/editor/Editor.svelte +61 -0
  86. package/dist/components/editor/Editor.svelte.d.ts +22 -0
  87. package/dist/components/editor/EditorGutter.svelte +119 -0
  88. package/dist/components/editor/EditorGutter.svelte.d.ts +19 -0
  89. package/dist/components/editor/EditorLines.svelte +182 -0
  90. package/dist/components/editor/EditorLines.svelte.d.ts +43 -0
  91. package/dist/components/editor/EditorPane.svelte +134 -0
  92. package/dist/components/editor/EditorPane.svelte.d.ts +9 -0
  93. package/dist/components/editor/EditorSelections.svelte +186 -0
  94. package/dist/components/editor/EditorSelections.svelte.d.ts +25 -0
  95. package/dist/components/editor/EditorTabs.svelte +170 -0
  96. package/dist/components/editor/EditorTabs.svelte.d.ts +12 -0
  97. package/dist/components/editor/FileExplorer.svelte +811 -0
  98. package/dist/components/editor/FileExplorer.svelte.d.ts +67 -0
  99. package/dist/components/editor/FileIcon.svelte +110 -0
  100. package/dist/components/editor/FileIcon.svelte.d.ts +10 -0
  101. package/dist/components/editor/FindReplace.svelte +448 -0
  102. package/dist/components/editor/FindReplace.svelte.d.ts +40 -0
  103. package/dist/components/editor/GhostBracketLayer.svelte +391 -0
  104. package/dist/components/editor/GhostBracketLayer.svelte.d.ts +24 -0
  105. package/dist/components/editor/GitBlameLayer.svelte +436 -0
  106. package/dist/components/editor/GitBlameLayer.svelte.d.ts +18 -0
  107. package/dist/components/editor/InlineDiagnosticsLayer.svelte +540 -0
  108. package/dist/components/editor/InlineDiagnosticsLayer.svelte.d.ts +35 -0
  109. package/dist/components/editor/InlineDiffLayer.svelte +337 -0
  110. package/dist/components/editor/InlineDiffLayer.svelte.d.ts +31 -0
  111. package/dist/components/editor/MinimalEditor.svelte +75 -0
  112. package/dist/components/editor/MinimalEditor.svelte.d.ts +6 -0
  113. package/dist/components/editor/MinimalEditor2.svelte +84 -0
  114. package/dist/components/editor/MinimalEditor2.svelte.d.ts +6 -0
  115. package/dist/components/editor/Minimap.svelte +327 -0
  116. package/dist/components/editor/Minimap.svelte.d.ts +34 -0
  117. package/dist/components/editor/PluginPreviewSandbox.svelte +793 -0
  118. package/dist/components/editor/PluginPreviewSandbox.svelte.d.ts +49 -0
  119. package/dist/components/editor/ProblemsPanel.svelte +628 -0
  120. package/dist/components/editor/ProblemsPanel.svelte.d.ts +25 -0
  121. package/dist/components/editor/QuickActionsMenu.svelte +403 -0
  122. package/dist/components/editor/QuickActionsMenu.svelte.d.ts +18 -0
  123. package/dist/components/editor/SnippetPalette.svelte +530 -0
  124. package/dist/components/editor/SnippetPalette.svelte.d.ts +16 -0
  125. package/dist/components/editor/StructureMap.svelte +431 -0
  126. package/dist/components/editor/StructureMap.svelte.d.ts +37 -0
  127. package/dist/components/editor/SymbolOutline.svelte +722 -0
  128. package/dist/components/editor/SymbolOutline.svelte.d.ts +44 -0
  129. package/dist/components/editor/TimelineScrubber.svelte +470 -0
  130. package/dist/components/editor/TimelineScrubber.svelte.d.ts +40 -0
  131. package/dist/components/editor/TokenRenderer.svelte +69 -0
  132. package/dist/components/editor/TokenRenderer.svelte.d.ts +15 -0
  133. package/dist/components/editor/constants.d.ts +32 -0
  134. package/dist/components/editor/constants.js +36 -0
  135. package/dist/components/editor/core/ai-awareness.d.ts +176 -0
  136. package/dist/components/editor/core/ai-awareness.js +210 -0
  137. package/dist/components/editor/core/bracket-healer.d.ts +189 -0
  138. package/dist/components/editor/core/bracket-healer.js +406 -0
  139. package/dist/components/editor/core/breakpoints.d.ts +203 -0
  140. package/dist/components/editor/core/breakpoints.js +414 -0
  141. package/dist/components/editor/core/commands.d.ts +108 -0
  142. package/dist/components/editor/core/commands.js +246 -0
  143. package/dist/components/editor/core/complexity-analyzer.d.ts +123 -0
  144. package/dist/components/editor/core/complexity-analyzer.js +376 -0
  145. package/dist/components/editor/core/conflict-predictor.d.ts +135 -0
  146. package/dist/components/editor/core/conflict-predictor.js +316 -0
  147. package/dist/components/editor/core/crdt-binding.d.ts +118 -0
  148. package/dist/components/editor/core/crdt-binding.js +286 -0
  149. package/dist/components/editor/core/diagnostics.d.ts +210 -0
  150. package/dist/components/editor/core/diagnostics.js +335 -0
  151. package/dist/components/editor/core/echo-cursor.d.ts +201 -0
  152. package/dist/components/editor/core/echo-cursor.js +267 -0
  153. package/dist/components/editor/core/folding.d.ts +124 -0
  154. package/dist/components/editor/core/folding.js +672 -0
  155. package/dist/components/editor/core/ghost-pair.d.ts +122 -0
  156. package/dist/components/editor/core/ghost-pair.js +221 -0
  157. package/dist/components/editor/core/git-blame.d.ts +170 -0
  158. package/dist/components/editor/core/git-blame.js +324 -0
  159. package/dist/components/editor/core/index.d.ts +26 -0
  160. package/dist/components/editor/core/index.js +24 -0
  161. package/dist/components/editor/core/keybindings.d.ts +79 -0
  162. package/dist/components/editor/core/keybindings.js +357 -0
  163. package/dist/components/editor/core/multi-cursor.d.ts +196 -0
  164. package/dist/components/editor/core/multi-cursor.js +521 -0
  165. package/dist/components/editor/core/navigation.d.ts +107 -0
  166. package/dist/components/editor/core/navigation.js +408 -0
  167. package/dist/components/editor/core/quick-actions.d.ts +189 -0
  168. package/dist/components/editor/core/quick-actions.js +427 -0
  169. package/dist/components/editor/core/search.d.ts +88 -0
  170. package/dist/components/editor/core/search.js +192 -0
  171. package/dist/components/editor/core/semantic-analyzer.d.ts +77 -0
  172. package/dist/components/editor/core/semantic-analyzer.js +424 -0
  173. package/dist/components/editor/core/snippet-manager.d.ts +202 -0
  174. package/dist/components/editor/core/snippet-manager.js +565 -0
  175. package/dist/components/editor/core/state.d.ts +367 -0
  176. package/dist/components/editor/core/state.js +900 -0
  177. package/dist/components/editor/core/timeline.d.ts +204 -0
  178. package/dist/components/editor/core/timeline.js +349 -0
  179. package/dist/components/editor/editor-find.d.ts +56 -0
  180. package/dist/components/editor/editor-find.js +148 -0
  181. package/dist/components/editor/editor-input.d.ts +77 -0
  182. package/dist/components/editor/editor-input.js +445 -0
  183. package/dist/components/editor/editor-multicursor.d.ts +21 -0
  184. package/dist/components/editor/editor-multicursor.js +196 -0
  185. package/dist/components/editor/editor-scroll.d.ts +14 -0
  186. package/dist/components/editor/editor-scroll.js +34 -0
  187. package/dist/components/editor/index.d.ts +15 -0
  188. package/dist/components/editor/index.js +21 -0
  189. package/dist/components/editor/languages.d.ts +62 -0
  190. package/dist/components/editor/languages.js +285 -0
  191. package/dist/components/editor/theme.d.ts +88 -0
  192. package/dist/components/editor/theme.js +139 -0
  193. package/dist/components/editor/tokenizer/base.d.ts +40 -0
  194. package/dist/components/editor/tokenizer/base.js +203 -0
  195. package/dist/components/editor/tokenizer/index.d.ts +56 -0
  196. package/dist/components/editor/tokenizer/index.js +215 -0
  197. package/dist/components/editor/tokenizer/languages/css.d.ts +17 -0
  198. package/dist/components/editor/tokenizer/languages/css.js +194 -0
  199. package/dist/components/editor/tokenizer/languages/go.d.ts +17 -0
  200. package/dist/components/editor/tokenizer/languages/go.js +220 -0
  201. package/dist/components/editor/tokenizer/languages/html.d.ts +24 -0
  202. package/dist/components/editor/tokenizer/languages/html.js +145 -0
  203. package/dist/components/editor/tokenizer/languages/javascript.d.ts +56 -0
  204. package/dist/components/editor/tokenizer/languages/javascript.js +452 -0
  205. package/dist/components/editor/tokenizer/languages/json.d.ts +12 -0
  206. package/dist/components/editor/tokenizer/languages/json.js +91 -0
  207. package/dist/components/editor/tokenizer/languages/markdown.d.ts +16 -0
  208. package/dist/components/editor/tokenizer/languages/markdown.js +156 -0
  209. package/dist/components/editor/tokenizer/languages/python.d.ts +20 -0
  210. package/dist/components/editor/tokenizer/languages/python.js +227 -0
  211. package/dist/components/editor/tokenizer/languages/svelte.d.ts +40 -0
  212. package/dist/components/editor/tokenizer/languages/svelte.js +326 -0
  213. package/dist/components/editor/tokenizer/types.d.ts +86 -0
  214. package/dist/components/editor/tokenizer/types.js +4 -0
  215. package/dist/components/layout/IDELayout.svelte +274 -0
  216. package/dist/components/layout/IDELayout.svelte.d.ts +29 -0
  217. package/dist/components/layout/StatusBar.svelte +511 -0
  218. package/dist/components/layout/StatusBar.svelte.d.ts +47 -0
  219. package/dist/components/layout/index.d.ts +2 -0
  220. package/dist/components/layout/index.js +3 -0
  221. package/dist/components/lsp/AutocompleteWidget.svelte +364 -0
  222. package/dist/components/lsp/AutocompleteWidget.svelte.d.ts +33 -0
  223. package/dist/components/lsp/DiagnosticMarker.svelte +166 -0
  224. package/dist/components/lsp/DiagnosticMarker.svelte.d.ts +19 -0
  225. package/dist/components/lsp/DiagnosticsPanel.svelte +388 -0
  226. package/dist/components/lsp/DiagnosticsPanel.svelte.d.ts +21 -0
  227. package/dist/components/lsp/HoverTooltip.svelte +274 -0
  228. package/dist/components/lsp/HoverTooltip.svelte.d.ts +24 -0
  229. package/dist/components/lsp/LSPEditor.svelte +486 -0
  230. package/dist/components/lsp/LSPEditor.svelte.d.ts +39 -0
  231. package/dist/components/lsp/SignatureHelpWidget.svelte +216 -0
  232. package/dist/components/lsp/SignatureHelpWidget.svelte.d.ts +22 -0
  233. package/dist/components/lsp/index.d.ts +6 -0
  234. package/dist/components/lsp/index.js +7 -0
  235. package/dist/components/plugins/PluginCard.svelte +153 -0
  236. package/dist/components/plugins/PluginCard.svelte.d.ts +19 -0
  237. package/dist/components/plugins/PluginPanel.svelte +280 -0
  238. package/dist/components/plugins/PluginPanel.svelte.d.ts +8 -0
  239. package/dist/components/plugins/PluginProposalForm.svelte +250 -0
  240. package/dist/components/plugins/PluginProposalForm.svelte.d.ts +6 -0
  241. package/dist/components/plugins/PluginStatusBadge.svelte +14 -0
  242. package/dist/components/plugins/PluginStatusBadge.svelte.d.ts +8 -0
  243. package/dist/components/plugins/index.d.ts +4 -0
  244. package/dist/components/plugins/index.js +5 -0
  245. package/dist/components/vfs/LockConflictDialog.svelte +705 -0
  246. package/dist/components/vfs/LockConflictDialog.svelte.d.ts +21 -0
  247. package/dist/components/vfs/LockIndicator.svelte +194 -0
  248. package/dist/components/vfs/LockIndicator.svelte.d.ts +29 -0
  249. package/dist/components/vfs/LockOverlay.svelte +344 -0
  250. package/dist/components/vfs/LockOverlay.svelte.d.ts +17 -0
  251. package/dist/components/vfs/VersionConflictDialog.svelte +549 -0
  252. package/dist/components/vfs/VersionConflictDialog.svelte.d.ts +24 -0
  253. package/dist/components/vfs/index.d.ts +4 -0
  254. package/dist/components/vfs/index.js +5 -0
  255. package/dist/crdt/awareness.d.ts +42 -0
  256. package/dist/crdt/awareness.js +109 -0
  257. package/dist/crdt/document.d.ts +101 -0
  258. package/dist/crdt/document.js +187 -0
  259. package/dist/crdt/index.d.ts +9 -0
  260. package/dist/crdt/index.js +8 -0
  261. package/dist/crdt/provider.d.ts +85 -0
  262. package/dist/crdt/provider.js +150 -0
  263. package/dist/crdt/types.d.ts +61 -0
  264. package/dist/crdt/types.js +4 -0
  265. package/dist/crdt/undo.d.ts +34 -0
  266. package/dist/crdt/undo.js +70 -0
  267. package/dist/index.d.ts +277 -0
  268. package/dist/index.js +280 -0
  269. package/dist/plugins/index.d.ts +103 -0
  270. package/dist/plugins/index.js +153 -0
  271. package/dist/services/error-handling.d.ts +95 -0
  272. package/dist/services/error-handling.js +413 -0
  273. package/dist/services/ide-integration.d.ts +83 -0
  274. package/dist/services/ide-integration.js +367 -0
  275. package/dist/services/lsp-client.d.ts +69 -0
  276. package/dist/services/lsp-client.js +667 -0
  277. package/dist/services/mock-ai.d.ts +37 -0
  278. package/dist/services/mock-ai.js +318 -0
  279. package/dist/services/optimistic.d.ts +141 -0
  280. package/dist/services/optimistic.js +367 -0
  281. package/dist/services/vfs-client.d.ts +81 -0
  282. package/dist/services/vfs-client.js +348 -0
  283. package/dist/stores/agents.svelte.d.ts +85 -0
  284. package/dist/stores/agents.svelte.js +459 -0
  285. package/dist/stores/ai-persistence.svelte.d.ts +76 -0
  286. package/dist/stores/ai-persistence.svelte.js +334 -0
  287. package/dist/stores/ai.svelte.d.ts +140 -0
  288. package/dist/stores/ai.svelte.js +383 -0
  289. package/dist/stores/collaboration.svelte.d.ts +164 -0
  290. package/dist/stores/collaboration.svelte.js +334 -0
  291. package/dist/stores/editor.svelte.d.ts +131 -0
  292. package/dist/stores/editor.svelte.js +250 -0
  293. package/dist/stores/index.d.ts +10 -0
  294. package/dist/stores/index.js +29 -0
  295. package/dist/stores/layout.svelte.d.ts +171 -0
  296. package/dist/stores/layout.svelte.js +351 -0
  297. package/dist/stores/plugin.svelte.d.ts +121 -0
  298. package/dist/stores/plugin.svelte.js +410 -0
  299. package/dist/stores/vfs.svelte.d.ts +123 -0
  300. package/dist/stores/vfs.svelte.js +680 -0
  301. package/dist/styles/theme.css +623 -0
  302. package/dist/types/agents.d.ts +127 -0
  303. package/dist/types/agents.js +5 -0
  304. package/dist/types/ai.d.ts +137 -0
  305. package/dist/types/ai.js +4 -0
  306. package/dist/types/crdt.d.ts +222 -0
  307. package/dist/types/crdt.js +5 -0
  308. package/dist/types/editor.d.ts +52 -0
  309. package/dist/types/editor.js +18 -0
  310. package/dist/types/events.d.ts +133 -0
  311. package/dist/types/events.js +4 -0
  312. package/dist/types/filesystem.d.ts +77 -0
  313. package/dist/types/filesystem.js +4 -0
  314. package/dist/types/index.d.ts +9 -0
  315. package/dist/types/index.js +12 -0
  316. package/dist/types/lsp.d.ts +691 -0
  317. package/dist/types/lsp.js +108 -0
  318. package/dist/types/plugin.d.ts +239 -0
  319. package/dist/types/plugin.js +5 -0
  320. package/dist/types/vfs.d.ts +191 -0
  321. package/dist/types/vfs.js +18 -0
  322. package/dist/utils/format.d.ts +55 -0
  323. package/dist/utils/format.js +152 -0
  324. package/dist/utils/index.d.ts +3 -0
  325. package/dist/utils/index.js +4 -0
  326. package/dist/utils/keybindings.d.ts +33 -0
  327. package/dist/utils/keybindings.js +171 -0
  328. package/dist/utils/language.d.ts +27 -0
  329. package/dist/utils/language.js +222 -0
  330. package/package.json +178 -0
@@ -0,0 +1,680 @@
1
+ /**
2
+ * VFS store using Svelte 5 runes
3
+ * Manages workspace state, file tree, locks, and SSE events
4
+ *
5
+ * Note: Svelte 5 modules cannot directly export $derived values.
6
+ * We use getter functions to expose reactive derived state.
7
+ */
8
+ import * as vfsClient from '../services/vfs-client';
9
+ import { parseError, isVFSError, isConflictError, logError } from '../services/error-handling';
10
+ import { optimisticUpdate } from '../services/optimistic';
11
+ // Reactive state using $state rune
12
+ let state = $state({
13
+ workspace: null,
14
+ workspaceLoading: false,
15
+ files: [],
16
+ fileMap: new Map(),
17
+ dirtyFiles: new Set(),
18
+ locks: new Map(),
19
+ lockStatuses: new Map(),
20
+ pendingLocks: new Set(),
21
+ activeTransactions: new Map(),
22
+ transactionHistory: [],
23
+ eventSource: null,
24
+ connected: false,
25
+ lastEventTime: null,
26
+ reconnectAttempts: 0,
27
+ syncing: false,
28
+ error: null,
29
+ errorCode: null,
30
+ structuredError: null,
31
+ version: 0,
32
+ pendingRetries: new Map(),
33
+ conflictQueue: []
34
+ });
35
+ // Event handlers for SSE
36
+ const eventHandlers = new Map();
37
+ // Lock TTL refresh intervals
38
+ const lockRefreshIntervals = new Map(); // path -> intervalId
39
+ // Current user ID (set during initialization)
40
+ let currentUserId = null;
41
+ // ============================================================================
42
+ // Getter Functions (Svelte 5 module-safe)
43
+ // ============================================================================
44
+ export function getWorkspace() {
45
+ return state.workspace;
46
+ }
47
+ export function getWorkspaceLoading() {
48
+ return state.workspaceLoading;
49
+ }
50
+ export function getFiles() {
51
+ return state.files;
52
+ }
53
+ export function getFileTree() {
54
+ return buildTree(state.files);
55
+ }
56
+ export function getFile(path) {
57
+ return state.fileMap.get(path);
58
+ }
59
+ export function getLocks() {
60
+ return Array.from(state.locks.values());
61
+ }
62
+ export function getLock(path) {
63
+ return state.locks.get(path);
64
+ }
65
+ export function getLockStatus(path) {
66
+ return state.lockStatuses.get(path) ?? { status: 'unlocked' };
67
+ }
68
+ export function isLocked(path) {
69
+ return state.locks.has(path);
70
+ }
71
+ export function isLockedByMe(path) {
72
+ if (!currentUserId)
73
+ return false;
74
+ const lock = state.locks.get(path);
75
+ return lock?.holder === currentUserId;
76
+ }
77
+ export function isLockedByOther(path) {
78
+ if (!currentUserId)
79
+ return false;
80
+ const lock = state.locks.get(path);
81
+ return lock !== undefined && lock.holder !== currentUserId;
82
+ }
83
+ export function getMyLocks() {
84
+ if (!currentUserId)
85
+ return [];
86
+ return getLocks().filter((lock) => lock.holder === currentUserId);
87
+ }
88
+ export function getOtherLocks() {
89
+ if (!currentUserId)
90
+ return getLocks();
91
+ return getLocks().filter((lock) => lock.holder !== currentUserId);
92
+ }
93
+ export function getDirtyFiles() {
94
+ return Array.from(state.dirtyFiles);
95
+ }
96
+ export function isDirty(path) {
97
+ return state.dirtyFiles.has(path);
98
+ }
99
+ export function getActiveTransactions() {
100
+ return Array.from(state.activeTransactions.values());
101
+ }
102
+ export function getTransactionHistory() {
103
+ return state.transactionHistory;
104
+ }
105
+ export function getConnected() {
106
+ return state.connected;
107
+ }
108
+ export function getSyncing() {
109
+ return state.syncing;
110
+ }
111
+ export function getError() {
112
+ return state.error;
113
+ }
114
+ export function getErrorCode() {
115
+ return state.errorCode;
116
+ }
117
+ export function getVersion() {
118
+ return state.version;
119
+ }
120
+ export function getReconnectAttempts() {
121
+ return state.reconnectAttempts;
122
+ }
123
+ export function getStructuredError() {
124
+ return state.structuredError;
125
+ }
126
+ export function getConflictQueue() {
127
+ return state.conflictQueue;
128
+ }
129
+ export function hasConflicts() {
130
+ return state.conflictQueue.length > 0;
131
+ }
132
+ export function getPendingRetryCount(operationId) {
133
+ return state.pendingRetries.get(operationId) ?? 0;
134
+ }
135
+ // Derived getters
136
+ export function getLockedFiles() {
137
+ return state.files.filter((f) => state.locks.has(f.path));
138
+ }
139
+ export function getMyLockedFiles() {
140
+ return state.files.filter((f) => isLockedByMe(f.path));
141
+ }
142
+ export function getDirectories() {
143
+ return state.files.filter((f) => f.isDir);
144
+ }
145
+ export function getFilesInDirectory(dirPath) {
146
+ const prefix = dirPath.endsWith('/') ? dirPath : `${dirPath}/`;
147
+ return state.files.filter((f) => f.path.startsWith(prefix) && !f.path.slice(prefix.length).includes('/'));
148
+ }
149
+ // Legacy aliases for backward compatibility
150
+ export const workspace = { get current() { return getWorkspace(); } };
151
+ export const workspaceLoading = { get current() { return getWorkspaceLoading(); } };
152
+ export const files = { get current() { return getFiles(); } };
153
+ export const fileTree = { get current() { return getFileTree(); } };
154
+ export const locks = { get current() { return getLocks(); } };
155
+ export const dirtyFiles = { get current() { return getDirtyFiles(); } };
156
+ export const activeTransactions = { get current() { return getActiveTransactions(); } };
157
+ export const connected = { get current() { return getConnected(); } };
158
+ export const syncing = { get current() { return getSyncing(); } };
159
+ export const error = { get current() { return getError(); } };
160
+ // ============================================================================
161
+ // Initialization
162
+ // ============================================================================
163
+ export function initialize(userId, workspaceId) {
164
+ currentUserId = userId;
165
+ if (workspaceId) {
166
+ loadWorkspace(workspaceId);
167
+ }
168
+ }
169
+ // ============================================================================
170
+ // Workspace Operations
171
+ // ============================================================================
172
+ export async function loadWorkspace(workspaceId) {
173
+ state.workspaceLoading = true;
174
+ state.error = null;
175
+ try {
176
+ const workspace = await vfsClient.getWorkspace(workspaceId);
177
+ state.workspace = workspace;
178
+ // Connect to SSE stream
179
+ connect(workspaceId);
180
+ }
181
+ catch (err) {
182
+ state.error = err instanceof Error ? err.message : 'Failed to load workspace';
183
+ }
184
+ finally {
185
+ state.workspaceLoading = false;
186
+ }
187
+ }
188
+ // ============================================================================
189
+ // File Operations
190
+ // ============================================================================
191
+ export function updateFileInfo(fileInfo) {
192
+ state.fileMap.set(fileInfo.path, fileInfo);
193
+ const index = state.files.findIndex((f) => f.path === fileInfo.path);
194
+ if (index >= 0) {
195
+ state.files[index] = fileInfo;
196
+ }
197
+ else {
198
+ state.files = [...state.files, fileInfo];
199
+ }
200
+ }
201
+ export function removeFileInfo(path) {
202
+ state.fileMap.delete(path);
203
+ state.files = state.files.filter((f) => f.path !== path);
204
+ state.dirtyFiles.delete(path);
205
+ removeLock(path);
206
+ }
207
+ export function markDirty(path) {
208
+ state.dirtyFiles.add(path);
209
+ }
210
+ export function markClean(path) {
211
+ state.dirtyFiles.delete(path);
212
+ }
213
+ export function setFiles(files) {
214
+ state.files = files;
215
+ state.fileMap = new Map(files.map((f) => [f.path, f]));
216
+ }
217
+ // ============================================================================
218
+ // Optimistic File Operations with Error Recovery
219
+ // ============================================================================
220
+ /**
221
+ * Write file content with optimistic update and automatic rollback on failure
222
+ */
223
+ export async function writeFileOptimistic(path, content, options = {}) {
224
+ if (!state.workspace) {
225
+ const error = parseError(new Error('Workspace not initialized'), { path });
226
+ return {
227
+ success: false,
228
+ error,
229
+ operation: { id: '', type: 'file_write', payload: { path, content }, timestamp: Date.now(), status: 'failed', retryCount: 0, maxRetries: 3 }
230
+ };
231
+ }
232
+ const previousFile = state.fileMap.get(path);
233
+ const workspaceId = state.workspace.id;
234
+ return await optimisticUpdate({
235
+ type: 'file_write',
236
+ payload: { path, content },
237
+ apply: () => {
238
+ // Optimistically update file info
239
+ markDirty(path);
240
+ if (previousFile) {
241
+ updateFileInfo({
242
+ ...previousFile,
243
+ size: new Blob([content]).size,
244
+ modTime: new Date().toISOString()
245
+ });
246
+ }
247
+ },
248
+ rollback: () => {
249
+ // Restore previous state
250
+ markClean(path);
251
+ if (previousFile) {
252
+ updateFileInfo(previousFile);
253
+ }
254
+ },
255
+ commit: async () => {
256
+ const result = await vfsClient.quickWriteFile(workspaceId, path, content);
257
+ markClean(path);
258
+ updateFileInfo(result);
259
+ return result;
260
+ },
261
+ config: {
262
+ maxRetries: 3,
263
+ retryDelay: 1000,
264
+ onRollback: (op, error) => {
265
+ const vfsError = parseError(error, { path, workspaceId });
266
+ state.structuredError = vfsError;
267
+ logError(vfsError, 'error', { operation: op.type, path });
268
+ // Queue conflict if version mismatch
269
+ if (isConflictError(vfsError)) {
270
+ state.conflictQueue.push({
271
+ path,
272
+ localContent: content,
273
+ serverVersion: state.version
274
+ });
275
+ }
276
+ }
277
+ }
278
+ });
279
+ }
280
+ /**
281
+ * Delete file with optimistic update
282
+ */
283
+ export async function deleteFileOptimistic(path) {
284
+ if (!state.workspace) {
285
+ const error = parseError(new Error('Workspace not initialized'), { path });
286
+ return {
287
+ success: false,
288
+ error,
289
+ operation: { id: '', type: 'file_delete', payload: { path }, timestamp: Date.now(), status: 'failed', retryCount: 0, maxRetries: 3 }
290
+ };
291
+ }
292
+ const previousFile = state.fileMap.get(path);
293
+ const previousLock = state.locks.get(path);
294
+ const workspaceId = state.workspace.id;
295
+ return await optimisticUpdate({
296
+ type: 'file_delete',
297
+ payload: { path },
298
+ apply: () => {
299
+ removeFileInfo(path);
300
+ },
301
+ rollback: () => {
302
+ if (previousFile) {
303
+ updateFileInfo(previousFile);
304
+ }
305
+ if (previousLock) {
306
+ setLock(previousLock);
307
+ }
308
+ },
309
+ commit: async () => {
310
+ await vfsClient.deleteFile(workspaceId, path);
311
+ },
312
+ config: {
313
+ maxRetries: 2,
314
+ retryDelay: 500,
315
+ onRollback: (op, error) => {
316
+ const vfsError = parseError(error, { path, workspaceId });
317
+ state.structuredError = vfsError;
318
+ logError(vfsError, 'error', { operation: op.type, path });
319
+ }
320
+ }
321
+ });
322
+ }
323
+ // ============================================================================
324
+ // Conflict Resolution
325
+ // ============================================================================
326
+ /**
327
+ * Resolve a file conflict
328
+ */
329
+ export function resolveConflict(path, resolution, mergedContent) {
330
+ const conflictIndex = state.conflictQueue.findIndex(c => c.path === path);
331
+ if (conflictIndex === -1)
332
+ return;
333
+ const conflict = state.conflictQueue[conflictIndex];
334
+ state.conflictQueue = state.conflictQueue.filter((_, i) => i !== conflictIndex);
335
+ switch (resolution) {
336
+ case 'use_local':
337
+ // Re-attempt write with force flag
338
+ if (state.workspace) {
339
+ vfsClient.quickWriteFile(state.workspace.id, path, conflict.localContent)
340
+ .then(result => {
341
+ updateFileInfo(result);
342
+ markClean(path);
343
+ })
344
+ .catch(err => {
345
+ state.structuredError = parseError(err, { path });
346
+ });
347
+ }
348
+ break;
349
+ case 'use_server':
350
+ // Discard local changes, mark clean
351
+ markClean(path);
352
+ break;
353
+ case 'merge':
354
+ // Write merged content
355
+ if (mergedContent && state.workspace) {
356
+ vfsClient.quickWriteFile(state.workspace.id, path, mergedContent)
357
+ .then(result => {
358
+ updateFileInfo(result);
359
+ markClean(path);
360
+ })
361
+ .catch(err => {
362
+ state.structuredError = parseError(err, { path });
363
+ });
364
+ }
365
+ break;
366
+ }
367
+ }
368
+ /**
369
+ * Dismiss all conflicts (use server versions)
370
+ */
371
+ export function dismissAllConflicts() {
372
+ for (const conflict of state.conflictQueue) {
373
+ markClean(conflict.path);
374
+ }
375
+ state.conflictQueue = [];
376
+ }
377
+ /**
378
+ * Retry a failed operation
379
+ */
380
+ export async function retryOperation(operationId) {
381
+ const retryCount = state.pendingRetries.get(operationId) ?? 0;
382
+ if (retryCount >= 3) {
383
+ return false;
384
+ }
385
+ state.pendingRetries.set(operationId, retryCount + 1);
386
+ // The actual retry logic would be implemented based on the operation type
387
+ // This is a placeholder for the retry mechanism
388
+ return true;
389
+ }
390
+ /**
391
+ * Clear structured error
392
+ */
393
+ export function clearStructuredError() {
394
+ state.structuredError = null;
395
+ }
396
+ // ============================================================================
397
+ // Lock Operations
398
+ // ============================================================================
399
+ export function setLock(lock) {
400
+ state.locks.set(lock.path, lock);
401
+ state.lockStatuses.set(lock.path, { status: 'locked', lock });
402
+ state.pendingLocks.delete(lock.path);
403
+ // Start TTL refresh if it's our lock
404
+ if (lock.holder === currentUserId) {
405
+ startLockRefresh(lock);
406
+ }
407
+ }
408
+ export function removeLock(path) {
409
+ state.locks.delete(path);
410
+ state.lockStatuses.set(path, { status: 'unlocked' });
411
+ state.pendingLocks.delete(path);
412
+ stopLockRefresh(path);
413
+ }
414
+ export function setLockStatus(path, status) {
415
+ state.lockStatuses.set(path, status);
416
+ if (status.status === 'acquiring') {
417
+ state.pendingLocks.add(path);
418
+ }
419
+ else {
420
+ state.pendingLocks.delete(path);
421
+ }
422
+ }
423
+ export function setLocks(locks) {
424
+ state.locks = new Map(locks.map((l) => [l.path, l]));
425
+ // Update lock statuses
426
+ for (const lock of locks) {
427
+ state.lockStatuses.set(lock.path, { status: 'locked', lock });
428
+ }
429
+ // Start refresh timers for our locks
430
+ for (const lock of locks) {
431
+ if (lock.holder === currentUserId) {
432
+ startLockRefresh(lock);
433
+ }
434
+ }
435
+ }
436
+ function startLockRefresh(lock) {
437
+ // Refresh at 80% of TTL to ensure we don't lose the lock
438
+ const refreshInterval = lock.ttl * 0.8;
439
+ const intervalId = window.setInterval(async () => {
440
+ await refreshLockTTL(lock.path);
441
+ }, refreshInterval);
442
+ lockRefreshIntervals.set(lock.path, intervalId);
443
+ }
444
+ function stopLockRefresh(path) {
445
+ const intervalId = lockRefreshIntervals.get(path);
446
+ if (intervalId) {
447
+ clearInterval(intervalId);
448
+ lockRefreshIntervals.delete(path);
449
+ }
450
+ }
451
+ async function refreshLockTTL(path) {
452
+ if (!state.workspace || !currentUserId)
453
+ return;
454
+ try {
455
+ const lock = await vfsClient.refreshLock(state.workspace.id, path, currentUserId);
456
+ setLock(lock);
457
+ }
458
+ catch (err) {
459
+ // Refresh failed - lock may have expired
460
+ removeLock(path);
461
+ state.error = `Lock expired on ${path}`;
462
+ }
463
+ }
464
+ // Public lock acquisition
465
+ export async function acquireLock(path, purpose) {
466
+ if (!state.workspace || !currentUserId) {
467
+ state.error = 'Workspace or user not initialized';
468
+ return null;
469
+ }
470
+ setLockStatus(path, { status: 'acquiring' });
471
+ try {
472
+ const lock = await vfsClient.acquireLock(state.workspace.id, path, currentUserId, { purpose });
473
+ setLock(lock);
474
+ return lock;
475
+ }
476
+ catch (err) {
477
+ const errorMessage = err instanceof Error ? err.message : 'Failed to acquire lock';
478
+ setLockStatus(path, { status: 'failed', error: errorMessage });
479
+ state.error = errorMessage;
480
+ return null;
481
+ }
482
+ }
483
+ export async function releaseLock(path) {
484
+ if (!state.workspace || !currentUserId)
485
+ return;
486
+ try {
487
+ await vfsClient.releaseLock(state.workspace.id, path, currentUserId);
488
+ removeLock(path);
489
+ }
490
+ catch (err) {
491
+ state.error = err instanceof Error ? err.message : 'Failed to release lock';
492
+ }
493
+ }
494
+ export async function releaseAllMyLocks() {
495
+ const myLocks = getMyLocks();
496
+ for (const lock of myLocks) {
497
+ await releaseLock(lock.path);
498
+ }
499
+ }
500
+ // ============================================================================
501
+ // Transaction Operations
502
+ // ============================================================================
503
+ export function startTransaction(transaction) {
504
+ state.activeTransactions.set(transaction.id, transaction);
505
+ }
506
+ export function completeTransaction(transactionId, status) {
507
+ const tx = state.activeTransactions.get(transactionId);
508
+ if (!tx)
509
+ return;
510
+ tx.status = status;
511
+ tx.committedAt = new Date().toISOString();
512
+ state.activeTransactions.delete(transactionId);
513
+ state.transactionHistory = [tx, ...state.transactionHistory].slice(0, 100); // Keep last 100
514
+ }
515
+ // ============================================================================
516
+ // SSE Connection
517
+ // ============================================================================
518
+ const BASE_RECONNECT_DELAY = 1000;
519
+ const MAX_RECONNECT_ATTEMPTS = 10;
520
+ export function connect(workspaceId) {
521
+ if (state.eventSource) {
522
+ state.eventSource.close();
523
+ }
524
+ const endpoint = `/api/vfs/workspaces/${workspaceId}/stream`;
525
+ const eventSource = new EventSource(endpoint);
526
+ eventSource.onopen = () => {
527
+ state.connected = true;
528
+ state.error = null;
529
+ state.reconnectAttempts = 0;
530
+ };
531
+ eventSource.onerror = () => {
532
+ state.connected = false;
533
+ state.error = 'VFS connection lost';
534
+ if (state.reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
535
+ const delay = BASE_RECONNECT_DELAY * Math.pow(2, state.reconnectAttempts);
536
+ state.reconnectAttempts++;
537
+ setTimeout(() => {
538
+ if (!state.connected) {
539
+ connect(workspaceId);
540
+ }
541
+ }, delay);
542
+ }
543
+ else {
544
+ state.error = 'Failed to reconnect after multiple attempts';
545
+ }
546
+ };
547
+ eventSource.onmessage = (event) => {
548
+ try {
549
+ const vfsEvent = JSON.parse(event.data);
550
+ handleEvent(vfsEvent);
551
+ }
552
+ catch {
553
+ console.error('Failed to parse VFS event');
554
+ }
555
+ };
556
+ state.eventSource = eventSource;
557
+ }
558
+ export function disconnect() {
559
+ if (state.eventSource) {
560
+ state.eventSource.close();
561
+ state.eventSource = null;
562
+ }
563
+ state.connected = false;
564
+ // Clear all lock refresh intervals
565
+ for (const intervalId of lockRefreshIntervals.values()) {
566
+ clearInterval(intervalId);
567
+ }
568
+ lockRefreshIntervals.clear();
569
+ }
570
+ function handleEvent(event) {
571
+ state.lastEventTime = event.timestamp;
572
+ switch (event.type) {
573
+ case 'snapshot':
574
+ handleSnapshot(event);
575
+ break;
576
+ case 'update':
577
+ handleUpdate(event);
578
+ break;
579
+ case 'ping':
580
+ // Keep-alive, no action needed
581
+ break;
582
+ case 'complete':
583
+ // Stream ended
584
+ disconnect();
585
+ break;
586
+ case 'error':
587
+ state.error = event.error;
588
+ state.errorCode = event.code;
589
+ break;
590
+ case 'lock_acquired':
591
+ handleLockAcquired(event);
592
+ break;
593
+ case 'lock_released':
594
+ handleLockReleased(event);
595
+ break;
596
+ case 'iteration_completed':
597
+ case 'change_classified':
598
+ case 'gate_progress':
599
+ // Forward to agent store (handled by subscriber)
600
+ break;
601
+ }
602
+ // Notify subscribed handlers
603
+ const handlers = eventHandlers.get(event.type);
604
+ if (handlers) {
605
+ handlers.forEach((handler) => handler(event));
606
+ }
607
+ const wildcardHandlers = eventHandlers.get('*');
608
+ if (wildcardHandlers) {
609
+ wildcardHandlers.forEach((handler) => handler(event));
610
+ }
611
+ }
612
+ function handleSnapshot(event) {
613
+ setFiles(event.files);
614
+ setLocks(event.locks);
615
+ state.version = event.version;
616
+ }
617
+ function handleUpdate(event) {
618
+ if (event.files) {
619
+ event.files.forEach(updateFileInfo);
620
+ }
621
+ state.version = event.version;
622
+ }
623
+ function handleLockAcquired(event) {
624
+ setLock(event.lock);
625
+ }
626
+ function handleLockReleased(event) {
627
+ removeLock(event.path);
628
+ }
629
+ export function onEvent(type, handler) {
630
+ if (!eventHandlers.has(type)) {
631
+ eventHandlers.set(type, new Set());
632
+ }
633
+ eventHandlers.get(type).add(handler);
634
+ return () => {
635
+ eventHandlers.get(type)?.delete(handler);
636
+ };
637
+ }
638
+ // ============================================================================
639
+ // Helper Functions
640
+ // ============================================================================
641
+ function buildTree(files) {
642
+ // For now, return sorted files
643
+ // A full tree implementation would nest children under directories
644
+ return [...files].sort((a, b) => {
645
+ // Directories first, then alphabetically
646
+ if (a.isDir !== b.isDir)
647
+ return a.isDir ? -1 : 1;
648
+ return a.path.localeCompare(b.path);
649
+ });
650
+ }
651
+ export function clearError() {
652
+ state.error = null;
653
+ state.errorCode = null;
654
+ }
655
+ export function reset() {
656
+ disconnect();
657
+ state.workspace = null;
658
+ state.files = [];
659
+ state.fileMap.clear();
660
+ state.dirtyFiles.clear();
661
+ state.locks.clear();
662
+ state.lockStatuses.clear();
663
+ state.pendingLocks.clear();
664
+ state.activeTransactions.clear();
665
+ state.transactionHistory = [];
666
+ state.error = null;
667
+ state.errorCode = null;
668
+ state.structuredError = null;
669
+ state.version = 0;
670
+ state.reconnectAttempts = 0;
671
+ state.pendingRetries.clear();
672
+ state.conflictQueue = [];
673
+ currentUserId = null;
674
+ }
675
+ // ============================================================================
676
+ // Sync Status
677
+ // ============================================================================
678
+ export function setSyncing(syncing) {
679
+ state.syncing = syncing;
680
+ }