collabmd 0.1.45 → 0.1.47

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 (356) hide show
  1. package/README.md +40 -19
  2. package/bin/collabmd.js +27 -1
  3. package/dist/client/assets/CodeMirrorEditor-UCMRAMCV-BPH4FJkv.js +1 -0
  4. package/dist/client/assets/{abnfDiagram-N423BO3Z-CUMTFJhI.js → abnfDiagram-VCTEODGH-BNDLzJ7L.js} +1 -1
  5. package/dist/client/assets/architecture-7GRP2DOG-BMOPAkUC.js +1 -0
  6. package/dist/client/assets/{architectureDiagram-T3A2C74G-w25m71oV.js → architectureDiagram-5GKGNRK7-I39sYjHI.js} +1 -1
  7. package/dist/client/assets/base-BYODPHdS.js +1 -0
  8. package/dist/client/assets/blockDiagram-NRAW4CY4-C6DtJy5Y.js +129 -0
  9. package/dist/client/assets/browser-BKLM0ThC-IxjwuOIz-BBqQnUEb.js +1 -0
  10. package/dist/client/assets/browser-utils--H_DkyEF.js +1 -0
  11. package/dist/client/assets/c4Diagram-UCG6FXSJ-CtWtxS9_.js +38 -0
  12. package/dist/client/assets/channel-CaoKA_We.js +1 -0
  13. package/dist/client/assets/{chunk-U6XO7XAA-BFxjP5U3.js → chunk-2ZTRR5NV-C2qZJila.js} +1 -1
  14. package/dist/client/assets/{chunk-YOTPTUD7-D0vRKqYp.js → chunk-3Z5EZCMW-DMniOlPd.js} +1 -1
  15. package/dist/client/assets/{chunk-BIQX33UG-DZEsGuSn.js → chunk-5V3GS4D5-i-ZDOEnP.js} +1 -1
  16. package/dist/client/assets/{chunk-MOZMSUNE-XVucA-n6.js → chunk-6AZGARVD-BB6FqMl-.js} +1 -1
  17. package/dist/client/assets/{chunk-5JV3BV7I-Cy5pj3QY.js → chunk-6EIED4P4-DdUJkP0f.js} +1 -1
  18. package/dist/client/assets/{chunk-R7FJI6CG-Cnn3gE-Y.js → chunk-6K3QC6MW-CqVsVn-y.js} +1 -1
  19. package/dist/client/assets/{chunk-OSBZ3O6U-CrKZOUOr.js → chunk-6TQVIW2G-DKlLbNIP.js} +1 -1
  20. package/dist/client/assets/{chunk-JG7HCLWE-BYfiSaVt.js → chunk-747NJXEK-BFkZPlab.js} +1 -1
  21. package/dist/client/assets/{chunk-NSK5VX7P-TLH37VXI.js → chunk-75Z2AOVW-CdFSaPjz.js} +1 -1
  22. package/dist/client/assets/chunk-CKBBP62Z-DG26UiBv.js +2 -0
  23. package/dist/client/assets/{chunk-3NCLNEKW-Y2Jf_LI-.js → chunk-CLGD4ZFX-DQLgbQrv.js} +1 -1
  24. package/dist/client/assets/chunk-DSNCTWBM-8xkug7fQ.js +2 -0
  25. package/dist/client/assets/chunk-DU6HZSFF-COXDzJK1.js +127 -0
  26. package/dist/client/assets/{chunk-2GRJ4B5K-DhnlUv3F.js → chunk-F27PBJKO-CNHIFhjo.js} +1 -1
  27. package/dist/client/assets/{chunk-YGJVY4SA-CjSe7Bx7.js → chunk-FLYJHZSQ-Be7lKF17.js} +1 -1
  28. package/dist/client/assets/{chunk-KEIR6QF5-BICK3FdT.js → chunk-FOHPRMQF-DHwB1DNv.js} +2 -2
  29. package/dist/client/assets/{chunk-5RXB4S5H-BWrwFqMI.js → chunk-G27WJ6UU-D9-eg537.js} +2 -2
  30. package/dist/client/assets/chunk-GMAD6QVW-CbcBqPM5.js +72 -0
  31. package/dist/client/assets/{chunk-QBLGF6JB-CxAyS2mW.js → chunk-I5DQTOEV-Hm2LfiYI.js} +1 -1
  32. package/dist/client/assets/{chunk-5FCAYU7R-Dh5nylU8.js → chunk-ICYGCRZG-749o-pGJ.js} +1 -1
  33. package/dist/client/assets/{chunk-CQNSW5MT-B8S09OIh.js → chunk-IH6LHLGP-CC6I9F3i.js} +1 -1
  34. package/dist/client/assets/chunk-JQ64N6SF-DSFNH4bh.js +1 -0
  35. package/dist/client/assets/{chunk-CYSBUYHQ-C1Rf4art.js → chunk-KI3K4JFJ-Rma-0k_M.js} +1 -1
  36. package/dist/client/assets/{chunk-GF5L2VYU-DkJpO7ff.js → chunk-LCL6LL3I-DmDkVukh.js} +1 -1
  37. package/dist/client/assets/chunk-LNGE3PJU-DWpGwHrW.js +34 -0
  38. package/dist/client/assets/chunk-OBVCFTLP-BJR7u1jh.js +1 -0
  39. package/dist/client/assets/{chunk-5TONJI2A-DQOY3q1H.js → chunk-OUJLGHUK-D0j2JRSg.js} +1 -1
  40. package/dist/client/assets/chunk-P2QGCYS3-BRpqY32H.js +1 -0
  41. package/dist/client/assets/{chunk-KBJHAD2P-CB_3zWUf.js → chunk-POPQ4Y6H-Cb4SlVZF.js} +1 -1
  42. package/dist/client/assets/{chunk-4I5QYGJK-CCB2YcIH.js → chunk-PWAF6VOD-Cz8IwBB5.js} +1 -1
  43. package/dist/client/assets/chunk-R7TYR2AO-CTd0Yj98.js +10 -0
  44. package/dist/client/assets/chunk-RHFEMEQ7-CWY8cf4H.js +168 -0
  45. package/dist/client/assets/chunk-RM4UPSZO-CWRwxNWJ.js +41 -0
  46. package/dist/client/assets/{chunk-6Q2QTUOP-CPlWzBtc.js → chunk-SVP7TREG-CyHOmcac.js} +1 -1
  47. package/dist/client/assets/chunk-TEH6E4GO-DVayPj-q.js +62 -0
  48. package/dist/client/assets/{chunk-EMLP6XTP-RLD4nXpI.js → chunk-UY3FDG6J-CnmaAkG5.js} +1 -1
  49. package/dist/client/assets/{chunk-5HE753X5-Bw-yg4kO.js → chunk-XHIXRSVI-BFQ6B8BE.js} +1 -1
  50. package/dist/client/assets/classDiagram-DTDB5LWJ-7fEmn0zV.js +1 -0
  51. package/dist/client/assets/classDiagram-v2-JRS7N3AN-7fEmn0zV.js +1 -0
  52. package/dist/client/assets/comment-thread-store-CdcwrObc.js +1 -0
  53. package/dist/client/assets/comment-threads-9J6Em-_y.js +4 -0
  54. package/dist/client/assets/comment-ui-shared-DWkk9Vh3.js +7 -0
  55. package/dist/client/assets/cynefin-OW5HDTMX-7tP7QfM3.js +1 -0
  56. package/dist/client/assets/{cynefinDiagram-MW4NZA55-CJxXfJek.js → cynefinDiagram-5FMLGOSQ-CdTPi2yO.js} +1 -1
  57. package/dist/client/assets/dagre-3AP2YEHR-5gtJtd0O.js +4 -0
  58. package/dist/client/assets/dagre-PrKaheQc.js +1 -0
  59. package/dist/client/assets/{diagram-7IWD3JNH-B2d2Bm15.js → diagram-S7CK7UJ4-BudWz9WU.js} +4 -4
  60. package/dist/client/assets/{diagram-UB23O5K3-DJcKCwv9.js → diagram-UQ7AKVKN-3v4rbH-s.js} +1 -1
  61. package/dist/client/assets/{diagram-B4RE2ZJO-NP4UAwjR.js → diagram-VSXAHHWV-CQRVEZR7.js} +1 -1
  62. package/dist/client/assets/{diagram-Q27KOJAE-DylDuwor.js → diagram-VX7I27RA-C_-rj0sw.js} +1 -1
  63. package/dist/client/assets/{diagram-LBJQPF4R-D1FJQV8t.js → diagram-Z3DM3KII-B4yxavgj.js} +1 -1
  64. package/dist/client/assets/direct-engine-DdrQKpgq-CtKfQzCt.js +5 -0
  65. package/dist/client/assets/dist-73RPdcyU.js +1 -0
  66. package/dist/client/assets/dist-BJ1bJs33.js +1 -0
  67. package/dist/client/assets/{dist-CYZmbokb.js → dist-BK9QvQrc.js} +9 -9
  68. package/dist/client/assets/dist-BPHLJ09L.js +11 -0
  69. package/dist/client/assets/dist-B_yQaEv_.js +6 -0
  70. package/dist/client/assets/dist-BeajGMhO.js +1 -0
  71. package/dist/client/assets/{dist-JYEDRrPT.js → dist-Bw3VIE_v.js} +1 -1
  72. package/dist/client/assets/dist-ByrG2qBA.js +1 -0
  73. package/dist/client/assets/dist-C5NgpdG5.js +1 -0
  74. package/dist/client/assets/dist-CH-sQu3r.js +1 -0
  75. package/dist/client/assets/dist-CKS_-are.js +1 -0
  76. package/dist/client/assets/{dist-MvcV4190.js → dist-CdmKGRGr.js} +1 -1
  77. package/dist/client/assets/{dist-DGb27Dl6.js → dist-CietwTVS.js} +1 -1
  78. package/dist/client/assets/dist-D37Hyu3h.js +9 -0
  79. package/dist/client/assets/dist-D3fehAZ2.js +2 -0
  80. package/dist/client/assets/dist-D5wCC32z.js +1 -0
  81. package/dist/client/assets/{dist-DCzuX1AF.js → dist-DDUn5zTz.js} +1 -1
  82. package/dist/client/assets/dist-DGEbEfw4.js +1 -0
  83. package/dist/client/assets/dist-DVy5woUy.js +1 -0
  84. package/dist/client/assets/{dist-3bgCp7iY.js → dist-DZAAWTG-.js} +1 -1
  85. package/dist/client/assets/{dist-Drp8uu5J.js → dist-Dir5dDKZ.js} +1 -1
  86. package/dist/client/assets/dist-Dl_tpDSr.js +1 -0
  87. package/dist/client/assets/dist-DuH3ZrHo.js +1 -0
  88. package/dist/client/assets/dist-DutPuFmC.js +1 -0
  89. package/dist/client/assets/{dist-D2AQ-76g.js → dist-LQ4kDCKz.js} +1 -1
  90. package/dist/client/assets/{dist-C11hHqv_.js → dist-RKBTaQMb.js} +12 -12
  91. package/dist/client/assets/dist-Tw-Fxpeb.js +1 -0
  92. package/dist/client/assets/dist-eBx8q6Sy.js +1 -0
  93. package/dist/client/assets/{dist-DK5Gs2wa.js → dist-mj2Ht75J.js} +1 -1
  94. package/dist/client/assets/{dist-CnL7Y2sM.js → dist-netZOQtD.js} +1 -1
  95. package/dist/client/assets/{drawio-editor-ClE8jpoG.js → drawio-editor-BjrxTe3l.js} +1 -1
  96. package/dist/client/assets/drawioEditor-DUVZjSSr.js +2 -0
  97. package/dist/client/assets/{ebnfDiagram-BXEA7PRR-nw0wmA6y.js → ebnfDiagram-PWID7BFC-OXa8VEp1.js} +1 -1
  98. package/dist/client/assets/editor-session-DfZuRgB_.js +54 -0
  99. package/dist/client/assets/embedded-editor-base-DvMynhKu.css +1 -0
  100. package/dist/client/assets/embedpdf-BmE13ZZ0.js +1 -0
  101. package/dist/client/assets/embedpdf-DA4bA119-BPqZxwjc.js +2363 -0
  102. package/dist/client/assets/{en-7NFCYGKX-BrgYZAEe.js → en-SJ2YIMFF-CJBL3nkq.js} +1 -1
  103. package/dist/client/assets/erDiagram-SSCWMZ5O-C-iy6VkX.js +99 -0
  104. package/dist/client/assets/es-CAsWR6hG.js +22 -0
  105. package/dist/client/assets/eventmodeling-NTZA5JFV-BR2FzK4R.js +1 -0
  106. package/dist/client/assets/excalidraw-editor-BlcN6umJ.css +1 -0
  107. package/dist/client/assets/excalidraw-editor-D7pnUCLH.js +9 -0
  108. package/dist/client/assets/excalidraw-scene-BOben2gr.js +1 -0
  109. package/dist/client/assets/excalidrawEditor-B0jOK3lv.js +2 -0
  110. package/dist/client/assets/export-page-Bs3xE1fg.js +57 -0
  111. package/dist/client/assets/exportDocument-Dg9MjkIk.js +2 -0
  112. package/dist/client/assets/exportDocument-PnYU3v1s.css +1 -0
  113. package/dist/client/assets/file-icon-svg-CQAjlWjK.js +1 -0
  114. package/dist/client/assets/file-kind-BbqgGzpc.js +1 -0
  115. package/dist/client/assets/flowDiagram-A5DVABFB-CiW1eN8C.js +1 -0
  116. package/dist/client/assets/{ganttDiagram-PKOTCBZU-BnM97BCT.js → ganttDiagram-EL5Y4UJY-DtKHAbez.js} +1 -1
  117. package/dist/client/assets/gitGraph-4MIJSDKK-DYlXv_yJ.js +1 -0
  118. package/dist/client/assets/{gitGraphDiagram-DS77QQ5N-BQpQhjuN.js → gitGraphDiagram-WWUBYQGX-oue8fQ5o.js} +1 -1
  119. package/dist/client/assets/github-dark.min-BV82VuKq.css +1 -0
  120. package/dist/client/assets/github.min-v3dd8CNH.css +1 -0
  121. package/dist/client/assets/image-ZVKINSPA-0zXSnE7M.js +1 -0
  122. package/dist/client/assets/index-BIpgxyGZ.css +1 -0
  123. package/dist/client/assets/index-Z5sI0Bj-.js +2 -0
  124. package/dist/client/assets/info-A6RAGUB7-BlU-8_xu.js +1 -0
  125. package/dist/client/assets/{infoDiagram-6WML65LV-DYB8HCDQ.js → infoDiagram-RXCK75RN-BMzo6KIn.js} +1 -1
  126. package/dist/client/assets/{ishikawaDiagram-WSZJBQD7-Coz_P3t6.js → ishikawaDiagram-5VMMS53U-BIM3h287.js} +1 -1
  127. package/dist/client/assets/{journeyDiagram-NVQOT4AX-B6edChce.js → journeyDiagram-EYS64GPL-mF6Lh8O-.js} +1 -1
  128. package/dist/client/assets/{kanban-definition-27J2QSJJ-BFd9-cLc.js → kanban-definition-3QL26DDD-DLN2NyoR.js} +2 -2
  129. package/dist/client/assets/{line-B_S_S8kV.js → line-BfTWJmgf.js} +1 -1
  130. package/dist/client/assets/main-Dwnj9Gaq.js +903 -0
  131. package/dist/client/assets/{mermaid-parser.core-CUVdebAV.js → mermaid-parser.core-Bl0efOMt.js} +2 -2
  132. package/dist/client/assets/mermaid.core-CLZBlpIW.js +11 -0
  133. package/dist/client/assets/{mindmap-definition-FAOFIHXS-C9PQ38Al.js → mindmap-definition-FBJOCRG2-CRyHaHTk.js} +1 -1
  134. package/dist/client/assets/packet-AYTQ26CC-BSJEH9Lb.js +1 -0
  135. package/dist/client/assets/pdfium-RAgkpwfK.wasm +0 -0
  136. package/dist/client/assets/{pegDiagram-VL7TDLO6-BMZUrSaU.js → pegDiagram-XKGWAZYB-B31fKPR2.js} +1 -1
  137. package/dist/client/assets/pie-WAS4IAKB-qxv01Y8W.js +1 -0
  138. package/dist/client/assets/{pieDiagram-7S7Q4E2Y-h8kbCL9o.js → pieDiagram-E7YTZNPT-B5OZPb88.js} +1 -1
  139. package/dist/client/assets/preview-render-compiler-BApvlLLo.js +35 -0
  140. package/dist/client/assets/preview-render-compiler-BQ_3h9vD.js +1 -0
  141. package/dist/client/assets/preview-render-profile-CE-5rVTC.js +1 -0
  142. package/dist/client/assets/preview-render-worker-BmN79bVc.js +60 -0
  143. package/dist/client/assets/prod-BiYPMR1i.js +177 -0
  144. package/dist/client/assets/prod-VxdGZ5Ly.js +1 -0
  145. package/dist/client/assets/{quadrantDiagram-CIZ2JOQS-CkElyfMi.js → quadrantDiagram-AXDQQJYC-Drrw14YH.js} +1 -1
  146. package/dist/client/assets/quick-switcher-controller-ButWPmCI.js +12 -0
  147. package/dist/client/assets/radar-RG4KPBEZ-B6h6pWOs.js +1 -0
  148. package/dist/client/assets/railroad-74A4TZTK-CGtTWdGc.js +1 -0
  149. package/dist/client/assets/railroad-abnf-HS5TGJTU-Cq7y22IA.js +1 -0
  150. package/dist/client/assets/railroad-ebnf-LZEXJU2U-V0aQVv9m.js +1 -0
  151. package/dist/client/assets/railroad-peg-WCYAUIDC-BQiIsX_p.js +1 -0
  152. package/dist/client/assets/{railroadDiagram-AXF67PYL-c75gnikD.js → railroadDiagram-O6MQD6OU-BPifP0MB.js} +1 -1
  153. package/dist/client/assets/{requirementDiagram-LRYGKXZP-mNxKLuHS.js → requirementDiagram-EFPCY7ZU-CRZG6mRB.js} +2 -2
  154. package/dist/client/assets/runtime-config-BLrEKVO1.js +1 -0
  155. package/dist/client/assets/{sankeyDiagram-W5VNT64P-0OhplksI.js → sankeyDiagram-P5KCCOFB-DvarEJpo.js} +1 -1
  156. package/dist/client/assets/{sequenceDiagram-SI44F4Z6-BJ_yAocC.js → sequenceDiagram-WJ2MYXX4-B4oWlb-w.js} +1 -1
  157. package/dist/client/assets/{stateDiagram-OKZ733FA-Cc83O86s.js → stateDiagram-HBIQ2CUA-qbHIIqRZ.js} +1 -1
  158. package/dist/client/assets/stateDiagram-v2-4QOOHH4V-B--gjrit.js +1 -0
  159. package/dist/client/assets/swimlanes-XN3QIQJK-xB6CtUGj.js +1 -0
  160. package/dist/client/assets/swimlanesDiagram-VK2B7HYN-DO8XNXEg.js +8 -0
  161. package/dist/client/assets/{timeline-definition-Z64GVDOM-BWDAGTHz.js → timeline-definition-24CTP7MA-Lj_yTNlY.js} +1 -1
  162. package/dist/client/assets/treeView-Q6P3EWNA-BTTYcvrF.js +1 -0
  163. package/dist/client/assets/treemap-WGGIJYW6-Cg7tyfH8.js +1 -0
  164. package/dist/client/assets/vault-api-client-_LpV1aIn.js +6 -0
  165. package/dist/client/assets/{vennDiagram-T6HMQDX7-LOSsnlMB.js → vennDiagram-4TSXK5OY-BekRp5Y0.js} +1 -1
  166. package/dist/client/assets/wardley-WFR3VGLG-C6N1rULY.js +1 -0
  167. package/dist/client/assets/{wardleyDiagram-T6FBY63Y-BxII3foY.js → wardleyDiagram-VM6X3IG4-kdnV_Hdm.js} +1 -1
  168. package/dist/client/assets/worker-engine-m0rlHn8R-S8mpn_O0.js +17369 -0
  169. package/dist/client/assets/{xychartDiagram-ELKLHX3M-B-pTEjju.js → xychartDiagram-S5SC5T6Z-DufGwNSB.js} +3 -3
  170. package/dist/client/drawio-editor.html +1 -1
  171. package/dist/client/excalidraw-editor.html +1 -1
  172. package/dist/client/export-document.html +3 -3
  173. package/dist/client/index.html +30 -8
  174. package/docker-compose.yml +42 -0
  175. package/package.json +32 -18
  176. package/scripts/local-structurizr-compose.mjs +101 -0
  177. package/src/client/app/index.html +27 -5
  178. package/src/client/application/app-shell/comments-feature.js +21 -13
  179. package/src/client/application/app-shell/git-feature.js +14 -2
  180. package/src/client/application/app-shell/presence-feature.js +4 -3
  181. package/src/client/application/app-shell/ui-feature-shell.js +109 -2
  182. package/src/client/application/app-shell/ui-feature-toolbar.js +2 -52
  183. package/src/client/application/app-shell/workspace-feature.js +5 -1
  184. package/src/client/application/app-shell-elements.js +3 -0
  185. package/src/client/application/pdf-preview-controller.js +75 -0
  186. package/src/client/application/preview-render-compiler.js +3 -0
  187. package/src/client/application/quick-switcher-loader.js +1 -1
  188. package/src/client/application/structurizr-preview-controller.js +395 -0
  189. package/src/client/application/workspace-coordinator.js +25 -3
  190. package/src/client/application/workspace-preview-controller.js +88 -2
  191. package/src/client/assets/vendor/highlight/github-dark.min.css +2 -10
  192. package/src/client/assets/vendor/highlight/github.min.css +2 -10
  193. package/src/client/bootstrap/collabmd-app-shell.js +39 -6
  194. package/src/client/browser-utils.js +44 -0
  195. package/src/client/domain/runtime-paths.js +26 -5
  196. package/src/client/domain/structurizr-language.js +99 -0
  197. package/src/client/domain/vault-paths.js +48 -0
  198. package/src/client/excalidraw-editor.js +844 -51
  199. package/src/client/export/export-host.js +1 -1
  200. package/src/client/export/export-page.js +6 -2
  201. package/src/client/export/export-pipeline.js +60 -1
  202. package/src/client/infrastructure/browser-preferences-port.js +10 -0
  203. package/src/client/infrastructure/comment-thread-store.js +34 -13
  204. package/src/client/infrastructure/editor-session.js +10 -0
  205. package/src/client/infrastructure/editor-view-adapter.js +46 -2
  206. package/src/client/infrastructure/excalidraw-room-client.js +103 -14
  207. package/src/client/infrastructure/git-api-client.js +5 -0
  208. package/src/client/infrastructure/vault-api-client.js +26 -0
  209. package/src/client/presentation/comment-ui/comment-ui-render.js +4 -0
  210. package/src/client/presentation/comment-ui/comment-ui-shared.js +4 -0
  211. package/src/client/presentation/comment-ui-controller.js +2 -0
  212. package/src/client/presentation/drawio-embed-controller.js +9 -102
  213. package/src/client/presentation/drawio-viewer.js +118 -0
  214. package/src/client/presentation/excalidraw-embed-controller.js +81 -8
  215. package/src/client/presentation/file-action-controller.js +102 -0
  216. package/src/client/presentation/file-explorer-controller.js +0 -5
  217. package/src/client/presentation/file-explorer-view.js +51 -73
  218. package/src/client/presentation/file-icon-svg.js +40 -0
  219. package/src/client/presentation/file-tree-state.js +6 -3
  220. package/src/client/presentation/git-diff-view-controller.js +731 -3
  221. package/src/client/presentation/git-panel-controller.js +2 -2
  222. package/src/client/presentation/outline-controller.js +86 -0
  223. package/src/client/presentation/quick-switcher-controller.js +73 -22
  224. package/src/client/presentation/quick-switcher-text-search.js +1 -1
  225. package/src/client/styles/components/navigation.css +11 -1
  226. package/src/client/styles/features/diagram-preview.css +76 -0
  227. package/src/client/styles/features/diff-special.css +309 -0
  228. package/src/client/styles/features/preview-markdown.css +25 -0
  229. package/src/client/styles/features/quick-switcher.css +8 -0
  230. package/src/client/styles/foundation/themes.css +7 -0
  231. package/src/client/styles/layout/shell.css +40 -1
  232. package/src/client/styles/layout/sidebar.css +42 -4
  233. package/src/client/styles/layout/view-modes.css +1 -0
  234. package/src/client/styles/overrides/highlightjs.css +20 -4
  235. package/src/client/styles/style.css +1 -0
  236. package/src/client/styles/surfaces/excalidraw-editor.css +294 -0
  237. package/src/domain/comment-threads.js +63 -5
  238. package/src/domain/excalidraw-room-codec.js +6 -4
  239. package/src/domain/file-kind.js +35 -3
  240. package/src/server/config/env.js +79 -0
  241. package/src/server/create-app-server.js +12 -0
  242. package/src/server/domain/bases/base-definition.js +1 -1
  243. package/src/server/domain/bases/base-expression-runtime.js +1 -4
  244. package/src/server/domain/bases/base-index-snapshot-store.js +1 -4
  245. package/src/server/domain/bases/base-transform.js +1 -4
  246. package/src/server/domain/collaboration/collaboration-room.js +55 -21
  247. package/src/server/domain/comment-overview.js +15 -5
  248. package/src/server/domain/ripgrep-search-service.js +4 -0
  249. package/src/server/infrastructure/git/diff-service.js +5 -0
  250. package/src/server/infrastructure/git/git-service.js +44 -11
  251. package/src/server/infrastructure/git/history-service.js +54 -0
  252. package/src/server/infrastructure/git/image-mime.js +14 -0
  253. package/src/server/infrastructure/git/untracked-files.js +28 -1
  254. package/src/server/infrastructure/http/create-git-api-command-handler.js +3 -1
  255. package/src/server/infrastructure/http/create-git-api-query-handler.js +31 -1
  256. package/src/server/infrastructure/http/create-request-handler.js +16 -23
  257. package/src/server/infrastructure/http/create-static-handler.js +5 -2
  258. package/src/server/infrastructure/http/create-structurizr-api-handler.js +147 -0
  259. package/src/server/infrastructure/http/create-vault-api-command-handler.js +66 -4
  260. package/src/server/infrastructure/http/create-vault-api-query-handler.js +51 -3
  261. package/src/server/infrastructure/http/http-request-helpers.js +22 -0
  262. package/src/server/infrastructure/persistence/path-utils.js +1 -1
  263. package/src/server/infrastructure/persistence/vault-file-store.js +3 -0
  264. package/src/server/infrastructure/structurizr/structurizr-embed.css +43 -0
  265. package/src/server/infrastructure/structurizr/structurizr-workspace-service.js +400 -0
  266. package/src/server/infrastructure/websocket/attach-collaboration-gateway.js +4 -22
  267. package/dist/client/assets/CodeMirrorEditor-UCMRAMCV-CbSISC1w.js +0 -1
  268. package/dist/client/assets/architecture-TIHT7OUA-CsHfPErM.js +0 -1
  269. package/dist/client/assets/base-CxPEiHuy.js +0 -1
  270. package/dist/client/assets/blockDiagram-VBNYF7ZC-CipJt9Jl.js +0 -132
  271. package/dist/client/assets/browser-utils-CKtI_rZl.js +0 -1
  272. package/dist/client/assets/c4Diagram-5PPSVZJV-B5ktVx6g.js +0 -10
  273. package/dist/client/assets/channel-4Jkmq93I.js +0 -1
  274. package/dist/client/assets/chunk-7BUUIJ7U-Bb538aSH.js +0 -1
  275. package/dist/client/assets/chunk-7Z6QIM7H-BP9Pdsm9.js +0 -10
  276. package/dist/client/assets/chunk-ERSWUTIN-ME9RVNel.js +0 -41
  277. package/dist/client/assets/chunk-I66GZJ75-CVFiiffQ.js +0 -127
  278. package/dist/client/assets/chunk-J7OUQ5F2-4CEb7pvH.js +0 -2
  279. package/dist/client/assets/chunk-JQJVKLGR-cx8X_24v.js +0 -156
  280. package/dist/client/assets/chunk-QR6OTTB3-CDAA_5Ib.js +0 -62
  281. package/dist/client/assets/chunk-RYQCIY6F-D-a0zH8K.js +0 -1
  282. package/dist/client/assets/chunk-UBXNYLIW-Bhk-PbFK.js +0 -1
  283. package/dist/client/assets/chunk-W5SLKNZC-BdNSCxZ9.js +0 -1
  284. package/dist/client/assets/chunk-WRU74C26-h6_XkD3_.js +0 -70
  285. package/dist/client/assets/chunk-ZIRB5QZD-O39ZBUk3.js +0 -32
  286. package/dist/client/assets/classDiagram-JCYQIIEL-L7lsyW_w.js +0 -1
  287. package/dist/client/assets/classDiagram-v2-OCEON4UE-L7lsyW_w.js +0 -1
  288. package/dist/client/assets/comment-threads-DDegmaFO.js +0 -4
  289. package/dist/client/assets/cynefin-VYW2F7L2-Ds2Fw2Y0.js +0 -1
  290. package/dist/client/assets/dagre-Buvkdvvj.js +0 -1
  291. package/dist/client/assets/dagre-VZM6K2ZE-L1xArOGs.js +0 -4
  292. package/dist/client/assets/dist-BH87JoHx.js +0 -11
  293. package/dist/client/assets/dist-BIkPRZO7.js +0 -6
  294. package/dist/client/assets/dist-BqCsFpx4.js +0 -1
  295. package/dist/client/assets/dist-C2JHCt8U.js +0 -1
  296. package/dist/client/assets/dist-CZjTtmTu.js +0 -1
  297. package/dist/client/assets/dist-Cig4Ov_X.js +0 -1
  298. package/dist/client/assets/dist-CqQEC8gR.js +0 -1
  299. package/dist/client/assets/dist-D4Rzqod1.js +0 -1
  300. package/dist/client/assets/dist-D5sn-mwk.js +0 -9
  301. package/dist/client/assets/dist-D7G-n87v.js +0 -1
  302. package/dist/client/assets/dist-DRqOrNgN.js +0 -1
  303. package/dist/client/assets/dist-DetkMS_4.js +0 -1
  304. package/dist/client/assets/dist-DhLvC8DS.js +0 -2
  305. package/dist/client/assets/dist-Dp6FVkQ1.js +0 -1
  306. package/dist/client/assets/dist-DtbjNB28.js +0 -1
  307. package/dist/client/assets/dist-DyrYcvpo2.js +0 -1
  308. package/dist/client/assets/dist-GrygD2zE.js +0 -1
  309. package/dist/client/assets/dist-HmEpWUow.js +0 -1
  310. package/dist/client/assets/dist-RTa81Km7.js +0 -1
  311. package/dist/client/assets/drawioEditor-CXaTM9Ds.js +0 -2
  312. package/dist/client/assets/editor-session-GTwZS6Df.js +0 -16
  313. package/dist/client/assets/embedded-editor-base-CiNEc8vy.css +0 -1
  314. package/dist/client/assets/erDiagram-JOGREHBK-cYUpEKpf.js +0 -85
  315. package/dist/client/assets/eventmodeling-45OFAUF4-CLEiVx5s.js +0 -1
  316. package/dist/client/assets/excalidraw-editor-BZVM81BO.js +0 -9
  317. package/dist/client/assets/excalidraw-editor-CyGwOhLz.css +0 -1
  318. package/dist/client/assets/excalidraw-scene-C8n9CHsK.js +0 -176
  319. package/dist/client/assets/excalidrawEditor-DxJanCze.js +0 -2
  320. package/dist/client/assets/export-page-CUfws2T-.js +0 -39
  321. package/dist/client/assets/exportDocument-BCuxqXDY.js +0 -2
  322. package/dist/client/assets/exportDocument-DV-OX74E.css +0 -1
  323. package/dist/client/assets/file-kind-BKPOIRPE.js +0 -1
  324. package/dist/client/assets/flowDiagram-UKHOOZJN-D0ypP0Ez.js +0 -1
  325. package/dist/client/assets/gitGraph-TEB2WS4Q-CZFBqwW-.js +0 -1
  326. package/dist/client/assets/github-dark.min-Z5exjX_n.css +0 -1
  327. package/dist/client/assets/github.min-D2BCvnWf.css +0 -1
  328. package/dist/client/assets/image-QXRYHUNP-lfTHE3bb.js +0 -1
  329. package/dist/client/assets/index-C3YnUgMD.css +0 -1
  330. package/dist/client/assets/index-D2wXraJ1.js +0 -2
  331. package/dist/client/assets/info-DKCQHKI2--1InqQeT.js +0 -1
  332. package/dist/client/assets/main-DtiGz7uI.js +0 -795
  333. package/dist/client/assets/map-BaFkSB1l.js +0 -1
  334. package/dist/client/assets/mermaid.core-DN-lF51U.js +0 -11
  335. package/dist/client/assets/packet-7NZHBO7P-BGMXB0XL.js +0 -1
  336. package/dist/client/assets/pie-RZYD4A2V-DgYg3tCt.js +0 -1
  337. package/dist/client/assets/preview-render-compiler-BSaQO_p3.js +0 -35
  338. package/dist/client/assets/preview-render-compiler-csGM5ZFz.js +0 -1
  339. package/dist/client/assets/preview-render-profile-BZzGSg6K.js +0 -22
  340. package/dist/client/assets/preview-render-worker-B42LWvrD.js +0 -60
  341. package/dist/client/assets/quick-switcher-controller-shW_6vPY.js +0 -12
  342. package/dist/client/assets/radar-I7S5WNFK-Ag-zp_Rq.js +0 -1
  343. package/dist/client/assets/railroad-3IZDKUUU-aibJDIDV.js +0 -1
  344. package/dist/client/assets/railroad-abnf-AHOZXSZD-BBMEuvIl.js +0 -1
  345. package/dist/client/assets/railroad-ebnf-EBAXGLYW-Be1nseej.js +0 -1
  346. package/dist/client/assets/railroad-peg-LSFZ7HO6-DIvD-Eyb.js +0 -1
  347. package/dist/client/assets/room-CQLoqtMo.js +0 -1
  348. package/dist/client/assets/runtime-config-DSGmGE9V.js +0 -1
  349. package/dist/client/assets/stateDiagram-v2-UEYNNEHI-B1-BufUk.js +0 -1
  350. package/dist/client/assets/swimlanes-SLNWSIFB-oVTWEJIj.js +0 -2
  351. package/dist/client/assets/swimlanesDiagram-ULZ7WXOC-C5OTVwEu.js +0 -8
  352. package/dist/client/assets/treeView-QDETBFTQ-uHZ-3Fz8.js +0 -1
  353. package/dist/client/assets/treemap-6X3UGDF4-BnUKyy3l.js +0 -1
  354. package/dist/client/assets/vault-api-client-C6bcGwy-.js +0 -5
  355. package/dist/client/assets/wardley-OPB4EBWU-Da7TLC2F.js +0 -1
  356. package/src/server/domain/collaboration/room-persistence-controller.js +0 -80
@@ -12,6 +12,7 @@ import {
12
12
  createReferenceTargetAliasMap,
13
13
  } from '../markdown-reference-extractor.js';
14
14
  import { mapWithConcurrency } from '../../shared/async-utils.js';
15
+ import { isPlainObject } from './base-definition.js';
15
16
  import { createLinkValue, dedupeLinkValues } from './base-expression-runtime.js';
16
17
 
17
18
  const INLINE_TAG_RE = /(^|[\s(])#([A-Za-z0-9/_-]+)/g;
@@ -39,10 +40,6 @@ function createRowCacheKey(filePath, workspaceState, fileSetSignature) {
39
40
  ].join('\0');
40
41
  }
41
42
 
42
- function isPlainObject(value) {
43
- return Object.prototype.toString.call(value) === '[object Object]';
44
- }
45
-
46
43
  function normalizeTags(value) {
47
44
  if (Array.isArray(value)) {
48
45
  return value
@@ -2,15 +2,12 @@ import { dump as dumpYaml } from 'js-yaml';
2
2
 
3
3
  import {
4
4
  findView,
5
+ isPlainObject,
5
6
  normalizeBaseDefinition,
6
7
  normalizeRawDefinitionForWrite,
7
8
  parseBaseSource,
8
9
  } from './base-definition.js';
9
10
 
10
- function isPlainObject(value) {
11
- return Object.prototype.toString.call(value) === '[object Object]';
12
- }
13
-
14
11
  function cloneRawSource(source = '') {
15
12
  const parsed = parseBaseSource(source);
16
13
  return isPlainObject(parsed) ? parsed : {};
@@ -6,9 +6,9 @@ import * as decoding from 'lib0/decoding';
6
6
 
7
7
  import { MSG_AWARENESS, MSG_SYNC } from './protocol.js';
8
8
  import { CollaborationDocumentStore } from './collaboration-document-store.js';
9
- import { RoomPersistenceController } from './room-persistence-controller.js';
10
9
  import { logPerfEvent } from '../../config/perf-logging.js';
11
10
  import { populateCommentThreads, serializeCommentThreads } from '../../../domain/comment-threads.js';
11
+ import { getVaultFileKind } from '../../../domain/file-kind.js';
12
12
  import {
13
13
  EXCALIDRAW_APP_STATE_KEY,
14
14
  EXCALIDRAW_ELEMENTS_KEY,
@@ -123,7 +123,7 @@ function readAwarenessEntries(update) {
123
123
  }
124
124
 
125
125
  function isExcalidrawRoom(name) {
126
- return typeof name === 'string' && name.endsWith('.excalidraw');
126
+ return getVaultFileKind(name) === 'excalidraw';
127
127
  }
128
128
 
129
129
  function isWorkspaceRoom(name) {
@@ -214,15 +214,10 @@ export class CollaborationRoom {
214
214
  lastHydrate: null,
215
215
  lastInitialSyncAt: 0,
216
216
  };
217
- this.persistence = new RoomPersistenceController({
218
- idleGraceMs: this.idleGraceMs,
219
- onDestroy: () => {
220
- this.awareness.destroy();
221
- this.doc.destroy();
222
- this.onEmpty?.(this.name);
223
- },
224
- onPersist: () => this.persist(),
225
- });
217
+ this.persistTimer = null;
218
+ this.destroyTimer = null;
219
+ this.finalizePromise = null;
220
+ this.shutdownGeneration = 0;
226
221
 
227
222
  this.awareness.setLocalState(null);
228
223
  this.registerDocListeners();
@@ -475,11 +470,12 @@ export class CollaborationRoom {
475
470
  return;
476
471
  }
477
472
 
478
- this.persistence.schedulePersist(async () => {
473
+ clearTimeout(this.persistTimer);
474
+ this.persistTimer = setTimeout(() => {
479
475
  this.persist().catch((error) => {
480
476
  console.error(`[room:${this.name}] Failed to persist document:`, error.message);
481
477
  });
482
- });
478
+ }, 500);
483
479
  }
484
480
 
485
481
  async persist() {
@@ -620,9 +616,17 @@ export class CollaborationRoom {
620
616
  this.documentStore?.rename(nextName);
621
617
  }
622
618
 
619
+ cancelPersistenceTimers() {
620
+ this.shutdownGeneration += 1;
621
+ clearTimeout(this.persistTimer);
622
+ clearTimeout(this.destroyTimer);
623
+ this.persistTimer = null;
624
+ this.destroyTimer = null;
625
+ }
626
+
623
627
  markDeleted() {
624
628
  this.deleted = true;
625
- this.persistence.cancelAll();
629
+ this.cancelPersistenceTimers();
626
630
  if (this.clients.size === 0) {
627
631
  this.finalizeIfIdle();
628
632
  }
@@ -669,7 +673,9 @@ export class CollaborationRoom {
669
673
  }
670
674
 
671
675
  async addClient(ws, { sendInitialSync: shouldSendInitialSync = true } = {}) {
672
- this.persistence.markActivity();
676
+ this.shutdownGeneration += 1;
677
+ clearTimeout(this.destroyTimer);
678
+ this.destroyTimer = null;
673
679
  await this.hydrate();
674
680
 
675
681
  this.ensureClientState(ws);
@@ -721,9 +727,9 @@ export class CollaborationRoom {
721
727
  }
722
728
 
723
729
  this.destroyed = true;
724
- this.persistence.cancelAll();
730
+ this.cancelPersistenceTimers();
725
731
  await Promise.allSettled([
726
- this.persistence.finalizePromise,
732
+ this.finalizePromise,
727
733
  this.activePersistPromise,
728
734
  ]);
729
735
 
@@ -745,12 +751,40 @@ export class CollaborationRoom {
745
751
  }
746
752
 
747
753
  finalizeIfIdle() {
748
- return this.persistence.finalizeIfIdle({
749
- isIdle: () => this.clients.size === 0,
750
- onPersistError: (error) => {
754
+ if (this.clients.size > 0 || this.finalizePromise) {
755
+ return;
756
+ }
757
+
758
+ const generation = ++this.shutdownGeneration;
759
+ this.finalizePromise = (async () => {
760
+ clearTimeout(this.persistTimer);
761
+ this.persistTimer = null;
762
+
763
+ try {
764
+ await this.persist();
765
+ } catch (error) {
751
766
  console.error(`[room:${this.name}] Failed to persist final room state:`, error.message);
752
- },
767
+ }
768
+
769
+ if (this.clients.size > 0 || generation !== this.shutdownGeneration) {
770
+ return;
771
+ }
772
+
773
+ this.destroyTimer = setTimeout(() => {
774
+ if (this.clients.size > 0 || generation !== this.shutdownGeneration) {
775
+ return;
776
+ }
777
+
778
+ this.awareness.destroy();
779
+ this.doc.destroy();
780
+ this.onEmpty?.(this.name);
781
+ }, this.idleGraceMs);
782
+ this.destroyTimer.unref?.();
783
+ })().finally(() => {
784
+ this.finalizePromise = null;
753
785
  });
786
+
787
+ return this.finalizePromise;
754
788
  }
755
789
 
756
790
  clearEphemeralExcalidrawHistory() {
@@ -39,10 +39,16 @@ function createThreadSummary(thread) {
39
39
  }
40
40
 
41
41
  const id = asString(thread.id);
42
+ const anchorKind = asString(thread.anchorKind) || 'line';
43
+ const isDiagramElementAnchor = anchorKind === 'diagram-element';
42
44
  const anchorStartLine = asFiniteNumber(thread.anchorStartLine);
43
45
  const anchorEndLine = asFiniteNumber(thread.anchorEndLine);
44
46
  const messages = asArray(thread.messages);
45
- if (!id || anchorStartLine === null || anchorEndLine === null || messages.length === 0) {
47
+ if (
48
+ !id
49
+ || messages.length === 0
50
+ || (!isDiagramElementAnchor && (anchorStartLine === null || anchorEndLine === null))
51
+ ) {
46
52
  return null;
47
53
  }
48
54
 
@@ -53,10 +59,14 @@ function createThreadSummary(thread) {
53
59
 
54
60
  return {
55
61
  anchor: {
56
- endLine: Math.max(anchorEndLine, anchorStartLine),
57
- kind: asString(thread.anchorKind) || 'line',
58
- quote: asString(thread.anchorQuote),
59
- startLine: Math.max(anchorStartLine, 1),
62
+ ...(isDiagramElementAnchor
63
+ ? {}
64
+ : {
65
+ endLine: Math.max(anchorEndLine, anchorStartLine),
66
+ startLine: Math.max(anchorStartLine, 1),
67
+ }),
68
+ kind: anchorKind,
69
+ quote: asString(thread.anchorQuote) || (isDiagramElementAnchor ? 'Diagram element' : ''),
60
70
  },
61
71
  createdAt: asFiniteNumber(thread.createdAt) ?? latestActivityAt,
62
72
  createdByColor: asString(thread.createdByColor),
@@ -22,7 +22,9 @@ const TEXT_SEARCH_GLOBS = Object.freeze([
22
22
  '*.mermaid',
23
23
  '*.puml',
24
24
  '*.plantuml',
25
+ '*.dsl',
25
26
  '*.drawio',
27
+ '*.excalidraw',
26
28
  ]);
27
29
 
28
30
  function normalizePositiveInt(value, fallback, { min = 1, max = Number.MAX_SAFE_INTEGER } = {}) {
@@ -278,6 +280,7 @@ export class RipgrepSearchService {
278
280
  async search({
279
281
  limit = DEFAULT_MAX_FILES,
280
282
  query = '',
283
+ signal = null,
281
284
  } = {}) {
282
285
  const normalizedQuery = String(query ?? '').trim();
283
286
  const maxFiles = normalizePositiveInt(limit, this.maxFiles, {
@@ -310,6 +313,7 @@ export class RipgrepSearchService {
310
313
  cwd: this.vaultDir,
311
314
  encoding: 'utf8',
312
315
  maxBuffer: this.maxBufferBytes,
316
+ ...(signal ? { signal } : {}),
313
317
  timeout: this.timeoutMs,
314
318
  });
315
319
  const parsed = parseRipgrepJson(result.stdout, {
@@ -1,5 +1,6 @@
1
1
  import { countPatchLines, parseNumstatOutput, parseUnifiedDiff } from './parsers.js';
2
2
  import { createEmptyStats } from './responses.js';
3
+ import { getVaultFileKind } from '../../../domain/file-kind.js';
3
4
 
4
5
  function createSectionMap(sections = []) {
5
6
  return new Map(sections.map((section) => [section.key, section]));
@@ -33,6 +34,7 @@ function mergeScopedFile(existingFile, nextFile, scope) {
33
34
  return {
34
35
  ...nextFile,
35
36
  ...nextFlags,
37
+ fileKind: nextFile.fileKind || getVaultFileKind(nextFile.path),
36
38
  scope,
37
39
  stats: createEmptyStats(),
38
40
  };
@@ -44,6 +46,7 @@ function mergeScopedFile(existingFile, nextFile, scope) {
44
46
  return {
45
47
  ...existingFile,
46
48
  ...nextFile,
49
+ fileKind: existingFile.fileKind || getVaultFileKind(nextFile.path),
47
50
  hasStagedChanges: existingFile.hasStagedChanges || nextFlags.hasStagedChanges,
48
51
  hasUntrackedChanges: existingFile.hasUntrackedChanges || nextFlags.hasUntrackedChanges,
49
52
  hasWorkingTreeChanges: existingFile.hasWorkingTreeChanges || nextFlags.hasWorkingTreeChanges,
@@ -54,6 +57,7 @@ function mergeScopedFile(existingFile, nextFile, scope) {
54
57
 
55
58
  return {
56
59
  ...existingFile,
60
+ fileKind: existingFile.fileKind || getVaultFileKind(nextFile.path),
57
61
  hasStagedChanges: existingFile.hasStagedChanges || nextFlags.hasStagedChanges,
58
62
  hasUntrackedChanges: existingFile.hasUntrackedChanges || nextFlags.hasUntrackedChanges,
59
63
  hasWorkingTreeChanges: existingFile.hasWorkingTreeChanges || nextFlags.hasWorkingTreeChanges,
@@ -372,6 +376,7 @@ export class GitDiffService {
372
376
  return {
373
377
  ...file,
374
378
  ...detail,
379
+ fileKind: file.fileKind || detail.fileKind || getVaultFileKind(file.path),
375
380
  canLoadFullPatch: false,
376
381
  patchLineCount,
377
382
  tooLarge: false,
@@ -70,6 +70,23 @@ export class GitService {
70
70
 
71
71
  return String(result.stdout ?? '');
72
72
  },
73
+ async execGitBuffer(args, { env = null } = {}) {
74
+ const result = await gitExecFile('git', ['-c', 'core.quotepath=false', ...args], {
75
+ cwd: vaultDir,
76
+ encoding: null,
77
+ env: {
78
+ ...process.env,
79
+ ...(commandEnv ?? {}),
80
+ ...(env ?? {}),
81
+ },
82
+ maxBuffer: 5 * 1024 * 1024,
83
+ timeout: 10_000,
84
+ });
85
+
86
+ return Buffer.isBuffer(result.stdout)
87
+ ? result.stdout
88
+ : Buffer.from(result.stdout ?? '');
89
+ },
73
90
  };
74
91
  this.untrackedFileService = new GitUntrackedFileService({ vaultDir });
75
92
  this.statusService = new GitStatusService({
@@ -186,7 +203,7 @@ export class GitService {
186
203
  };
187
204
  }
188
205
 
189
- async pullBranch() {
206
+ async pullBranch({ author = null } = {}) {
190
207
  const status = await this.getStatus({ force: true });
191
208
  if (status.branch?.detached) {
192
209
  throw createGitRequestError(409, 'Cannot pull from a detached HEAD');
@@ -203,16 +220,22 @@ export class GitService {
203
220
  throw createGitRequestError(409, 'Unable to resolve upstream branch for pull');
204
221
  }
205
222
 
206
- if (!(await this.canFastForwardTo(targetRef))) {
223
+ const mergeBaseRef = beforeRef
224
+ ? await this.resolveMergeBase(beforeRef, targetRef)
225
+ : null;
226
+ if (beforeRef && !mergeBaseRef) {
227
+ throw createGitRequestError(409, 'Cannot pull because local and remote histories are unrelated');
228
+ }
229
+ if (beforeRef && !(await this.canMergeWith(targetRef))) {
207
230
  throw createGitRequestError(
208
231
  409,
209
- 'Cannot pull because local and remote commits have diverged; fast-forward only pull is not possible.',
210
- { requestCode: 'pull_diverged_ff_only' },
232
+ 'Cannot pull automatically because local and remote commits conflict.',
233
+ { requestCode: 'pull_conflicted_commits' },
211
234
  );
212
235
  }
213
236
 
214
237
  const dirtyEntries = this.collectDirtyEntries(status);
215
- const upstreamWorkspaceChange = await this.createWorkspaceChangeFromRefs(beforeRef, targetRef);
238
+ const upstreamWorkspaceChange = await this.createWorkspaceChangeFromRefs(mergeBaseRef, targetRef);
216
239
  const overlappingEntries = this.findOverlappingDirtyEntries({
217
240
  dirtyEntries,
218
241
  workspaceChange: upstreamWorkspaceChange,
@@ -230,7 +253,9 @@ export class GitService {
230
253
 
231
254
  let output;
232
255
  try {
233
- output = await this.commandRunner.execGit(['pull', '--ff-only', '--autostash']);
256
+ output = await this.commandRunner.execGit(['merge', '--no-edit', '--autostash', targetRef], {
257
+ env: buildAuthorEnv(author),
258
+ });
234
259
  } catch (error) {
235
260
  throw await this.classifyPullError(error);
236
261
  }
@@ -293,6 +318,10 @@ export class GitService {
293
318
  return this.historyService.getFileSnapshot({ hash, path });
294
319
  }
295
320
 
321
+ async getFileAttachment({ hash, path } = {}) {
322
+ return this.historyService.getFileAttachment({ hash, path });
323
+ }
324
+
296
325
  async pathExistsAtRef(ref, path) {
297
326
  try {
298
327
  await this.commandRunner.execGit(['cat-file', '-e', `${ref}:${path}`]);
@@ -318,14 +347,18 @@ export class GitService {
318
347
  : ['fetch', '--prune']);
319
348
  }
320
349
 
321
- async canFastForwardTo(targetRef) {
322
- const headRef = await this.resolveRef('HEAD');
323
- if (!headRef || !targetRef || headRef === targetRef) {
324
- return true;
350
+ async resolveMergeBase(leftRef, rightRef) {
351
+ try {
352
+ const output = await this.commandRunner.execGit(['merge-base', leftRef, rightRef]);
353
+ return output.trim() || null;
354
+ } catch {
355
+ return null;
325
356
  }
357
+ }
326
358
 
359
+ async canMergeWith(targetRef) {
327
360
  try {
328
- await this.commandRunner.execGit(['merge-base', '--is-ancestor', headRef, targetRef]);
361
+ await this.commandRunner.execGit(['merge-tree', '--write-tree', '--quiet', 'HEAD', targetRef]);
329
362
  return true;
330
363
  } catch (error) {
331
364
  if (error?.code === 1) {
@@ -1,6 +1,7 @@
1
1
  import { createRequestError as createGitRequestError } from '../http/http-errors.js';
2
2
  import { getVaultFileKind, isImageAttachmentFilePath } from '../../../domain/file-kind.js';
3
3
  import { normalizeRelativeGitPath } from './path-utils.js';
4
+ import { getImageMimeType } from './image-mime.js';
4
5
  import {
5
6
  countPatchLines,
6
7
  parseGitLogHeader,
@@ -308,6 +309,7 @@ export class GitHistoryService {
308
309
 
309
310
  if (metaOnly) {
310
311
  const metaResponse = {
312
+ baseRef: meta.baseRef,
311
313
  commit: meta.commit,
312
314
  files: meta.files,
313
315
  isGitRepo: true,
@@ -335,6 +337,7 @@ export class GitHistoryService {
335
337
  && (fileSummary.additions + fileSummary.deletions) > this.maxInitialPatchLines
336
338
  ) {
337
339
  const guardedResponse = {
340
+ baseRef: meta.baseRef,
338
341
  commit: meta.commit,
339
342
  files: [{
340
343
  ...baseFile,
@@ -396,6 +399,7 @@ export class GitHistoryService {
396
399
  };
397
400
 
398
401
  const response = {
402
+ baseRef: meta.baseRef,
399
403
  commit: meta.commit,
400
404
  files: [detailedFile],
401
405
  isGitRepo: true,
@@ -460,6 +464,46 @@ export class GitHistoryService {
460
464
  });
461
465
  }
462
466
 
467
+ async getFileAttachment({ hash, path } = {}) {
468
+ const isGitRepo = await this.commandRunner.isGitRepo();
469
+ const normalizedHash = this.normalizeAttachmentRef(hash);
470
+ const normalizedPath = normalizeRelativeGitPath(path);
471
+
472
+ if (!isGitRepo) {
473
+ throw createGitRequestError(404, 'Git repository not found');
474
+ }
475
+
476
+ if (!isImageAttachmentFilePath(normalizedPath)) {
477
+ throw createGitRequestError(400, 'Unsupported image attachment path');
478
+ }
479
+
480
+ const cacheKey = JSON.stringify({
481
+ hash: normalizedHash,
482
+ path: normalizedPath,
483
+ type: 'file-attachment',
484
+ });
485
+ const cached = getCachedValue(this.commitCache, cacheKey);
486
+ if (cached) {
487
+ return cached;
488
+ }
489
+
490
+ return this.runRequest(cacheKey, async () => {
491
+ let content;
492
+ try {
493
+ content = await this.commandRunner.execGitBuffer(['cat-file', 'blob', `${normalizedHash}:${normalizedPath}`]);
494
+ } catch {
495
+ throw createGitRequestError(404, 'Commit image not found');
496
+ }
497
+
498
+ return setCachedValue(this.commitCache, cacheKey, {
499
+ content,
500
+ hash: normalizedHash,
501
+ mimeType: getImageMimeType(normalizedPath),
502
+ path: normalizedPath,
503
+ }, this.responseCacheTtlMs);
504
+ });
505
+ }
506
+
463
507
  async runRequest(key, callback) {
464
508
  if (this.pendingRequests.has(key)) {
465
509
  return this.pendingRequests.get(key);
@@ -494,6 +538,14 @@ export class GitHistoryService {
494
538
  return normalizedHash;
495
539
  }
496
540
 
541
+ normalizeAttachmentRef(hash) {
542
+ const normalizedHash = String(hash ?? '').trim();
543
+ if (normalizedHash === 'HEAD') {
544
+ return normalizedHash;
545
+ }
546
+ return this.normalizeCommitHash(normalizedHash);
547
+ }
548
+
497
549
  parseHistoryChunk(chunk) {
498
550
  const [headerLine = '', ...statLines] = String(chunk ?? '')
499
551
  .split(/\r?\n/u)
@@ -588,6 +640,7 @@ export class GitHistoryService {
588
640
  authoredAt: chunk.authoredAt,
589
641
  deletions: Number(numstatEntry?.deletions || 0),
590
642
  filesChanged: 1,
643
+ fileKind: getVaultFileKind(nameStatusEntry?.path),
591
644
  hash: chunk.hash,
592
645
  isMergeCommit: chunk.isMergeCommit,
593
646
  oldPath: nameStatusEntry?.oldPath ?? null,
@@ -679,6 +732,7 @@ export class GitHistoryService {
679
732
  const numstatEntry = numstatEntries[index];
680
733
  return {
681
734
  ...entry,
735
+ fileKind: getVaultFileKind(entry.path),
682
736
  stats: numstatEntry
683
737
  ? {
684
738
  additions: numstatEntry.additions,
@@ -0,0 +1,14 @@
1
+ const IMAGE_MIME_TYPES = Object.freeze({
2
+ '.gif': 'image/gif',
3
+ '.jpeg': 'image/jpeg',
4
+ '.jpg': 'image/jpeg',
5
+ '.png': 'image/png',
6
+ '.svg': 'image/svg+xml',
7
+ '.webp': 'image/webp',
8
+ });
9
+
10
+ export function getImageMimeType(filePath) {
11
+ const normalizedPath = String(filePath ?? '').toLowerCase();
12
+ const extension = Object.keys(IMAGE_MIME_TYPES).find((candidate) => normalizedPath.endsWith(candidate));
13
+ return extension ? IMAGE_MIME_TYPES[extension] : 'application/octet-stream';
14
+ }
@@ -2,11 +2,17 @@ import { createReadStream } from 'node:fs';
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
 
5
+ import { getVaultFileKind, isImageAttachmentFilePath } from '../../../domain/file-kind.js';
5
6
  import { mapWithConcurrency } from '../../shared/async-utils.js';
6
7
  import { createEmptyStats } from './responses.js';
7
8
  import { splitContentLines } from './parsers.js';
9
+ import { getImageMimeType } from './image-mime.js';
8
10
 
9
11
  async function countFileLines(filePath) {
12
+ if (isImageAttachmentFilePath(filePath)) {
13
+ return 0;
14
+ }
15
+
10
16
  return new Promise((resolve, reject) => {
11
17
  const stream = createReadStream(filePath, { encoding: 'utf8' });
12
18
  let lineCount = 0;
@@ -36,6 +42,7 @@ function buildSyntheticAddedFileDiff(pathValue, content) {
36
42
  const normalizedContent = String(content ?? '').replace(/\r\n/g, '\n');
37
43
  const file = {
38
44
  code: 'U',
45
+ fileKind: getVaultFileKind(pathValue),
39
46
  hunks: [],
40
47
  isBinary: false,
41
48
  oldPath: null,
@@ -89,7 +96,27 @@ export class GitUntrackedFileService {
89
96
  async buildSyntheticDiffs(files = []) {
90
97
  const results = await mapWithConcurrency(files, 2, async (file) => {
91
98
  try {
92
- const content = await readFile(join(this.vaultDir, file.path), 'utf8');
99
+ const absolutePath = join(this.vaultDir, file.path);
100
+ if (isImageAttachmentFilePath(file.path)) {
101
+ const content = await readFile(absolutePath);
102
+ return {
103
+ ...file,
104
+ binaryMessage: 'Binary image file',
105
+ byteLength: content.byteLength,
106
+ code: file.code || 'U',
107
+ fileKind: 'image',
108
+ hunks: [],
109
+ isBinary: true,
110
+ mimeType: getImageMimeType(file.path),
111
+ oldPath: file.oldPath ?? null,
112
+ path: file.path,
113
+ stats: createEmptyStats(),
114
+ status: file.status || 'untracked',
115
+ synthetic: true,
116
+ };
117
+ }
118
+
119
+ const content = await readFile(absolutePath, 'utf8');
93
120
  return buildSyntheticAddedFileDiff(file.path, content);
94
121
  } catch {
95
122
  return null;
@@ -130,7 +130,9 @@ export function createGitApiCommandHandler({
130
130
  action: 'pull',
131
131
  req,
132
132
  responsePayload: await workspaceMutationCoordinator.runManagedWorkspaceMutation(
133
- () => gitService.pullBranch(),
133
+ () => gitService.pullBranch({
134
+ author: authService?.getAuthenticatedUser?.(req) ?? null,
135
+ }),
134
136
  ),
135
137
  workspaceMutationCoordinator,
136
138
  }));
@@ -1,5 +1,12 @@
1
+ import { basename } from 'node:path';
2
+
1
3
  import { handleApiError } from './http-request-helpers.js';
2
- import { jsonResponse } from './http-response.js';
4
+ import {
5
+ createSafeAsciiFilename,
6
+ encodeContentDispositionFilename,
7
+ jsonResponse,
8
+ sendResponse,
9
+ } from './http-response.js';
3
10
 
4
11
  function resolveDiffScope(requestUrl) {
5
12
  const scope = String(requestUrl.searchParams.get('scope') || '').trim().toLowerCase();
@@ -106,6 +113,29 @@ export function createGitApiQueryHandler({ gitService }) {
106
113
  return true;
107
114
  }
108
115
 
116
+ if (requestUrl.pathname === '/api/git/file-attachment' && req.method === 'GET') {
117
+ try {
118
+ const attachment = await gitService.getFileAttachment({
119
+ hash: requestUrl.searchParams.get('hash'),
120
+ path: requestUrl.searchParams.get('path'),
121
+ });
122
+ const fileName = basename(String(attachment.path || 'image'));
123
+ sendResponse(req, res, {
124
+ body: attachment.content,
125
+ headers: {
126
+ 'Cache-Control': 'private, max-age=300, stale-while-revalidate=3600',
127
+ 'Content-Disposition': `inline; filename="${createSafeAsciiFilename(fileName)}"; filename*=UTF-8''${encodeContentDispositionFilename(fileName)}`,
128
+ 'Content-Type': attachment.mimeType || 'application/octet-stream',
129
+ 'X-Content-Type-Options': 'nosniff',
130
+ },
131
+ statusCode: 200,
132
+ });
133
+ } catch (error) {
134
+ handleApiError(req, res, error, '[api] Failed to read git file attachment:', 'Failed to read git file attachment');
135
+ }
136
+ return true;
137
+ }
138
+
109
139
  if (requestUrl.pathname === '/api/git/pull-backups' && req.method === 'GET') {
110
140
  try {
111
141
  jsonResponse(req, res, 200, {