@wonderwhy-er/desktop-commander 0.2.38 → 0.2.39

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 (364) hide show
  1. package/README.md +53 -2
  2. package/dist/handlers/filesystem-handlers.d.ts +5 -0
  3. package/dist/handlers/filesystem-handlers.js +14 -2
  4. package/dist/remote-device/desktop-commander-integration.js +1 -1
  5. package/dist/search-manager.js +31 -38
  6. package/dist/server.js +8 -3
  7. package/dist/terminal-manager.js +4 -2
  8. package/dist/tools/edit.js +34 -1
  9. package/dist/tools/filesystem.js +91 -3
  10. package/dist/tools/improved-process-tools.js +2 -1
  11. package/dist/ui/config-editor/app.js +840 -0
  12. package/dist/ui/config-editor/array-modal.d.ts +19 -0
  13. package/dist/ui/config-editor/array-modal.js +185 -0
  14. package/dist/ui/config-editor/config-editor-runtime.js +65 -14096
  15. package/dist/ui/config-editor/main.js +2 -0
  16. package/dist/ui/config-editor/src/App.d.ts +43 -0
  17. package/dist/ui/config-editor/src/components/layout.d.ts +4 -0
  18. package/dist/ui/config-editor/src/components/layout.js +83 -0
  19. package/dist/ui/config-editor/src/components/toolbar.d.ts +1 -0
  20. package/dist/ui/config-editor/src/components/toolbar.js +21 -0
  21. package/dist/ui/config-editor/src/config-values.d.ts +6 -0
  22. package/dist/ui/config-editor/src/config-values.js +61 -0
  23. package/dist/ui/config-editor/src/contracts.d.ts +14 -0
  24. package/dist/ui/config-editor/src/contracts.js +3 -0
  25. package/dist/ui/config-editor/src/directory-browser.d.ts +6 -0
  26. package/dist/ui/config-editor/src/directory-browser.js +71 -0
  27. package/dist/ui/config-editor/src/layout.d.ts +5 -0
  28. package/dist/ui/config-editor/src/layout.js +90 -0
  29. package/dist/ui/config-editor/src/parsing.d.ts +5 -0
  30. package/dist/ui/config-editor/src/parsing.js +50 -0
  31. package/dist/ui/config-editor/src/toolbar.d.ts +1 -0
  32. package/dist/ui/config-editor/src/toolbar.js +18 -0
  33. package/dist/ui/config-editor/src/types.d.ts +17 -0
  34. package/dist/ui/config-editor/src/types.js +3 -0
  35. package/dist/ui/config-editor/src/utils/config-values.d.ts +9 -0
  36. package/dist/ui/config-editor/src/utils/config-values.js +61 -0
  37. package/dist/ui/config-editor/src/utils/directory-browser.d.ts +31 -0
  38. package/dist/ui/config-editor/src/utils/directory-browser.js +201 -0
  39. package/dist/ui/config-editor/src/utils/parsing.d.ts +8 -0
  40. package/dist/ui/config-editor/src/utils/parsing.js +50 -0
  41. package/dist/ui/config-editor/styles.css +2 -1
  42. package/dist/ui/file-preview/{src/app.d.ts → app.d.ts} +1 -1
  43. package/dist/ui/file-preview/app.js +2020 -0
  44. package/dist/ui/file-preview/components/code-viewer.d.ts +6 -0
  45. package/dist/ui/file-preview/components/code-viewer.js +73 -0
  46. package/dist/ui/file-preview/components/highlighting.d.ts +2 -0
  47. package/dist/ui/file-preview/components/highlighting.js +54 -0
  48. package/dist/ui/file-preview/components/html-renderer.d.ts +5 -0
  49. package/dist/ui/file-preview/components/html-renderer.js +47 -0
  50. package/dist/ui/file-preview/components/markdown-renderer.d.ts +1 -0
  51. package/dist/ui/file-preview/components/markdown-renderer.js +67 -0
  52. package/dist/ui/file-preview/components/toolbar.d.ts +6 -0
  53. package/dist/ui/file-preview/components/toolbar.js +75 -0
  54. package/dist/ui/file-preview/image-preview.d.ts +3 -0
  55. package/dist/ui/file-preview/image-preview.js +21 -0
  56. package/dist/ui/file-preview/main.js +5 -0
  57. package/dist/ui/file-preview/markdown/editor.d.ts +36 -0
  58. package/dist/ui/file-preview/markdown/editor.js +643 -0
  59. package/dist/ui/file-preview/markdown/linking.d.ts +9 -0
  60. package/dist/ui/file-preview/markdown/linking.js +210 -0
  61. package/dist/ui/file-preview/markdown/outline.d.ts +7 -0
  62. package/dist/ui/file-preview/markdown/outline.js +40 -0
  63. package/dist/ui/file-preview/markdown/preview.d.ts +8 -0
  64. package/dist/ui/file-preview/markdown/preview.js +33 -0
  65. package/dist/ui/file-preview/markdown/slugify.d.ts +3 -0
  66. package/dist/ui/file-preview/markdown/slugify.js +31 -0
  67. package/dist/ui/file-preview/markdown/toc.d.ts +11 -0
  68. package/dist/ui/file-preview/markdown/toc.js +75 -0
  69. package/dist/ui/file-preview/markdown/utils.d.ts +1 -0
  70. package/dist/ui/file-preview/markdown/utils.js +15 -0
  71. package/dist/ui/file-preview/markdown/workspace-controller.d.ts +25 -0
  72. package/dist/ui/file-preview/markdown/workspace-controller.js +40 -0
  73. package/dist/ui/file-preview/preview-runtime.js +384 -26533
  74. package/dist/ui/file-preview/shared/preview-file-types.d.ts +1 -1
  75. package/dist/ui/file-preview/src/App.d.ts +4 -0
  76. package/dist/ui/file-preview/src/App.js +564 -0
  77. package/dist/ui/file-preview/src/components/CodeViewer.d.ts +6 -0
  78. package/dist/ui/file-preview/src/components/CodeViewer.js +60 -0
  79. package/dist/ui/file-preview/src/components/HtmlRenderer.d.ts +8 -0
  80. package/dist/ui/file-preview/src/components/HtmlRenderer.js +45 -0
  81. package/dist/ui/file-preview/src/components/MarkdownRenderer.d.ts +1 -0
  82. package/dist/ui/file-preview/src/components/MarkdownRenderer.js +15 -0
  83. package/dist/ui/file-preview/src/components/editor-toolbar.d.ts +15 -0
  84. package/dist/ui/file-preview/src/components/editor-toolbar.js +384 -0
  85. package/dist/ui/file-preview/src/components/markdown-editor.d.ts +29 -0
  86. package/dist/ui/file-preview/src/components/markdown-editor.js +535 -0
  87. package/dist/ui/file-preview/src/components/markdown-renderer.js +47 -9
  88. package/dist/ui/file-preview/src/directory-controller.d.ts +8 -0
  89. package/dist/ui/file-preview/src/directory-controller.js +233 -0
  90. package/dist/ui/file-preview/src/document-layout.d.ts +20 -0
  91. package/dist/ui/file-preview/src/document-layout.js +109 -0
  92. package/dist/ui/file-preview/src/document-outline.d.ts +17 -0
  93. package/dist/ui/file-preview/src/document-outline.js +97 -0
  94. package/dist/ui/file-preview/src/document-workspace.d.ts +19 -0
  95. package/dist/ui/file-preview/src/document-workspace.js +33 -0
  96. package/dist/ui/file-preview/src/file-type-handlers.d.ts +10 -0
  97. package/dist/ui/file-preview/src/file-type-handlers.js +98 -0
  98. package/dist/ui/file-preview/src/host/external-actions.d.ts +19 -0
  99. package/dist/ui/file-preview/src/host/external-actions.js +94 -0
  100. package/dist/ui/file-preview/src/host/selection-context.d.ts +9 -0
  101. package/dist/ui/file-preview/src/host/selection-context.js +106 -0
  102. package/dist/ui/file-preview/src/markdown/block-merge.d.ts +25 -0
  103. package/dist/ui/file-preview/src/markdown/block-merge.js +86 -0
  104. package/dist/ui/file-preview/src/markdown/conflict-dialog.d.ts +40 -0
  105. package/dist/ui/file-preview/src/markdown/conflict-dialog.js +163 -0
  106. package/dist/ui/file-preview/src/markdown/controller.d.ts +38 -0
  107. package/dist/ui/file-preview/src/markdown/controller.js +921 -0
  108. package/dist/ui/file-preview/src/markdown/editor.d.ts +35 -0
  109. package/dist/ui/file-preview/src/markdown/editor.js +691 -0
  110. package/dist/ui/file-preview/src/markdown/link-modal.d.ts +13 -0
  111. package/dist/ui/file-preview/src/markdown/link-modal.js +213 -0
  112. package/dist/ui/file-preview/src/markdown/linking.d.ts +16 -0
  113. package/dist/ui/file-preview/src/markdown/linking.js +228 -0
  114. package/dist/ui/file-preview/src/markdown/outline.d.ts +2 -0
  115. package/dist/ui/file-preview/src/markdown/outline.js +16 -0
  116. package/dist/ui/file-preview/src/markdown/parser.d.ts +30 -0
  117. package/dist/ui/file-preview/src/markdown/parser.js +38 -0
  118. package/dist/ui/file-preview/src/markdown/preview.d.ts +1 -0
  119. package/dist/ui/file-preview/src/markdown/preview.js +20 -0
  120. package/dist/ui/file-preview/src/markdown/raw-editor.d.ts +8 -0
  121. package/dist/ui/file-preview/src/markdown/raw-editor.js +61 -0
  122. package/dist/ui/file-preview/src/markdown/selection-toolbar.d.ts +14 -0
  123. package/dist/ui/file-preview/src/markdown/selection-toolbar.js +128 -0
  124. package/dist/ui/file-preview/src/markdown/slugify.d.ts +3 -0
  125. package/dist/ui/file-preview/src/markdown/slugify.js +31 -0
  126. package/dist/ui/file-preview/src/markdown/toc.d.ts +11 -0
  127. package/dist/ui/file-preview/src/markdown/toc.js +75 -0
  128. package/dist/ui/file-preview/src/markdown/utils.d.ts +1 -0
  129. package/dist/ui/file-preview/src/markdown/utils.js +15 -0
  130. package/dist/ui/file-preview/src/markdown-workspace/editor.d.ts +36 -0
  131. package/dist/ui/file-preview/src/markdown-workspace/editor.js +643 -0
  132. package/dist/ui/file-preview/src/markdown-workspace/linking.d.ts +9 -0
  133. package/dist/ui/file-preview/src/markdown-workspace/linking.js +210 -0
  134. package/dist/ui/file-preview/src/markdown-workspace/outline.d.ts +7 -0
  135. package/dist/ui/file-preview/src/markdown-workspace/outline.js +40 -0
  136. package/dist/ui/file-preview/src/markdown-workspace/preview.d.ts +8 -0
  137. package/dist/ui/file-preview/src/markdown-workspace/preview.js +33 -0
  138. package/dist/ui/file-preview/src/markdown-workspace/slugify.d.ts +3 -0
  139. package/dist/ui/file-preview/src/markdown-workspace/slugify.js +31 -0
  140. package/dist/ui/file-preview/src/markdown-workspace/toc.d.ts +11 -0
  141. package/dist/ui/file-preview/src/markdown-workspace/toc.js +75 -0
  142. package/dist/ui/file-preview/src/markdown-workspace/utils.d.ts +1 -0
  143. package/dist/ui/file-preview/src/markdown-workspace/utils.js +15 -0
  144. package/dist/ui/file-preview/src/markdown-workspace/workspace-controller.d.ts +25 -0
  145. package/dist/ui/file-preview/src/markdown-workspace/workspace-controller.js +40 -0
  146. package/dist/ui/file-preview/src/model.d.ts +34 -0
  147. package/dist/ui/file-preview/src/panel-actions.d.ts +17 -0
  148. package/dist/ui/file-preview/src/panel-actions.js +182 -0
  149. package/dist/ui/file-preview/src/path-utils.d.ts +6 -0
  150. package/dist/ui/file-preview/src/path-utils.js +64 -0
  151. package/dist/ui/file-preview/src/payload-utils.d.ts +11 -0
  152. package/dist/ui/file-preview/src/payload-utils.js +94 -0
  153. package/dist/ui/file-preview/styles.css +1066 -233
  154. package/dist/ui/file-preview/types.d.ts +1 -0
  155. package/dist/ui/server-integration.d.ts +13 -0
  156. package/dist/ui/server-integration.js +31 -0
  157. package/dist/ui/shared/ToolHeader.d.ts +9 -0
  158. package/dist/ui/shared/ToolHeader.js +29 -0
  159. package/dist/ui/shared/app-bootstrap.d.ts +9 -0
  160. package/dist/ui/shared/app-bootstrap.js +15 -0
  161. package/dist/ui/shared/guards.d.ts +1 -0
  162. package/dist/ui/shared/guards.js +3 -0
  163. package/dist/ui/shared/host-lifecycle.d.ts +1 -0
  164. package/dist/ui/shared/host-lifecycle.js +8 -2
  165. package/dist/ui/shared/widget-state.d.ts +6 -1
  166. package/dist/ui/shared/widget-state.js +102 -4
  167. package/dist/utils/files/base.d.ts +2 -0
  168. package/dist/utils/open-browser.js +1 -1
  169. package/dist/utils/ui-call-context.d.ts +8 -0
  170. package/dist/utils/ui-call-context.js +72 -0
  171. package/dist/version.d.ts +1 -1
  172. package/dist/version.js +1 -1
  173. package/package.json +6 -1
  174. package/dist/data/spec-kit-prompts.json +0 -123
  175. package/dist/handlers/macos-control-handlers.d.ts +0 -16
  176. package/dist/handlers/macos-control-handlers.js +0 -81
  177. package/dist/handlers/node-handlers.d.ts +0 -6
  178. package/dist/handlers/node-handlers.js +0 -73
  179. package/dist/handlers/test-crash-handler.d.ts +0 -11
  180. package/dist/handlers/test-crash-handler.js +0 -26
  181. package/dist/http-index.d.ts +0 -45
  182. package/dist/http-index.js +0 -51
  183. package/dist/http-server-auto-tunnel.js +0 -667
  184. package/dist/http-server-named-tunnel.d.ts +0 -2
  185. package/dist/http-server-named-tunnel.js +0 -167
  186. package/dist/http-server-tunnel.d.ts +0 -2
  187. package/dist/http-server-tunnel.js +0 -111
  188. package/dist/http-server.d.ts +0 -2
  189. package/dist/http-server.js +0 -270
  190. package/dist/index-oauth.d.ts +0 -2
  191. package/dist/index-oauth.js +0 -201
  192. package/dist/lib.d.ts +0 -10
  193. package/dist/lib.js +0 -10
  194. package/dist/oauth/auth-middleware.d.ts +0 -20
  195. package/dist/oauth/auth-middleware.js +0 -62
  196. package/dist/oauth/index.d.ts +0 -3
  197. package/dist/oauth/index.js +0 -3
  198. package/dist/oauth/oauth-manager.d.ts +0 -80
  199. package/dist/oauth/oauth-manager.js +0 -179
  200. package/dist/oauth/oauth-routes.d.ts +0 -3
  201. package/dist/oauth/oauth-routes.js +0 -377
  202. package/dist/oauth/provider.d.ts +0 -22
  203. package/dist/oauth/provider.js +0 -124
  204. package/dist/oauth/server.d.ts +0 -18
  205. package/dist/oauth/server.js +0 -160
  206. package/dist/oauth/types.d.ts +0 -54
  207. package/dist/oauth/types.js +0 -2
  208. package/dist/remote-device/templates/auth-success.d.ts +0 -1
  209. package/dist/remote-device/templates/auth-success.js +0 -30
  210. package/dist/setup.log +0 -275
  211. package/dist/test-setup.js +0 -14
  212. package/dist/tools/docx/builders/html-builder.d.ts +0 -17
  213. package/dist/tools/docx/builders/html-builder.js +0 -92
  214. package/dist/tools/docx/builders/image.d.ts +0 -14
  215. package/dist/tools/docx/builders/image.js +0 -84
  216. package/dist/tools/docx/builders/index.d.ts +0 -11
  217. package/dist/tools/docx/builders/index.js +0 -11
  218. package/dist/tools/docx/builders/markdown-builder.d.ts +0 -2
  219. package/dist/tools/docx/builders/markdown-builder.js +0 -260
  220. package/dist/tools/docx/builders/paragraph.d.ts +0 -12
  221. package/dist/tools/docx/builders/paragraph.js +0 -29
  222. package/dist/tools/docx/builders/table.d.ts +0 -10
  223. package/dist/tools/docx/builders/table.js +0 -138
  224. package/dist/tools/docx/builders/utils.d.ts +0 -5
  225. package/dist/tools/docx/builders/utils.js +0 -18
  226. package/dist/tools/docx/constants.d.ts +0 -32
  227. package/dist/tools/docx/constants.js +0 -61
  228. package/dist/tools/docx/converters/markdown-to-html.d.ts +0 -17
  229. package/dist/tools/docx/converters/markdown-to-html.js +0 -111
  230. package/dist/tools/docx/create.d.ts +0 -21
  231. package/dist/tools/docx/create.js +0 -386
  232. package/dist/tools/docx/dom.d.ts +0 -139
  233. package/dist/tools/docx/dom.js +0 -448
  234. package/dist/tools/docx/errors.d.ts +0 -28
  235. package/dist/tools/docx/errors.js +0 -48
  236. package/dist/tools/docx/extractors/images.d.ts +0 -14
  237. package/dist/tools/docx/extractors/images.js +0 -40
  238. package/dist/tools/docx/extractors/metadata.d.ts +0 -14
  239. package/dist/tools/docx/extractors/metadata.js +0 -64
  240. package/dist/tools/docx/extractors/sections.d.ts +0 -14
  241. package/dist/tools/docx/extractors/sections.js +0 -61
  242. package/dist/tools/docx/html.d.ts +0 -17
  243. package/dist/tools/docx/html.js +0 -111
  244. package/dist/tools/docx/index.d.ts +0 -10
  245. package/dist/tools/docx/index.js +0 -10
  246. package/dist/tools/docx/markdown.d.ts +0 -84
  247. package/dist/tools/docx/markdown.js +0 -507
  248. package/dist/tools/docx/modify.d.ts +0 -28
  249. package/dist/tools/docx/modify.js +0 -271
  250. package/dist/tools/docx/operations/handlers/index.d.ts +0 -39
  251. package/dist/tools/docx/operations/handlers/index.js +0 -152
  252. package/dist/tools/docx/operations/html-manipulator.d.ts +0 -24
  253. package/dist/tools/docx/operations/html-manipulator.js +0 -352
  254. package/dist/tools/docx/operations/index.d.ts +0 -14
  255. package/dist/tools/docx/operations/index.js +0 -61
  256. package/dist/tools/docx/operations/operation-handlers.d.ts +0 -3
  257. package/dist/tools/docx/operations/operation-handlers.js +0 -67
  258. package/dist/tools/docx/operations/preprocessor.d.ts +0 -14
  259. package/dist/tools/docx/operations/preprocessor.js +0 -44
  260. package/dist/tools/docx/operations/xml-replacer.d.ts +0 -9
  261. package/dist/tools/docx/operations/xml-replacer.js +0 -35
  262. package/dist/tools/docx/operations.d.ts +0 -13
  263. package/dist/tools/docx/operations.js +0 -13
  264. package/dist/tools/docx/ops/delete-paragraph-at-body-index.d.ts +0 -11
  265. package/dist/tools/docx/ops/delete-paragraph-at-body-index.js +0 -23
  266. package/dist/tools/docx/ops/header-replace-text-exact.d.ts +0 -13
  267. package/dist/tools/docx/ops/header-replace-text-exact.js +0 -55
  268. package/dist/tools/docx/ops/index.d.ts +0 -17
  269. package/dist/tools/docx/ops/index.js +0 -70
  270. package/dist/tools/docx/ops/insert-image-after-text.d.ts +0 -24
  271. package/dist/tools/docx/ops/insert-image-after-text.js +0 -128
  272. package/dist/tools/docx/ops/insert-paragraph-after-text.d.ts +0 -12
  273. package/dist/tools/docx/ops/insert-paragraph-after-text.js +0 -74
  274. package/dist/tools/docx/ops/insert-table-after-text.d.ts +0 -19
  275. package/dist/tools/docx/ops/insert-table-after-text.js +0 -57
  276. package/dist/tools/docx/ops/replace-hyperlink-url.d.ts +0 -12
  277. package/dist/tools/docx/ops/replace-hyperlink-url.js +0 -37
  278. package/dist/tools/docx/ops/replace-paragraph-at-body-index.d.ts +0 -9
  279. package/dist/tools/docx/ops/replace-paragraph-at-body-index.js +0 -25
  280. package/dist/tools/docx/ops/replace-paragraph-text-exact.d.ts +0 -21
  281. package/dist/tools/docx/ops/replace-paragraph-text-exact.js +0 -36
  282. package/dist/tools/docx/ops/replace-table-cell-text.d.ts +0 -25
  283. package/dist/tools/docx/ops/replace-table-cell-text.js +0 -85
  284. package/dist/tools/docx/ops/set-color-for-paragraph-exact.d.ts +0 -9
  285. package/dist/tools/docx/ops/set-color-for-paragraph-exact.js +0 -24
  286. package/dist/tools/docx/ops/set-color-for-style.d.ts +0 -13
  287. package/dist/tools/docx/ops/set-color-for-style.js +0 -31
  288. package/dist/tools/docx/ops/set-paragraph-style-at-body-index.d.ts +0 -8
  289. package/dist/tools/docx/ops/set-paragraph-style-at-body-index.js +0 -57
  290. package/dist/tools/docx/ops/table-set-cell-text.d.ts +0 -9
  291. package/dist/tools/docx/ops/table-set-cell-text.js +0 -40
  292. package/dist/tools/docx/parsers/image-extractor.d.ts +0 -18
  293. package/dist/tools/docx/parsers/image-extractor.js +0 -61
  294. package/dist/tools/docx/parsers/index.d.ts +0 -9
  295. package/dist/tools/docx/parsers/index.js +0 -9
  296. package/dist/tools/docx/parsers/paragraph-parser.d.ts +0 -2
  297. package/dist/tools/docx/parsers/paragraph-parser.js +0 -88
  298. package/dist/tools/docx/parsers/table-parser.d.ts +0 -9
  299. package/dist/tools/docx/parsers/table-parser.js +0 -72
  300. package/dist/tools/docx/parsers/xml-parser.d.ts +0 -25
  301. package/dist/tools/docx/parsers/xml-parser.js +0 -71
  302. package/dist/tools/docx/parsers/zip-reader.d.ts +0 -23
  303. package/dist/tools/docx/parsers/zip-reader.js +0 -52
  304. package/dist/tools/docx/read.d.ts +0 -27
  305. package/dist/tools/docx/read.js +0 -308
  306. package/dist/tools/docx/relationships.d.ts +0 -22
  307. package/dist/tools/docx/relationships.js +0 -76
  308. package/dist/tools/docx/structure.d.ts +0 -25
  309. package/dist/tools/docx/structure.js +0 -102
  310. package/dist/tools/docx/styled-html-parser.d.ts +0 -23
  311. package/dist/tools/docx/styled-html-parser.js +0 -1262
  312. package/dist/tools/docx/types.d.ts +0 -213
  313. package/dist/tools/docx/types.js +0 -5
  314. package/dist/tools/docx/utils/escaping.d.ts +0 -13
  315. package/dist/tools/docx/utils/escaping.js +0 -26
  316. package/dist/tools/docx/utils/images.d.ts +0 -9
  317. package/dist/tools/docx/utils/images.js +0 -26
  318. package/dist/tools/docx/utils/index.d.ts +0 -12
  319. package/dist/tools/docx/utils/index.js +0 -17
  320. package/dist/tools/docx/utils/markdown.d.ts +0 -13
  321. package/dist/tools/docx/utils/markdown.js +0 -32
  322. package/dist/tools/docx/utils/paths.d.ts +0 -15
  323. package/dist/tools/docx/utils/paths.js +0 -27
  324. package/dist/tools/docx/utils/versioning.d.ts +0 -25
  325. package/dist/tools/docx/utils/versioning.js +0 -55
  326. package/dist/tools/docx/utils.d.ts +0 -101
  327. package/dist/tools/docx/utils.js +0 -299
  328. package/dist/tools/docx/validate.d.ts +0 -33
  329. package/dist/tools/docx/validate.js +0 -49
  330. package/dist/tools/docx/validators.d.ts +0 -13
  331. package/dist/tools/docx/validators.js +0 -40
  332. package/dist/tools/docx/write.d.ts +0 -17
  333. package/dist/tools/docx/write.js +0 -88
  334. package/dist/tools/docx/xml-view-test.js +0 -63
  335. package/dist/tools/docx/xml-view.d.ts +0 -56
  336. package/dist/tools/docx/xml-view.js +0 -169
  337. package/dist/tools/docx/zip.d.ts +0 -21
  338. package/dist/tools/docx/zip.js +0 -35
  339. package/dist/tools/macos-control/ax-adapter.d.ts +0 -55
  340. package/dist/tools/macos-control/ax-adapter.js +0 -438
  341. package/dist/tools/macos-control/cdp-adapter.d.ts +0 -23
  342. package/dist/tools/macos-control/cdp-adapter.js +0 -402
  343. package/dist/tools/macos-control/orchestrator.d.ts +0 -77
  344. package/dist/tools/macos-control/orchestrator.js +0 -136
  345. package/dist/tools/macos-control/role-aliases.d.ts +0 -5
  346. package/dist/tools/macos-control/role-aliases.js +0 -34
  347. package/dist/tools/macos-control/types.d.ts +0 -129
  348. package/dist/tools/pdf-processor.d.ts +0 -1
  349. package/dist/tools/pdf-processor.js +0 -3
  350. package/dist/tools/search.d.ts +0 -32
  351. package/dist/tools/search.js +0 -202
  352. package/dist/ui/file-preview/src/app.js +0 -714
  353. package/dist/utils/crash-logger.d.ts +0 -18
  354. package/dist/utils/crash-logger.js +0 -44
  355. package/dist/utils/dedent.d.ts +0 -8
  356. package/dist/utils/dedent.js +0 -38
  357. /package/dist/ui/config-editor/{src/app.d.ts → app.d.ts} +0 -0
  358. /package/dist/{http-server-auto-tunnel.d.ts → ui/config-editor/main.d.ts} +0 -0
  359. /package/dist/ui/config-editor/src/{app.js → App.js} +0 -0
  360. /package/dist/{test-docx.d.ts → ui/file-preview/main.d.ts} +0 -0
  361. /package/dist/ui/file-preview/src/components/{toolbar.d.ts → Toolbar.d.ts} +0 -0
  362. /package/dist/ui/file-preview/src/components/{toolbar.js → Toolbar.js} +0 -0
  363. /package/dist/{tools/docx/xml-view-test.d.ts → ui/file-preview/src/model.js} +0 -0
  364. /package/dist/{tools/macos-control → ui/file-preview}/types.js +0 -0
@@ -0,0 +1,921 @@
1
+ import { attachDocumentOutline, renderDocumentOutline } from '../document-outline.js';
2
+ import { getDocumentFullscreenAvailability, parseReadRange, shouldAutoLoadDocumentOnEnterFullscreen, stripReadStatusLine } from '../document-workspace.js';
3
+ import { assertSuccessfulEditBlockResult, extractRenderPayload, extractToolText } from '../payload-utils.js';
4
+ import { getAncestorDirectories, getParentDirectory, toPosixRelativePath } from '../path-utils.js';
5
+ import { mountMarkdownEditor, renderMarkdownEditorShell } from './editor.js';
6
+ import { resolveMarkdownLink } from './linking.js';
7
+ import { extractMarkdownOutline } from './outline.js';
8
+ import { getRenderedMarkdownCopyText } from './preview.js';
9
+ import { slugifyMarkdownHeading } from './slugify.js';
10
+ import { getFileExtensionForAnalytics } from '../payload-utils.js';
11
+ function areOutlineItemsEqual(left, right) {
12
+ if (left.length !== right.length) {
13
+ return false;
14
+ }
15
+ return left.every((item, index) => {
16
+ const other = right[index];
17
+ return item.id === other.id
18
+ && item.text === other.text
19
+ && item.level === other.level
20
+ && item.line === other.line;
21
+ });
22
+ }
23
+ function splitListingLines(text) {
24
+ return text.split('\n').map((line) => line.trim()).filter(Boolean);
25
+ }
26
+ function parseFileSearchResults(text) {
27
+ return text.split('\n')
28
+ .map((line) => line.trim())
29
+ .filter((line) => line.startsWith('📁 '))
30
+ .map((line) => line.slice(3).trim());
31
+ }
32
+ function stripMarkdownExtension(filePath) {
33
+ return filePath.replace(/\.md$/i, '');
34
+ }
35
+ function computeDiffHunks(oldLines, newLines) {
36
+ const oldLength = oldLines.length;
37
+ const newLength = newLines.length;
38
+ if (oldLength * newLength > 1000000) {
39
+ return [{ oldStart: 0, oldEnd: oldLength, newStart: 0, newEnd: newLength }];
40
+ }
41
+ const dp = Array.from({ length: oldLength + 1 }, () => Array(newLength + 1).fill(0));
42
+ for (let i = 1; i <= oldLength; i += 1) {
43
+ for (let j = 1; j <= newLength; j += 1) {
44
+ dp[i][j] = oldLines[i - 1] === newLines[j - 1]
45
+ ? dp[i - 1][j - 1] + 1
46
+ : Math.max(dp[i - 1][j], dp[i][j - 1]);
47
+ }
48
+ }
49
+ const matches = [];
50
+ let oldIndex = oldLength;
51
+ let newIndex = newLength;
52
+ while (oldIndex > 0 && newIndex > 0) {
53
+ if (oldLines[oldIndex - 1] === newLines[newIndex - 1]) {
54
+ matches.unshift([oldIndex - 1, newIndex - 1]);
55
+ oldIndex -= 1;
56
+ newIndex -= 1;
57
+ }
58
+ else if (dp[oldIndex - 1][newIndex] >= dp[oldIndex][newIndex - 1]) {
59
+ oldIndex -= 1;
60
+ }
61
+ else {
62
+ newIndex -= 1;
63
+ }
64
+ }
65
+ const hunks = [];
66
+ let previousOld = 0;
67
+ let previousNew = 0;
68
+ for (const [matchOld, matchNew] of matches) {
69
+ if (matchOld > previousOld || matchNew > previousNew) {
70
+ hunks.push({ oldStart: previousOld, oldEnd: matchOld, newStart: previousNew, newEnd: matchNew });
71
+ }
72
+ previousOld = matchOld + 1;
73
+ previousNew = matchNew + 1;
74
+ }
75
+ if (previousOld < oldLength || previousNew < newLength) {
76
+ hunks.push({ oldStart: previousOld, oldEnd: oldLength, newStart: previousNew, newEnd: newLength });
77
+ }
78
+ return hunks;
79
+ }
80
+ function mergeCloseHunks(hunks, minGap) {
81
+ if (hunks.length <= 1) {
82
+ return hunks;
83
+ }
84
+ const merged = [{ ...hunks[0] }];
85
+ for (let index = 1; index < hunks.length; index += 1) {
86
+ const previous = merged[merged.length - 1];
87
+ const current = hunks[index];
88
+ if (current.oldStart - previous.oldEnd < minGap) {
89
+ previous.oldEnd = current.oldEnd;
90
+ previous.newEnd = current.newEnd;
91
+ continue;
92
+ }
93
+ merged.push({ ...current });
94
+ }
95
+ return merged;
96
+ }
97
+ function computeEditBlocks(oldText, newText) {
98
+ if (oldText === newText) {
99
+ return [];
100
+ }
101
+ const oldLines = oldText.split('\n');
102
+ const newLines = newText.split('\n');
103
+ const hunks = computeDiffHunks(oldLines, newLines);
104
+ if (hunks.length === 0) {
105
+ return [];
106
+ }
107
+ const context = 3;
108
+ const merged = mergeCloseHunks(hunks, context * 2 + 1);
109
+ const totalChanged = merged.reduce((sum, hunk) => sum + (hunk.oldEnd - hunk.oldStart), 0);
110
+ if (totalChanged > oldLines.length * 0.7) {
111
+ return [{ old_string: oldText, new_string: newText }];
112
+ }
113
+ return merged.map((hunk) => {
114
+ const contextBefore = Math.max(0, hunk.oldStart - context);
115
+ const contextAfter = Math.min(oldLines.length, hunk.oldEnd + context);
116
+ const oldBlock = oldLines.slice(contextBefore, contextAfter).join('\n');
117
+ const newBlock = [
118
+ ...oldLines.slice(contextBefore, hunk.oldStart),
119
+ ...newLines.slice(hunk.newStart, hunk.newEnd),
120
+ ...oldLines.slice(hunk.oldEnd, contextAfter),
121
+ ].join('\n');
122
+ return { old_string: oldBlock, new_string: newBlock };
123
+ }).filter((block) => block.old_string !== block.new_string);
124
+ }
125
+ function isToolErrorResult(value) {
126
+ return typeof value === 'object' && value !== null;
127
+ }
128
+ function isMissingFileErrorResult(result) {
129
+ if (!isToolErrorResult(result) || result.isError !== true) {
130
+ return false;
131
+ }
132
+ const message = extractToolText(result)?.toLowerCase() ?? '';
133
+ return message.includes('not found')
134
+ || message.includes('no such file')
135
+ || message.includes('enoent');
136
+ }
137
+ export function createMarkdownController(dependencies) {
138
+ let workspaceState;
139
+ let markdownEditorHandle;
140
+ let markdownTocHandle;
141
+ let autosaveTimer = null;
142
+ const AUTOSAVE_DEBOUNCE_MS = 1000;
143
+ function scheduleAutosave() {
144
+ if (autosaveTimer !== null) {
145
+ clearTimeout(autosaveTimer);
146
+ }
147
+ autosaveTimer = setTimeout(() => {
148
+ autosaveTimer = null;
149
+ void saveDocument();
150
+ }, AUTOSAVE_DEBOUNCE_MS);
151
+ }
152
+ function cancelAutosave() {
153
+ if (autosaveTimer !== null) {
154
+ clearTimeout(autosaveTimer);
155
+ autosaveTimer = null;
156
+ }
157
+ }
158
+ function disposeHandles() {
159
+ cancelAutosave();
160
+ markdownEditorHandle?.destroy();
161
+ markdownEditorHandle = undefined;
162
+ markdownTocHandle?.dispose();
163
+ markdownTocHandle = undefined;
164
+ }
165
+ function clear() {
166
+ workspaceState = undefined;
167
+ disposeHandles();
168
+ }
169
+ function readPayloadContent(payload) {
170
+ return stripReadStatusLine(payload.content);
171
+ }
172
+ function syncStateFromContent(state, content, options = {}) {
173
+ const nextDraftContent = options.keepDraft ? state.draftContent : content;
174
+ state.sourceContent = content;
175
+ state.fullDocumentContent = content;
176
+ state.draftContent = nextDraftContent;
177
+ state.outline = extractMarkdownOutline(content);
178
+ state.dirty = nextDraftContent !== content;
179
+ state.fileDeleted = false;
180
+ if (!state.outline.some((item) => item.id === state.activeHeadingId)) {
181
+ state.activeHeadingId = state.outline[0]?.id ?? null;
182
+ }
183
+ }
184
+ async function callReadFile(filePath, length, offset) {
185
+ const rawResult = await dependencies.callTool?.('read_file', {
186
+ path: filePath,
187
+ ...(typeof length === 'number' ? { offset: offset ?? 0, length } : {}),
188
+ });
189
+ return { rawResult, payload: extractRenderPayload(rawResult) ?? null };
190
+ }
191
+ async function readPayload(filePath, length, offset) {
192
+ return (await callReadFile(filePath, length, offset)).payload;
193
+ }
194
+ async function ensureCompletePayload(payload) {
195
+ const range = parseReadRange(payload.content);
196
+ if (!range?.isPartial) {
197
+ return payload;
198
+ }
199
+ return (await readPayload(payload.filePath, range.totalLines)) ?? payload;
200
+ }
201
+ async function readCompletePayload(filePath) {
202
+ const payload = await readPayload(filePath);
203
+ if (!payload) {
204
+ return null;
205
+ }
206
+ return ensureCompletePayload(payload);
207
+ }
208
+ function getState(payload) {
209
+ const cleanedContent = stripReadStatusLine(payload.content);
210
+ if (!workspaceState || workspaceState.filePath !== payload.filePath || workspaceState.sourceContent !== cleanedContent) {
211
+ const outline = extractMarkdownOutline(cleanedContent);
212
+ workspaceState = {
213
+ filePath: payload.filePath,
214
+ sourceContent: cleanedContent,
215
+ fullDocumentContent: cleanedContent,
216
+ draftContent: cleanedContent,
217
+ outline,
218
+ mode: 'edit',
219
+ dirty: false,
220
+ activeHeadingId: outline[0]?.id ?? null,
221
+ pendingAnchor: null,
222
+ notice: null,
223
+ error: null,
224
+ saving: false,
225
+ loadingDocument: false,
226
+ editorView: 'markdown',
227
+ editorScrollTop: 0,
228
+ saveIndicator: 'idle',
229
+ fileDeleted: false,
230
+ };
231
+ }
232
+ return workspaceState;
233
+ }
234
+ function isUndoAvailable(state) {
235
+ return state.draftContent !== state.fullDocumentContent;
236
+ }
237
+ function buildBody(payload) {
238
+ const state = getState(payload);
239
+ const outline = state.outline;
240
+ const isFullscreen = dependencies.getCurrentDisplayMode() === 'fullscreen';
241
+ const tocHtml = isFullscreen ? renderDocumentOutline(outline, state.activeHeadingId) : '';
242
+ if (!state.activeHeadingId && outline.length > 0) {
243
+ state.activeHeadingId = outline[0].id;
244
+ }
245
+ const notice = [state.error, state.notice]
246
+ .find((value) => typeof value === 'string' && value.trim().length > 0);
247
+ return {
248
+ notice,
249
+ html: `
250
+ <div class="panel-content markdown-content markdown-content--workspace">
251
+ <div class="markdown-workspace markdown-workspace--edit${tocHtml ? ' markdown-workspace--with-toc' : ''}">
252
+ ${tocHtml}
253
+ <section class="markdown-workspace-main markdown-workspace-main--editor">
254
+ ${renderMarkdownEditorShell({ view: state.editorView })}
255
+ </section>
256
+ </div>
257
+ </div>
258
+ `,
259
+ };
260
+ }
261
+ async function resolveLinkSearchRoot(filePath) {
262
+ const ancestors = getAncestorDirectories(filePath);
263
+ const markers = new Set(['[DIR] .git', '[DIR] .obsidian', '[FILE] package.json', '[FILE] pnpm-workspace.yaml', '[FILE] turbo.json']);
264
+ for (const ancestor of ancestors) {
265
+ try {
266
+ const result = await dependencies.callTool?.('list_directory', { path: ancestor, depth: 1 });
267
+ const text = extractToolText(result) ?? '';
268
+ const entries = splitListingLines(text);
269
+ if (entries.some((entry) => markers.has(entry))) {
270
+ return ancestor;
271
+ }
272
+ }
273
+ catch {
274
+ // Ignore and continue up the tree.
275
+ }
276
+ }
277
+ return getParentDirectory(filePath);
278
+ }
279
+ async function searchLinkTargets(filePath, query) {
280
+ const trimmedQuery = query.trim();
281
+ if (trimmedQuery.length === 0) {
282
+ return [];
283
+ }
284
+ const rootPath = await resolveLinkSearchRoot(filePath);
285
+ const result = await dependencies.callTool?.('start_search', {
286
+ path: rootPath,
287
+ pattern: trimmedQuery,
288
+ searchType: 'files',
289
+ filePattern: '*.md',
290
+ maxResults: 20,
291
+ earlyTermination: false,
292
+ literalSearch: true,
293
+ });
294
+ const text = extractToolText(result) ?? '';
295
+ const filePaths = parseFileSearchResults(text);
296
+ const currentDirectory = getParentDirectory(filePath);
297
+ return filePaths.map((targetPath) => {
298
+ const normalized = targetPath.replace(/\\/g, '/');
299
+ const fileName = normalized.split('/').pop() ?? normalized;
300
+ const title = stripMarkdownExtension(fileName);
301
+ const relativePath = toPosixRelativePath(currentDirectory, normalized);
302
+ const wikiPath = stripMarkdownExtension(relativePath.startsWith('./') ? relativePath.slice(2) : relativePath);
303
+ return {
304
+ path: normalized,
305
+ title,
306
+ wikiPath,
307
+ relativePath,
308
+ };
309
+ });
310
+ }
311
+ async function loadLinkHeadings(currentPayloadPath, targetPath) {
312
+ if (targetPath === currentPayloadPath && workspaceState) {
313
+ return workspaceState.outline.map((item) => ({ id: item.id, text: item.text }));
314
+ }
315
+ const payload = await readCompletePayload(targetPath);
316
+ if (!payload) {
317
+ return [];
318
+ }
319
+ return extractMarkdownOutline(readPayloadContent(payload)).map((item) => ({ id: item.id, text: item.text }));
320
+ }
321
+ function findHeading(anchor) {
322
+ const trimmedAnchor = anchor.trim();
323
+ if (!trimmedAnchor) {
324
+ return null;
325
+ }
326
+ return document.getElementById(trimmedAnchor) ?? document.getElementById(slugifyMarkdownHeading(trimmedAnchor));
327
+ }
328
+ function scrollHeadingIntoView(anchor) {
329
+ const heading = findHeading(anchor);
330
+ if (!heading) {
331
+ return false;
332
+ }
333
+ const scrollParents = [];
334
+ let current = heading.parentElement;
335
+ while (current) {
336
+ const style = window.getComputedStyle(current);
337
+ const overflowY = style.overflowY;
338
+ const isScrollable = (overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay')
339
+ && current.scrollHeight > current.clientHeight;
340
+ if (isScrollable) {
341
+ scrollParents.push(current);
342
+ }
343
+ current = current.parentElement;
344
+ }
345
+ heading.scrollIntoView({ block: 'start', inline: 'nearest' });
346
+ for (const parent of scrollParents) {
347
+ const parentRect = parent.getBoundingClientRect();
348
+ const headingRect = heading.getBoundingClientRect();
349
+ const nextTop = Math.max(parent.scrollTop + (headingRect.top - parentRect.top) - 24, 0);
350
+ parent.scrollTop = nextTop;
351
+ }
352
+ const rootScroller = document.scrollingElement;
353
+ if (rootScroller) {
354
+ const rootRectTop = heading.getBoundingClientRect().top;
355
+ const nextRootTop = Math.max(rootScroller.scrollTop + rootRectTop - 24, 0);
356
+ rootScroller.scrollTop = nextRootTop;
357
+ }
358
+ heading.setAttribute('tabindex', '-1');
359
+ heading.focus({ preventScroll: true });
360
+ if (workspaceState) {
361
+ workspaceState.activeHeadingId = heading.id || slugifyMarkdownHeading(anchor);
362
+ }
363
+ return true;
364
+ }
365
+ function applyPendingAnchor() {
366
+ const pendingAnchor = workspaceState?.pendingAnchor;
367
+ if (!workspaceState || !pendingAnchor) {
368
+ return;
369
+ }
370
+ workspaceState.pendingAnchor = null;
371
+ if (!scrollHeadingIntoView(pendingAnchor)) {
372
+ workspaceState.error = `Heading not found: ${pendingAnchor}`;
373
+ dependencies.rerender();
374
+ }
375
+ }
376
+ function flashSaveStatus(label, statusClass, timeoutMs, beforeClear) {
377
+ dependencies.updateSaveStatus(label, statusClass);
378
+ window.setTimeout(() => {
379
+ if (beforeClear && !beforeClear()) {
380
+ return;
381
+ }
382
+ dependencies.updateSaveStatus('', '');
383
+ }, timeoutMs);
384
+ }
385
+ async function refreshFromDisk(payload) {
386
+ try {
387
+ const range = parseReadRange(payload.content);
388
+ const { rawResult, payload: freshPayload } = range?.isPartial
389
+ ? await callReadFile(payload.filePath, range.toLine - range.fromLine + 1, range.readOffset)
390
+ : await callReadFile(payload.filePath);
391
+ if (!freshPayload) {
392
+ if (isMissingFileErrorResult(rawResult)) {
393
+ if (workspaceState) {
394
+ workspaceState.fileDeleted = true;
395
+ }
396
+ dependencies.updateSaveStatus('File deleted', 'saved');
397
+ }
398
+ return;
399
+ }
400
+ const freshContent = readPayloadContent(freshPayload);
401
+ const currentContent = readPayloadContent(payload);
402
+ if (freshContent === currentContent) {
403
+ return;
404
+ }
405
+ // refreshFromDisk only runs at mount (no file watcher in this app),
406
+ // so disk-vs-payload mismatch means the host sent a stale cached
407
+ // payload — trust the disk read and reload silently.
408
+ dependencies.storePayloadOverride(freshPayload);
409
+ workspaceState = undefined;
410
+ dependencies.rerender();
411
+ }
412
+ catch {
413
+ // Silently fall back to host payload.
414
+ }
415
+ }
416
+ async function loadFullDocument(payload, options = {}) {
417
+ const state = getState(payload);
418
+ const range = parseReadRange(payload.content);
419
+ if (!range?.isPartial) {
420
+ if (options.keepEditMode) {
421
+ state.mode = 'edit';
422
+ state.editorView = 'markdown';
423
+ state.notice = null;
424
+ state.error = null;
425
+ state.draftContent = state.sourceContent;
426
+ state.dirty = false;
427
+ dependencies.rerender();
428
+ }
429
+ return;
430
+ }
431
+ state.loadingDocument = true;
432
+ state.notice = 'Loading full document…';
433
+ state.error = null;
434
+ dependencies.rerender();
435
+ try {
436
+ const nextPayload = await readPayload(payload.filePath, range.totalLines);
437
+ if (!nextPayload) {
438
+ state.error = 'Failed to load the full document.';
439
+ state.notice = null;
440
+ state.loadingDocument = false;
441
+ dependencies.rerender();
442
+ return;
443
+ }
444
+ dependencies.syncPayload?.(nextPayload);
445
+ const nextState = getState(nextPayload);
446
+ nextState.loadingDocument = false;
447
+ nextState.notice = null;
448
+ nextState.error = null;
449
+ syncStateFromContent(nextState, nextState.sourceContent);
450
+ if (options.keepEditMode) {
451
+ nextState.mode = 'edit';
452
+ nextState.editorView = 'markdown';
453
+ dependencies.rerender();
454
+ }
455
+ }
456
+ catch {
457
+ state.loadingDocument = false;
458
+ state.notice = null;
459
+ state.error = 'Failed to load the full document.';
460
+ dependencies.rerender();
461
+ }
462
+ }
463
+ async function navigateLink(payload, href) {
464
+ const state = getState(payload);
465
+ if (state.dirty) {
466
+ const shouldDiscard = window.confirm('Discard unsaved changes and follow this link?');
467
+ if (!shouldDiscard) {
468
+ return;
469
+ }
470
+ }
471
+ const resolvedLink = resolveMarkdownLink(payload.filePath, href);
472
+ state.notice = null;
473
+ state.error = null;
474
+ if (resolvedLink.kind === 'external' && resolvedLink.url) {
475
+ const opened = await dependencies.openExternalLink?.(resolvedLink.url);
476
+ if (!opened) {
477
+ try {
478
+ window.open(resolvedLink.url, '_blank', 'noopener');
479
+ }
480
+ catch { /* sandbox may block */ }
481
+ }
482
+ return;
483
+ }
484
+ if (resolvedLink.kind === 'anchor' && resolvedLink.anchor) {
485
+ if (!scrollHeadingIntoView(resolvedLink.anchor) && workspaceState) {
486
+ workspaceState.error = `Heading not found: ${resolvedLink.anchor}`;
487
+ dependencies.rerender();
488
+ }
489
+ return;
490
+ }
491
+ if (resolvedLink.kind === 'file' && resolvedLink.targetPath) {
492
+ const hostHandled = await dependencies.openExternalLink?.(resolvedLink.targetPath);
493
+ if (hostHandled) {
494
+ return;
495
+ }
496
+ const nextPayload = await readPayload(resolvedLink.targetPath);
497
+ if (!nextPayload) {
498
+ if (workspaceState) {
499
+ workspaceState.error = `Unable to open ${resolvedLink.targetPath}.`;
500
+ dependencies.rerender();
501
+ }
502
+ return;
503
+ }
504
+ dependencies.syncPayload?.(nextPayload);
505
+ const nextState = getState(nextPayload);
506
+ nextState.pendingAnchor = resolvedLink.anchor ?? null;
507
+ nextState.error = null;
508
+ nextState.notice = null;
509
+ dependencies.rerender();
510
+ }
511
+ }
512
+ async function requestEditMode(payload) {
513
+ const state = getState(payload);
514
+ state.error = null;
515
+ state.notice = null;
516
+ if (shouldAutoLoadDocumentOnEnterFullscreen(payload.content)) {
517
+ await loadFullDocument(payload, { keepEditMode: true });
518
+ return;
519
+ }
520
+ state.mode = 'edit';
521
+ state.draftContent = state.fullDocumentContent;
522
+ state.dirty = false;
523
+ state.editorView = 'markdown';
524
+ dependencies.setExpanded(true);
525
+ dependencies.rerender();
526
+ }
527
+ async function requestFullscreen() {
528
+ const fullscreenAvailability = getDocumentFullscreenAvailability({
529
+ availableDisplayModes: dependencies.getAvailableDisplayModes(),
530
+ });
531
+ if (!fullscreenAvailability.canFullscreen) {
532
+ return false;
533
+ }
534
+ const nextMode = await dependencies.requestDisplayMode?.('fullscreen');
535
+ return nextMode === 'fullscreen';
536
+ }
537
+ function revertEditing() {
538
+ if (!workspaceState) {
539
+ return;
540
+ }
541
+ const filePath = workspaceState.filePath;
542
+ workspaceState.draftContent = workspaceState.fullDocumentContent;
543
+ workspaceState.dirty = false;
544
+ workspaceState.error = null;
545
+ workspaceState.notice = null;
546
+ dependencies.rerender();
547
+ flashSaveStatus('Reverted', 'saved', 1500);
548
+ dependencies.trackUiEvent?.('markdown_reverted', {
549
+ file_extension: getFileExtensionForAnalytics(filePath),
550
+ });
551
+ }
552
+ async function saveDocument() {
553
+ if (!workspaceState || workspaceState.saving || !workspaceState.dirty || workspaceState.fileDeleted) {
554
+ return;
555
+ }
556
+ cancelAutosave();
557
+ const state = workspaceState;
558
+ state.saving = true;
559
+ state.saveIndicator = 'saving';
560
+ state.error = null;
561
+ state.notice = null;
562
+ try {
563
+ const blocks = computeEditBlocks(state.fullDocumentContent, state.draftContent);
564
+ if (blocks.length === 0) {
565
+ state.saving = false;
566
+ state.saveIndicator = 'idle';
567
+ state.dirty = false;
568
+ return;
569
+ }
570
+ // Try each hunk independently. Previously the loop threw on the
571
+ // first soft-failure, which left earlier hunks already written to
572
+ // disk while the UI claimed "Save failed" — making it look like the
573
+ // editor had silently overwritten external changes. Now we track
574
+ // per-hunk outcomes so we can give the user an honest accounting.
575
+ let appliedCount = 0;
576
+ const skippedHunks = [];
577
+ let lastHardError = null;
578
+ for (const block of blocks) {
579
+ try {
580
+ const editResult = await dependencies.callTool?.('edit_block', {
581
+ file_path: state.filePath,
582
+ old_string: block.old_string,
583
+ new_string: block.new_string,
584
+ expected_replacements: 1,
585
+ });
586
+ assertSuccessfulEditBlockResult(editResult);
587
+ appliedCount++;
588
+ }
589
+ catch (hunkError) {
590
+ // A per-hunk failure is almost always "old_string not on
591
+ // disk" because the file changed there. Record it and keep
592
+ // going so other hunks still get their chance.
593
+ skippedHunks.push(block);
594
+ lastHardError = hunkError;
595
+ }
596
+ }
597
+ if (skippedHunks.length > 0) {
598
+ // Partial (or total) failure. Let the catch branch take it —
599
+ // throw an error carrying counts so the catch can decide how
600
+ // to communicate and how to resync baseline vs. disk.
601
+ const err = new Error(`${appliedCount} of ${blocks.length} edit${blocks.length === 1 ? '' : 's'} applied; ` +
602
+ `${skippedHunks.length} could not land because the text changed on disk.`);
603
+ err.appliedCount = appliedCount;
604
+ err.skippedCount = skippedHunks.length;
605
+ err.totalCount = blocks.length;
606
+ err.underlyingError = lastHardError;
607
+ throw err;
608
+ }
609
+ state.fullDocumentContent = state.draftContent;
610
+ state.sourceContent = state.draftContent;
611
+ state.outline = extractMarkdownOutline(state.sourceContent);
612
+ state.dirty = false;
613
+ state.saving = false;
614
+ state.saveIndicator = 'saved';
615
+ if (!state.outline.some((item) => item.id === state.activeHeadingId)) {
616
+ state.activeHeadingId = state.outline[0]?.id ?? null;
617
+ }
618
+ const savedContent = state.draftContent;
619
+ const currentPayload = dependencies.getCurrentPayload();
620
+ if (currentPayload) {
621
+ const statusLineMatch = currentPayload.content.match(/^(\[Reading [^\]]+\]\r?\n(?:\r?\n)?)/);
622
+ const statusLine = statusLineMatch?.[1] ?? '';
623
+ dependencies.storePayloadOverride({ ...currentPayload, content: statusLine + savedContent });
624
+ }
625
+ const revert = document.getElementById('revert-markdown');
626
+ if (revert) {
627
+ revert.disabled = !isUndoAvailable(state);
628
+ }
629
+ flashSaveStatus('Saved', 'saved', 1800, () => {
630
+ if (!state.dirty && !state.saving) {
631
+ state.saveIndicator = 'idle';
632
+ return true;
633
+ }
634
+ return false;
635
+ });
636
+ dependencies.trackUiEvent?.('markdown_saved', {
637
+ file_extension: getFileExtensionForAnalytics(state.filePath),
638
+ blocks: blocks.length,
639
+ });
640
+ }
641
+ catch (error) {
642
+ state.saving = false;
643
+ state.saveIndicator = 'idle';
644
+ // Pull per-hunk counts from the synthetic error thrown by the save
645
+ // loop, when this catch is reached via a partial/total hunk failure.
646
+ const errWithCounts = error;
647
+ const appliedCount = typeof errWithCounts.appliedCount === 'number' ? errWithCounts.appliedCount : 0;
648
+ const skippedCount = typeof errWithCounts.skippedCount === 'number' ? errWithCounts.skippedCount : 0;
649
+ const totalCount = typeof errWithCounts.totalCount === 'number' ? errWithCounts.totalCount : 0;
650
+ const isPartialSuccess = appliedCount > 0 && skippedCount > 0;
651
+ const isTotalFailure = appliedCount === 0 && skippedCount > 0;
652
+ const freshPayload = await readCompletePayload(state.filePath).catch(() => null);
653
+ let reloadedFromDisk = false;
654
+ let freshContentForDialog = null;
655
+ if (freshPayload) {
656
+ const freshContent = readPayloadContent(freshPayload);
657
+ if (freshContent !== state.fullDocumentContent) {
658
+ syncStateFromContent(state, freshContent, { keepDraft: true });
659
+ dependencies.storePayloadOverride(freshPayload);
660
+ reloadedFromDisk = true;
661
+ freshContentForDialog = freshContent;
662
+ }
663
+ }
664
+ state.notice = null;
665
+ if (isPartialSuccess) {
666
+ // Some hunks landed on disk, some didn't. The user would be
667
+ // misled by a "Save failed" message — and would be misled by
668
+ // a conflict dialog claiming nothing was saved. Tell them the
669
+ // truth: N saved, M skipped, with the skipped lines preserved
670
+ // in their draft so they can re-try or edit around the
671
+ // external change.
672
+ state.error = (`${appliedCount} of ${totalCount} edit${totalCount === 1 ? '' : 's'} saved. ` +
673
+ `${skippedCount} ${skippedCount === 1 ? 'edit' : 'edits'} did not apply because that text changed on disk — ` +
674
+ `your draft still has them; save again to merge.`);
675
+ dependencies.rerender();
676
+ flashSaveStatus('Saved (partial)', 'saved', 3000);
677
+ dependencies.trackUiEvent?.('markdown_save_partial', {
678
+ file_extension: getFileExtensionForAnalytics(state.filePath),
679
+ applied: appliedCount,
680
+ skipped: skippedCount,
681
+ total: totalCount,
682
+ });
683
+ }
684
+ else if (isTotalFailure && reloadedFromDisk && dependencies.showConflictDialog && freshContentForDialog !== null) {
685
+ // No hunks landed and disk has changed. Genuine conflict — show
686
+ // the modal so the user can pick keep-mine / use-disk.
687
+ const savedFreshContent = freshContentForDialog;
688
+ const normalized = state.filePath.replace(/\\/g, '/');
689
+ const displayName = normalized.split('/').pop() || state.filePath;
690
+ state.error = null;
691
+ dependencies.rerender();
692
+ dependencies.trackUiEvent?.('markdown_save_conflict_shown', {
693
+ file_extension: getFileExtensionForAnalytics(state.filePath),
694
+ });
695
+ dependencies.showConflictDialog({
696
+ fileName: displayName,
697
+ onUseDiskVersion: () => {
698
+ if (workspaceState === state) {
699
+ syncStateFromContent(state, savedFreshContent);
700
+ dependencies.rerender();
701
+ }
702
+ dependencies.trackUiEvent?.('markdown_save_conflict_resolved', {
703
+ file_extension: getFileExtensionForAnalytics(state.filePath),
704
+ action: 'use_disk',
705
+ });
706
+ },
707
+ onSaveMyChanges: () => {
708
+ dependencies.trackUiEvent?.('markdown_save_conflict_resolved', {
709
+ file_extension: getFileExtensionForAnalytics(state.filePath),
710
+ action: 'save_mine',
711
+ });
712
+ // Re-run saveDocument. computeEditBlocks will diff against
713
+ // the fresh sourceContent that keepDraft: true left in place,
714
+ // so hunks the user actually modified win over disk on
715
+ // those specific lines and disk-only changes elsewhere are
716
+ // preserved.
717
+ void saveDocument();
718
+ },
719
+ onCancel: () => {
720
+ if (workspaceState === state) {
721
+ state.error = 'File changed on disk. Save again to merge your edits, or reopen to discard them.';
722
+ dependencies.rerender();
723
+ }
724
+ dependencies.trackUiEvent?.('markdown_save_conflict_resolved', {
725
+ file_extension: getFileExtensionForAnalytics(state.filePath),
726
+ action: 'dismissed',
727
+ });
728
+ },
729
+ });
730
+ }
731
+ else {
732
+ // Fallback: unexpected error that isn't a per-hunk soft-fail
733
+ // (e.g. read_file failure during resync, or an exception before
734
+ // the save loop started). Use a generic inline message.
735
+ state.error = reloadedFromDisk
736
+ ? 'File changed on disk. Save again to merge your edits, or reopen the file to discard them.'
737
+ : error instanceof Error ? error.message : 'Save failed.';
738
+ dependencies.rerender();
739
+ flashSaveStatus('Save failed', 'saving', 3000);
740
+ }
741
+ dependencies.trackUiEvent?.('markdown_save_failed', {
742
+ file_extension: getFileExtensionForAnalytics(state.filePath),
743
+ reloaded_from_disk: reloadedFromDisk,
744
+ applied: appliedCount,
745
+ skipped: skippedCount,
746
+ total: totalCount,
747
+ });
748
+ }
749
+ }
750
+ function setEditorView(payload, view) {
751
+ const state = getState(payload);
752
+ const wrapper = document.querySelector('.panel-content-wrapper');
753
+ state.editorScrollTop = wrapper?.scrollTop ?? 0;
754
+ const previousView = state.editorView;
755
+ state.editorView = view;
756
+ state.notice = null;
757
+ state.error = null;
758
+ dependencies.rerender();
759
+ if (previousView !== view) {
760
+ dependencies.trackUiEvent?.('markdown_view_toggled', {
761
+ file_extension: getFileExtensionForAnalytics(payload.filePath),
762
+ view,
763
+ });
764
+ }
765
+ if (typeof state.editorScrollTop === 'number') {
766
+ window.requestAnimationFrame(() => {
767
+ const nextWrapper = document.querySelector('.panel-content-wrapper');
768
+ if (nextWrapper) {
769
+ nextWrapper.scrollTop = state.editorScrollTop;
770
+ }
771
+ });
772
+ }
773
+ }
774
+ function attachHandlers(payload) {
775
+ const state = getState(payload);
776
+ const wrapper = document.querySelector('.panel-content-wrapper');
777
+ const outline = state.outline;
778
+ const fileExtension = getFileExtensionForAnalytics(payload.filePath);
779
+ let editStartedFired = false;
780
+ {
781
+ const editorRoot = document.getElementById('markdown-editor-root');
782
+ if (editorRoot) {
783
+ markdownEditorHandle = mountMarkdownEditor({
784
+ target: editorRoot,
785
+ value: state.draftContent,
786
+ view: state.editorView,
787
+ initialScrollTop: state.editorScrollTop,
788
+ currentFilePath: payload.filePath,
789
+ searchLinks: (query) => searchLinkTargets(payload.filePath, query),
790
+ loadHeadings: (targetPath) => loadLinkHeadings(payload.filePath, targetPath),
791
+ onChange: (value) => {
792
+ state.draftContent = value;
793
+ state.dirty = value !== state.fullDocumentContent;
794
+ if (state.dirty && !editStartedFired) {
795
+ editStartedFired = true;
796
+ dependencies.trackUiEvent?.('markdown_edit_started', {
797
+ file_extension: fileExtension,
798
+ view: state.editorView,
799
+ });
800
+ }
801
+ if (state.dirty) {
802
+ scheduleAutosave();
803
+ }
804
+ const nextOutline = extractMarkdownOutline(value);
805
+ if (!areOutlineItemsEqual(state.outline, nextOutline)) {
806
+ state.outline = nextOutline;
807
+ if (!state.outline.some((item) => item.id === state.activeHeadingId)) {
808
+ state.activeHeadingId = state.outline[0]?.id ?? null;
809
+ }
810
+ markdownTocHandle?.refresh(state.outline, state.activeHeadingId);
811
+ }
812
+ if (state.dirty && state.saveIndicator === 'saved') {
813
+ state.saveIndicator = 'idle';
814
+ }
815
+ const revert = document.getElementById('revert-markdown');
816
+ if (revert) {
817
+ revert.disabled = !isUndoAvailable(state);
818
+ }
819
+ },
820
+ onBlur: () => {
821
+ cancelAutosave();
822
+ void saveDocument();
823
+ },
824
+ });
825
+ markdownEditorHandle.focus();
826
+ }
827
+ const revertButton = document.getElementById('revert-markdown');
828
+ revertButton?.addEventListener('click', () => {
829
+ revertEditing();
830
+ });
831
+ const rawModeButton = document.getElementById('markdown-mode-raw');
832
+ rawModeButton?.addEventListener('click', () => {
833
+ setEditorView(payload, 'raw');
834
+ });
835
+ const previewModeButton = document.getElementById('markdown-mode-markdown');
836
+ previewModeButton?.addEventListener('click', () => {
837
+ setEditorView(payload, 'markdown');
838
+ });
839
+ }
840
+ const expandButton = document.getElementById('expand-fullscreen');
841
+ expandButton?.addEventListener('click', () => {
842
+ void requestFullscreen();
843
+ });
844
+ if (wrapper) {
845
+ wrapper.addEventListener('click', (event) => {
846
+ const target = event.target;
847
+ const link = target?.closest('a[href]');
848
+ if (!link || !link.closest('.markdown-doc')) {
849
+ return;
850
+ }
851
+ const href = link.getAttribute('href');
852
+ if (!href) {
853
+ return;
854
+ }
855
+ event.preventDefault();
856
+ void navigateLink(payload, href);
857
+ });
858
+ }
859
+ const tocShell = document.querySelector('.document-outline-shell');
860
+ if (tocShell && wrapper) {
861
+ markdownTocHandle = attachDocumentOutline({
862
+ shell: tocShell,
863
+ outline,
864
+ scrollContainer: wrapper,
865
+ onSelect: (headingId) => {
866
+ const selectedHeading = state.outline.find((item) => item.id === headingId);
867
+ if (selectedHeading && typeof selectedHeading.line === 'number') {
868
+ markdownEditorHandle?.revealLine(selectedHeading.line, selectedHeading.id);
869
+ state.activeHeadingId = selectedHeading.id;
870
+ }
871
+ },
872
+ }) ?? undefined;
873
+ }
874
+ window.setTimeout(() => {
875
+ applyPendingAnchor();
876
+ }, 0);
877
+ }
878
+ function getCopyText(payload) {
879
+ const state = getState(payload);
880
+ const source = state.draftContent;
881
+ return state.editorView === 'raw'
882
+ ? source
883
+ : (getRenderedMarkdownCopyText(source) || source);
884
+ }
885
+ async function handleInlineExitFromFullscreen(originalPayload) {
886
+ const wasDirty = workspaceState?.saveIndicator === 'saved' || workspaceState?.dirty;
887
+ if (workspaceState) {
888
+ workspaceState.notice = null;
889
+ workspaceState.editorView = 'markdown';
890
+ }
891
+ if (wasDirty && originalPayload) {
892
+ const range = parseReadRange(originalPayload.content);
893
+ if (range?.isPartial) {
894
+ const freshPayload = await readPayload(originalPayload.filePath, range.toLine - range.fromLine + 1, range.readOffset);
895
+ if (freshPayload) {
896
+ return freshPayload;
897
+ }
898
+ }
899
+ }
900
+ return undefined;
901
+ }
902
+ return {
903
+ attachHandlers,
904
+ buildBody,
905
+ clear,
906
+ disposeHandles,
907
+ ensureCompletePayload,
908
+ getCopyText,
909
+ getState,
910
+ handleInlineExitFromFullscreen,
911
+ isUndoAvailable,
912
+ readCompletePayload,
913
+ readPayload,
914
+ readPayloadContent,
915
+ refreshFromDisk,
916
+ requestEditMode,
917
+ requestFullscreen,
918
+ saveDocument,
919
+ setEditorView,
920
+ };
921
+ }